The claim receipt: news once, and a fold that keeps it (§17) - #13
Merged
Merged
Conversation
The geometry gate compared .hl-receipt's rect against its own padded parent's border-box rect, which can only ever be off by the parent's own padding (px-5 md:px-6) since getBoundingClientRect includes it. Compare against the content box instead: parent rect plus/minus its own computed padding.
…line (§17.5) The "What it does NOT do" section still said the file rendered conditionally, contradicting its own §17.5 return-null docblock further down. And nothing cleared the announce flag on sign-out, which is a client call with no navigation, so a claim announced on the same page the reader signs out on stayed on screen.
Two Important, ten Minor. Nothing here changes what a claim decides; it changes
what the gates can see, what the outline reads as, where a link lands, and how
much a bad field costs.
**A marker both receipt states carry (I1a).** `data-hl-receipt` is on both.
Both e2e gates located `.hl-receipt`, which only the routine one-line state
carries, so the action-needed panel — the state holding an act the reader has to
take — was structurally invisible to them, including to the geometry gate that
is the point of §17's layout half. The class is not shared instead: it is a flex
rule with `justify-content: space-between` and would fight a panel's layout. The
action-needed state carries the attribute on a wrapper, so the marker costs
`ClaimSummary` no prop and the located element's parent is still the page column
in both states, which is what the geometry gate measures against.
**The heading that jumped the h1 (I1b).** `ClaimSummary` takes a `heading` prop,
`h2` by default. `PageShell` renders the receipt before `{children}`, so the
panel's `h2` landed ahead of every page's own `h1`; on /profile/ it also gave
the document two `Record claimed` headings. The arrival line passes `p`, and
`aria-labelledby` still names it. The register fold passes nothing.
**DETAILS lands on the fold, not on its lid.** `#claim` sits on the `h2` inside
a closed `<summary>`, so the affordance delivered the one line the reader
already had. `FoldFragment` is one client island, mounted once by /profile/,
that opens the `<details>` containing the fragment's target on mount and on
`hashchange`. Not a prop — a static export has one document for every reader —
and not CSS, because `open` is DOM state and `:target` cannot force it. The
export link's no-handler fallback now points at `/profile/#data` rather than at
the page it is already on.
**A bad field loses the field, never the record (M1).** `asClaimReceipt`
discarded a whole receipt over an off-vocabulary `identity.name`, `markSeed` or
`role`. After §17.2 nothing reads those three provenances, so they default to
`absent`; `outcome`, `at` and the counts keep whole-receipt rejection because
they are printed.
**Counts that are counts (M3).** `Number.isFinite` admitted `-5` and `1.5`, and
the register would have printed `1.5 MERGED · 0 LOST`. All eight counts now
require a non-negative integer.
**The invariant `claimIsNews` rests on, named and pinned (M2).** Its submittal
term compares an array length against a set deduped by `slug · owner/repo`; the
two agree only because `addSubmittal` and `coerceSubmittals` refuse a duplicate
repository per sheet. Loosen submittal identity and the receipt returns on every
page load. Commented on the term and pinned by a test that asserts both halves.
**The shape §17 does not guard (M6).** A merge written locally whose push never
lands recomputes as news on every load. Named in `claimIsNews`'s docblock, with
why it stays: a claim that cannot converge is a fact about the sync, and the
footer's `NOT SYNCED` is the signal the reader can act on.
**Test 9 marked fixme (I2).** `a manager sees the whole organisation` fails at
`getByText('E2E Manager')`, measured failing at the merge base with only §17's
strict-mode fix applied, cause unknown, one hypothesis disproved against the
live schema. The file is `mode: 'serial'`, so its failure took §17's own two
gates with it as "did not run" — they had never executed in a whole-file run.
Marking it lets the chain reach them, and reaching them found one more thing:
the once-only gate pinned `MOVED INTO YOUR ACCOUNT`, which is true only for an
account no earlier test has signed in. It now captures the reading whichever
branch the claim took and asserts the register prints the same string, which is
§16.4.2's rule and a stronger claim than either wording.
Also: `ClaimReading`'s "three callers" is two, and two untested `asClaimReceipt`
branches now have cases.
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 claim summary had stopped being a receipt. The complaint was that the block
on the home screen was left-aligned and cluttered, and that meeting it on
arrival was uncomfortable. It measured, on the deployed build at 1440×900:
.hl-claim-shellpadding: 0,max-width: none, no border, transparentx: 0, width: 1440, while the page column starts atx≈143y: 1165, with the footer at1092–1165— below the footer, and below the foldTWO RECORDSDISMISSThose are five layout faults. The sixth changed what this section is about.
The panel was not a receipt — it came back on every full page load
AccountSync's own comment already said it: "this effect runs on EVERY mountwith a session, so every page load with an existing account row takes the
mergedbranch." That branch calledsetSummaryunconditionally. Measured,with a real session and a real magic link:
here == account == shared == mergedis not an event, it is a state.DISMISSsilenced only the current document, and dismissing destroyed the reader's only
account of what had happened to their own signatures.
The identity lines reported a provenance, not a change
claim.tsstates the rule — "stated only where they changed something thereader can see. A rule that fired invisibly is not news." — and did not keep
it.
sourceOf(claim.ts:188) returns'account'whenever the merged valueequals the account's, even when this browser held the identical value, so
The name on the record is the one your account holds.printed on every load.markChangedwas already a real change test; two fields were not.What ships
claimIsNews— eight terms, every one a change test. The steady state isnot news, so nothing is written and nothing is shown.
nameChangedandroleChangedjoinmarkChanged, and the two copy lines move onto them.meta.lastClaim— one slot, written only when the claim was news, by onewriter (
noteClaim, which stamps no day: signing in is not a day the readerworked).
mergeRecordsalready resolvesmetalocal-wins, so a receipt is afact about this browser and never travels;
carriesNothingignores it, so areceipt alone is not a record; an erase drops it.
One line, in the page column —
RECORD CLAIMED · 1 MERGED · 0 LOST · DETAILS, rendered byPageShellinside the column every page's content is in.Navigation closes it; there is no dismiss button, because nothing is lost by
closing it. When something is unaccounted for (
claimNeedsExport) it does notcollapse: it keeps the sentences and the export affordance.
/profile/›Last claim, the register's eleventh fold — the whole summary,for as long as the record exists. Its closed reading and the arrival line come
from one function (§16.4.2), and
DETAILSopens the fold rather than landingon a closed one.
Two defects from §16, found on the way
getByText(<email>)on
/profile/resolves to two elements —SignInPanel's identity line andAuthPanels' session readout — a strict-mode violation in test 2 of aserial describe. Measured at this branch's merge base: 1 passed, 1
failed, 9 did not run. The assertion now names the surface it means.
E2E Manager) fails behind it. Measured pre-existing:identical at the merge base with only the fix above applied, and twice here.
Its cause is unknown and one hypothesis was disproved by probing the live
schema — a profile-row upsert that omits
display_namedoes not destroyit. Marked
test.fixmewith all of that recorded, so the serial chain reachesthe tests behind it; §17's own two gates now execute in a whole-file run for
the first time.
Gates
npm run typecheck— cleannpm test— 97 files, 2894 testsnpm run build— 57 pages;SITE_BASE_PATH=/ai-engineering-bazaar npm run build— 57 pagesnpm run test:e2e— 368 passed, 17 skipping cleanlyE2E_ACCOUNTS=1 accounts.spec.ts— 13 run, 12 passed, 1 fixme, 0 "did not run"Mutation-proven rather than asserted: replacing
nameChangedwith the oldprovenance turns the steady-state tests red; disabling the news guard turns the
reload gate red on
reload 1 reported a claim that moved nothing; freezingClaimReadingturns §16.4.1's per-row table red on theclaimrow; offsetting.hl-receiptturns the alignment gate red. Each was applied, observed, andrestored.
Schema
metagains one nullable field,lastClaim. A widening, not a migration:coerceRecordDatadefaults it and reads it defensively (a bad count or anunknown outcome loses the receipt, never the record; the three provenance
fields, which nothing reads, default rather than reject), records written before
it still load, and
carriesNothingdoes not consult it. No table, no policy, noRLS change.
Known and left
tests/e2e/accounts.spec.ts:161's comment states the wrong reason for acorrect assertion — the account is not signing in for the first time there.
ClaimSummary'sheading='p'branch and theneeds-exportwrapper areexercised by no test; three lines of
renderToStaticMarkupwould pin both.sync.ts:498-504pushes an empty envelope on theadoptedbranch, creating a
record_staterow for a reader who signed in with nothing.Spec: §17,
docs/superpowers/specs/2026-09-02-lms-claim-receipt-design.md