A couple of Amazon things

Two recent Amazon purchases got me thinking about design and manufacturing.

Let’s start with design. On the recommendation of a couple of friends, I bought this LitraTorch LED light cube.

LitraTorch

It’s a nice compact little light that generates fairly uniform coverage at three intensity levels (and there’s a strobe mode, which I find useless). Also, it starts at the lowest intensity so you don’t get immediately blinded when you turn it on in a dark room. It’s not cheap, but it’s a professional tool for the inspections I do at work. Overall, I’m happy with it.

But…

It has a permanent battery that recharges through a micro USB port. I always struggle getting the orientation of the plug and port to match, and working with this one is going to be even more trouble than I’m used to.

Litra micro USB port

Every port I’ve run across before is shaped like the plug in sort of a curved trapezoid. Not this guy. The only way to know which end is up is to see the black connector part inside the black rectangle on the back of a black cube. I assume the decision to go with a rectangular port was driven by cost, but it’s an unfortunate choice, especially in an $80 flashlight.

Now let’s move on to a manufacturing problem. I also bought this LIHIT LAB pen case to hold my pens, pencils, erasers, and tweezers.

LIHIT LAB pen case

It’s a simple case and not expensive, but the first time I tried to unzip the main compartment, the zipper pull came off in my hands.

Zippers

The little hook on the slider that holds the pull had snapped off.

Broken hook from slider

Since I was at work, I took the broken hook into the lab to have a look at it under the microscope. Please forgive the fuzziness; it’s hard to get good depth of field in optical microscopy.

Zipper hook fracture surface

So many problems in how this was made. The immediately obvious flaw is the bubble in the center. This is a cheap casting made of cheap pot metal and the casting left a huge (relative to the size of the part) bubble in the center of the hook.

But I doubt that was the main cause of the failure. The hook is loaded mainly in bending, and the bending strength of a hollow part isn’t usually significantly different from that of a solid part. Many parts are deliberately made hollow to save material without reducing strength much.

You may also notice the crack extending inward from the edge on the right side. That, too, is a flaw, but didn’t lead to the failure. That crack is perpendicular to the final fracture surface and would not be affected by the bending stresses that broke the hook.

Now look at the black areas at the upper right and upper left corners of the cross-section. This is paint, and because it’s on the fracture surface, we know that cracks in those areas were already present when the hook was painted. And because they’re on the outer edges of the cross-section, where the stresses are high during bending, they are the flaws that were the most direct cause of the failure. I’m guessing the casting cracked during cooling but was not weeded out during whatever quality control process the manufacturer used.


A TaskPaper/Drafts improvement

While looking through the Drafts scripting documentation for help with another idea for an action, I found a couple of Editor functions that I hadn’t noticed before. One of them led to a big improvement to my TaskPaper action group.

As explained in this post, I use Drafts now for handling TaskPaper task lists on my iPhone and iPad. Drafts has some nice built-in features for TaskPaper lists, and its extensibility through actions allows more features to be added. I wrote a handful of actions geared toward my use of TaskPaper, and added them to the Drafts Action Directory, a repository of community-created actions. You can download them from here.

One of the actions in my TaskPaper group worked as a toggle to add/remove blank lines between tasks. I keep my lists in a format that looks like this,

TaskPaper list in Drafts

with no blank lines between projects except for the Archive section at the end. This is great for keeping the list tight but is a problem when I want to rearrange projects to bring the more important ones to the top. Drafts has an Arrange Mode which is perfect for this sort of thing, but it requires blank lines delimiters to define text groupings that can be moved as blocks.

Originally, I made an Un/Block action, which added or removed blank lines between projects, depending on the list’s current state. My rearranging workflow was this:

  1. Tap Un/Block to add the blank lines.
  2. Tap the Arrange Mode button at the bottom of the screen to enter Arrange Mode.
  3. Rearrange the projects.
  4. Tap the Un/Block button to remove the blank lines.

