Skip to content

feat(lists): badge legacy lists in the index and the list header - #226

Merged
brianorwhatever merged 1 commit into
mainfrom
feat/legacy-list-badge
Aug 21, 2026
Merged

feat(lists): badge legacy lists in the index and the list header#226
brianorwhatever merged 1 commit into
mainfrom
feat/legacy-list-badge

Conversation

@brianorwhatever

Copy link
Copy Markdown
Contributor

The only signal that a list carried migration-sealed provenance was three levels deep: open Share → expand the collapsed "Originals Provenance" section → read the event-log row. 22 of 28 production lists are in that state and none of their owners had any reason to look.

This badges them where lists are actually seen — every card in the index, and the list's own header.

Getting the flag there cheaply is most of the change

The envelope is kept off the lists row deliberately ("so the hot list subscriptions stay small"), and parsing a growing JSON log per card per subscription tick would undo exactly that.

So genesisSealedAt is captured once, at write time, in upsertListEnvelope — the single envelope write path — and getLegacyListIds then compares two numbers and returns ids only. Home asks once for every card on the page rather than once per card.

events[0] never changes as later events append, so the field is written once and only repaired if an older row is missing it.

⚠️ Needs a migration run after deploy

npx convex run --prod migrations/backfillGenesisSealedAt:runAll

Without it every list reads as non-legacy. It's idempotent — rows already set are skipped, so a re-run after a partial failure only touches what's left. Verified on dev: 5 filled, then 0 filled / 5 already set.

Unknown is not legacy

An envelope that can't be parsed leaves the field unset and the list is treated as unknown, not legacy. Claiming a list can't record history when we simply don't know would push its owner into a copy they may not need.

Why it's neutral, not loud

Four of every five lists carry this badge. The brand accent would compete with real calls to action — note amber-* is remapped to electric violet in index.css, so it is the accent — and a warning colour would make a working app look broken. Nothing about these lists is broken: items, sharing and publishing all work. The border, weight and letter-spacing make it read as a deliberate chip rather than more meta text.

Verified in both themes against the real built CSS, not a harness approximation — the first attempt rendered violet precisely because of that palette remap, which a naive preview hid.

Light personal · 3d ago [LEGACY]
Dark same chip, stone-700 on stone-600 border

Duplication, on purpose

The threshold rule exists in both convex/lib/legacyList.ts and src/lib/originals.ts because Convex modules can't import from src/. The test asserts the two constants agree — a drift would have the list index and the provenance panel disagreeing about the same list.

176 tests pass; tsc -b clean; the 3 remaining eslint errors in these files are pre-existing (3 before this change too).

🤖 Generated with Claude Code

The only signal that a list carries migration-sealed provenance was three
levels deep: open Share, expand a collapsed "Originals Provenance" section,
read the event-log row. 22 of 28 production lists are in that state and none of
their owners had any reason to look.

Badges them where lists are actually seen — on every card in the index, and in
the list's own header.

Getting the flag there cheaply is most of this change. The envelope is kept off
the lists row on purpose ("so the hot list subscriptions stay small"), and
parsing a growing JSON log per card per subscription tick would undo that. So
genesisSealedAt is captured once, at write time, in upsertListEnvelope — the
single envelope write path — and getLegacyListIds then compares two numbers and
returns ids only. Home asks once for every card rather than once per card.

events[0] never changes as later events append, so the field is written once
and only repaired if an older row is missing it. backfillGenesisSealedAt fills
rows written before the field existed; it is idempotent and must be run on prod
after deploy or every list reads as non-legacy.

An envelope that cannot be parsed leaves the field unset and the list is
treated as UNKNOWN, not legacy. Claiming a list cannot record history when we
simply do not know would push its owner into a copy they may not need.

Neutral styling, deliberately. Four of every five lists carry this badge, so
the brand accent (amber-* is remapped to electric violet in index.css) would
compete with real calls to action, and a warning colour would make a working
app look broken. Nothing about these lists is broken — items, sharing and
publishing all work. The border, weight and letter-spacing make it read as a
chip rather than more meta text; verified in both themes against the real
built CSS, not a harness approximation.

The threshold rule exists twice because Convex cannot import from src/. The
test asserts the two constants agree, since a drift would have the list index
and the provenance panel disagree about the same list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brianorwhatever
brianorwhatever merged commit a82c11c into main Aug 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant