chore(sync): close the ledger's decision vocabulary and guard its shape - #515
Merged
Conversation
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
force-pushed
the
chore/sync-ledger-decision-vocab
branch
from
August 30, 2026 03:55
84d8c89 to
5d313d9
Compare
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.
This was referenced Aug 30, 2026
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.
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.
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.jsonis tallied whole — which is why neither survived review and both survived 280 entries.decisionhad two spellings for one verdict50 entries said
no-op, 14 saidnoop, interleaved across the same period —f2ff8241andb0461e72are 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.mdexists precisely because one such query was answered wrongly — and a query written againstno-opwas 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 inPORTING.mdbefore it appears in a data file.prwas a number in 277 entries and a string in 3The three are the ones #511 added an hour earlier — this session's own error, and
vue-tsccaught it the moment the file was typed at all.pr === 509does not match"509", and a sort orders one as text. Fixed to numbers.b24ui_shasplits the same way and is left alone155 abbreviated to eight characters, 125 full. Unlike
pr, that split costs nothing: both forms resolve undergit 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:.sync/log/<full-sha>.md)cursoris one of the processed commits (§6 4b describes it going backwards as a real failure mode)pending-merge— forgetting the reconciliation PR is invisible, since the entry looks complete and says nothing falsesummaryMutation-verified, ten mutations, each failing exactly one assertion:
decision: 'noop'·pr: 'pending-merge'·pr: '509'·pr: 0·summary: ' '·cursorset to an unknown SHA · a key shortened to 10 chars ·b24ui_sha: 'abc'·b24ui_shaset 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 thePORTING.mdchangelog entry.Gate
lint·typecheck·build(exit 0, 3.86 MB) green. Fulltestrun was still going at push time; the file's own suite is 8/8.Generated by Claude Code