This worked well but was a little clumsy. Then I saw the showArrange and arrange functions in the Drafts Editor library. The showArrange function wasn’t quite what I needed, but the arrange function was perfect. It takes text as an argument, stops the flow of the script while the user rearranges it, then returns the rearranged text.

By incorporating arrange, I was able to change the script of the Un/Block action into this:

javascript:
 1:  // Allow user to rearrange projects by temporarily adding
 2:  // blank lines above all projects
 3:  
 4:  // Regexes for project headers.
 5:  var projREtight = /([^\n])(\n[^:\n]+:)/g;
 6:  var projREloose = /(\n\n)([^:\n]+:)/g;
 7:  var archiveRE = /^(Archive:)$/m;
 8:  
 9:  // Get the text of the current draft
10:  var d = draft.content;
11:  
12:  // Make new text with blank lines above all project
13:  // headers that didn't have them.
14:  var e = d.replace(projREtight, '$1\n$2');
15:  
16:  // Go into arrange mode with this new text
17:  // and wait until the user is done rearranging.
18:  e = editor.arrange(e);
19:  
20:  // Take blank lines away except above the Archive section.
21:  e = e.replace(projREloose, '\n$2');
22:  e = e.replace(archiveRE, '\n$1');
23:  editor.setText(e);

It adds the blank lines in Line 14, lets the user rearrange the blocks in Line 18, and then removes the blank lines in Line 20 (Line 21 restores the blank line above the Archive section). Now the rearranging happens within the execution of the script, so I’ve renamed the action Rearrange. The workflow is reduced to two steps:

  1. Tap Rearrange.
  2. Rearrange the projects.

Rearranging projects

This is not just faster, it’s more natural because the action is named for what I really want to do and all the necessary-but-incidental format fiddling happens behind the scenes.

So, yeah, reading the documentation can be helpful. I have a sense the activate, deactivate, and dictate functions, which are also things I missed when I first looked in the Editor section, will be finding their way into my scripts soon.


Lint

About a month ago, I started having trouble charging my iPhone 6S. I’m not talking about the need to charge my phone more often because the battery isn’t what it used to be, although that’s definitely happening and I need to cough up the $30 to get the battery replaced. No, I’m talking about the Lightning plug on the charger cable not seating well in the port on the bottom of the phone. The plug would wiggle and often lose contact, leaving me with a phone that was still draining when I thought it was charging.1

My first thought was that lint had built up in the port and needed to be cleaned out. I was at home without good lighting or good magnification, but I got a toothpick and dug around in the port, figuring that if anything was in there, that would loosen it and pull it out. When nothing emerged, I started thinking there was a problem with either the port itself or with the third-party cables I was using.

Yesterday afternoon I learned the truth. On the cusp of a long weekend, I didn’t feel like working and neither did my clients, as the usual stream of phone calls and emails dried up. I decided to go back in the lab at work and take a serious look at the Lightning port though a stereo microscope.

Lightning port before cleaning

Depth-of-field is terrible in optical microscopy, and I didn’t adjust the white balance before capturing the image, but you get the idea. The lint had been compressed by the plug until it was almost as solid as felt.

For whatever reason—most likely because the lint was too tightly packed—the toothpick hadn’t done its job a month ago and led me to think the port was clean when it obviously wasn’t. I went at it with a pair of fine-tipped tweezers and, after several minutes of digging, had a little pile of lint and a clean Lightning port.

Tweezers and lint

Lightning port after cleaning

The Lightning port is so narrow that even these tweezers couldn’t reach in and pluck out the lint. I had to use one side of the tweezers as a pick to poke at the lint and draw it out. And it took several minutes to clean because even the sharp pointy ends of the tweezers had trouble digging in and getting under the lint.

The two lessons here are don’t assume you’ve done something right without checking and don’t wait over 2½ years before cleaning out your Lightning port.


  1. I keep my phone on Mute, so there’s no sound when contact is lost. The phone does still vibrate, but I don’t always notice that because the phone isn’t in my hands. 


