Skip to content

chore(sync): close the ledger's decision vocabulary and guard its shape - #515

Merged
IgorShevchik merged 1 commit into
mainfrom
chore/sync-ledger-decision-vocab
Aug 30, 2026
Merged

chore(sync): close the ledger's decision vocabulary and guard its shape#515
IgorShevchik merged 1 commit into
mainfrom
chore/sync-ledger-decision-vocab

Conversation

@IgorShevchik

Copy link
Copy Markdown
Collaborator

Found by a routine sync check, not by anything red. Both drifts below are invisible in a one-entry diff and only appear when .sync/nuxt-ui.json is tallied whole — which is why neither survived review and both survived 280 entries.

decision had two spellings for one verdict

50 entries said no-op, 14 said noop, interleaved across the same period — f2ff8241 and b0461e72 are twenty entries apart and disagree. Neither form is wrong on its face.

It is not cosmetic. The ledger's whole purpose is being queryable after the fact — the §1 component-name rule in PORTING.md exists precisely because one such query was answered wrongly — and a query written against no-op was quietly missing a fifth of them.

The 14 are normalised, and §6 step 4 now writes the vocabulary down as closed: port, no-op, skip, n/a. A fifth verdict is a process change and belongs in PORTING.md before it appears in a data file.

pr was a number in 277 entries and a string in 3

The three are the ones #511 added an hour earlier — this session's own error, and vue-tsc caught it the moment the file was typed at all. pr === 509 does not match "509", and a sort orders one as text. Fixed to numbers.

b24ui_sha splits the same way and is left alone

155 abbreviated to eight characters, 125 full. Unlike pr, that split costs nothing: both forms resolve under git show, which is the field's only use. Normalising 280 rows to buy nothing is not worth the diff, so the assertion checks for a value that resolves to nothing — 7-to-40 lowercase hex — rather than for uniformity.

The guard

test/utils/sync-ledger.spec.ts, 8 assertions. Alongside the three above it pins the invariants that were unenforced either way:

  • keys are full 40-char SHAs (short ones would break the pairing with .sync/log/<full-sha>.md)
  • cursor is one of the processed commits (§6 4b describes it going backwards as a real failure mode)
  • no entry still carries pending-merge — forgetting the reconciliation PR is invisible, since the entry looks complete and says nothing false
  • every entry has a non-empty summary

Mutation-verified, ten mutations, each failing exactly one assertion: decision: 'noop' · pr: 'pending-merge' · pr: '509' · pr: 0 · summary: ' ' · cursor set to an unknown SHA · a key shortened to 10 chars · b24ui_sha: 'abc' · b24ui_sha set to non-hex · and the restored file green.

Deliberately not fixed here

Four entries at the very start of the ledger — 2799fa6f, 631f5dc5, 6102a87b, 007b136a (PRs #68#72) — have no .sync/log/<sha>.md. They predate the convention by one commit (position 1 has one). Writing that reasoning now would be reconstruction rather than record, so a matching "every entry has a log" guard is deferred too: it would go red on exactly those four. Both are recorded in the PORTING.md changelog entry.

Gate

lint · typecheck · build (exit 0, 3.86 MB) green. Full test run was still going at push time; the file's own suite is 8/8.


Generated by Claude Code

A routine sync check turned up two drifts in `.sync/nuxt-ui.json` that no
guard could see, because both are only visible when the whole file is
tallied rather than diffed one entry at a time.

`decision` had two spellings for one verdict: 50 entries `no-op`, 14
`noop`, interleaved across the same period. That is not cosmetic — the
ledger is queried by decision when answering "what did we skip, and why",
and a query written against one spelling silently omits the rest. The 14
are normalised, and §6 step 4 now writes the vocabulary down as closed:
`port`, `no-op`, `skip`, `n/a`.

`pr` was a number in all 277 older entries and a string in exactly the
three #511 added, which is this session's own error; `vue-tsc` caught it
the moment the file was typed at all. Those three are now numbers.

`b24ui_sha` splits the same way — 155 abbreviated, 125 full — and is left
split on purpose: both resolve under `git show`, which is the field's only
use, so the guard checks for a value that resolves to *nothing* rather
than for a uniformity worth 280 rows of diff.

`test/utils/sync-ledger.spec.ts` holds all of it, plus the invariants that
were unenforced either way: full-SHA keys, a `cursor` that is one of the
processed commits, no entry still carrying `pending-merge`, and a
non-empty `summary`. Ten mutations verified, each failing exactly one
assertion.

Two findings from the same audit are deliberately not fixed here and are
recorded in PORTING.md: four entries at the very start of the ledger have
no `.sync/log/<sha>.md`, predating the convention by one commit, and
writing that reasoning now would be reconstruction rather than record.
@IgorShevchik
IgorShevchik force-pushed the chore/sync-ledger-decision-vocab branch from 84d8c89 to 5d313d9 Compare August 30, 2026 03:55
@IgorShevchik
IgorShevchik merged commit 147a8fc into main Aug 30, 2026
2 checks passed
@IgorShevchik
IgorShevchik deleted the chore/sync-ledger-decision-vocab branch August 30, 2026 04:08
IgorShevchik pushed a commit that referenced this pull request Aug 30, 2026
… reasoning

Review of #516. Two findings were mine, and one of them was a false claim.

The gate stopped watching before teardown ran. `enableAutoUnmount(afterEach)`
was registered before `installConsoleGate()`, and vitest runs same-level
`afterEach` hooks last-registered-first — so the gate restored `console` and
made its check while the wrapper was still mounted, and a `console.warn` from
`onUnmounted` was never seen. Probed with a component that warns while
unmounting: it passed. The two calls are swapped, the probe now goes red, and
the whole suite stays green — nothing warns during teardown today, but it would
be caught from here on.

The reason given for disabling axe's `aria-hidden-focus` was wrong. It said the
violation is absent under the production default `portal: true`. Measured
directly on the DOM rather than through axe: with `portal: false` the trigger
itself carries `aria-hidden` while the popup is open; with `portal: true` the
`[data-v-app]` ancestor carries it instead and the trigger is still focusable
inside. The arrangement is the same either way — the rule only stops firing
because `axe(wrapper.element)` cannot see an ancestor above its own root. The
comment now says that, and says the underlying question is open. Raised with
the maintainer, who decided against an issue and against an upstream report.

Also from review:

- `attach-mount.ts` took `attachTo` from a plain spread, so an explicit
  `attachTo: undefined` mounted detached there and attached in the `vue`
  project, where `defu` drops it. Now `?? document.body` in both.
- The alias guard compared `importer` with `endsWith('test/utils/attach-mount.ts')`.
  On Windows `importer` arrives with backslashes, the guard never matches and
  the module resolves to itself — invisible here, fatal there. Now a resolved
  path comparison.
- Benchmarks inherit both defaults, and `afterEach` never fires in bench mode,
  so every wrapper stayed in Vue Test Utils' tracking array — measured at 711
  iterations with 0 `afterEach` calls. `disableAutoUnmount()` at the top of the
  bench file, with a note that timings are not comparable to ones recorded
  before mounts were attached.
- Four comments still credited `componentRender` with the unmounting it no
  longer does, one of them contradicting a paragraph thirty lines below it.
- `patchComputedStyle.ts` carried 24 lines of comment on 6 lines of code.

`main` is merged in: the branch was cut before #515 and would have reverted it.

The claim in the previous commit message that Vue warns on a second
`app.unmount()` does not hold on the versions this repo pins — two reviewers
measured it independently. `componentRender` still leaves teardown to
`enableAutoUnmount`, because one mechanism is better than two, but that is the
reason and the warning is not.
IgorShevchik added a commit that referenced this pull request Aug 31, 2026
…#518)

The ledger's log convention starts one entry in, at `d50c121c`, so the four
commits before it — `2799fa6f`, `631f5dc5`, `6102a87b`, `007b136a` (#68-#72) —
had a `processed` entry and no `.sync/log/<sha>.md`. #515 deferred both these and
the guard that would have caught them, on the grounds that writing a rationale
months later is reconstruction rather than record.

The record turned out to still exist. Both commits are readable on both sides,
and three of the four fork commits carry contemporaneous reasoning in their own
messages, so each log is derived from `git show` on the upstream commit and on
ours, and says at the top that it was backfilled and from what.

Re-deriving them surfaced an omission nobody had recorded: `2799fa6f` added an
autocomplete-mode row to upstream's InputMenu playground, the port dropped it,
and neither playground has one today. It also made two ports legible that the
one-line summaries had flattened — `631f5dc5` must NOT copy upstream's prop list
verbatim, because ours omits `loadingIcon` on purpose, and `007b136a` had to move
a cap between slots rather than add one, which is why it is breaking here and is
not upstream.

The guard asserts the pairing in both directions plus a floor on file size: a
missing log is the obvious failure, an orphaned log is what a mistyped SHA in a
filename looks like, and an empty file satisfies pairing while documenting
nothing. Three mutations verified. The heading format is deliberately not
guarded — it varies across the 280 files, and a rule there would be invented
rather than enforced.
IgorShevchik added a commit that referenced this pull request Sep 1, 2026
Ports `9c99bf16`, `9d46fc7e` and `20c1954d` — a contiguous run taken in one PR
per PORTING.md §6 4b, because separating them would rewrite the lockfile three
times over the same packages.

Not only versions. `9c99bf16` carries a runtime fix that exists because of one of
its own bumps: `validateSchema` now unwraps its argument with `toRaw`, because
Zod 4.5 resolves `~standard` through a lazy getter that captures `this`, so a
schema held in reactive state arrives as a Vue proxy and the getter reads
non-configurable `_zod` internals through it. This fork was on zod ^4.4.3, where
the bug does not bite — it goes live with the ^4.5.4 bump in the same commit, so
the fix arrives with its trigger rather than speculatively.

`pnpm-workspace.yaml` takes one of upstream's two removals, each decided by
measurement. `minimumReleaseAgeExclude` is removed: without it, install still
reports the lockfile passing supply-chain policies, so the entry was spent. The
`@nuxt/content>@nuxtjs/mdc` override is kept against upstream: removing it
demonstrably resolves two mdc copies, because the root peer `@nuxt/content:
^3.0.0` — deliberately wide — sits at 3.14.0.

`20c1954d` moves 1316 snapshot lines. What reka 2.10.4 changes was derived from
upstream's own diff rather than its notes, and our regenerated snapshots were
checked against it rather than trusted: the same three deltas and nothing else.
The RadioGroup one is a correction, not a loss — `aria-label` carried the item's
value and won over the wrapping label, so a radio reading "Option 1" announced
itself as "1".

Also corrects the ledger guard added in #515. It asserted that no entry carries
`pending-merge`, which §6 step 4 requires them to, so it went red on the very
next port and would have blocked the documented process rather than guarding it.
The real invariant is ordering: pending entries must form a suffix.
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