tag:blogger.com,1999:blog-375838562024-03-08T00:56:29.653-08:00Cat in EnglishCat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.comBlogger48125tag:blogger.com,1999:blog-37583856.post-1566245091204612352023-04-08T04:00:00.003-07:002023-04-08T04:00:00.139-07:00Strongly Typed String Literal Split-Map-Join in TypeScript<p><strong>The Problem</strong>: Write a strongly typed TypeScript function that takes a valid CSS property name in <code>kebab-case</code> (used in CSS) and return the same property name in <code>camelCase</code> (usually used in CSS-in-JS). For example, call this function with <code>"font-size"</code> and it should return <code>"fontSize"</code>.</p>
<p><strong>The Strongly Typed String Literal Requirement</strong>: A TypeScript type that contains all valid <code>kebab-case</code> CSS property names is provided. Make sure TypeScript can infer to correct <code>camelCase</code> output from this function. To explain it in code:</p>
<pre><code>type kebabCasePropertyName =
| 'align-content'
| 'align-items'
| 'align-self'
| 'background'
| 'background-attachment'
| 'background-color'
| 'background-image'
/* remaining valid property names */
function convert(propertyName: kebabCasePropertyName) /*: define return type */ {
/* implement function */
}
const camelCasePropertyName = convert('align-content');
// typeof camelCasePropertyName should be 'alignContent'
convert('invalid-property-name');
// TypeScript should throw a compile time error
</code></pre>
<p>Here is a <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBA1hBGBDeBhRBnCAFATge0h1ADlEBbaAXgCgooAfKAckQBsBLAcwDsBaAYzzdgEYUyi0GzNlz7sRZdE0mMWHHr0ysAZsrqqk-GJ3wBXbgBM9UpoeNnLvRMGCJ+ACwpiVzOybzmFgJ4rHg41gZu9gGO7GSInBDWAPQAVFA4EPHs3DmcUABuMhZQYPiEoFDc5BDoUKnJ1NTa5vzA7EJQgtwFEEQAFGUEfSQ1AFywCMhomLjDRCCkFACUUGkTFhDaOdCZwKY43FCgkPXJUADekmlQcWCsWaLAUC3cbR1HDdQAvk3d6M9+DVWDNsOURosalBKF0hL0BmpZMFhE8mMsANzUZLnE4QPDaLrA0FzCqQihQdDuAKsErwaCIngoIQibzUbrw4D9Jg5IocIJDUm8aoUNGY7FQAAq4AgAGV+Dh2GBnpTqSVgO58AB3KCIWFkMDsB7HOLQPr4HDUIA">TypeScript Playground with the same code</a>. If you want to try solving this problem yourself, go ahead and try it out. You may find a solution better than the one I’m going to share below.</p>
<p><strong>The Solution</strong>: This is the <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBA1hBGBDeBhRBnCAFATge0h1ADlEBbaAXgCgooAfKAckQBsBLAcwDsBaAYzzdgEYUyi0GzNlz7sRZdE0mMWHHr0ysAZsrqqk-GJ3wBXbgBM9UpoeNnLvRMGCJ+ACwpiVzOybzmFgJ4rHg41gZu9gGO7GSInBDWAPQAVFA4EPHs3DmcUABuMhZQYPiEoFDc5BDoUKnJ1NTJyVAAymAcwAA8AEQAggD6AEKDKL0ANFC9g70AfFC8CwDaA5PTw+u94wC61KCQ7Z3y3W1QEAAeIpZ16MA4eVMAIudXoha393lzkpRQdw9uPlLtcPu0oAB+KD9HA4RAgboA76SABc4JB7zqTHEUOWezoaLOGJuUAABgASADeOW0EBwUAAKgBfKlPFnU7i0+kAVSZpMhUGWDKmADoxR0ut1uc85vioGjlm0dgBuJotKAAKTwOW6q0QW3gW34vR2U16vHmiwWvUQvHgAl6+3A0C1Ov6r1BdRhcIRSKBc2eHsx-y+-t+0KDJLxAuxqIjxLByxpdMZkbBfs4OwFFMpzNJcfdCbqSc5KYZac+gM4orFyZ5FehsPhiNDnFl2apzNZ7Nd3ClMr5cYzquarQAMngAO50-gYCDe5urfrLrbDNdbFCbk0LJaCm2IfVm+DHo38Y17A7QCfTnCzzALhGFt4kh8tqtzH50P5Pz2CrO4uU0R-YMSy5VMixDKsaxFOsoG5BtXwzdtcWvGc526BkAygMURVQ2851fblZQLEd1TQMB5BkAAvecmwRPUDwNE8zTPc8dxWAZGLNYZmOmFAzxNJ1DnIyiOBo19gJfOi32+T8oG-BtowAgtFNg8sIIzaDYPgiDENbZDBRE1wxIgbo8LvUzMKwnCjOo2ifSlYiCWhUjWjQChWDQe9pIYw9pmPQ0WIEnZ2L3A8-N6HjAr44KhOgdyIE8gjpMksE9PfOSFIgpS-xU7K1IbTTsNrUt6106SkP-QVzPQzDoNskzCKc+UXLVNyaiSzA+hkDRBGEURgEtXcbXUbgUCEa5BriqAEs60yiWfdN9PkzVtT7WavPs5sJRONopiYXgmA-fajpHbRzH4YB2CEKA+oKOkenUxa6jgJBUDnXACAekBSAoOYAAoyi+ogfpqNEGQASjRDb0MvPBtFKcpvt+iAFkpSRMmAUwcG4RHgZIGpJDoEV0GOYB-oOpgIaJ7D4jAf7-snMILCmHILEuCH5IWNnLnkyg-gABhpugoSZnALGFlqczFiwRQ8RAcH6cmBYhkVgDwbkwEITb-oh9kZZJ0x4Azf6AEY9dJam6GJgArNaKapqAMBmjrNu6OGEaBipQb+1UmSaPq7lu12PqRkGUZWu6Hopnq+D6yaqdVdUPdKDBZzmz7vYj9B3ACVgSngaA1Fkcb+u8ago6ICmciKDggi977eGqChE9HRlnTafgHjAYB-lz0x86gYB3HwScnduvAyAo1hoCuihzlhMJqCAA">TypeScript Playground with the solution code</a>. Now we can go into understanding how it works.</p>
<h2><code>type Split<S, D></code></h2>
<p>This generic type splits string literal <code>S</code> with string literal delimiter <code>D</code>. </p>
<pre><code>type Split<S extends string, D extends string>
= string extends S ? Array<string>
: S extends '' ? []
: S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>]
: [S];
</code></pre>
<p>Here <code>S extends string</code> means <code>S</code> has to be a string. The more precise definition is <code>S</code> has to be a subset of all possible string values. The same applies to <code>D</code>, so <code>S</code> and <code>D</code> have to be strings or TypeScript will throw a compile time error.</p>
<p>The next few lines use the ternary conditional operator several times to pattern match different possible types of <code>S</code>. The following pseudo-code may help you follow the logic:</p>
<pre><code>type Split<S extends string, D extends string>
if (string extends S) then return Array<string>
else if (S extends '') then return []
else if (S extends `${infer T}${D}${infer U}`) then return [T, ...Split<U, D>]
else return [S];
</code></pre>
<p>First, we try to match <code>string extends S</code>. If it passes, that means S isn’t a string literal. (Previously we already knew <code>S</code> is a subset of string. If string is also a subset of <code>S</code> then <code>S</code> is exactly string. Nothing more. Nothing less.) It’s a string and its value is unknown at compile time. There’s nothing we can do here. <code>Split<S, D></code> can only be narrowed down to <code>Array<string></code>.</p>
<p>Then we try to match <code>S extends ''</code>. It just means <code>S</code> is an empty string because the subset of empty string is just an empty string. Then we can narrow <code>Split<S, D></code> down to an empty array.</p>
<p>And then we try to match <code>S extends</code>${infer T}{$D}${infer U}`. There are two concepts we need to understand here:</p>
<ol>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">TypeScript template literal types</a>. When using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">JavaScript template literals</a>, TypeScript can infer all the possible string interpolation outcomes.</li>
<li><a href="https://blog.logrocket.com/understanding-infer-typescript/">The <code>infer</code> keyword</a>. It can only be used after the <code>extends</code> keyword. It can be used to deconstruct a type that’s constructed from other types.</li>
</ol>
<p>So here we try to deconstruct <code>S</code> into template literal type <code>`${infer T}${D}${infer U}`</code>. For example, <code>Split<'hello-world', '-'></code> has <code>D extends '-'</code>, so it can be deconstructed into <code>T extends 'hello'</code> and <code>U extends 'world'</code>, because <code>${T}${D}${U}</code> will construct <code>'hello-world'</code>. By using <code>infer</code>, we ask TypeScript to figure out <code>T</code> and <code>U</code> for us.</p>
<p>If the deconstructing works. we can narrow down <code>Split<S, D></code> into <code>[T, ...Split<U, D>]</code>. This is very similar to how we would implement a JavaScript <code>split</code> function with recursion:</p>
<pre><code>function split(string, delimiter) {
const index = string.indexOf(delimiter);
return index >= 0
? [
string.substring(0, index),
...split(string.substring(index + 1), delimiter)
]
: [string];
}
</code></pre>
<p>If the deconstructing doesn’t work, the last line in the pseudo-code is just like the last line inside the JavaScript above. It means <code>S</code> is a string literal but it doesn’t contain <code>D</code>, so we return <code>[S]</code>. We can see the similarity between JavaScript and TypeScript type expressions.</p>
<h2><code>type Join<A, D></code></h2>
<p>This is like reversing <code>Split<S, D></code>, in a very similar recursive manner. <code>`${T}${D}${Join<U, D>}`</code> represents that recursion.</p>
<pre><code>type Join<A extends Array<string>, D extends string>
= A extends [] ? ''
: A extends [infer T extends string] ? `${T}`
: A extends [infer T extends string, ...infer U extends Array<string>] ? `${T}${D}${Join<U, D>}`
: string;
</code></pre>
<p><code>Split<S, D></code> requires <code>S</code> and <code>D</code> to be string literals. <code>Join<A, D></code> requires <code>A</code> to be an array literal and all of its elements are string literals. If <code>A</code> doesn’t satisfy these requirements, we can only narrow <code>Join<A, D></code> down to <code>string</code>.</p>
<p>Here we use template literal type <code>`${T}${D}${Join<U, D>}`</code> to construct one string type from multiple string types. This is the opposite operation of how we deconstruct in <code>Split<S, D></code>.</p>
<h2><code>type LowercaseArray<A>></code></h2>
<p>Again we are using recursion to iterate through an array. This is similar to <code>Join<A, D></code>. However, we don’t return a template literal type. We return a new array type that contains new string literal types.</p>
<pre><code>type LowercaseArray<A extends Array<string>>
= A extends [] ? []
: A extends [infer T extends string, ...infer U extends Array<string>] ? [Lowercase<T>, ...LowercaseArray<U>]
: A;
</code></pre>
<p>TypeScript has a built-in <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#lowercasestringtype"><code>Lowercase<T></code></a> that returns the string literal type of the lower case of another string leteral type. We don’t need to do this ourselves. </p>
<h2><code>type CapitalizeArray<A></code></h2>
<p>It’s very similar to <code>LowercaseArray<A></code>. We use the built-in <code>Capitalize<T></code> to capitalize the first letter of a string literal type.</p>
<pre><code>type CapitalizeArray<A extends Array<string>>
= A extends [] ? []
: A extends [infer T extends string, ...infer U extends Array<string>] ? [Capitalize<Lowercase<T>>, ...CapitalizeArray<U>]
: A;
</code></pre>
<h2><code>type CamelCaseArray<A></code></h2>
<p><code>camelCase</code> has first word in all lowercase and subsequent words in lowercase with first character capitalized. This can be achieved by combining <code>LowercaseArray<A></code> and <code>CapitalizeArray<A></code> into a new type <code>CamelCaseArray<A></code>.</p>
<p>In the end, we can combine this type of <code>Split<S, D></code> and <code>Join<A, D></code> to create <code>CamelCase<S></code>. It’s just like how we would implement this as a JavaScript function: a chained split-map-join operation.</p>
<pre><code>function convert(propertyName) {
return propertyName
.split('-')
.map((word, index) => index === 0
? word
: `${word.charAt(0).toUpperCase()}${word.substring(1)}`)
.join('');
}
</code></pre>
<p>How about the opposite operation? How can we create a TypeScript generic type that converts <code>camelCase</code> back to <code>kebab-case</code>? That’s an exercise for you. There’s no clear delimiter like <code>'-'</code> in this operation. Think about how you would do it in JavaScript and use pattern matching in TypeScript to achieve the same result.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-67356095317340259892020-06-19T15:53:00.002-07:002020-06-22T00:24:00.105-07:00Job Promotion: Scaling-bound or Opportunity-bound?<p>This is my thought after reading a conversation on how promotion to some levels is harder than some other levels in Facebook. The promotion is easier when the next level is the same job as your current level but a more mature version. It’s harder when the next level is a different job that requires new skills.</p>
<p>Based on this I would break down promotion requirements into two categories:</p>
<ol>
<li>Scaling your current skills.</li>
<li>Learning and practicing new skills.</li>
</ol>
<p>Promotions that mostly require the first category are the easier ones. The next level is more or less the same job. Let’s call it <strong>scale-bound</strong> promotion. Promotions that heavily involves the second category are the harder ones. The next level is like a different job. I’ll call it <strong>opportunity-bound</strong> promotion.</p>
<p>The first step to optimize your promotion is to identify whether your next level is scale-bound or opportunity-bound. In the conversation that I read about, people divided Facebook levels into buckets – [3, 4], [5], [6, 7], [8, 9] – and it’s the same job in the same bucket. Crossing the buckets requires you to learn and practice new skills because it’s a different job.</p>
<hr />
<p>Opportunity-bound promotion is harder because you need opportunities to learn and practice new skills that are required by your next level while your current level doesn’t provide such opportunities. Usually, those opportunities come to you naturally when you are at your next level. This becomes a chicken and egg problem – you are not at the next level so you don’t get opportunities required by your next level.</p>
<p>Even if the promotion to your next level is opportunity-bound, you need to deal with the scale-bound part well first. Otherwise, when someone offers you an opportunity you might fail because your current skills don’t scale enough. That person will regret offering you the opportunity. The next opportunity will be harder to come by. It’s better to make sure your current skills are mostly scaled to meet the expectation of your next level first.</p>
<hr />
<p>When you are confronted with an opportunity-bound situation, the best-case scenario is having a great manager. The manager should connect you with the right opportunities to develop the skills you don’t have. These opportunities should push you out of your comfort zone but not too far away. That’s the tailwind setup. You are in good hands.</p>
<p>The meh scenario is when the team is functioning okay but your manager isn’t actively optimizing the part of the team that you are in. That means your manager isn’t actively helping you with new opportunities. This scenario is common when your manager is too aggressive and spread himself too thin. It’s also common when your manager is complacent and doesn’t want to further develop the team.</p>
<p>You need to have better than average (comparing to your peers at your level) soft skills to build relationships with a broader group of people in your company. Learn about the broader business your team is in and discover opportunities yourself. You might also need to perform better than your peers when there are fewer opportunities than the people who are qualified.</p>
<p>The worst-case scenario is when the team is malfunctioning. People don’t know what they are supposed to do or what they can do to meaningfully help the team.</p>
<p>It’s like war. There are casualties. People can’t perform well because the team is broken but they got fired anyway. People who don’t get fired will realize the situation they are in and might jump ship as fast as they could. Interestingly, war heroes only emerge from the war. Field promotion can fast track you at a speed that’s not achievable in peacetime. Opportunities open up when there are casualties and deserters.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-3691331780044916142019-11-26T20:40:00.000-08:002019-11-26T20:40:04.747-08:00Front-End Learning for Programmer<p>If you are an existing programmer (fluent in one common programming language) and want to learn Front-End (HTML+CSS+JS), I would recommend using <a href="https://www.freecodecamp.org/">freeCodeCamp</a> and picking only the modules you need. If you want to learn just enough to work on modern front-end projects or start a new project with <a href="https://create-react-app.dev/">Create React App</a>, below is what modules I think you should learn on freeCodeCamp.</p>
<p>“✔️” means you should learn it. “❌” means you could skip it. “❗” means you could skip if you are in a hurry but you should learn if you have time.</p>
<ul>
<li>Responsive Web Design
<ul>
<li>✔️ Basic HTML and HTML5</li>
<li>✔️ Basic CSS</li>
<li>✔️ Applied Visual Design</li>
<li>❌ Applied Accessibility</li>
<li>❌ Responsive Web Design Principles</li>
<li>✔️ CSS Flexbox</li>
<li>❌ CSS Grid</li>
<li>✔️ Responsive Web Design Projects</li>
</ul></li>
<li>JavaScript Algorithms and Data Structures
<ul>
<li>✔️ Basic JavaScript</li>
<li>✔️ ES6</li>
<li>❌ Regular Expressions</li>
<li>✔️ Debugging</li>
<li>✔️ Basic Data Structures</li>
<li>❗ Basic Algorithm Scripting
<ul>
<li>(Use it as a practice to write more JavaScript code.)</li>
</ul></li>
<li>✔️ Object Oriented Programming</li>
<li>✔️ Functional Programming</li>
<li>❗ Intermediate Algorithm Scripting
<ul>
<li>(Use it as a practice to write more JavaScript code.)</li>
</ul></li>
<li>❗ JavaScript Algorithms and Data Structures Projects
<ul>
<li>(Use it as a practice to write more JavaScript code.)</li>
</ul></li>
</ul></li>
<li>Front End Libraries
<ul>
<li>✔️ Bootstrap</li>
<li>❌ jQuery</li>
<li>❌ Sass</li>
<li>✔️ React</li>
<li>✔️ Redux</li>
<li>✔️ React and Redux</li>
<li>✔️ Front End Libraries Projects</li>
</ul></li>
</ul>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com2tag:blogger.com,1999:blog-37583856.post-3789610845670141572019-11-12T22:35:00.000-08:002019-11-12T22:35:06.383-08:00Progressive Web App as Share Target on Android<p>I built a PWA (Progressive Web App) to trace shortened URL back to its original URL. (You can find it <a href="https://traceurl.catchen.app/">here</a>.) I don’t want to copy a URL and paste it into my app. I want to use the Android’s <a href="https://developer.android.com/training/sharing/send">sharesheet</a> to send any link in Chrome straight to my app. How do I do that?</p>
<p>Google provides <a href="https://developers.google.com/web/updates/2018/12/web-share-target">good documentation</a> on this. We need to add a <code>share_target</code> section in <code>manifest.json</code> and then declare that our PWA can act as a share target. Most of the properties in this section can be thought of as attributes on a <code><form></code> element with the same name. For example, <code>{ "action": "/share", "method": "POST" }</code> is like <code><form action="/share" method="POST"></code>.</p>
<p><code>params</code> subsection let us change parameter names if we already have a convension of naming search parameters in GET requests or form fields in POST requests. Otherwise, we can keep them in their original names. One caveat is Android doesn’t use <code>url</code> parameter so when sharing a URL it comes through the <code>text</code> parameter. In my app I need to coalesce these two parameters to get the input from the user.</p>
<p>Is there more? Yes! Twitter makes a great PWA and we can check their <a href="https://twitter.com/manifest.json"><code>manifest.json</code></a>. Here’s the beautified version of the <code>share_target</code> section:</p>
<pre><code>"share_target": {
"action": "compose/tweet",
"enctype": "multipart/form-data",
"method": "POST",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [
{
"name": "externalMedia",
"accept": [
"image/jpeg",
"image/png",
"image/gif",
"video/quicktime",
"video/mp4"
]
}
]
}
}
</code></pre>
<p>It has a <code>files</code> subsection under the <code>params</code> section. This is part of the <a href="https://wicg.github.io/web-share-target/level-2/#sharetargetfiles-and-its-members">Web Share Target Level 2</a>. We can accept files from sharesheet and we can assign a file to different parameter name based on MIME type or file extension. My app doesn’t need this capability but it’s good to know what’s possible.</p>
<p>If you like my post, you can subscribe through <a href="https://chen.cat/subscribe-to-blogs">email</a> or <a href="http://feedproxy.google.com/CatChen/English">RSS/Atom</a>. That makes sure you won’t miss my future posts.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-31851974925916806082019-11-06T09:46:00.003-08:002019-11-06T09:46:49.395-08:00Batch Sending Email with Attachments through AppleScript<p>I want to learn a little bit of AppleScript. I need to help a friend send out emails to welcome new students to the school. The requirements are:</p>
<ol>
<li>addressing each recipient by their name in email content;</li>
<li>attaching the same file in all emails.</li>
</ol>
<p>After some research and tinkering I have a script to send emails:</p>
<script src="https://gist.github.com/CatChen/fe07ab39c7ea27d296e57838d7b5f1a4.js"></script>
<p><code>theRecipients</code> is the list of recipient names and email addresses. This is for requirement #1. <code>theAttachment</code> isn’t hardcoded to any file path. It will prompt and let me choose a file when I run the AppleScript.</p>
<p>The trickiest part is the <code>delay 1</code>. Without this line, emails will be sent without the attachment. It’s a hack to make sure each email has the attachment. I don’t know why it works and I can’t find an explanation online.</p>
<p>After building this AppleScript, I learn that Google App Script is another great way to automate sending emails through Gmail (or GSuite). I will learn App Script and write a post about that next time. If you like this kind of posts, you can subscribe through <a href="https://chen.cat/subscribe-to-blogs">email</a> or <a href="http://feedproxy.google.com/CatChen/English">RSS/Atom</a>.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-38630721295004533952019-09-24T10:42:00.000-07:002019-09-24T10:42:08.908-07:00MailChimp Popup Dialog on Click<h2>Problem</h2>
<p>MailChimp provides sign-up form as pop-up dialog, but has limited options for when to trigger it. Available triggers are like “immediately after the page is open”, “when the user scrolls to the bottom of the page”, etc. <strong>I want to trigger the dialog when a reader clicks the sign-up link on my blog.</strong> Instead of navigating to the sign-up page, I want to open the dialog and speed up the experience. I hope this can improve subscription rate.</p>
<h2>Solution</h2>
<p>MailChimp provides an HTML snippet for the pop-up dialog. It contains two <code><script></code> tags. I left the first one untouched and modified the second one. Read the code below for reference:</p>
<script src="https://gist.github.com/CatChen/06063464c7c205fe477243f18b439f27.js"></script>
<p>In the first <code><script></code> tag, I did nothing. In the second <code><script></code> tag, I wrapped the original JavaScript in a function called <code>displayDialog</code>, which will be called in the sign-up link’s click event. I added a third <code><script></code> tag to search for sign-up links in the page and add the click event handler.</p>
<p>Within <code>displayDialog</code> function, I not only call the original code in the second <code><script></code> tag but also delete two cookies before that. That’s because MailChimp set one of these cookies when a user dismisses the dialog or subscribes through the dialog. The presence of one of the cookies will prevent the dialog from opening again. This behavior makes sense when using MailChimp’s automatic triggers – a user shouldn’t see a dialog again after either dismissing or subscribing. It doesn’t make sense when the trigger is user clicking a link because it’s a clear intention to open the dialog. <strong>(If you want to reuse my code, remember to replace <code>window.dojoRequire(...)</code> with the code from your own MailChimp campaign.)</strong></p>
<p>The third <code><script></code> tag is customized for my own blog. It looks for any link that points to the sign-up page and add the click event handler. The event handler calls <code>displayDialog</code> to trigger the dialog and then cancels the browser’s navigation to the sign-up page.</p>
<p>If you want to test this feature, make sure you open this post from my blog and <a href="https://chen.cat/subscribe-to-blogs">click this link to subscribe</a>. It should open the sign-up dialog instead of the sign-up page. If you like this post, remember to put in your email and subscribe!</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-51851388899917173002019-08-28T22:05:00.000-07:002019-08-28T22:05:01.497-07:00Is Targeted Ads Price Discrimination?<p>If you use a service for free but you need to see ads targeting towards you, how much do you actually pay? I didn’t think about this before I used <a href="https://en.wikipedia.org/wiki/Google_Contributor">Google Contributor</a> a few years ago. Now I think I pay the whatever the price advertisers pay to show me the ads, but then the advertisers subsidize me fully to get me to see their ads.</p>
<p>The concept of Google Contributor is very simple. You can understand it if you ever purchased targeted ads online. You set up a budget between $2 and $15 per month. You use that budget to buy ads targeting a single person – yourself. You compete against any advertisers that happen to target you. Every time when you are supposed to see an ad, the bidding process happens between you and other advertisers. If you win, you see a message saying “thank you for being a contributor” in the place where the ad should be displayed. If you lose, you see the ad from the winning advertiser, just like if you were not using Google Contributor.</p>
<p>I would imagine I win in every case so I never see any ads. The cost I pay for that is the price I actually pay for using the “free” service. When I don’t win or when I don’t use Google Contributor altogether, it’s advertisers subsidizing me but I have to see their ads, but it’s still the same price.</p>
<p>It’s interesting that different user pays different price. It’s like price discrimination. If you are worth more in the eyes of advertisers, you pay more. If your impression is worth less, you pay less. It’s easy to guess that an average user in the US would pay more than an average user in India.</p>
<p>If we apply similar price discrimination to a subscription based service (e.g. Microsoft Office 365, which is cheaper in China), we need to use IP check or other methods to prevent customers in a higher price region from purchasing from a lower price region. There’s no such need when the service is paid by targeted ads. A user in the US can’t pretend to be in India and then “pay” less by seeing lower cost ads. It’s also not in the US user’s interest to do so, because ads targeting an Indian user is less relevant and doesn’t improve experience.</p>
<p>In conclusion, I think targeted ads is an effective form of price discrimination. What do you think? (Feel free to comment after the post.)</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-87394245525429891752018-08-31T07:59:00.000-07:002018-08-31T07:59:10.595-07:00Harp/Jade Debug Snippet<p>I’m using <a href="http://harpjs.com/">Harp</a> with <a href="http://jade-lang.com/">Jade</a> recently. At the beginning, it was hard for me to figure out the JSON data structure used by Harp at build time. It was also hard to debug JavaScript function written in Jade and executed at Harp compile time. In the end, I figured out that I could dump that JSON as a string to <code>console.log</code> in browser. Everything is so much easier now.</p>
<script src="https://gist.github.com/CatChen/e2ad53a2050b76e3d15fc5f33ea37ecc.js"></script>
<p>Now I have that <a href="https://github.com/CatChen/catchen.me/blob/master/public/_partials/debug.jade"><code>debug.jade</code></a> file in <a href="https://github.com/CatChen/catchen.me">my project</a>. Whenever I want to examine some JSON data in Harp, I just call <code>!= partial('debug', { data: anything })</code> and pass the right <code>data</code>.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-60868819359277328452018-05-16T23:50:00.002-07:002018-05-17T22:04:03.065-07:00Divide Notifications into Interrupt, Reminder and Backlog<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/cat_hsfz/27292153457/in/datetaken-public/" title="iOS push notification"><img src="https://farm1.staticflickr.com/907/27292153457_6bf1f82b0c_b.jpg" width="512" height="453" alt="iOS push notification"></a></p>
<p>I ran an experimentation for more than a year. I put all of my iOS devices into permanent Do Not Disturb mode. It was a great experience, but I felt I should let very small amount of push notifications through.</p>
<p>My initial intention of setting permanent Do Not Disturb was to avoid distraction in my 1:1s. I could see how other people looked at their phones during 1:1. Sometimes it’s a push notification. Sometimes they were just not paying attention to me. I wanted to avoid doing that, so I put my iPhone into DND mode. My Apple Watch mirrored, so it didn’t make a sound or vibrate either.</p>
<p>My experience with permanent DND was good. I no longer had the unconscious reaction to look at my phone when it vibrated. I didn’t need to put away my phone, because it acted like it never received those push notifications until I proactively look at it. There was one small problem: Somtimes I missed those really important push notifications, and then I missed meeting or a time sensitive message.</p>
<p>Because I was very happy with the silence of almost all notifications, I didn’t want to change that. I only wanted a small patch to let some of them through. That triggered me to think about what my ideal push notification model would be. My conclusion was push notifications should be divided into 3 categories:</p>
<ul>
<li><strong>Interrupt</strong>: An interrupt should interrupt whatever transaction I’m in and get me to deal with it immediately. This should be extremely rare, and I shouldn’t miss when it happens.</li>
<li><strong>Reminder</strong>: An ideal reminder should remind me of things in the perfect context, which usually means right time (in between transactions) and right place.</li>
<li><strong>Backlog</strong>: This is like an email sitting in my Gmail unopened for years. If I want to take a look, I can. Otherwise, don’t come to me. Backlog should never be pushed. It should be pulled.</li>
</ul>
<p>These are the ideal categories, but iOS doesn’t allow exact setup like this. I have to tweak a little bit and make it implementable within iOS. There are these constraints:</p>
<p>First of all, iOS push notification settings are on per app basis. Because most apps don’t provide finer granularity on push notification control, that means I have to assign category to apps. If I assign interrupt category to an app, every push notifications from that app is an interrupt.</p>
<p>Second, only built-in apps have complex notification settings for iOS and watchOS. For most apps, there’s only one setting for sound. It’s either on or off. Vibration is associated with sound and they are toggled together. There’s an additional toggle for Apple Watch. It’s all or nothing. That means if sound and vibration is on for iPhone, it’s also on for Apple Watch.</p>
<p>One more thing. Because existing technology can’t remind me in perfect context, I have to allow apps to remind me at its convenience and use snooze to make it wait for the perfect context.</p>
<p>Based on these constraints, here is my setup:</p>
<ul>
<li><strong>Interrupt</strong>: Apps in interrupt category can use sound/vibration. They can show notifications on Apple Watch. I have certain messaging apps in this category. They are not my main messaging apps.</li>
<li><strong>Reminder</strong>: Apps in reminder category can also use sound/vibration. They need to provide a snooze button in notification. They show up on Apple Watch. I have some calendar and todo apps in this category.</li>
<li><strong>Backlog</strong>: No sound and thus no vibration. Not allowed on Apple Watch. This is exactly the same as permanent DND mode.</li>
</ul>
<p>Besides, I disabled badge for apps that are abusing it. If an app uses badge to get my attention frequently, I disabled badge for it.</p>
<p>There’s a 4th category that I would call <strong>null</strong>. Apps in this category use push notification to promote themselves in a way that’s not valuable to me. I disabled push notification for them.</p>
<p>This setup isn’t perfect. Apple doesn’t design iOS to work in this way. Apple gives some level of control to users, but no automation for power users. At the moment, I have to turn off sound for most apps one by one because they are in <strong>backlog</strong> category. I have to turn them off for Apple Watch, too. If I can make this default settings for all new apps, it could save me time.</p>
<p>This is my new experimentation for 2018. It’s working well so far. Maybe I’ll give another round of update in 2019 and identify new ways to tweak this. I hope iOS and Android have more power ways to manage notifications at that point.</p>
<script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-3778577062415468392018-05-14T09:20:00.000-07:002018-05-15T09:15:33.632-07:00DNS over Phone<p><iframe width="560" height="315" src="https://www.youtube.com/embed/xQoR9sBsEtk" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></p>
<p>This is very impractical, and I did it for fun anyway. If Cloudflare can do <a href="https://developers.cloudflare.com/1.1.1.1/fun-stuff/dns-over-sms/">DNS over SMS</a>, then somebody is going to build DNS over snail mail some day.</p>
<p>The DNS over Phone setup is very simple. We need a <a href="https://itunes.apple.com/us/app/id915249334?at=1001lLer">Workflow (iOS)</a> to query DNS over HTTPS. We also need an <a href="https://ifttt.com/">IFTTT</a> service to call ourselves. That’s it. Below are the Workflow and IFTTT applet you can import immediately:</p>
<h2><a href="https://ifttt.com/applets/N7NwURKq-call-me-with-result-from-workflow">IFTTT Applet</a></h2>
<p>This applet uses “Workflow” as the trigger and uses “Phone Call (US only)” as the action. It’s built on <a href="https://platform.ifttt.com/">IFTTT Platform</a> so it’s shareable. It takes one ingredient from the input and announce it in the phone call. That ingredient’s value would be coming from the Workflow.</p>
<h2><a href="https://workflow.is/workflows/350519b9240749658ed21089e8603429">Workflow</a></h2>
<p>This Workflow asks for a domain. (If we give it an URL, it extracts the domain from the URL.) Then it sends the domain to <a href="https://dns.google.com/">Google Public DNS</a>, which provides DNS over HTTPS service. The response from Google Public DNS is in JSON. We want to read <code>json.Answer[json.Answer.length - 1].data</code> from it, because that would be the IP address we are looking for. In the end, we trigger the IFTTT Applet with the IP address as the only ingredient.</p>
<h2>FAQ</h2>
<p><strong>Q: Why do we use Google instead of Cloudflare for DNS over HTTPS?</strong><br/>
A: They provide JSON response in very similar format. Google’s response has <code>Content-Type: application/x-javascript; charset=UTF-8</code> header, while Cloudflare’s has <code>Content-Type: application/dns-json</code>. That tiny bit of difference makes Workflow treating Cloudflare’s response as a binary file instead of text. There might be a way to get the text out of a file. When I figure that out I can provide Cloudflare as an option.</p>
<p><strong>Q: Why do we read from the last item of <code>json.Answer</code> array?</strong><br/>
A: If the domain uses <code>CNAME</code> record, then <code>json.Answer</code> will contain multiple items. The last item would be the <code>A</code> record pointing to the IP address. Other items would be <code>CNAME</code> records.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-47032602860532775922018-04-08T15:08:00.003-07:002018-04-08T15:08:33.890-07:00Starting my Patreon experiments<p>I decided to start two experiments with two <a href="https://www.patreon.com/posts/welcome-to-my-18059804">Patreon</a> tiers.</p>
<p>The first one would allow patrons to read my blog posts 1 week before they are publicly available on my regular blogs (on catchen.me). I’m not sure how many people would actually buy this, because my blog posts are usually not time sensitive at all. It’s a way to allow patrons to encourage me to write blog posts more often. The 1 week gap is more like a symbolic reward.</p>
<p>The second one is more interesting. I’m opening 1:1 at $100/hr for anybody interested in talking to me. I know some people are willing to pay me to answer questions on Zhihu, but at a much lower price and probably needs much less time. The reason I hesitate to answer those questions is the lack of building trust and relationship. People can get an answer and then go, but that doesn’t creates any long term value. I want to see if I can building long lasting relationships through monthly 1:1 while getting paid at a reasonable rate.</p>
<p>Because monthly 1:1 isn’t really scalable, I limit 5 patrons at this tier. If this becomes popular, I’ll find a solution for the scalability problem. It’s a good-to-have problem, so I don’t think about it for now.</p>
<p>If you are interested in, here’s a link to <a href="https://www.patreon.com/catchen">my Patreon profile</a>. You will be able to find the reward tiers there. </p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-66986997285480623992016-12-06T23:22:00.001-08:002017-01-02T00:45:42.837-08:00Anova Precision Cooker (Wi-Fi)<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/cat_hsfz/31074967890/in/datetaken-public/" title="IMG_0743"><img src="https://c3.staticflickr.com/6/5777/31074967890_a79e637060.jpg" width="375" height="500" alt="IMG_0743"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p>
<p>I was one of the backers of Anova’s Bluetooth precision cooker when it was on <a href="https://www.kickstarter.com/projects/anova/anova-precision-cooker-cook-sous-vide-with-your-ip">Kickstarter</a>. It was useful and reliable. I started doing a lot of sous vide after I got it (because I really like meat and seafood). When their <a href="http://amzn.to/2izB2ZG">Wi-Fi version</a> came out, I really want one. And finally, it’s on discount so I bought one.</p>
<p>The box looks nicer and bigger than the tube that came with the Bluetooth version. There are more friendly informations in it to help you get started. Overall, those are useful for new customers but are meaningless to me. I would just install the app and start cooking.</p>
<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/cat_hsfz/31074969600/in/datetaken-public/" title="IMG_0744"><img src="https://c1.staticflickr.com/6/5752/31074969600_954fce64d0_m.jpg" width="240" height="180" alt="IMG_0744"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<a data-flickr-embed="true" href="https://www.flickr.com/photos/cat_hsfz/31074971200/in/datetaken-public/" title="IMG_0745"><img src="https://c1.staticflickr.com/6/5793/31074971200_9820631d48_m.jpg" width="240" height="180" alt="IMG_0745"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p>
<p>The first problem I ran into is it couldn’t connect to my Wi-Fi. I read through their <a href="https://support.anovaculinary.com/hc/en-us/articles/205924616-Precision-Cooker-WI-FI-Connectivity-Troubleshooting">troubleshooting</a> for a few times and wondered what was wrong. I thought maybe because I had 5GHz network sharing the same SSID as 2.4GHz network, but I couldn’t turn it off because it’s <a href="https://eero.com/">Eero</a>. What else could be wrong? Anova said Wi-Fi password should be between 8 and 18 characters. Mine is definitely much longer than that. I changed the password and it worked.</p>
<p>Changing Wi-Fi password isn’t free though. I have other smart things connected to my Wi-Fi, for example my <a href="http://english.catchen.me/2016/07/alexa-tell-me-a-joke.html">Amazon Echo</a>. After changing the Wi-Fi password, I have to update all these devices. None of them have password length limit. Anova is the first one that has such weird restriction.</p>
<p>Naturally my next step was to compare it with the Bluetooth version. They look almost exactly the same. You can only tell the difference by looking at the max/min water level marker. Their temperature readings were also different, so I got to run calibration.</p>
<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/cat_hsfz/31074972220/in/datetaken-public/" title="IMG_0746"><img src="https://c5.staticflickr.com/6/5621/31074972220_fa0b3c599b_z.jpg" width="480" height="640" alt="IMG_0746"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p>
<p>When I received my Bluetooth version from Kickstarter, Anova said the first batch was not correctly calibrated. They said we could ship it back for calibration or we could go through ice bath to calibrate by ourselves. I was too lazy to do either one, so I expected it to be slightly off.</p>
<p>Now they were different, and they were both different from my thermometer, I wanted to calibrate. I didn’t know which one to trust, so I chose to use my thermometer as standard. (I’m still too lazy to do ice bath, because I don’t keep ice in my freezer.) I used their apps to adjust the offsets. In the end they were within 0.1 degree difference and I was happy with that.</p>
<p>Overall I like the Wi-Fi version, even though I haven’t had a chance to use it through Wi-Fi yet. The reason is I don’t cook at workdays, so it’s not common for me to start cooking while away. For food safety reason, I should keep the food in ice bath before starting cooking from remote. That’s another hard to satisfy requirement.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-17122725873835652142016-07-17T23:18:00.001-07:002017-01-02T00:47:11.930-08:00Alexa, tell me a joke.<p>I bought <a href="http://amzn.to/2hJ5IYJ">Amazon Echo</a> during the Prime Day. I wanted to know how useful this is and it was $50 off, so I placed the order. On the same day I bought 2 sets of Philips Hue starter kit from Best Buy. Each was $50 off, so it’s a great deal. </p>
<p>When I received the Amazon Echo, I thought the set up would be easy like Siri. I was wrong. First it needed to connect to Wi-Fi. There’s no way to use Bluetooth to control Echo and set up Wi-Fi. The way it works is it broadcasts an ad-hoc Wi-Fi and you connect to it to set up. After spending 20 minutes on setting it up, the only thing that’s useful and works for me is “Alexa, tell me a joke”.</p>
<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/cat_hsfz/28100419770/in/datetaken-public/" title="IMG_0639"><img src="https://c3.staticflickr.com/8/7635/28100419770_f528a99afb.jpg" width="500" height="375" alt="IMG_0639"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p>
<p>What I really want Alexa to work with is Philips Hue and Logitech Harmony. It can scan Hue lightblubs by itself. It’s smart enough to get all of them. However when I said “Alexa, turn on lights in the bedroom” it told me there’s no “bedroom”. It took me some time to understand that the bedroom concept set within the Hue app isn’t shared. I needed to create a group named “bedroom” in Alexa. That is not very smart.</p>
<p>The next one to set up is Harmony. The <a href="https://support.myharmony.com/en-us/harmony-experience-with-ifttt">official page</a> says it’s compatible with Alex, but in reality it needs to go through IFTTT. After setting up IFTTT recipe, I found out I need to use the magic word “trigger” when proxying command through IFTTT. Instead of “Alexa, watch AppleTV”, I need to say “Alexa, trigger watch AppleTV”. If I forget the magic word, Alexa will complain about there’s no “AppleTV”. This is really annoying, because now I need to consciously think about the whether I’m talking to Harmony before issuing a command.</p>
<p>I set up Hue two days before Alexa. After setting up Alexa, it seems to create some kind of wireless interference around it. That made some of my Hue light bulbs unreachable from the bridge. I had to move the bridge around and switch channel. That’s extra inconvenience. </p>
<p>Sometimes I forget that I should control lights through Alexa or my phone. I just flip the switch and then realize I shouldn’t. I’m going to order light switch cover and Hue wireless dimmer. These two things are two separate patches. They don’t work together, which is a little bit disappointing. I don’t want to modify my light switch. I want to cover it and put the wireless dimmer on top of the cover. Since old style light switch cover doesn’t have a flat surface, I will have to put the wireless dimmer on the side.</p>
<p>Overall Alex does what I want it to do but doesn’t meet my expectation of intelligence. I think the problem is the lack of domain knowledge of each device it connects. Unlike Siri, which knows every type of query Apple can handle, Alexa doesn’t know how I group light bulbs into rooms or what kind of devices Harmony controls. I have to set up all kinds of routines to handle the domain knowledge while Alexa is just a voice interface to execute command. In some sense, Alexa isn’t much better than CLI or Alfred. I still need to do the scripting by myself. At best it can accept some parameters when triggering the script. </p>
<p>This reminds me of <a href="https://blog.mozilla.org/labs/2008/08/introducing-ubiquity/">Firefox Ubuiquity</a>, an ambitious project that wanted to process command like “book a flight to Chicago next Monday to Thursday, no red-eyes, the cheapest”. After 8 years, the ability to process this kind of command still seems so far away. Domain knowledge discovery is not solved.</p>
<p>Actually, it’s solved in theory but nobody cares. Look at the landscape of public APIs. All these so-called RESTful APIs are not RESTful. They are only CRUD APIs. Nobody cares about the real RESTful API that supports discovery and uses hypermedia transition. (If you don’t know the difference, read <a href="http://shop.oreilly.com/product/9780596805838.do">REST in Practice</a>.) If devices have truly RESTful APIs, it’s possible for client to discover and negotiate parameters. With a hypertext document describing the APIs, Alexa or any voice interface should be able to describe parameters to me. If I ask Alexa to book a flight, it should be able to learn from the API that it requires date time and destination. If I’ve given these parameters, Alexa should pass them through; otherwise it can ask me and keep the conversation going.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com1tag:blogger.com,1999:blog-37583856.post-41851595925802733332014-07-01T00:10:00.000-07:002014-07-01T00:10:12.816-07:00The Dreams Fight Back<p>I have this <a href="http://en.wikipedia.org/wiki/Lucid_dream">lucid dream</a> ability, or maybe I used to have. The ability grew gradually since high school, but it seems I started losing it since last year.</p>
<p>Before I had this ability, I would wake up (or not) after a nightmare. Then I found out I could continue the dream after waking up. When I wake up, I can clearly remember the last part of the nightmare. Because I’ve woken up, I can reconsider the situation and rewrite the dream as I like. If I was chased by enemy, I can give weapon to myself just like typing a cheat code in an FPS game.</p>
<p>Gradually this process became more and more smooth. Suppressing waking up became not waking up at all. As soon as I feel I’m unreasonably threatened, I know it’s a dream and I can turn the table around. After several years of practice, I can enter a state like god mode in a game. Chased by enemy again? Freeze the time and fly up to the sky. From there I can reduce the number of enemy and increase the number of ally. Then I can come back to the ground and resume the game. If it’s unbalanced due to the enemy being too weak, I can pause the game and rebalance it. It’s just like balancing a custom RTS map.</p>
<p>I also gained the ability to wake myself up if I’m aware of being in a dream. It’s like the opposite of suppressing waking up. If I go to the bathroom for three times, no matter whether I manage to use it, I know it’s a dream. Then I can tell myself I really need to leave the dream and use the bathroom. All I need to do is trying to feel my sleeping body and open my eyelids. That’s enough to kick me out of the dream.</p>
<p>The ability isn’t perfect. It’s based on pattern recognition as far as I can understand. Unreasonable threat is the easier one. After that, I observed the repeating bathroom pattern and set up the rule of three for myself. However there are situations that I’m aware of being in a dream but I can’t do anything about it. One example is when I feel I really miss someone while I manage to keep contact with that person in my dream.</p>
<p>There was one time I recognized that person I miss doesn’t exist in real life, so it must be a dream. However I can only alter dream content but I can’t suppress feeling from the outside. It’s just like the need to use bathroom, but the result is the opposite. Instead of waking myself up after realizing it’s a dream, I chose to cling to that feeling and refuse to wake up as long as possible.</p>
<p>That wasn’t the weirdest case, as my dreams started to fight back and take the ability from me. Like I said, feeling being threatened can make me recognize it’s a dream. So my dreams started to steer away from that pattern. Now I stopped dreaming about I’m being chased. Instead, I dreamed about city being blown up. It’s just like immersive gaming experience. I watch unreasonable things happen, but I don’t feel getting hurt thus I don’t recognize being in a dream.</p>
<p>The weirdest thing is I started to use in-dream ability without realizing it’s a dream. There was a recent experience like this: I felt somewhat threatened. I closed my eyes and tried to think about me being in another location. I opened my eyes to see if I’ve been teleported to that location. I knew that I don’t have the teleportation ability all the time and I couldn’t tell when I have it. This strange understanding tricked me into not recognizing the dream, but I managed to teleport myself anyway.</p>
<p>In the end, I can’t tell whether the recent experience is a step forward or backward. It can be explained as a step forward. The explanation would be I can recognize and alter my dream so smoothly that I don’t consciously recognize my dream and it happens unconsciously. The opposite is also obvious: My dreams try to fight back and stay away from the pattern that I already can recognize. I don’t know which one is true, but it’s an interesting experience.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com3tag:blogger.com,1999:blog-37583856.post-12448610715256844172013-12-13T00:06:00.001-08:002013-12-13T00:06:30.920-08:00Pencil from FiftyThree<p>I just received my <a href="http://www.fiftythree.com/pencil">Pencil</a> from FiftyThree today, and here’s how it looks like and how it feels like.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/11349562136/" title="2013-12-12 20.35.41 by Cat Chen, on Flickr"><img src="http://farm3.staticflickr.com/2833/11349562136_b46df50977.jpg" width="375" height="500" alt="2013-12-12 20.35.41"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/11349483195/" title="2013-12-12 20.38.35 by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7369/11349483195_3ca21b4c0b.jpg" width="375" height="500" alt="2013-12-12 20.38.35"></a></p>
<p>It comes within a nice paper tube. When you open it, you will notice the packaging is really nice. You would first pull out the Pencil, and then you would see the quick start manual. The plastic wrapper around the Pencil says you have to charge it first, so I pulled out the battery and plug it into an USB port.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/11349642643/" title="2013-12-12 21.41.42 by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7313/11349642643_2a60e0db42.jpg" width="500" height="375" alt="2013-12-12 21.41.42"></a></p>
<p>It took maybe an hour to charge, and then the light turned from yellow to green. I don’t know how long the battery will last, but I think Bluetooth LE shouldn’t use a lot of energy. Now let’s start the Paper app.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/11349557566/" title="2013-12-12 21.44.24 by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7430/11349557566_e5c67e0719.jpg" width="500" height="375" alt="2013-12-12 21.44.24"></a></p>
<p>I picked the walnut version. My piece of walnut wood doesn’t look as good as advertised, with less wooden color strips but more of those small cracks on the surface. I wouldn’t say it’s ugly, but just not as polished as I imagined. I tried to smell it. It was a silly idea because it doesn’t smell like wood at all.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/11349639223/" title="2013-12-12 21.46.24 by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7455/11349639223_b57e43dfb1.jpg" width="500" height="375" alt="2013-12-12 21.46.24"></a></p>
<p>Establishing connection with the app is much easier than <a href="http://www.tenonedesign.com/connect.php">Pogo Connect</a>. You don’t need to install another app (of course), and you don’t need to click a button on the stylus to initiate the connection. Just use the Pencil to touch the pencil icon in Paper for a few seconds and it’s connected. Then you can start drawing without worrying about how it recognizes the Pencil (including the eraser), your fingers and your palm.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/11349586404/" title="2013-12-12 23.17.47 by Cat Chen, on Flickr"><img src="http://farm6.staticflickr.com/5532/11349586404_1616cfb523.jpg" width="500" height="375" alt="2013-12-12 23.17.47"></a></p>
<p>During my drawing, I noticed that Paper might recognize the Pencil (including eraser) as my finger. I guess it’s because I was not pressing the Pencil hard enough, so Paper received a touch without receiving Pencil down through Bluetooth. I tried to press harder when I use the Pencil and it seems that fixed the issue.</p>
<p>Drawing with Pencil is quite comfortable to me. It’s easier to control where the tip goes compared to Pogo Connect (or maybe my control over stylus has improved). The erasing area is not as big as the eraser, so I’d rather use undo if possible, because erasing a large area needs a lot of movement. Overall, I like drawing with Pencil more than with my previous styli, and $60 seems like a reasonable price to me.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com1tag:blogger.com,1999:blog-37583856.post-30708338431572488202013-08-31T13:16:00.000-07:002013-08-31T13:16:59.210-07:00W/Me - health tracker or notifier?<p>If you don’t know what W/Me is, here’s its Kickstarter introduction:</p>
<p><iframe width="480" height="360" src="http://www.kickstarter.com/projects/723246920/finally-a-wearable-device-that-can-improve-your-li/widget/video.html" frameborder="0"> </iframe></p>
<p>It’s the fastest Kickstarter project delivery I’ve ever seen. It’s funded on 7/2 and shipped on 8/26. That’s less than 2 months. I don’t know if they’ve secured funding and built the thing then used Kickstarter to get some early adopters. This speed is really amazing to me.</p>
<p>I got mine a few days ago. It came with a Kickstarter limited edition wooden box. Inside the box, there’s W/Me plus 3 color bands. (I grabbed the last early bird spot at the last moment. Maybe somebody got cold feet?) There’s quick start guide, alcohol pad and a small note that says “the device may not be functioning perfectly in some particular individuals”. (Hope I’m not that special!)</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/9628439216/" title="Untitled by Cat Chen, on Flickr"><img src="http://farm4.staticflickr.com/3702/9628439216_21c72af640.jpg" width="500" height="375" alt="Untitled"></a></p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/9628440364/" title="Untitled by Cat Chen, on Flickr"><img src="http://farm3.staticflickr.com/2811/9628440364_670661df25.jpg" width="500" height="375" alt="Untitled"></a></p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/9625208977/" title="Untitled by Cat Chen, on Flickr"><img src="http://farm3.staticflickr.com/2861/9625208977_30d36b22ba.jpg" width="500" height="375" alt="Untitled"></a></p>
<p>Before I received my W/Me, I noticed some Kickstarter backers said that it’s too small for them. However it’s obvious that W/Me is too large for my wrist. That made me wonder whether I should wear it around my wrist or around my forearm. (Later I found out I have to wear it around my wrist.)</p>
<p>Setting up the Bluetooth was easy, but starting the first measurement was rough. It kept saying that it couldn’t get good signal. I tried to touch it with different positions and my first measurement was still ended with a time-out. So I cleaned the sensors and tried again. The measurement wasn’t easy. It needs 3 minutes of data and any interruption requires at least 5 seconds to resume. At the end of the measurement I got 3 numbers: Agility, ANS Age, BPM. The problem is I don’t know their accuracy and I can’t verify them. (I can verify BPM with another app.)</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/9639919970/" title="W/Me measurement by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7333/9639919970_1ecab8803d.jpg" width="282" height="500" alt="W/Me measurement"></a></p>
<p>After a few measurements, I started to learn how to do it properly. The best position to measure is above my wrist bone. Touching that area of the skin will give me stablest signal. However that only means no interruption. It doesn’t mean anything in terms of data quality. When the app shows a line that doesn’t look like a normal pulse line or a BPM way beyond 100, it’s collecting garbage. (Garbage in, garbage out.) So the real goal is to get data that makes sense to me.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/9639920482/" title="W/Me measurement by Cat Chen, on Flickr"><img src="http://farm6.staticflickr.com/5537/9639920482_d89dc8c0c4.jpg" width="282" height="500" alt="W/Me measurement"></a></p>
<p>(As you can see, when I tried to touch two buttons at the same time in order to take this screenshot, the pulse line went awry immediately.) I have to say that collecting the right data takes determination plus a little bit of luck. That discourages me from taking more measurement and makes coaching really hard to follow. Without the right data there’s no feedback, and without feedback coaching is a useless feature.</p>
<p>Besides health tracker features, W/Me has some small utility features. For example, in flashlight mode it turns on all its white LED lights and that’s actually brighter than my iPhone screen when in dark. It can be used as a remote control to iPhone camera, but I wish it works with <a href="https://itunes.apple.com/us/app/camera+/id329670577?mt=8">Camera+</a>. (Update: It actually works with Camera+ but in a weird way. I need to turn on find-my-iPhone and that signals Camera+ to take one photo.)</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/9637005323/" title="W/Me display by Cat Chen, on Flickr"><img src="http://farm4.staticflickr.com/3788/9637005323_8850c886ba_q.jpg" width="150" height="150" alt="W/Me display"></a><a href="http://www.flickr.com/photos/cat_hsfz/9640243136/" title="W/Me display by Cat Chen, on Flickr"><img src="http://farm6.staticflickr.com/5503/9640243136_8462358cba_q.jpg" width="150" height="150" alt="W/Me display"></a><a href="http://www.flickr.com/photos/cat_hsfz/9640244150/" title="W/Me display by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7428/9640244150_ded5d74558_q.jpg" width="150" height="150" alt="W/Me display"></a></p>
<p>It vibrates when there’s new email or Facebook notification, but the unread number disappears so fast that I usually only get to know that there’s unread email or there’s unread Facebook notification. I wish they increase that delay (or allow me to adjust it) in next update. It can signal the iPhone to start playing W/Me theme music, which acts a find-my-iPhone feature. However when my iPhone was connected to a Bluetooth speaker, that music was played through that Bluetooth speaker, which makes the feature useless.</p>
<p>In conclusion, W/Me is a cool gadget but it’s not something that “just works”. If you just want some reliable health tracker, go back to Fitbit. To me, as long as it doesn’t break like Jawbone UP, I’m okay with it.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-14875743464286700122012-06-14T09:34:00.001-07:002014-03-30T22:32:15.585-07:00JAMBOX from Jawbone<div class='posterous_autopost'><p><div class='p_embed p_image_embed'>
<a href="http://www.flickr.com/photos/cat_hsfz/7186948557/" title="JAMBOX from Jawbone by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7214/7186948557_69f3f477ff.jpg" width="500" height="375" alt="JAMBOX from Jawbone"></a></div> </p>JAMBOX is my second purchase from Jawbone. I bought UP, and because a lot of people found hardware problem with UP Jawbone gave everybody $150 credit without asking it back. I didn't want to buy another headset so I got a JAMBOX. <p>The first JAMBOX I received was broken. It was frustrating. I tried my best to fix it because I don't want to give it a round trip to the US. The problem is the battery. It never charged. Updating firmware didn't help. There's no fix. When I talked to Jawbone, they sent me a new one after they confirmed my purchase and allowed me to ship the broken one back after I received the new one. That's convenient. </p><p>When I got the new one, I found that it sounds good! When I watch a movie on my iPad, I connect JAMBOX and it makes the surrounding sound more real. When I need to play Diablo III on my MacBook Pro, I also use JAMBOX to enhance the sound effect. It's a really good companion if you have several device with just okay speakers. </p><p>I also use it when I make conference call. I know it has a microphone but I think it's more convenient to use my computer's. I might put my JAMBOX at any place but I don't what to yell at my JAMBOX, so speaking to my computer feels more comfortable to me. (Maybe I should test JAMBOX's microphone range in the future.)</p><p>The only problem of JAMBOX is that it falls into sleep when it's connected but no sound is played. Sleeping is not a problem indeed, but waking up is. For instance, when you resume a movie that you paused, JAMBOX will wake up but skip a few seconds. Your computer doesn't know that and wouldn't wait for that, so if you pause during a conversation you will miss a few seconds of that conversation when resumed. </p><p>Maybe sleep is designed to save battery life, but I think the battery life is so good that it shouldn't sleep when idling for less than 5 minutes. I have fully charged my JAMBOX once, and it's still half full after two weeks. Why not allowing me delaying sleep while giving me better experience? </p><p>My overall experience with JAMBOX is good. If the price could be lower, I think there will be more people buying it to enhance their portable devices' sound.</p> <p style="font-size: 10px;"> <a href="http://posterous.com">Posted via email</a> from <a href="http://posterous.catchen.me/jambox-from-jawbone">Cat Chen's Posterous</a> </p> </div>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com2tag:blogger.com,1999:blog-37583856.post-56702405573602851372012-03-02T19:55:00.000-08:002012-03-02T20:17:04.856-08:00iOS Simulator disables system location services<p>When I wanted to make a change to my iCloud settings last night, I noticed that Find My Mac had a problem. The problem was location services was turned off, so I turned it on and went back to iCloud settings. However that annoying exclamation mark is still there.</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/6948096757/" title="iCloud by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7186/6948096757_0268f86389.jpg" width="500" height="395" alt="iCloud"></a></p>
<p>Then I went to Security & Privacy panel and enabled it again and checked if it's reset when I left. The result is positive. Whenever I left this panel it's reset. That means there was something broken with my MacBook Air. Its mother board was just replaced and I don't want to visit Apple Store again. I wished it's just software</p>
<p><a href="http://www.flickr.com/photos/cat_hsfz/6948095759/" title="Security & Privacy by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7208/6948095759_0662d969c3.jpg" width="500" height="413" alt="Security & Privacy"></a></p>
<p>I googled it and found <a href="https://discussions.apple.com/message/16487603#16487603">the answer</a>. It's not the time zone. It's the iOS Simulator. I was experimenting something with jQuery Mobile and thus running iOS Simulator.</p>
<p>So does it mean if you lost your Mac when iOS Simulator is running you are not going to get it back by using Find My Mac? No. The location services toggle just appears to be off but no change actually happens to your Mac. I guess iOS Simulator's fake location just affects Mac's system level settings.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-63130856736358151332011-12-29T08:31:00.001-08:002013-08-03T10:37:04.339-07:00UP to 72 hours<div class="posterous_autopost">
<br />
<div class="p_embed p_image_embed">
<a href="http://www.flickr.com/photos/cat_hsfz/6594751505/" title="UP to 72 hours by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7165/6594751505_fb27df1e2f.jpg" width="374" height="500" alt="UP to 72 hours"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6594752013/" title="UP to 72 hours by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7143/6594752013_77ae685b85.jpg" width="500" height="374" alt="UP to 72 hours"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6594752475/" title="UP to 72 hours by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7144/6594752475_d54720382e.jpg" width="500" height="374" alt="UP to 72 hours"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6594753039/" title="UP to 72 hours by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7172/6594753039_b5cb7dae83.jpg" width="500" height="374" alt="UP to 72 hours"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6594753569/" title="UP to 72 hours by Cat Chen, on Flickr"><img src="http://farm8.staticflickr.com/7001/6594753569_3345bd4472.jpg" width="333" height="500" alt="UP to 72 hours"></a>
</div>
<br />
Since I care more and more about my health, I started looking at health gadgets. Fitbit looks popular but Jawbone UP got my attention. It's new and the concept looks great: a bracelet that you don't need to be aware of. Unlike Fitbit, you don't need to remember clipping it to your clothes because you don't take it off.<br />
After its launch, most reviews were positive. Then something just went wrong. People started reporting their UPs are dead after several days of use. I doubted if I should buy one but I still wanted to take the risk, so I bought one. <br />
Now I've been using my UP for 72 hours and luckily it's not dead yet. I love using it though there are minor problems. <br />
The good parts are it does encourage me to take more walk and track my meals. These two are very important to me. I'm spending too much time sitting and I'm eating too much, which make me uncomfortable. Reminding me every 30 minutes of sitting makes me leave the seat and refill my cup. Tracking meals helps me understand in what situation I might eat more than I should.<br />
The sleep tracking satisfies my curiosity but that's all of it. I can do nothing to improve my sleep quality. I don't have an experiment plan to explore what affects my sleep yet.<br />
The bad parts are it only tracks arm movement and it's not accurate in all circumstances. Workout tracking only tracks activities involve arm movement. Push up tracks nothing while brushing my teeth looks like some intensive workout. <br />
Overall, I still like my UP and wish Jawbone could make it better in the future. <br />
P.S. Because Beijing is covered by hazardous haze I only do indoor workout. That means GPS tracking is meaningless to me. I'll try that when I get back to Guangzhou.<br />
<div style="font-size: 10px;">
<a href="http://posterous.com/">Posted via email</a> from <a href="http://posterous.catchen.me/up-to-72-hours">Cat Chen's Posterous</a> </div>
</div>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-41393998664394694252011-12-02T21:36:00.001-08:002013-08-03T10:35:56.665-07:00Unboxing Xiaomi Phone<div class="posterous_autopost">
<br />
<div class="p_embed p_image_embed">
<a href="http://www.flickr.com/photos/cat_hsfz/6445175869/" title="Unboxing Xiaomi Phone by Cat Chen, on Flickr"><img alt="Unboxing Xiaomi Phone" height="500" src="http://farm8.staticflickr.com/7168/6445175869_b543735d63.jpg" width="374" /></a><a href="http://www.flickr.com/photos/cat_hsfz/6445176053/" title="Unboxing Xiaomi Phone by Cat Chen, on Flickr"><img alt="Unboxing Xiaomi Phone" height="500" src="http://farm8.staticflickr.com/7161/6445176053_4dabcf7595.jpg" width="374" /></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6445176249/" title="Unboxing Xiaomi Phone by Cat Chen, on Flickr"><img alt="Unboxing Xiaomi Phone" height="500" src="http://farm8.staticflickr.com/7165/6445176249_3990f216a4.jpg" width="374" /></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6445176477/" title="Unboxing Xiaomi Phone by Cat Chen, on Flickr"><img alt="Unboxing Xiaomi Phone" height="500" src="http://farm8.staticflickr.com/7031/6445176477_ffebc3c12a.jpg" width="374" /></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6445176665/" title="Unboxing Xiaomi Phone by Cat Chen, on Flickr"><img alt="Unboxing Xiaomi Phone" height="500" src="http://farm8.staticflickr.com/7147/6445176665_dc8e8e5ebc.jpg" width="374" /></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6445176905/" title="Unboxing Xiaomi Phone by Cat Chen, on Flickr"><img alt="Unboxing Xiaomi Phone" height="500" src="http://farm8.staticflickr.com/7032/6445176905_99919c43bd.jpg" width="374" /></a></div>
<br />
Just photos. No test. Maybe next time. Because my friend wants this phone really bad, I will give this one to him and try to get another as my Android test device. <br />
<div style="font-size: 10px;">
<a href="http://posterous.com/">Posted via email</a> from <a href="http://posterous.catchen.me/unboxing-xiaomi-phone">Cat Chen's Posterous</a> </div>
</div>
Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-61663770571068701212011-09-25T03:35:00.001-07:002013-08-03T10:33:41.252-07:00Unboxing Shengda Bambook<div class='posterous_autopost'><p><div class='p_embed p_image_embed'>
<a href="http://www.flickr.com/photos/cat_hsfz/6180372945/" title="Unboxing Shengda Bambook by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6158/6180372945_89c1c8b722.jpg" width="374" height="500" alt="Unboxing Shengda Bambook"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6180898648/" title="Unboxing Shengda Bambook by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6163/6180898648_7ee11ac7db.jpg" width="500" height="374" alt="Unboxing Shengda Bambook"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6180373407/" title="Unboxing Shengda Bambook by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6160/6180373407_f528f66d87.jpg" width="374" height="500" alt="Unboxing Shengda Bambook"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/6180373657/" title="Unboxing Shengda Bambook by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6154/6180373657_eeaaf84901.jpg" width="500" height="374" alt="Unboxing Shengda Bambook"></a></div> </p>I should have posted this two months ago. I can't comment on this product because I don't use it by myself. I got it by giving a speech in a Shengda sponsored event and then I gave it to my parents. It seems my dad enjoys its Chinese text-to-speech feature a lot. <p style="font-size: 10px;"> <a href="http://posterous.com">Posted via email</a> from <a href="http://posterous.catchen.me/unboxing-shengda-bambook">Cat Chen's Posterous</a> </p> </div>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-48547304969209524032011-08-09T00:46:00.001-07:002013-08-03T10:32:49.647-07:00Unboxing Sennheiser PXC360BT<div class='posterous_autopost'><p><div class='p_embed p_image_embed'>
<a href="http://www.flickr.com/photos/cat_hsfz/5945931481/" title="Unboxing Sennheiser PXC360BT by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6133/5945931481_f07ac63510.jpg" width="500" height="374" alt="Unboxing Sennheiser PXC360BT"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/5945931871/" title="Unboxing Sennheiser PXC360BT by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6124/5945931871_7fdfc01a5c.jpg" width="500" height="374" alt="Unboxing Sennheiser PXC360BT"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/5945932205/" title="Unboxing Sennheiser PXC360BT by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6012/5945932205_ed849f7142.jpg" width="500" height="374" alt="Unboxing Sennheiser PXC360BT"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/5946488624/" title="Unboxing Sennheiser PXC360BT by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6030/5946488624_3941939a33.jpg" width="500" height="374" alt="Unboxing Sennheiser PXC360BT"></a>
<a href="http://www.flickr.com/photos/cat_hsfz/5945932967/" title="Unboxing Sennheiser PXC360BT by Cat Chen, on Flickr"><img src="http://farm7.staticflickr.com/6014/5945932967_5560f87696.jpg" width="500" height="374" alt="Unboxing Sennheiser PXC360BT"></a></div> </p>I just received my new gadget today. It's Sennheiser PXC360BT headphones. I bought it for its active noise canceling. I want to know if it's effective on a plane. I will field test it on my way back to Canton. <p>The box is not big, and you can see the headphones inside with a paper box underneath. The headphones come with a removable battery, a Micro-USB connector, audio cable, and all kinds of adapters. They are put in the traveler's bag inside that paper box. </p><p>The battery was empty, so I charged it with my MacBook Pro over the Micro-USB connector. I didn't finished the charging within an hour long lunch, so I need to measure the charge time in the future. I tested it via Bluetooth with my iPhone and also via audio cable with my MacBook Pro. The sound quality is good to me and it feels comfortable to wear. </p><p>Pairing the headphones with iPhone is simple and straightforward. Press and hold the Bluetooth button and you can connect it on the iPhone. The iPod app treats it like an AirPlay device so you can easily switch between internal speaker and Bluetooth headphones. </p><p>After lunch, I've tested it on the subway. Wearing the headphones will reduce the noise. Enabling active noise canceling will further reduce low frequency noise. With both Noise Guard and Talk Through enabled, you will hear people on the train shouting to each other in front of you. In their perspective, it's just talking. With low frequency noise canceled, it sounds like shouting. </p><p>So far, I've noticed two small problems with Sennheiser PXC360BT. One is that when charging and listening over the cables at the same time, static is noticeable. The other is that when Bluetooth is interfered sound will jump. If it's interfered for several seconds, iPhone treats it as AirPlay device disconnected and then pauses the music.</p> <p style="font-size: 10px;"> <a href="http://posterous.com">Posted via email</a> from <a href="http://posterous.catchen.me/unboxing-sennheiser-pxc360bt">Cat Chen's Posterous</a> </p> </div>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com1tag:blogger.com,1999:blog-37583856.post-85554341346351096472011-08-09T00:41:00.001-07:002011-08-09T00:41:13.914-07:00Merging Two English Blogs<div class='posterous_autopost'>I have two English blogs now. One on Blogger and another on Posterous. I wanted to post different things on different blogs. Posterous should be more light-weight and photo driven. However, I don't post often so I can't differentiate two blogs. <p>I just (eventually) started my Cantonese blog. For Cantonese input convenience, I type on my iPhone and then post it to Blogger via Posterous. That means I have the same Cantonese content on both Blogger and Posterous. (I will hide one in the future to avoid duplicated content.) so I think using the same method for my English blog should be feasible. </p><p>After merging two blogs, I don't have to worry about differentiation any more. I don't have to think about which blog to post depending on the type of content. I can focus on writing instead of tools. If I'm with my Mac, I will use MarsEdit; If only my iPhone is available, I will use Posterous. No matter what I use, all content merges to my Blogger's stream.</p> <p style="font-size: 10px;"> <a href="http://posterous.com">Posted via email</a> from <a href="http://posterous.catchen.me/merging-two-english-blogs">Cat Chen's Posterous</a> </p> </div>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-46340793977144378672011-08-08T01:57:00.000-07:002011-08-08T01:57:02.950-07:00Moving Blogs to New DomainI bought catchen.me last year and point it to my old domain catchen.biz. Now I want to use catchen.me as my main entrance. That means I need to migrate all sub-domains under catchen.biz to catchen.me.<br />
<br />
Why did I use catchen.biz at the first place? I wanted to register a domain from Google Apps in order to make sure that my domain is associated with Google Apps. Neither catchen.com nor catchen.net is available so I picked catchen.biz. However, I found out that people don't respond well to .biz domains. When I say "you can find me on catchen dot biz", they will ask "catchen dot what?" Then I understood that I needed to get a easily recognizable domain. That's why I bought catchen.me.<br />
<br />
I let catchen.me point to catchen.biz for several months and it works well, but the content stays on catchen.biz. Now I want to move the content to catchen.me and make catchen.biz a simple redirection. I wanted to do this long ago but I'm too lazy to set up an Apache for the redirection. I don't want to touch all those configurations so I kept postponing this task. Recently I found out that I can do this with pure JavaScript and that's a language I'm comfortable with, so I decided to give it a try.<br />
<br />
I wrote a <a href="https://github.com/CatChen/biz-to-me/blob/master/web.js">less than 30 lines JavaScript file</a> and push it to <a href="http://www.heroku.com/">Heroku</a>. Then it's done. And it's free! I don't need to maintain my own server. Heroku's free plan should be enough to handle a few request to my old domain. I don't need to know anything about Apache configuration. All I need to learn is Node.js, but I want to learn it for fun anyway.<br />
<br />
So far, I've migrated three sub-domains: english, cantonese, and dotnet. If the solution is stable, I will move chinese, which is the sub-domain with most content.Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-37583856.post-85420078781525084122010-12-08T01:05:00.001-08:002011-06-30T08:39:56.766-07:00Blogging with Wacom Bamboo Special Edition<p>I bought a Wacom tablet last month. This is the first blog post I try to write with it in English. (I've tried that in <a href="http://chinese.catchen.biz/2010/11/wacom-bamboo-special-edition.html">Chinese</a>.) It's nice to write instead of type, though it needs some training.</p><p>Writing in English on Windows 7 requires little training. The initial accuracy is not bad. In contrast, Snow Leopard couldn't recognize my handwriting at all. Experience of writing on Windows 7 is smooth. I can write in my way, which doesn't look good, and Windows 7 can recognize most of them. With some training, Windows 7 can do it better. Less correction is needed after writing several training sentences. And the correction works in a smart way. Windows 7 can remember what you have written even after you inserted them to the editing area, and it provides meaningful options if you want to correct a word. Space will be inserted if needed during correcting and editing. Windows 7 knows what words and sentences are. </p><p>Writing in Chinese doesn't include many features in English counterpart. You can't write freely on the baseline. You have to write one Chinese character in one box, and write them one by one. Correction is based on character instead of word. The good news is Windows 7 knows Chinese words and it recognizes Chinese characters by seeing it as part of a word. Snow Leopard doesn't supports Chinese recognition at all. I can only wish they do a better job with Lion.</p> Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0