TaskPaper actions for Drafts 5

As I’ve written before, I use the TaskPaper plain-text format to keep track of my to-do lists. In particular, I have a main to-do list that I adjust every day, adding new projects and tasks as they come in and (I hope) marking older tasks as completed. At the beginning of every month, I make a new TaskPaper document, filling it with the uncompleted tasks from the previous month. In this way, the documents from the previous months are a record of what I was planning to do and what I finished.

On the Mac, I use Hog Bay Software’s TaskPaper app. Jesse Grosjean of Hog Bay created the TaskPaper format, and his app is still the best for creating and organizing TaskPaper documents. Unfortunately, Jesse stopped supporting his iOS TaskPaper app, and I haven’t thought much of replacements like Taskmator. As a result, I’ve been doing almost all of my TaskPaper tracking on the Mac, which isn’t the best way to keep on top of things, especially when I’m traveling.

But with a little bit of work, Drafts 5 can become a great TaskPaper app. Out of the box, it understands the project and task syntax and formats them nicely, including a strikeout for tasks marked with the @done tag.

TaskPaper list in Drafts

I created an action group that lets me do common manipulations of TaskPaper lists in Drafts. Be warned: by “common” I mean “common to me.” TaskPaper is a very flexible format, and I know a lot of people do things with it that I don’t. The actions in the group I made are tuned to my use. If you’re a TaskPaper user, some of them will be useful to you and some of them won’t. But I hope that even the actions that aren’t immediately useful to you will help you develop your own.

Let’s start with actions that upload and download my current monthly task list. I need this because I still edit this list on my Mac, so having it exclusively in Drafts isn’t an option. The current month’s task list is saved in a file named yyyy-mm.taskpaper in the Elements/tasks subfolder of my Dropbox folder,1 where yyyy and mm represent the current year and month.

The Upload Dated action (which appears as just Upload in the keyboard row) has just a single Dropbox action, reflecting the file name and folder choices:

Upload Dated action

The file name is set using template tags for the current date, and the action overwrites whatever is in that file with the contents of the current draft.

The Reload Dated action (which appears as Reload in the keyboard row) does the opposite, opening the appropriate file from Dropbox and replacing the current draft with its contents. Drafts doesn’t have a handy-dandy action for opening from Dropbox, but it does have a library of JavaScript functions for dealing with Dropbox files. Here’s the script step for Reload Dated:

javascript:
 1:  // Get TaskPaper file for current month and set the draft to it.
 2:  
 3:  // Dropbox folder where task file is.
 4:  var path = '/Elements/tasks/';
 5:  
 6:  // Assemble filename from today's date.
 7:  var today = new Date();
 8:  var yr = today.getFullYear().toString();
 9:  var mo = today.getMonth() + 1;
10:  mo = mo.toString().padStart(2, '0');
11:  var filename = path + yr + '-' + mo + '.taskpaper';
12:  
13:  // Get the file from Dropbox and set draft to it.
14:  var db = Dropbox.create();
15:  var content = db.read(filename);
16:  editor.setText(content);

I think this is mostly self-explanatory. To me, the most interesting part is handling the month number in Lines 9 and 10. The + 1 in Line 9 is there because JavaScript counts months starting from zero. Line 10 uses the padStart function to add a leading zero to single-digit months. I’m not sure when padStart was added to JavaScript, but it wasn’t there the last time I decided it worth buying a JavaScript reference book.

With the upload/download actions out of the way, let’s move on to the actions that manipulate the lists themselves.

We’ll start with New Item. When you’re adding a series of tasks to a project, Drafts helpfully inserts the tab-hyphen-space at the beginning of the line. but it doesn’t do that for the first task in a newly added project. The New Item action is a simple text insertion action that adds those three characters at the current insertion point. Nothing special, but it does help, especially when working with the software keyboard, which doesn’t have a Tab key.

Next is the Mark Done action, which adds a @done tag and the current date to the end of the current line or the end of all the selected lines if text is selected. Mark Done is smart enough to ignore project lines.

javascript:
 1:  // Mark current task or selected tasks as done today.
 2:  
 3:  // Set the done marker text with today's date.
 4:  var today = new Date();
 5:  var yr = today.getFullYear().toString();
 6:  var mo = today.getMonth() + 1;
 7:  mo = mo.toString().padStart(2, '0');
 8:  var da = today.getDate()
 9:  da = da.toString().padStart(2, '0');
10:  var marker = ' @done(' + yr + '-' + mo + '-' + da + ')';
11:  
12:  // Get all the currently selected lines.
13:  var lines = editor.getSelectedLineRange();
14:  var sel = editor.getTextInRange(lines[0], lines[1]).replace(/\n$/, '');
15:  
16:  // Loop through the lines, adding the done marker to tasks only.
17:  var selArray = sel.split('\n');
18:  var numLines = selArray.length;
19:  for (var i=0; i<numLines; i++) {
20:    if (selArray[i].startsWith('\t-')) {
21:      selArray[i] += marker;
22:    }
23:  }
24:  
25:  //Replace the selected lines with the marked lines.
26:  var doneText = selArray.join('\n');
27:  var doneLength = doneText.length;
28:  editor.setTextInRange(lines[0], lines[1], doneText + '\n');
29:  editor.setSelectedRange(lines[0] + doneLength, 0);

Lines 4–10 get today’s date and use it to format the marker to appear at the ends of the lines. It looks like this:

@done(yyyy-mm-dd)

The TaskPaper app for Mac has a setting for adding the date to @done tags, and I use it because I like to be able to look back to confirm when I finished something.

Lines 12–13 get the selected lines (which is just the current line if there is no selection) and put the text from those lines into the variable sel. The trailing newline is omitted.

Lines 17–23 split sel into an array of lines and loop through that array, adding the done marker to each task line. Task lines are distinguished from others by the tab-hyphen they start with.

Lines 26–29 clean up by replacing the originally selected lines with the altered text from the previous step. The cursor is then placed at the end of the last selected line.

The Marked Waiting action is basically the same as Mark Done except it puts a @waiting tag at the end of each selected task line.

Archive Done mimics another feature of the Mac TaskPaper app. It goes through the list, finds all the tasks marked @done and adds them to an Archive section at the bottom of the document. The archived tasks are marked with the name of the project they came from (in a @project tag at the end of the line), and they are placed at the top of the Archive section. If there isn’t already an Archive section, one is created.

Here’s how it works. Let’s go back to the task list I showed at the top of the post:

Before archiving

As you can see, there are a couple of done tasks in the Administration project. After running the Archive Done action, the draft looks like this:

After archiving done tasks

Here’s the script step of Archive Done:

javascript:
 1:  // Archive @done tasks
 2:  
 3:  // Set up variables.
 4:  var orig = draft.content;
 5:  var active = '';
 6:  var archive = '';
 7:  var project = '';
 8:  var archiveRE = /^Archive:$/m;
 9:  var projectRE = /^([^:]+):\s*$/;
10:  var doneRE = /@done/;
11:  
12:  // Save the Archive section if there is one.
13:  var archStart = orig.search(archiveRE);
14:  if (archStart >= 0) {
15:     archive = orig.substring(archStart, orig.length);
16:     archive = archive.replace(/\s*$/, '\n');
17:     archive = archive.replace(/^Archive:\n/, '');
18:  } else {
19:     archStart = orig.length;
20:  }
21:  
22:  // Go through the unarchived tasks, line by line.
23:  // Keep track of the current project and collect @done items.
24:  orig = orig.substring(0, archStart).replace(/\s+$/, '')
25:  var lines =orig.split('\n');
26:  var len = lines.length;
27:  var i;
28:  for (i=0; i<len; i++) {
29:     var isProject = lines[i].match(projectRE);
30:     if (isProject) {
31:         project = isProject[1];
32:         active += lines[i] +  '\n';
33:     } else {
34:         if (lines[i].match(doneRE)) {
35:             // New archived lines go on top of existing archive
36:             // to match Mac TaskPaper behavior.
37:             archive = lines[i] + ' @project(' + project + ')\n' + archive;
38:         } else {
39:             active += lines[i] + '\n';
40:         }
41:     } 
42:  }
43:  
44:  // Replace the draft with the edited active and archive sections.
45:  editor.setText(active + '\nArchive:\n' + archive);
46:  editor.setSelectedRange(0, 0);

Lines 4–10 set up a bunch of variables for later use. Lines 13–20 find the Archive section and save all of its lines to the variable archive (which was initialized to an empty string). They also set the archStart variable to the character index of the beginning of the Archive section. This will be used later to determine the end of the active (unarchived) tasks. If there is no Archive section, archive remains blank and archStart is the end of the document.

Lines 24–42 are the heart of the script. They put the text of active section of the document into the active variable, turn it into an array of lines, and loop through the lines, looking for done tasks. Whenever a project header line is encountered (Lines 29–32), the project variable is updated to keep track of the current project. Done tasks (found in Line 34) are extracted and marked with the current project name and added to the front of the archive variable (Line 37)

When the looping is done, the active and archive sections are concatenated and the draft is replaced with the result. The cursor is put at the top of the draft.

One last action. As you can see in the screenshots, it’s my habit to have no space between the projects. I think the formatting is sufficient to make the list readable without extra whitespace.2 This does, however, make rearranging projects difficult in Drafts. Drafts has a rearranging mode, but it won’t work on a project-by-project basis unless there’s a blank line between the projects.

The Un/Block action addresses this problem by adding or deleting blank lines between the projects. It toggles between the tightly spaced format I like and the blank-line-separated format needed to use Drafts’s block rearrangement. When I want to move a project to the top of the list to make it more prominent, I run Un/Block to add blank lines between the projects, use Drafts block rearranging to drag the projects into the order I want, and then run Un/Block again to get rid of the extra blank lines. Un/Block is smart enough to maintain the blank line above the Archive section.

Here’s the script step for Un/Block:

javascript:
 1:  // Toggle the presence of blank lines above all projects.
 2:  
 3:  // Regexes for project headers.
 4:  var projREtight = /([^\n])(\n[^:\n]+:)/g;
 5:  var projREloose = /(\n\n)([^:\n]+:)/g;
 6:  var archiveRE = /^(Archive:)$/m;
 7:  
 8:  // Get the text of the current draft
 9:  var d = draft.content;
10:  
11:  // Make new text with blank lines above all project
12:  // headers that didn't have them.
13:  var e = d.replace(projREtight, '$1\n$2');
14:  
15:  // If there was no change, take the blank lines away.
16:  if (e == d) {
17:    e = d.replace(projREloose, '\n$2');
18:    e = e.replace(archiveRE, '\n$1');
19:   }
20:  
21:  // Replace the text of the current draft
22:  editor.setText(e);

Lines 4–6 define a set of regular expressions used to find project headers under different conditions. Line 13 replaces all “tight” project headers with “loose” ones. If that replacement didn’t do anything (Line 16), the spacing must already be loose, so Lines 17–18 replace the loose spacing with tight (except for the extra line above the Archive section).

I’ve been using these actions for over a month, and I’m pretty happy with the way they work. The functionality has remained the same for the past few weeks, with most of the effort going into simplifying the actions to make them easier to understand and maintain. As I say in the Drafts Action Directory, these actions are idiosyncratic, but I hope they can be used by others to make actions that fit their TaskPapering needs.


  1. This subfolder choice is historical rather than rational, and reflects my long-ago use of the iOS plain text editor, Elements, which required an Elements folder. I suppose I should reorganize things, but I’ve never bothered. 

  2. The Archive section is set off from the rest by a blank line. This is something the Mac TaskPaper app does, and I mimic that behavior in Drafts.