The Door That Was Never Open

I had a clean, evidence-backed negative result about my own product. It was meaningless, because it measured a door nobody could walk through.

Date

/

Category

Strategy

Strategy

/

Writer

Keon Hwui Kim

Keon Hwui Kim

For about a month I believed I had the beginnings of an answer about my own product, and the answer was bad.

I had built the thing. It was deployed. It was on my phone's home screen. And the data from my own use was unambiguous: nothing was happening.

What the log said

I read my own history carefully, and the care matters here. From a copy of browser storage, through a path that never booted the application so the reading itself would not be recorded as a visit, and without reading a single line of my own text. The copies were deleted afterwards.

Measure

Value

Reading

Arrivals

60

Looks like use

Lines left

56

Looks like capture

Lines of 5 characters or fewer

40

Not capture. Typing tests

Longest line

20 characters

Nothing real was ever left

Cat touches

433

64% of all events

Arrivals within a single minute

~25

Reloading a build

Longest gap between arrivals, ever

10.4 hours

Never went a day without opening it

Table 1. The founder's own log. Every row is the signature of development, not use.

The last row is the one that settles it. A person using a product has gaps. Weekends. Busy days. A week where they forget. The longest gap in this product's entire history was ten hours, which is the shape of someone who works on it every day and checks it before bed.

So I recorded the honest conclusion: capture never happened, re-finding could not have happened, value cannot be assessed. Not a failure. An absence. You cannot falsify a hypothesis that was never tested.

And then the actual reason

The next round was supposed to be about product direction. It turned into something else, because I went to check a boring technical detail and found this:

The product had no web app manifest.

Three consequences, and each one is fatal on its own.

On Android

A web application must be installed before it can register as a share target. Chrome requires this. Without a manifest there is no installation, so there was no installation, so the product could not appear in the share sheet at all.

Not ranked low. Not hard to find. Absent.

On iOS

WebKit's Intelligent Tracking Prevention caps all script-writable storage at seven days of Safari use without user interaction on the site. John Wilander's post is explicit about the scope: IndexedDB, LocalStorage, Media keys, SessionStorage, and Service Worker registrations.

There is exactly one exemption, and it is the one I did not qualify for: "Web applications added to the home screen are not part of Safari and thus have their own counter of days of use."

So every world I created on my phone was being deleted by the browser after a week, silently, as designed, by a privacy mechanism working exactly as intended.

Together

The entire premise of the experiment, put it on your phone, drop real things in, come back later, was mechanically impossible on both platforms at once.

The experiment did not fail. It was never possible to run.

Why I had not noticed

Every round of work had been driven from a desktop. On a desktop the manifest is irrelevant: the site loads, storage persists, everything works. The product was fine in the only place anyone ever looked at it.

This is the real lesson and it is not about manifests. A missing manifest is a trivial bug. What is not trivial is that it produced a false negative about the product's value, which I then acted on, documented, and nearly made a strategic decision from.

The fix, which took an afternoon

{
  "name": "JEFIELD",
  "display": "standalone",
  "start_url": "/",
  "icons": [ ... ],
  "share_target": {
    "action": "/",
    "method": "GET",
    "params": { "title": "title", "text": "text", "url": "url" }
  }
}
{
  "name": "JEFIELD",
  "display": "standalone",
  "start_url": "/",
  "icons": [ ... ],
  "share_target": {
    "action": "/",
    "method": "GET",
    "params": { "title": "title", "text": "text", "url": "url" }
  }
}
{
  "name": "JEFIELD",
  "display": "standalone",
  "start_url": "/",
  "icons": [ ... ],
  "share_target": {
    "action": "/",
    "method": "GET",
    "params": { "title": "title", "text": "text", "url": "url" }
  }
}

Plus the icons, drawn by the program's own rendering code so they use the same palette as the product rather than being a separate asset that drifts. Plus the handful of meta tags iOS actually reads.

Verified live rather than assumed: bundle hash matching the local build, manifest served with the correct content type, both icons returning 200.

One deliberate restraint in the share adapter

Worth noting because it was tempting to do otherwise. The share adapter never writes.

A shared item arrives in the input, focused, and the person presses the button. It is not saved automatically.

The reason: any link of the form /?text=... would otherwise put a note into someone's world, recorded with provenance "observed" and actor "user", when the user typed nothing. A false record is worse than an extra tap. Provenance has to mean something or it means nothing, and one automatic write would poison it permanently.

The platform map, which I should have made a year earlier

Capability

Android

iOS

Installable to home screen

Yes

Yes

Survives storage eviction once installed

Yes

Yes, exempt from the 7-day cap

Appears in the system share sheet

Yes, after install

Never, for a web app

Share-sheet position controllable

No

No

File sharing into the app

Needs POST and a worker

Not available

Table 2. Platform capability map. The third row is permanent and cannot be designed around: on iOS only a native share extension can register there.

Making this table is a two-hour job. I did it in month eleven. Had I done it in month one, the entire share-target question would have been correctly scoped from the start, and I would not have spent a month drawing conclusions from a broken instrument.

The discipline I took from it

I had a clean, defensible, evidence-backed negative result. I had numbers. I had documented them carefully and written a considered analysis. And the result was meaningless, because it measured a door nobody could walk through.

So this now runs before any negative conclusion about anything:

Before concluding that people did not choose your product, prove they could have.

Not "the site loads." Not "it works on my machine." Specifically: at the moment of the behaviour you are hoping for, on the device where it would actually happen, was the path open? Was the icon there? Was the share-sheet entry there? Did the data survive the week?

Negative evidence is the most valuable kind and the easiest to fake, because absence looks identical whether the cause is rejection or inaccessibility. One of those tells you to change the product. The other tells you to check the plumbing.

They are indistinguishable in the log. They are not remotely the same thing.

Sources

  • Wilander, J. (2020). Full Third-Party Cookie Blocking and More. WebKit blog, 24 March 2020. webkit.org/blog/10218

Related reading

More from the Moc Mallen Studio journal