feat(lists): badge legacy lists in the index and the list header - #226
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
listsrow deliberately ("so the hot list subscriptions stay small"), and parsing a growing JSON log per card per subscription tick would undo exactly that.So
genesisSealedAtis captured once, at write time, inupsertListEnvelope— the single envelope write path — andgetLegacyListIdsthen compares two numbers and returns ids only.Homeasks 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.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, then0 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 inindex.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.
personal · 3d ago[LEGACY]stone-700onstone-600borderDuplication, on purpose
The threshold rule exists in both
convex/lib/legacyList.tsandsrc/lib/originals.tsbecause Convex modules can't import fromsrc/. 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 -bclean; the 3 remaining eslint errors in these files are pre-existing (3 before this change too).🤖 Generated with Claude Code