Skip to content

The drafter block and the register: /profile/, rebuilt (§16) - #12

Merged
cevheri merged 4 commits into
mainfrom
phase6-profile-drafter-block
Sep 2, 2026
Merged

cevheri merged 4 commits into
mainfrom
phase6-profile-drafter-block

Conversation

@cevheri

@cevheri cevheri commented Sep 2, 2026

Copy link
Copy Markdown
Member

The profile sheet had stopped being readable. The complaint was that there was
too much text and that finding anything was hard, and it measured: <main> in
the export held 1260 words, eleven <h2> panels and twenty form controls
before a single React island mounted, with the two controls a reader comes here
for about 700 words apart.

Three things were invisible without counting them:

  • the mark picker was rendered twice on one pageIdentityPanel's
    MarkPicker and, directly below it, RolePanel's MarkOffer drawing the
    same eight options again with two confirm buttons of its own — with a third
    copy on /sign-in/alias/;
  • four surfaces wrote identity.name;
  • the identity card's five-row glossary explained five fields whose values
    were not on it
    — five paragraphs, zero readings.

What ships

One open drafter block. The drawing with its MARK and SEED lines; then
the alias field and one compact mark row; then sign-in. One box holding the two
answers to how this record is known — the alias in this browser, the account
across browsers — a 1.5px painted rule apart. The rule is the argument: same
subject, different reach.

One register of ten folds, closed. Readout, Uptime, Stamps, Submittal
register, Role and path, Organisation, Storage, Stored values, Export/import/
erase, Keyboard.

The rule that makes folding honest (§16.4.1). A closed row prints the number
its panel exists to report, so folding removes prose and never a fact — §10.4's
contract on a silent gauge, applied to a disclosure. RegisterRow refuses a
blank reading at render time, and the strongest of the five e2e gates is that a
row's closed reading equals the reading its opened body prints. Every reading
comes from the selector its own body already uses; none is a new count. Readings
that are reader state print -- in the prerender, which is the house spelling
for "no reading taken yet".

One mark picker, two modes. Uncontrolled it writes on change, which is what
/profile/ wants; controlled the parent holds the draft, because AliasSheet's
KEEP THIS ALIAS must not claim an act already performed. data-hl-mark stays
on the label with the 44px touch floor, and idPrefix keeps all three radio
group names exactly as they were.

An email sign-in names the alias once, from the local part, with a line
saying where the name came from. It writes, so every guard is load-bearing.

before after
words on arrival 1260 351
top-level sections 11 2
mark picker implementations 3 1

Review

Five lenses — correctness, truthfulness, design and accessibility, test quality,
and a reader's own errands — produced nineteen findings. Eight were refuted by
verifiers whose job was to refute them. Twelve were fixed. Two mattered:

The alias offer overruled REMOVE NAME. The flag recorded that an offer had
been taken, never that one had been decided, so a reader whose record already
carried a name at sign-in never got the flag written — the first guard returned
before the flag's only writer was reached. Clearing the name then let the next
claim write the address's local part over it. Reproduced independently twice; the
file's own docblock claimed the opposite invariant. The offer is now decided once
per account, and an erase keeps the flag, because carriesNothing deliberately
ignores prefs so keeping it cannot resurrect an erased record — without it, a
record the reader had just erased came back holding their name.

The §16.4 gates could not tell a row from its reading. A reviewer swapped
uptime's reading with stamps', hard-coded the storage reading to
PERSISTENT · QUERIED with its hydration gate dropped, froze the role reading
and inverted the keyboard's — four simultaneously lying readings, one asserting
persistence before navigator.storage had been asked — and the suite stayed
green: 2782 unit, 365 e2e, byte-identical to a clean run. The gates were one-way
implications and vacuous for a reading with no digits. There is now a per-row
table of expected readings at two fixed seeds, and a row with no entry fails.
Verified: the same swap turns that gate red and leaves the other four green.

That gate earned itself before shipping. With the storage row closed and the
browser having answered, the summary printed UNKNOWN while the body one line
below printed BEST-EFFORTstorageReadout() was a bare call and the grant is
deliberately not written through the reducer, so the summary subscribed to
nothing and froze. useStorageReadout subscribes it.

Gates

  • npm run typecheck — clean
  • npm test — 94 files, 2847 tests
  • npm run build — 57 pages exported
  • npm run test:e2e366 passed, 15 credentialed tests skipping cleanly
  • SITE_BASE_PATH=/ai-engineering-bazaar npm run build — the deployed sub-path

Mutation-proven, not asserted: eleven deliberate breaks across the two commits,
each rebuilt before its run and restored after — a row rendered open, a
reading deleted, data-hl-selected dropped, the offer marker reduced to a tint,
inline chrome re-emitting a heading id, a readout given a full stop, and the
reading swap above.

Schema

prefs gains one nullable field, aliasNamedFor. A widening, not a migration:
coerceRecordData defaults it, records written before it still load, and
carriesNothing still ignores prefs. No table, no policy, no RLS change.

Spec: §16, docs/superpowers/specs/2026-09-02-lms-phase6-drafter-block-design.md

… (§16)

The complaint was that the profile sheet had stopped being readable, and it
measured: `<main>` in the export held 1260 words, eleven `<h2>` panels and
twenty form controls before a single React island mounted, with the two
controls a reader comes here for about 700 words apart. Three things were
invisible without counting them — the mark picker was rendered twice on one
page (`IdentityPanel` and `RolePanel`'s `MarkOffer`) with a third copy on
`/sign-in/alias/`; four surfaces wrote `identity.name`; and the identity
card's five-row glossary explained five fields whose values were not on it.

So the page is now two blocks. The drafter block is open: the drawing with
its MARK and SEED lines, then the alias field and one compact mark row, then
sign-in — one box holding the two answers to how this record is known, a
1.5px painted rule apart. Everything else is a register of ten folds, closed.

The rule that makes folding honest is §16.4.1: a closed row prints the number
its panel exists to report, so folding removes prose and never a fact. It is
§10.4's contract on a silent gauge applied to a disclosure, `RegisterRow`
refuses a blank reading at render time, and three e2e gates hold it — the
strongest being that a row's closed reading must equal the reading its opened
body prints. Every reading comes from the selector its own body already uses;
none is a new count.

The mark picker is one implementation with two modes, because `AliasSheet`'s
KEEP THIS ALIAS must not claim an act already performed: uncontrolled it
writes on change, controlled the parent holds the draft. `data-hl-mark` stays
on the label with the 44px touch floor, and `idPrefix` keeps all three radio
group names as they were.

An email sign-in now names the alias once, from the local part, with a line
saying where the name came from. It writes, so every guard is load-bearing:
inside the claim's `.then` from a fresh snapshot, only when the name is null,
only with an email identity, and only once — `prefs.aliasNamedFor` is the
durable flag, because `AccountSync`'s effect remints its user object on every
token refresh and without it clearing your name would not be final.

Measured after: 1060 words in `<main>` counting every closed body still in
the DOM, 426 on arrival. Four gates green, plus the sub-path build: typecheck
clean, 94 files / 2838 tests, 57 pages exported, 365 e2e passed.

Spec: §16, docs/superpowers/specs/2026-09-02-lms-phase6-drafter-block-design.md
…ay in

Five review lenses over 884b603 produced nineteen findings; eight were refuted
by a verifier whose job was to refute them, and these are the eleven that
survived plus one whose verifier crashed and which measured itself.

**The alias offer overruled REMOVE NAME.** `prefs.aliasNamedFor` recorded that
an offer had been TAKEN, never that one had been DECIDED, so a reader whose
record already carried a name at sign-in never got the flag written — guard one
returned before the flag's only writer was reached. Clear the name afterwards
and the next claim wrote the local part of the address over it. Two agents
reproduced it independently, and the file's own docblock claimed the opposite
invariant. The offer is now decided once per account: the flag is written
whenever the question is settled, and the name only when there is a null name to
fill. An erase keeps the flag, because `carriesNothing` deliberately ignores
`prefs`, so keeping it cannot resurrect an erased record — and without it a
record the reader had just erased came back holding their name and a day.

**The §16.4 gates could not tell a row from its reading.** A reviewer swapped
uptime's reading with stamps', hard-coded the storage reading to
`PERSISTENT · QUERIED` with its hydration gate dropped, froze the role reading
and inverted the keyboard's — four simultaneously lying readings, one of them
asserting persistence before `navigator.storage` had been asked — and the whole
suite stayed green: 2782 unit, 365 e2e, byte-identical. The gates were one-way
implications, vacuous for a reading with no digits. There is now a per-row table
of the expected reading at two fixed seeds, and a row in `REGISTER_ROWS` with no
entry fails. Measured after: the same swap turns that gate red and leaves the
other four green, which is what the finding said and where it said it.

That gate then earned itself before shipping: with the storage row closed and
the browser having answered, the summary printed `UNKNOWN` while the body one
line below printed `BEST-EFFORT`. `storageReadout()` was a bare call, and the
grant is deliberately not written through the reducer, so the summary subscribed
to nothing and froze. `useStorageReadout` subscribes it.

The rest: the Organisation row's closed reading contradicted its own body under
the default flag; `ACCOUNTS NOT ENABLED` and `ACCOUNTS NOT ENABLED YET` were two
spellings of one status five lines apart in one box; `OFFERED FOR YOUR ROLE`
printed inside a 54px cell wrapped to four lines and made every cell 110px tall,
which is the compact row's whole purpose undone, so the offer is now the dashed
cell plus the words in the shared description line; the print block could not
open a `<details>`, so paper got ten readings and no bodies; `.hl-drafter-note`
was dead CSS quoting a string the site had rejected; and three docblocks stated
things the code does not do — a heading level that moved, a writer that writes
nothing, and 163 words measured off the mockup rather than the build.

Arrival is 351 words, from 1260. Five gates green: typecheck clean, 94 files /
2847 tests, 57 pages exported, 366 e2e, and the sub-path build.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Alias decision persistence, provenance, account switching, and profile synchronization have unresolved correctness issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Rebuilds /profile/ around an open drafter block and a ten-row disclosure register while consolidating mark selection and alias naming.

Changes:

  • Introduces the drafter/register layout and responsive styling.
  • Unifies mark pickers and adds account-derived alias handling.
  • Expands unit and end-to-end coverage for disclosures, authentication, accessibility, and record behavior.
File summaries
File Description
tests/unit/record/validate.test.ts Derives expected schema keys from EMPTY_RECORD.
tests/unit/record/stamp.test.ts Updates preference fixtures.
tests/unit/record/schema.test.ts Verifies the widened preference schema.
tests/unit/record/register.test.tsx Tests register structure and readings.
tests/unit/record/merge.test.ts Updates merge fixtures.
tests/unit/record/boot.test.ts Updates boot-state fixtures.
tests/unit/record/alias-naming.test.ts Tests alias decisions and erase behavior.
tests/unit/identity/alias-offer.test.ts Tests email alias extraction and schema handling.
tests/unit/copy-register.test.ts Extends reader-visible copy scanning.
tests/unit/auth/chrome.test.tsx Tests inline and panel auth variants.
tests/e2e/responsive.spec.ts Adds responsive profile coverage.
tests/e2e/record.ts Adds register helpers and preference typing.
tests/e2e/colour-not-alone.spec.ts Covers forced-color profile states.
tests/e2e/alias.spec.ts Uses label-driven mark selection.
tests/e2e/accounts.spec.ts Opens the register before erasing.
tests/e2e/accounts-disabled.spec.ts Verifies disabled authentication on profile.
src/lib/record/validate.ts Coerces the alias-decision preference.
src/lib/record/store.ts Adds a subscribed storage readout.
src/lib/record/scope.ts Defines alias provenance copy.
src/lib/record/schema.ts Adds aliasNamedFor to records.
src/lib/record/events.ts Adds the alias-decision writer.
src/lib/record/erase.ts Preserves alias state during in-memory erase.
src/lib/identity/alias-offer.ts Derives aliases from email addresses.
src/components/record/RolePanel.tsx Moves mark offers to the shared picker.
src/components/record/Register.tsx Implements native disclosure rows.
src/components/record/ProfilePanels.tsx Adds register readings and reorganizes panels.
src/components/record/MarkPicker.tsx Consolidates controlled and immediate-write modes.
src/components/record/IdentityPanel.tsx Adds alias provenance display.
src/components/record/DrafterBlock.tsx Composes identity and account controls.
src/components/record/DataPanel.tsx Uses the new erased-record state.
src/components/record/AccountSync.tsx Applies account-derived alias decisions.
src/components/identity/AliasSheet.tsx Reuses the shared mark picker.
src/components/auth/SignInPanel.tsx Adds reusable inline auth chrome.
src/components/auth/AuthPanels.tsx Supports inline account and organization panels.
src/app/profile/page.tsx Rebuilds the profile page composition.
src/app/profile.css Styles the drafter, picker, and register.
src/app/globals.css Loads profile-specific styles.
Review details
  • Files reviewed: 39/39 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/components/record/DataPanel.tsx
Comment thread src/lib/record/schema.ts
Comment thread src/components/record/AccountSync.tsx
Comment thread src/components/record/IdentityPanel.tsx
…aiming

Copilot's review of #12 raised four points against `prefs.aliasNamedFor`.
Three were real; the fourth was already argued in the file it was raised on.

**§14.8.2 — the profile row was built from a stale identity.** It was pushed
on the statement after `claim()` was STARTED, so on a first email sign-in it
read the record before §16.3 derived a name from the address. `profileRowFor`
omitted `display_name`, and every manager panel printed `USER 1a2b3c4d` for
that whole session, healing only on the next mount. Extracted as
`pushProfileRow`, which takes the identity at call time; the immediate push is
kept, so a hung claim still cannot cost the row, and the decider pushes a
second time when — and only when — it actually wrote a name. `upsert` on the
primary key makes the second call idempotent.

Two guards, each mutation-proved to fail alone: deleting the deferred push
reddens one case, moving the immediate push behind the claim reddens the other.

**Two comments claimed more than the code delivers.** `erasedRecord` carries
the flag in the record, and `eraseStored()` removes that record's key a line
later — so the tombstone holds for this tab and not across a load. The limit is
now written down beside the argument for accepting it (a durable tombstone is a
durable fact about the reader, under a key §12.15's panel prints as empty), and
asserted in both directions rather than left unstated.

`prefs.aliasNamedFor`'s docblock argued that an id beats a boolean because it
tells two accounts apart. One slot only defers the collision: A clears the
name, B settles, B clears, A returns — and A's removal is offered over. Filed
as §16.11, which is a schema widening with a `merge.ts` question of its own.

**Not changed.** The provenance note in `IdentityPanel` tests a value and not
its provenance, and lines 136-145 there already record that as a measured
imprecision with its cost priced.

typecheck 0 · 2851 unit · 57 pages · 366 e2e
Enhanced the `manager-queries.md` to include the new `prefs.aliasNamedFor` field, clarifying its purpose in the context of account aliases. Additionally, removed outdated CSS rules from `profile.css` to streamline the layout of drafter actions, ensuring a more consistent user interface. Updated text in various components for clarity and accuracy, including adjustments to the legend and account sync documentation.
@cevheri
cevheri merged commit 31117ac into main Sep 2, 2026
1 check passed
@cevheri
cevheri deleted the phase6-profile-drafter-block branch September 2, 2026 11:17
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.

2 participants