Skip to content

NamedListView's member figures are wrong in four places, and the pin that should have caught it is floored below the truth (follow-up to #7779 / PR #7922) #7947

Closed

Description

@os-justin

Follow-up carrier for a correction that PR #7922 landed with, filed by the domain:spec @ objectui execution seat (session session_01BAZFhALsQsGqxui8sNqM8s). The card exists because the remedy is otherwise owned by nobody: #7779 is closed, its thread is not a work queue, and objectui#5741 is this repo's standing record of what happens when a remedy lives only in a closed thread.

What is wrong, on origin/main 5b5a5c3a3

The listViews measurement that justifies leaving the key unmirrored is quoted in four places. Its two figures are both wrong.

"about 52 members" → 47. The contract-review pass re-counted NamedListView's top-level members with the pin's own namedListViewMemberCount() regex, at the branch tip and at the merge-base: 47. A looser count that also matches nested object-literal lines gives 59, and that is where "about 52" came from — a hand figure between two instruments, matching neither.

"about 45 unread" → 41. Of the seven keys the renderer reads off a named view, six are declared NamedListView members (label, type, columns, filter, sort, options). The seventh, data, is not a declared member at all — it reaches the renderer through an as any cast at packages/plugin-view/src/ObjectView.tsx:1815. So the arithmetic is 47 − 6 = 41 members that a key-for-key local mirror would enforce unread, not 52 − 7 = 45.

The four sites:

file what it says
.changeset/object-view-unmirrored-keys-7779.md "about 52 members, of which the renderer reads seven"; "roughly 45 unread members"
packages/types/src/zod/objectql.zod.ts (the ObjectViewSchema docblock) "~52 members"; "~45 unread members"
packages/types/src/__tests__/zod-mirror-parity.test.ts (the UnmirroredDeclared note) "~52 members"; "~45 unread members"
packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts (header) "about 52 members, of which the renderer reads seven"

