A third of the freeform notes in the backlog are nothing but a pasted
URL, and the capture screen still offered them the seven-step spec
stepper. NoteTemplate.forDraft now picks `blank` for a draft that is
only links, so filing a link costs one paste.
The switch runs on every raw keystroke and is lossless in both
directions — it only ever fires while the raw body is the source, so
there are no section values to convert. Type prose alongside the link
and the spec template comes straight back. An explicit choice from the
dropdown or the guided wizard sets _templatePickedByUser and is never
overridden.
isBareLink is deliberately strict: one word of context ("read this
https://…") means the note has prose in it and keeps the spec template.
Verified on the phone (release APK, adb install -r): typing a bare URL
flips Template to Blank and drops the Guided segment; appending " port
this to Dart" restores LLM design spec and Guided. Test note created by
that check was deleted and the deletion synced (back to 49 notes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audited the template against 514 real Claude sessions (1503 top-level
prompts) joined to the 49 notes in BACKLOG.md. Sections were judged on
whether the agent actually read them, not on fill rate — four of them
said "leave blank if none" in their own helper, so a low fill rate was
the section working correctly.
Measured over the 22 note/session pairs:
estimate filled 22, referenced 1 (4%)
done filled 22, quoted back 4 (18%)
refs filled 14, URLs fetched 5 (35%)
tech the agent opened pubspec.yaml/CLAUDE.md anyway in 54%
ask 100% ask rate with it, 92% without — no measurable effect
Kept: title, what, where, must, done. Dropped: tech (into `where`),
ask, nice and never (into `must` as `optional:` / `must not:` lines),
depends, estimate. Renamed `refs` -> `read first`. Added `verify`, the
only addition: "test it yourself, on the phone" was the single most
repeated correction in the corpus, and 3 of the 7 filled `never`
sections were already smuggling it in.
Fixes a latent data-loss bug found while testing this. parse() treats
an unknown `## heading` as content on purpose (values may contain
subheadings), so a legacy `## tech` block was folded into the preceding
section's value AND the note still reported conforming — opening any
old note in the stepper would have swallowed it on the next save.
NoteTemplate.retiredLabels now makes those notes explicitly
non-conforming, so they open raw and untouched.
tool/migrate_backlog.dart converts an old export (export -> migrate ->
import). It is idempotent and gated: it throws if a rewritten note does
not conform or if any source line vanishes. All 49 notes migrated on
the desktop and merged to the phone (0 new, 25 updated, 24 unchanged).
Also formats the few files dart format had never been run over, and
extracts a local in frame_stats_io.dart so the reflowed line stays
under 80 chars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>