Skip to content
Huy Tran

Case study · One Frame

One photograph and one sentence a day

A local-first Android journal whose whole design is a refusal: today only, on this device only. Taken to a signed release build and validated on hardware, then deliberately not shipped to a store.

NOT PUBLISHED
Three One Frame screens photographed on Android: today's frame, the archive grid, and the calendar
Captured on an Android device from the release build — today's frame, the archive, and the calendar with its gaps left visible.

Product decisions

The constraint is the product

Today only

One entry per local calendar day, enforced by the database key rather than by the interface. You can rewrite today as often as you like; you cannot fill in yesterday.

A missed day stays missed

A journal you can backfill becomes a backlog, and a backlog becomes homework. The calendar shows the gap instead of inviting you to repair it.

No account, no server

V1 has no backend at all. Which also means uninstalling loses everything — so the app says that on the first screen, before anyone has anything to lose.

Backup is the user's job, stated plainly

One encrypted file through the system share sheet, with the part most apps leave out: forget the password and nobody can recover it.

Implementation evidence

The repository is private, so most of this is Partial rather than verified: the unedited output of each command is committed to this site and can be read, but you cannot re-run it yourself. That distinction is kept rather than smoothed over.

Evidence supporting each claim, with verification status and link.
Claim supportedStatusEvidence
The local-first data and backup paths hold
flutter test — 220 tests passed. The source repository is private, so the unedited output is committed here: readable, not re-runnable
PARTIALOpen ↗
A release build refuses to produce an unsigned artifact
flutter build apk --release fails by design when the signing key is absent. A green build here would mean signing had regressed to the debug key
PARTIALOpen ↗
Quality workflow green on the default branch
Format, analyze, test and Android build. The repository is private, so the run itself is not publicly inspectable
PARTIAL
The app was run and photographed on an Android device
Three screens captured on hardware. Screenshots are the artifact; there is no public run to accompany them
PARTIALOpen ↗
The privacy position is published and checkable
Public repository holding the privacy policy
VERIFIEDOpen ↗
Source code
Private repository. Access for review on request
UNAVAILABLE
Play Store or App Store publication
Not published to any store, and no store listing is claimed. No GitHub release exists either
UNAVAILABLE

The release gate, and why it is a failing build

In a clean checkout the release build fails, on purpose: the signing key is gitignored, so Gradle refuses rather than falling back to the debug key. A green build in that state would mean signing had silently regressed.

BUILD FAILED in 18s · exit=1 — read the unedited output ↗

Distribution and access

Stated here rather than left for a client to discover.

  • The source repository is private. The committed command output is what a reader can inspect — it can be read, not re-run.
  • Not published to Google Play or the App Store, and there is no GitHub release. The Android build exists; distribution does not.
  • iOS is a release-parity target, not a shipped platform. No iOS build has been verified here.
  • No sync, no cloud backup and no account, by design rather than by omission.
  • A portfolio project with no users.