And the pin does not bind. The same test file asserts expect(declared).toBeGreaterThanOrEqual(40). That floor was chosen against the wrong count: at a true 47 it permits the declaration to shed seven members — including a shrink toward the read set, which is precisely the condition that would re-open the listViews decision (#7928) — without failing. It also cannot catch growth, so the quoted figures can stale silently in either direction.

Why it was not fixed in PR #7922

The fix was prepared and verified before the PR landed. It could not be pushed: another seat had already flipped the PR ready and armed auto-merge, the branch was in the merge queue, and the push was rejected GH006. Dequeuing would have rebuilt four other lanes' queued PRs (#7929, #7930, #7937, #7939) and re-run their CI in order to correct comment prose — not proportionate. The seat ruled: let it land, follow up. This card is the follow-up.

The prepared work is retained as a verified commit and can be reapplied onto current main; it is text plus the one assertion, 8 files untouched beyond the four above.

Scope

Text and figures only, plus one pin tightening. No schema, no type, and no assertion about what is accepted or refused changes.

  1. All four sites read 47 declared top-level members and 41 unread, each stating that the seventh read (data) is not a declared member and arrives via an as any cast.
  2. toBeGreaterThanOrEqual(40) becomes an exact toBe(47), with a failure message that names the three files whose figures must move together when the count legitimately changes.
  3. The pin comment records how the count is taken (this file's own regex) and where "about 52" came from, so the next reader does not re-derive the looser number and re-introduce it.
  4. Stale line range objectql.ts:1957-2134 is dropped rather than re-stated: it was already off after the mirror edit, and a line range is not a durable citation.

Acceptance

  • pnpm exec vitest run packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts packages/types/src/__tests__/zod-mirror-parity.test.ts packages/types/src/__tests__/object-view-spec-parity.test.ts green.
  • A reverse check: temporarily perturbing NamedListView by one member fails the new toBe(47) with the message that names the three files. Restore, tree clean.
  • The changeset guard passes; the changeset for @object-ui/types carries the corrected figures.

Deadline that matters

.changeset/object-view-unmirrored-keys-7779.md is still unconsumed. Once the next @object-ui/types release consumes it into a published CHANGELOG, the wrong figure is permanent in release history and can only be footnoted, not corrected. Everything else here stays fixable indefinitely.

Refs: #7779 (the card PR #7922 closed) · #7928 (the listViews value-type decision the pin guards) · #5741 (the carrier-discipline record this card exists to honour).

Activity

  1. added theissue type on Sep 6, 2026
  2. self-assigned this
    on Sep 6, 2026
  3. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Claim: domain:spec @ objectui execution seat — session session_01BAZFhALsQsGqxui8sNqM8s, 2026-09-06T04:07Z. Atomic pair written: assignee os-justin + pm:dispatched (the card was filed unlabelled by this same seat one minute earlier, so there is no pm:queue to strip); read back and confirmed.

    Clause-②: no — the diff changes prose in four places and tightens one test assertion. No schema, no type, no describe/tombstone message, and nothing about which documents the validator accepts or refuses. object-view-unmirrored-keys-7779.test.ts's toBeGreaterThanOrEqual(40) → toBe(47) is a pin on a count taken off disk, not on the contract's accept set.

    Routing — maintainer direct-dispatch channel. This card is remedial work inside batch 2, item 4 of the maintainer's instruction, quoted verbatim: 「第二批(4 项,契约形状族) 帮我处理,全部同意」. It is not routed through triage first, and no domain:* is stamped by this seat.

    File face declared for serial-queue purposes: .changeset/object-view-unmirrored-keys-7779.md, packages/types/src/zod/objectql.zod.ts (the ObjectViewSchema docblock only), packages/types/src/__tests__/zod-mirror-parity.test.ts (the UnmirroredDeclared note only), packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts. The two shared test files are hot: #7493's PR #7932 touches zod-mirror-parity.test.ts at the base.zod.ts#ComponentInputSchema rows and its own header figures — a different region of the same file. This card's edit waits behind #7932's landing rather than racing it into the queue.

    Container & model recorded at dispatch.


    Generated by Claude Code

  4. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Dispatched 2026-09-06T04:49Z — domain:spec @ objectui seat, session session_01BAZFhALsQsGqxui8sNqM8s.

    The serialization declared in the claim comment is withdrawn — it was based on a false reading, and the card starts now. That comment said this work had to queue behind PR #7932 because both edit packages/types/src/__tests__/zod-mirror-parity.test.ts. #7932 does not touch that file at all: two independent git diff --stat legs (against its old base and against current origin/main) come back empty, and its own body said so at open time — the tombstone route keeps every mirror pair, so declared and enforced stay equal and no ledger row moves. I inherited the overlap claim from #7922's "Sibling overlap" section and repeated it without deriving it myself. Same failure mode as the census error this card exists to fix, which is worth naming rather than quietly correcting.

    What is actually shared is one file with separated regions, and the lane's file/region granularity ruling covers it. object-view-unmirrored-keys-7779.test.ts: #7932 edits only REGISTRATION_TEXT (:113–:117); this card edits the header and the member-count assertion (:537). The four substitute disciplines are carried into the dispatch in full — region-level file face declared in the PR body, merge origin/main before opening, merge again once #7932 lands and re-run the gates on that head, and any conflict goes to the merge queue with ⛔ no hand-ordering and no touching the sibling's region.

    Tier reading, taken for this dispatch and not inherited. Issued at CONTRACT_REVIEW_TIER first; it returned HTTP 429 (req_011CemadkixNjBggSSDsDeLU, "You've reached your Fable limit") at 04:48Z. The tier is therefore measurably unavailable and the round runs exactly one tier down, at opus, and no further. The exemption covers implementation only. This card has no contract judgement in it — text, figures, and one pin tightening — and finding one is the dev's stop condition, not something the exemption lets it resolve.

    What the dev was told not to inherit. The card's figures (47 declared, 41 unread) are the previous review pass's readings, and the brief requires them re-taken with the pin file's own namedListViewMemberCount() regex — the instrument the assertion uses, so any other count is a different measurement and must not be quoted beside it. A disagreement with 47 is a needs_decision, not a number to write down: the card's premise would then be wrong and that is the seat's to re-rule. Same for the read set: the claim that data reaches the renderer through an as any cast is to be verified as a cast, with the line number treated as a pointer rather than the fact.

    And the pin must be seen to fail. Perturbing NamedListView by one member has to turn the new exact assertion red with the message that names the three files whose figures move together, then restore to a clean tree. A pin written to match a number nobody has watched reject a different number is a transcription.

    Premise re-verified on origin/main 5b5a5c3a3 before dispatch: all six wrong-figure occurrences and the loose floor are present at the four sites the card lists, with a non-zero control.


    Generated by Claude Code

  5. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    os-dev-report
    {
    "issue": 7947,
    "status": "done",
    "branch": "claude/issue-7947-named-list-view-figures",
    "pr": "#7969",
    "premise_still_valid": true,
    "summary": "Re-derived both figures on my own head rather than inheriting the card's, and they agree: 47 declared top-level NamedListView members and 41 unread. Corrected all four sites (.changeset/object-view-unmirrored-keys-7779.md, the ObjectViewSchema docblock in packages/types/src/zod/objectql.zod.ts, the UnmirroredDeclared note in packages/types/src/tests/zod-mirror-parity.test.ts, and the header of packages/types/src/tests/object-view-unmirrored-keys-7779.test.ts), each now saying in its own words that the seventh read key, data, is not a declared member and arrives through an as-any cast on the named-view config in packages/plugin-view/src/ObjectView.tsx (line 1815 on this head, verified present). Turned the non-binding floor toBeGreaterThanOrEqual(40) into an exact toBe(47) whose failure message names the three sibling files that carry the figures, with a comment recording how the count is taken (this file's own regex, indent anchor and all) and that the looser count gives 59, which is where the retired 'about 52' came from. No schema, no type, no accept/refuse assertion moved; REGISTRATION_TEXT untouched. Merged origin/main once (859de84) before opening the PR and re-ran everything on the merged head 27bb40b. PR is DRAFT, not enqueued, no auto-merge. Model tier: the contract-review tier returned HTTP 429 (req_011CemadkixNjBggSSDsDeLU, account usage limit) at 04:48Z, so this round ran exactly one tier down at opus under the seat's quota exemption; the exemption covered implementation only and this card required no contract judgement, so none was made.",
    "figures_rederived": {
    "command": "node scratchpad/count.mjs, a byte-for-byte reimplementation of the pin file's namedListViewMemberCount(): read packages/types/src/objectql.ts, slice from the literal 'export interface NamedListView {' to the next newline-brace-newline, count matches of the regex anchored at start-of-line plus exactly two spaces plus an identifier plus an optional question mark plus a colon, flags g and m",
    "declared_top_level_members": 47,
    "loose_count_any_indent": 59,
    "read_set_from_namedViewReads": [
    "columns",
    "data",
    "filter",
    "label",
    "options",
    "sort",
    "type"
    ],
    "declared_of_those_read": 6,
    "unread": 41,
    "taken_at_base": "d4864f3a4",
    "retaken_after_merge_at": "859de84a0 (merged head 27bb40b), still 47",
    "as_any_cast_verified": "packages/plugin-view/src/ObjectView.tsx:1815, the line reads: data: (currentNamedViewConfig as any)?.data ?? (activeView as any)?.data ?? (schema as any).data"
    },
    "tests": "All exits captured before any pipe (redirect to a file, EXIT=$?, then tail). [1] pnpm exec vitest run packages/types/src/tests/object-view-unmirrored-keys-7779.test.ts packages/types/src/tests/zod-mirror-parity.test.ts packages/types/src/tests/object-view-spec-parity.test.ts : exit=0, 'Test Files 3 passed (3)' / 'Tests 137 passed (137)' (re-run green on merged head 27bb40b). [2] pnpm exec vitest run packages/types/ : exit=0, 'Test Files 129 passed (129)' / 'Tests 2388 passed (2388)'. [3] pnpm --filter @object-ui/types type-check : exit=0 (objectui spells it type-check with a hyphen; the script echo confirms tsc --noEmit plus tsconfig.examples.json plus tsconfig.test.json all ran). [4] pnpm --filter @object-ui/types lint : exit=0, '274 problems (0 errors, 274 warnings)', every warning pre-existing. [5] node scripts/check-changeset-presence.mjs : exit=0, 'declares 1 changeset(s): .changeset/7947-named-list-view-figures.md. Every one of them has an EMPTY frontmatter, declared as releasing nothing, which is the explicit exemption'. [6] node scripts/check-changeset-no-major.mjs : exit=0, 'No changeset declares a major bump.' [7] node scripts/check-changeset-fixed.mjs : exit=0, 'All workspace packages are in the changeset fixed group.' [8] node scripts/check-changeset-overwrite.mjs : exit=0 (report-only); it flags the in-place correction of the existing changeset as its own documented case 2, 'You are CORRECTING a declaration on purpose', which is exactly what the card asked for; its 'declared at base' and 'declares now' lines both read '@object-ui/types: minor', so no third party's declaration was overwritten. [9] node scripts/check-control-bytes.mjs : exit=0, 'scanned 6418 tracked text file(s); skipped 85 binary'; plus a manual grep -naP over my five files for the control-byte class : exit=1, no hits. [10] pnpm run check:spec-symbols : exit=0, including 'spec member citations: no comment cites a key its spec symbol does not declare', the gate that actually reads the docblock I edited. [11] pnpm run check:handler-key-reads : exit=0. [12] node scripts/check-governed-queue-guard.mjs --test on my five paths : exit=0, 'NOT GOVERNED, 5 path(s) checked against 5 governed surface(s); none matched.' REVERSE VERIFICATION (implementation committed first at 456b307; the mutation script carried trap restore on EXIT INT TERM with absolute paths): HEAD blob for packages/types/src/objectql.ts = 9d3a05b45c633f13ff4ea723fc51ed3d56fb00e6, non-empty and checked before use. Mutation proved on disk: marker occurrences BEFORE = 0, AFTER = 1; mutated blob = aa032c87e33ca480f0431f727a22b1728f905088, different from the HEAD blob; pin regex count moved from 47 to 48 and the loose count from 59 to 60. MUTATED VITEST exit=1, 'Tests 1 failed | 89 passed (90)', AssertionError text: "NamedListView's top-level member count moved (was 47). Re-derive it with this file's own namedListViewMemberCount() regex, then update the '47 declared / 41 unread' figures in the three files that carry them together: .changeset/object-view-unmirrored-keys-7779.md, packages/types/src/zod/objectql.zod.ts and packages/types/src/tests/zod-mirror-parity.test.ts, plus this file's own header. A shrink toward the read set also re-opens the listViews decision (objectui#7928).: expected 48 to be 47". RESTORE leg: git checkout HEAD -- (absolute path to the declaration), then proved by state and not by an exit code: restored blob = 9d3a05b45c633f13ff4ea723fc51ed3d56fb00e6, identical to the HEAD blob; marker occurrences AFTER RESTORE = 0; git diff HEAD output byte size = 0; git status --short empty; pin regex count back to 47; the three pins then re-run green. The direction observed was the predicted one, turns red, not a reversal and not a diagnostic-count change. NARROWING, DECLARED AND MEASURED: the only published source file in the diff is packages/types/src/zod/objectql.zod.ts and its change is confined to a JSDoc block, proved rather than asserted by stripping comments and collapsing whitespace on base versus head: code-only characters 30654 / 30654, CODE-ONLY IDENTICAL: true, while raw bytes differ 66612 / 67026. TURBO_SCM_BASE=d4864f3a4 pnpm exec turbo ls --affected names all 43 packages only because @object-ui/types is a universal dependency; running their suites locally could not measure anything this diff can move, and the repo-wide farm (pnpm lint, pnpm test, the full check:* set) is CI's run. No leg needed a build: the pin reads source off disk, not a dist artifact.",
    "mcp_calls": "3: create_pull_request, pull_request_read (body read-back), add_issue_comment (this report). Everything else went through git, the container's zero-quota GitHub page payload channel for the card body, and local scripts. Repo-scoped REST is NOT available to this session: GET /repos//issues/7947 returned HTTP 403 'GitHub access is not enabled for this session', so the REST channel was unusable and the degradation ladder (git, then the public-repo page payload, then MCP for writes) was used instead.",
    "open_questions": [
    {
    "question": "Card item 4 asks that the stale line range objectql.ts:1957-2134 be dropped where it appears, but it appears nowhere: not on this branch's base d4864f3 and not on the card's own head 5b5a5c3. git grep for the literal returned exit 1 at both refs, with a positive control on the same refs returning hits, and a wider grep for the bare numbers 1957 and 2134 finds only unrelated chart image hashes and a #2134 issue reference. NamedListView does live at objectql.ts:1983 through roughly :2159 on my head, so the range was plausibly a citation of it that had already been removed before the card was written. Nothing was dropped because nothing was there, and no line range was introduced anywhere in the diff. Recorded because it is a card premise that did not hold, even though it does not change the outcome.",
    "options": [
    "A: treat item 4 as vacuously satisfied and close it with this PR (what I did)",
    "B: the seat re-checks whether the range lives somewhere outside the repo, such as the #7779 report or PR #7922's body, and files that separately if it matters"
    ],
    "recommendation": "A, because the requirement is stated as 'dropped where it appears' and a grep with a positive control at both relevant refs shows it appears in no tracked file; B is cheap for the seat to confirm but is outside a dev's file face."
    },
    {
    "question": "The card asks for the existing changeset to be corrected in place 'unless the changeset guards refuse that'. They refuse: scripts/check-changeset-presence.mjs counts only ADDED .changeset/*.md files (git diff --filter=A), so an in-place correction alone left that gate at exit 1 with '0 changeset(s) added'. I corrected the existing changeset in place anyway, since that is the deadline the card names and its wrong figures must never reach a published CHANGELOG, and ALSO added .changeset/7947-named-list-view-figures.md with an EMPTY frontmatter, the gate's own documented first-class 'this releases nothing' declaration.",
    "options": [
    "A: keep both, the existing changeset corrected in place plus an empty-frontmatter declaration for this change (what I did)",
    "B: leave the existing changeset untouched and put the corrected figures only in a new bumping changeset",
    "C: ask for a gate change so an in-place correction counts as a declaration"
    ],
    "recommendation": "A. B loses the card's whole point, since the unconsumed changeset is the one artifact that becomes uncorrectable once a release consumes it. C would be a gate change on a card whose scope is text and figures, and the gate's behaviour here is deliberate and documented in its own header."
    },
    {
    "question": "The PR body contains one angle-bracket-shaped placeholder, in the prose sentence describing the restore command. It survived creation intact, verified by reading the stored body back in full, because it has a space inside and so is not tag-shaped. I did not rewrite it: AGENTS.md is explicit that an unnecessary rewrite of an intact body is the more expensive error, and a PATCH would also downgrade the session-URL footer to bare form. The session URL is additionally carried in the body prose as a code span, which survives PATCH.",
    "options": [
    "A: leave the body as created (what I did)",
    "B: PATCH the body to spell the placeholder as a word, accepting the footer downgrade and the documented risk of a second footer being appended"
    ],
    "recommendation": "A, because the read-back shows the stored body is complete and correct, and the sanitizer acts at write time, so the only way that fragment can now be eaten is a future PATCH, which would carry its own mutations regardless."
    }
    ],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  6. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    ACCEPT — PR #7969, head 27bb40b2a. Seat reading 2026-09-06T05:10Z.

    The figures were re-taken, not inherited — which is the whole point of this card. The dev reimplemented the pin's own namedListViewMemberCount() byte for byte (slice from export interface NamedListView { to the closing brace, count the start-of-line-plus-exactly-two-spaces regex) and ran it at its own base d4864f3a4 and again after merging main at 859de84a0: 47 both times, with the loose any-indent count at 59. The read set comes off namedViewReads(), six of the seven read keys are declared members, and the seventh is verified as a cast rather than as a line number — ObjectView.tsx:1815 reads data: (currentNamedViewConfig as any)?.data ?? (activeView as any)?.data ?? (schema as any).data. 41 follows. Independent agreement with the card, from a separate instrument.

    The pin now fails, and fails saying the right thing. Mutation proved on disk by blob hash (9d3a05b45c… → aa032c87e3…) with the regex count moving 47 → 48; the assertion then reads:

    NamedListView's top-level member count moved (was 47). Re-derive it with this file's own namedListViewMemberCount() regex, then update the '47 declared / 41 unread' figures in the three files that carry them together: … plus this file's own header. A shrink toward the read set also re-opens the listViews decision (objectui#7928).: expected 48 to be 47

    That is a failure message that tells the next person where to go instead of telling them a number. Restore proven by state: blob back to the HEAD hash, git diff HEAD byte-empty, status clean, pins green.

    Narrowing declared and measured, not asserted. The only published source file in the diff is packages/types/src/zod/objectql.zod.ts, and its change is confined to a JSDoc block — proved by stripping comments and collapsing whitespace on base vs head: code-only characters 30654 / 30654, identical, while raw bytes differ. Clause-② stands at no on that measurement, so no contract-review gate applies and none was manufactured.

    The three open questions

    1 — item 4's premise was wrong, and it is mine. The card told the dev to drop the stale line range objectql.ts:1957-2134 "where it appears". It appears nowhere: git grep returns exit 1 at both relevant refs with a positive control returning hits on the same refs. That range lived in #7928's body, which I had already corrected at 04:00Z — I then wrote the instruction into this card as if it were still in the tree. Answer: A, vacuously satisfied, nothing dropped because nothing was there, and no line range introduced. Recorded as the fourth instance this shift of the same failure mode: an assertion carried from one artifact to another without being re-derived against the thing it describes. The dev catching it with a controlled grep instead of quietly doing nothing is the right shape.

    2 — the changeset guard genuinely refuses an in-place correction. Answer: A, keep both. check-changeset-presence.mjs counts only added .changeset/*.md files (git diff --filter=A), so correcting the existing changeset in place leaves the gate at exit 1 with "0 changeset(s) added". The dev corrected it in place anyway — that file is the one artifact that becomes uncorrectable once a release consumes it, which is this card's entire deadline — and added .changeset/7947-named-list-view-figures.md with empty frontmatter, the gate's own documented "this releases nothing" form. B would abandon the card's purpose; C would be a gate change riding a text-and-figures card, and the gate's behaviour here is deliberate and documented in its own header. check-changeset-overwrite.mjs classifies this as its case 2, "you are CORRECTING a declaration on purpose", with both its "declared at base" and "declares now" lines reading @object-ui/types: minor — no third party's declaration was overwritten.

    3 — PR body placeholder. Answer: A, leave it. The read-back shows the stored body complete; the sanitizer acts at write time, so the only way that fragment can now be eaten is a future PATCH, which would also downgrade the session-URL footer. An unnecessary rewrite of an intact body is the more expensive error.

    Landing — held deliberately, and this is the handoff

    PR #7969 stays DRAFT and unqueued. It shares object-view-unmirrored-keys-7779.test.ts with PR #7932 at a separated region (this PR: the header and the :537 count assertion; #7932: REGISTRATION_TEXT at :113–:117), and #7932 is in the merge queue now, third behind #7960 and #7961. The lane's third substitute discipline is explicit: merge origin/main again once the sibling lands and re-run the gates on that head.

    I am ending this shift, so I am not enqueueing it. Sending a PR into the queue whose declared discipline calls for a post-sibling re-merge, and then leaving with nobody to attend a kick-out, is the shape that produces an unattended red. The next step is mechanical and stated here so it needs no reconstruction: once #7932 is on origin/main, merge it into this branch, re-run the three pins plus packages/types/, then ready + auto-merge SQUASH. ⛔ REGISTRATION_TEXT is not this PR's to touch in that merge — take #7932's side of it.


    Generated by Claude Code

  7. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    on Sep 6, 2026
  8. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊:补 domain:spec + bug + documentation + tests + priority:p2(pm:dispatched 保留)

    ⭐ 本席用一个独立的、错误的工具,复现了卡片对「52 是怎么来的」的诊断

    卡片说:"A looser count that also matches nested object-literal lines gives 59, and that is where 'about 52' came from — a hand figure between two instruments, matching neither."

    本席对 NamedListView 的接口体跑了一次朴素的成员正则(^\s+name\??: 形状,不排除嵌套对象字面量行):

    59
    

    ⇒ 逐数命中卡片指认的那个"更宽的计数"。 ⭐ 这不是对 47 的确认(本席没跑 pin 自己的 namedListViewMemberCount()),而是对卡片病理学的确认:确实存在两个工具、两个数(47 与 59),而 52 两个都不是。⇒ 卡片对「手写数字介于两个仪器之间」的诊断,本席用其中一个仪器复现了。

    其余锚点逐条核实(origin/main)——四个站点全部逐字命中

    .changeset/object-view-unmirrored-keys-7779.md:48   … the local `NamedListView` (about 52 members, of which the
    .changeset/object-view-unmirrored-keys-7779.md:56   … the local value would enforce roughly 45 unread members into the contract — the
    packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts:48   * (about 52 members, of which the renderer reads seven), the spec's
    packages/types/src/__tests__/zod-mirror-parity.test.ts:1467   * … the local `NamedListView` (~52
    packages/types/src/__tests__/zod-mirror-parity.test.ts:1474   * … the local value enforces ~45 unread members into the contract (the reason ruling B
    packages/types/src/zod/objectql.zod.ts:235   *     (~52 members, of which the renderer reads `label`, `type`, `columns`,
    packages/types/src/zod/objectql.zod.ts:242   *     ~45 unread members into the contract (local), so the key stays in the
    

    那条不绑定的 pin 也逐字在,连失败消息都对得上:

    packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts:200   function namedListViewMemberCount(): number {
    packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts:536     const declared = namedListViewMemberCount();
    packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts:537     expect(declared, 'NamedListView shrank to (near) its read set — re-take the listViews decision').toBeGreaterThanOrEqual(40);
    

    第七个读键的 as any 也在:

    packages/plugin-view/src/ObjectView.tsx:1815   data: (currentNamedViewConfig as any)?.data ?? (activeView as any)?.data ?? (schema as any).data,
    

    ⇒ 卡片零错。


    为什么定 p2 —— 因为它带一个会到期的窗口

    ⭐ 卡片自己给了这个理由,本席认为它是决定性的:

    .changeset/object-view-unmirrored-keys-7779.md is still unconsumed. Once the next @object-ui/types release consumes it into a published CHANGELOG, the wrong figure is permanent in release history and can only be footnoted, not corrected. Everything else here stays fixable indefinitely.

    ⇒ 四个站点里有三个可以永远修;第四个有一个发布窗口。 本席核实该 changeset 确实仍未被消费(它还在 .changeset/ 下)。⇒ p2,且它的紧迫性来自一个日期而不是一个严重度。

    ⛔ 不到 p1:无运行期后果、无用户可见故障;错的是四处散文里的两个数字。
    ⛔ 不降 p3:发布窗口一旦关闭,代价从「改四处文本」变成「永久错误 + 一条脚注」。

    ⭐ 而那条 pin 的失效方式,值得单独记名

    That floor was chosen against the wrong count: at a true 47 it permits the declaration to shed seven members — including a shrink toward the read set, which is precisely the condition that would re-open the listViews decision (#7928) — without failing. It also cannot catch growth, so the quoted figures can stale silently in either direction.

    ⇒ 这是本 lane 一整天在追的那个失败类的又一变体:一条按错误基准设的下限,使它对它自己声明要守护的那个条件(声明收缩到读集)结构性失明。⭐ 而失败消息 'NamedListView shrank to (near) its read set — re-take the listViews decision' 一字不差地写着它要守的东西——它只是守不住。

    ⇒ 交付物里的第 2 项(toBeGreaterThanOrEqual(40) → 精确 toBe(47),且失败消息点名那三个必须同步移动的文件)是本卡最持久的一半,⛔ 比改数字更重要。

    定级理由(其余)

    • domain:spec:落点 packages/types(声明面、其 zod 镜像、其 pin)+ .changeset ⇒ objectui 的 domain:spec(标签自述:fix lands on packages/types, schema corpus or spec pin coupling)。
    • bug + documentation + tests:四处散文的数字为假 ⇒ 有东西是假的;载体含 changeset、docblock 与测试头;且交付物含一条 pin 的收紧。⛔ 无 schema、无类型、无接受/拒绝面的改动(卡片自陈,本席背书)⇒ Bug/tidy 侧,无人工地板。

    ⚠️ 承接指引

    PR #7969 已开且在跑(docs(types): correct the NamedListView member figures and pin the count exactly)。⇒ 本卡已 pm:dispatched、已有 assignee,本席只补标签,不动状态。

    ⭐ 卡片自己写下的第 3、4 项值得在评审时确认落实:

    • 第 3 项:pin 的注释要记下计数是怎么取的(该文件自己的正则)以及「about 52」是从哪来的 ⇒ 下一个读者不会再推导出那个更宽的数并把它重新引入。 ⚠️ 本席刚才正是用那个更宽的工具得到 59 的——⇒ 这条注释不是可选的。
    • 第 4 项:陈旧的行区间 objectql.ts:1957-2134 丢弃而非重述。⭐ 理由本席逐字背书:「a line range is not a durable citation」——本轮本席在十余张卡上撞到行号漂移,这条判断是对的。

    Refs:#7779(PR #7922 关闭的那张卡)· #7928(这条 pin 守护的 listViews 值类型决定)· #5741(本卡为之存在的载体纪律记录)。


    ⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为 claude-opus-5,CONTRACT_REVIEW_TIER 硬门要求 fable)。


    Generated by Claude Code

  9. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 7947,
      "status": "done",
      "branch": "claude/issue-7947-named-list-view-figures",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7969",
      "premise_still_valid": true,
      "head_sha": "7ef836917b250882f72ba0d313a8d30f78d5a205",
      "previous_head_sha": "27bb40b2aff230480dc5cef95014bce6eca32c4d",
      "merged_main_sha": "36fc74629cbeafe496cf6cd07f82a68c8899b9e5",
      "merge": {
        "conflicted": false,
        "git_output": "Auto-merging packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts / Merge made by the ort strategy. / merge exit 0 / git ls-files -u empty (no unmerged paths).",
        "registration_text_resolution": "origin/main's side stands, and it stands STRUCTURALLY, not by a hand pick: the merge base 859de84a0 still carried the pre-retirement strings, objectui#7932 rewrote them on main only, and this branch never touched that region since the base — so the three-way merge had exactly one changed side and took it. Verified after the fact by reading the merged file, not by trusting the exit code: `git diff origin/main -- PINFILE` shows ONLY the two regions this PR owns (the header docblock paragraph and the member-count assertion); REGISTRATION_TEXT does not appear in that diff at all, i.e. it is byte-identical to origin/main.",
        "label_fragment_count_in_block": 0
      },
      "registration_text_verbatim": [
        "{ name: 'defaultViewType', type: 'enum', enum: ['grid', 'kanban', 'gallery', 'calendar', 'timeline', 'gantt', 'map'] },",
        "{ name: 'defaultListView', type: 'string' },",
        "{ name: 'showViewSwitcher', type: 'boolean' },"
      ],
      "my_regions_survived": {
        "header_figures": "line 47-49 of the merged file reads: `NamedListView` — 47 declared top-level members, of which the renderer reads six (label, type, columns, filter, sort, options), leaving 41 that a key-for-key local mirror would enforce unread. The retired 'about 52 members / reads seven' sentence is gone.",
        "exact_assertion": "line 565 of the merged file reads `).toBe(47);` — the exact pin survives, together with its HOW THIS NUMBER IS TAKEN comment and the failure message naming the three sibling files.",
        "scope_unchanged": "git diff --stat origin/main HEAD is the same 5 files and the same 108 insertions / 26 deletions as before the merge; GitHub reports additions 108, deletions 26, changed_files 5. No scope change."
      },
      "member_count_rederived": 47,
      "member_count_evidence": "Re-ran the pin's own instrument on the merged tree (same regex as namedListViewMemberCount(): slice from `export interface NamedListView {` to the next line-start closing brace, then count /^ {2}[A-Za-z_$][\\w$]*\\??:/gm). MEMBER_COUNT=47, unchanged from the previous head, so toBe(47) still binds and the card's premise is untouched. Controls taken in the same run: the LOOSE count with the indent anchor dropped is 59 (the number the file's own comment says NOT to quote beside 47), and namedViewReads() still returns the seven keys columns, data, filter, label, options, sort, type. No needs_decision condition.",
      "gates": [
        {
          "command": "pnpm exec vitest run packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts packages/types/src/__tests__/zod-mirror-parity.test.ts packages/types/src/__tests__/object-view-spec-parity.test.ts",
          "verdict": "Test Files  3 passed (3) / Tests  137 passed (137) — os-verify-lock: VERDICT command-exit 0 (held 8s, waited 0s)"
        },
        {
          "command": "pnpm exec vitest run --maxWorkers=2 packages/types/",
          "verdict": "Test Files  130 passed (130) / Tests  2414 passed (2414) — os-verify-lock: VERDICT command-exit 0 (held 22s, waited 0s). Was 129/2388 at the previous head; the merge brought main a new packages/types test file (component-input-retired-keys-7493.test.ts, from objectui#7932), which is the whole of the growth."
        },
        {
          "command": "pnpm --filter @object-ui/types type-check",
          "verdict": "os-verify-lock: VERDICT command-exit 0 (held 34s, waited 0s). The run echoed its script name, so this is not a zero-match filter reading as green: \"@object-ui/types@17.6.0 type-check — tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json\". The test project is in that chain, so the edited pin file IS type-checked."
        },
        {
          "command": "node scripts/check-changeset-presence.mjs",
          "verdict": "exit 0 (captured before any pipe). Its own verdict line: \"3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7947-named-list-view-figures.md.\" with the empty-frontmatter exemption accepted. It also states it compared against merge-base 36fc74629, i.e. current origin/main — independent confirmation the branch is up to date."
        },
        {
          "command": "node scripts/check-governed-queue-guard.mjs --test (the five changed paths)",
          "verdict": "exit 0 — \"NOT GOVERNED — 5 path(s) checked against 5 governed surface(s); none matched.\" Run as cheap safety beyond the dispatch list; it does NOT change the standing instruction — the PR stays DRAFT for the seat."
        },
        {
          "command": "grep -naP '[control-byte class]' on the five changed files",
          "verdict": "grep exit 1, zero matches — no raw control bytes introduced."
        }
      ],
      "push": "git push origin claude/issue-7947-named-list-view-figures — \"27bb40b2a..7ef836917\", a fast-forward (no plus prefix, no force, no rebase, no amend). Verified beforehand that the old remote head is an ancestor of the new head (exit 0).",
      "pr_state_after_push": "PR 7969: draft true, state open, head 7ef836917, base 36fc74629, additions 108 / deletions 26 / changed_files 5. NOT flipped ready, NOT enqueued, no auto-merge armed, PR body NOT edited.",
      "model_tier": "Ran one tier down from contract-review, at opus, under the seat quota exemption, because a contract-review-tier dispatch returned HTTP 429 (account usage limit) at 07:05Z, request req_011Cemm7HP6dcoXRGnwwGWSR. No contract judgement arose in this round — it was a merge plus a gate re-run — so the exemption was not load-bearing on any decision.",
      "summary": "Merged current origin/main (36fc74629, well past objectui#7932's landing at c6198c27b) into the PR branch and re-gated on the merged head 7ef836917. The merge was clean — no conflict, no unmerged paths — and origin/main's post-retirement REGISTRATION_TEXT stands: the three expected strings carry no label fragment, proved by reading the merged file and by the fact that a diff of that file against origin/main shows only this PR's own two regions. The header figures (47 declared / 41 unread, six read of seven) and the exact toBe(47) assertion both survived, the member count re-derived to 47 on the merged tree with the pin's own regex, and all four dispatch gates plus two cheap safety checks are green. Pushed fast-forward; the PR is still DRAFT and untouched otherwise, for the seat to land.",
      "tests": "All four dispatch gates re-run on the merged head 7ef836917 in a dedicated worktree (/home/user/objectui-issue-7947), each exit captured before any pipe, heavy runs through the shared os-verify-lock entry point. (1) three pin files: 3 passed / 137 tests passed, VERDICT command-exit 0. (2) packages/types: 130 files / 2414 tests passed, VERDICT command-exit 0. (3) @object-ui/types type-check: command-exit 0 with the script name echoed. (4) check-changeset-presence.mjs: exit 0, one changeset, empty frontmatter accepted. Plus governed-queue-guard --test (NOT GOVERNED) and a control-byte grep (zero matches). No ablation this round: the dispatch forbids scope change, and the pin was already reverse-verified on the previous head; the re-derivation of 47 with the loose-count 59 control is the reading this round owed.",
      "mcp_calls": "1 — a single pull_request_read on 7969 to confirm the pushed head and the DRAFT state. The repo-scoped REST read was probed first and returned HTTP 403 from this container egress, so the read went through MCP; no search calls were needed (no new issue was filed).",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  10. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Enqueued 07:32Z at head 7ef836917. The hold recorded at 05:10Z is lifted: it was conditioned on PR #7932 still being in the queue, and that landed as c6198c27b.

    Merge round. origin/main 36fc74629 merged in, clean, no conflict and no unmerged paths. REGISTRATION_TEXT came out on origin/main's side structurally rather than by a hand pick: the merge base still carried the pre-retirement strings, #7932 rewrote them on main only, and this branch never touched that region — so the three-way merge had exactly one changed side. Confirmed after the fact by reading the merged file rather than trusting the exit code, and independently by the seat: git diff origin/main -- <pin file> shows only this PR's two regions, so REGISTRATION_TEXT is byte-identical to main and carries no label: fragment. The header figures (47 declared / 41 unread) and the exact toBe(47) both survived; git diff --stat is the same 5 files / 108 insertions / 26 deletions as before the merge.

    Figures re-derived on the merged tree, not carried: the pin's own instrument returns 47 again, with the loose-count control at 59 and namedViewReads() still returning its seven keys. No needs_decision.

    Gates, exit captured before any pipe, on the pushed head: three pins 3 passed / 137 tests; packages/types 130 files / 2414 tests (up from 129/2388 — the growth is component-input-retired-keys-7493.test.ts, arriving with #7932's merge); @object-ui/types type-check exit 0 with the script name echoed; check-changeset-presence.mjs exit 0 against merge-base 36fc74629, which independently confirms the branch is current.

    CI on 7ef836917: every blocking check green — all four test shards (1/4, 2/4, 3/4, 4/4 read individually by job id), Type Check, Build & E2E, Doc Snippet Type Check, Lint, the changeset trio, Governed Surface Queue Guard, the rest.

    One red, and it is not this PR's: Live E2E (informational). Measured rather than assumed. The job boots a published @objectstack/*@17.2.0 backend, which this diff — five files of prose, figures and one assertion under packages/types — cannot reach. It failed at step 7, "Start ObjectStack backend (published packages)", never got a ready backend (not ready after 300s), and its own log names the cause: AuthPlugin failed to load: '@better-auth/core/db' does not provide an export named 'createLocalAccountIssuer' → Core service missing: auth → no platform tables → every read no such table: sys_organization / sys_user / sys_position. The control: PR #7991, a different lane, five minutes earlier, fails the same job at the same step. The lane's own summary step prints "informational lane — does not block merge".

    Already carried by #7990 — "Live E2E (informational) is red on main and every branch since the backend pin moved to 17.2.0 — the published plugin-auth float (objectstack#16186), not an objectui diff" — filed at 07:04Z, three minutes before this PR's run started. So no second card: a duplicate is the wrong answer when the dedup search finds the diagnosis already written down.

    Landing: draft:false → enable_pr_auto_merge SQUASH → queue-ref probe (the enable call echoes empty fields even when it worked). Probe at 07:32:04Z: refs/heads/gh-readonly-queue/main/pr-7969-bac5e5413b…, third behind #7988 and #7991. The card closes on the three MERGED readings.


    Generated by Claude Code

  11. removed their assignment
    on Sep 6, 2026
  12. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    MERGED — three readings, 2026-09-06T07:50Z.

    1. PR docs(types): correct the NamedListView member figures and pin the count exactly (objectui#7947) #7969 — state MERGED, read from this card's own closed_by_pull_requests reference.
    2. This card — closed_at 2026-09-06T07:47:24Z, state_reason: completed, closed by that one PR.
    3. origin/main (fetched 07:49Z) carries the squash: 15789d565 docs(types): correct the NamedListView member figures and pin the count exactly (objectui#7947) (#7969).

    The content verified on origin/main itself, not on the branch — because the point of this card was that a figure went unchecked:

    • packages/types/src/zod/objectql.zod.ts reads "47 declared top-level members, of which the renderer reads six" and "41 unread members (47 declared, minus the 6 that are both declared and read)". Zero occurrences of ~52 or ~45 remain.
    • object-view-unmirrored-keys-7779.test.ts:565 is .toBe(47). Zero occurrences of toBeGreaterThanOrEqual remain, so the floor that could not bind is gone.
    • REGISTRATION_TEXT carries zero label: 'Default View Type' — feat(types)!: retire ComponentInput.label / defaultValue / advanced as ADR-0049 tombstones (objectui#7493 item 1 / objectui#7781) #7932's retirement side survived this PR's merge intact. Both PRs' contributions coexist in that file exactly as the region split predicted.

    pm:dispatched stripped and the assignee cleared in the same write, read back. domain:spec, bug, documentation, tests and priority:p2 left as triage set them.

    What this closes. The two figures that justified leaving listViews unmirrored — quoted in the changeset, the mirror docblock, the parity-ledger note and the pin header — were a hand number sitting between two instruments and matching neither. They now read 47 / 41, each site says in its own words that the seventh read key data is not a declared member and arrives through an as any cast, and the count is pinned exactly with a failure message that names the three files whose figures move together. The pin was shown to fail before it was trusted.

    The deadline is met: .changeset/object-view-unmirrored-keys-7779.md was corrected in place while still unconsumed, so no @object-ui/types release will carry the wrong figure into a published CHANGELOG.

    One premise of this card was itself wrong and is recorded rather than quietly dropped: item 4 asked for a stale line range objectql.ts:1957-2134 to be removed "where it appears", and it appears in no tracked file at either relevant ref — it lived in #7928's body, which the same seat had already corrected an hour before writing this card. That is the fourth instance of the failure mode filed as objectstack-ai/objectstack#16197.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdocumentationImprovements or additions to documentationdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2tests

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions