Repository navigation
console(detail): a field group's visibleWhen gates the form but not the record detail page — the section renders whatever the predicate says #11630
Description
Activity
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsPath: forms that work | records-forms.field-group-visible-when | P2
Triage: first grade —
bug·priority:p2·domain:ui·area:records·pm:queue. The synthesized detail page evaluates each group'svisibleWhenwith the form's evaluatorTriage: lands in
packages/plugin-detail/src/synth/buildDefaultPageSchema.ts⇒domain:ui; rationale: the form half already holds; the detail page is the one reader that skips the predicate.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T02:02Z. ⛔ Not a claim, ⛔ not a dispatch.- Why p2. The record page shows a section its author gated off, so the form and the detail page disagree on the same record. The verifier confirmed it (VF2). It predates 17.6.0.
- Not an access boundary.
visibleWhenis a display predicate. The gated fields are readable through the record API either way, so this is not a security card. - Direction.
buildDefaultPageSchema.ts(about:717–:729) carries each group's predicate and evaluates it with the evaluatorplugin-form/src/fieldGroups.tsalready uses. One evaluator, not a second copy.
Generated by Claude Code
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 2
Session:session_015W8GBu6sBiqus2L2xjMsAL
Account:os-steve
Branch:claude/issue-11630-detail-group-visible-when
Worktree:objectui-issue-11630
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-detail/src/synth/buildDefaultPageSchema.ts(the section emission, about:717–:729), therecord:detailssection renderer inpackages/plugin-detail/srcthat draws those sections, the type and zod mirror that declare that section's shape (inpackages/types/src/views.ts/packages/types/src/zod/views.zod.ts, only if the shape is declared there), the tests beside them, and.changeset/11630-*.md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default tierfor the build (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; floor sonnet · default opus · ceiling fable). The delivered PR owes an isolated review atCONTRACT_REVIEW_TIER(reason:Clause-②: yes, below).
Clause-②: yes
Thread-read: 5986853410
Serial constraints cleared: none, read 2026-10-05T03:07Z. The open objectui PRs (#11636app-shellflow-runner files, in the merge queue; #11069packages/cli; #11600 the release PR) touch none of these files. The in-flight objectui#11625 and objectui#11628 declareapp-shellfiles only.Why
Clause-②: yes:buildDefaultPageSchema's output is public API (its own comment, about:713). A section that carries the group'svisibleWhenfor the renderer to evaluate adds a key to that published shape. Triage's direction (5986853410) is that the section carries the predicate and is evaluated with the form's evaluator.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 11630,
"status": "done",
"branch": "claude/issue-11630-detail-group-visible-when",
"pr": "#11641",
"session": "session_015W8GBu6sBiqus2L2xjMsAL (subagent; the parent PM seat's harness-stamped id, as on every commit's Claude-Session line)",
"premise_still_valid": true,
"summary": "deriveFieldGroupDetailSections (plugin-detail synth) now carries each group's visibleWhen verbatim and unevaluated, and record:details evaluates it per record with the form's own evaluator (resolveFieldRuleState from @object-ui/core under usePredicateScope from @object-ui/react, both existing peer deps; no plugin-form dependency, no copy), dropping a FALSE-verdict section whole; an authored { group } reference inherits the predicate the same way. Record assembly mirrors the form's ruleRecord: declared fields seeded null, relations collapsed to their stored id (toPredicateRecord), previous bound to the persisted row as on the edit form; a faulted predicate SHOWS the section (the form's VISIBLE_WHEN_FAULTED). Zone 2 readings: #1 confirmed (no visibleWhen emitted on origin/main 22ddcd5); #2 falsified in the good direction: the form's section gate is resolveFieldRuleState + usePredicateScope in core/react, not plugin-form; #3 measured by a form-vs-detail parity matrix; #4 the record:details section shape IS mirrored in @object-ui/types (RecordDetailsComponentProps.sections[]) but the installed spec 17.6.0 RecordDetailsProps.sections[] refuses visibleWhen (unrecognized_keys, with control), so nothing in @object-ui/types was widened and the emitted key is undeclared there (contract-review item, see open_questions); #5 NOT MEASURED (see tests).",
"tests": "Final HEAD 37d6d62 (after one merge of origin/main c4c506b as merge commit 69eac22), all from the repo root under the shared verify lock (VERDICT command-exit 0, waited 307s, held 489s): pnpm exec vitest run on the two new files → Test Files 2 passed (2), Tests 15 passed (15); pnpm exec vitest run packages/plugin-detail/ → Test Files 238 passed | 1 skipped (239), Tests 2379 passed | 8 skipped (2387); pnpm --filter @object-ui/plugin-detail type-check → exit 0 (dependency closure built first on 69eac22: turbo run build --filter=@object-ui/plugin-detail^... --concurrency=2 → 11 successful, 11 total; deps unchanged by 37d6d62); pnpm --filter @object-ui/plugin-detail lint → exit 0, 0 errors, 1139 warnings (base run 1143; new renderer code adds none, the two new test files carry 6 no-explicit-any). Unlocked root checks on 37d6d62, exit 0 each: check:new-line-citations (VERDICT 0 new citations), check:control-bytes, check:changeset-claims, check:pending-changeset-literals, check:test-path-roots, check:phantom-deps, check:installed-pin-claims, check:esm-specifiers, scripts/check-changeset-presence.mjs (1 changeset for 4 published source files of 1 package). Parity matrix (record-details.groupVisibleWhen-11630.test.tsx): each case rendered through record:details (fed by buildDefaultDetails) AND through @object-ui/components form with the projectSectionDivider-shaped divider as an edit form of the same row; asserts form, detail and expected on [basic, pro]: envelope and bare string record.kind == 'pro' → hidden/shown; no predicate → shown/shown; bare kind == 'pro' (unbound) and a parse error → shown/shown (fail-open, both surfaces); record.tier == 'gold' on a declared absent field → hidden/hidden (null seed); record.owner == 'u2' with the lookup expanded on the detail row → hidden/shown; previous.kind == 'pro' → hidden/shown; current_user.positions denied/allowed → hidden/shown; { group } reference → hidden/shown on both spellings. HARNESS DEAD anchors on both surfaces. Ablation on 69eac22 (same runtime code as 37d6d62), via objectstack scripts/ablation-replace.mjs (anchor hit x1 → x0, blob changed on disk; each restore: blob == HEAD and empty git diff HEAD; post-ablation diff bytes 0; restored rerun 15/15), direction predicted before running: drop carriage → 10 failed / 5 passed (predicted 3 synth + 7 renderer red); drop .filter(sectionShown) → 7 failed / 3 passed; previous unbound → 1 failed (previous leg only); no relation collapse → 1 failed (lookup leg only); no null seed → 1 failed (absent-field leg only). Reds are verdict diffs in the bug's direction (detail [true,true] vs expected [false,true]); no HARNESS DEAD. Spec probe on installed @objectstack/spec 17.6.0 RecordDetailsProps.safeParse: enumerated section control OK; + visibleWhen (string and envelope) → unrecognized_keys at sections.0 keys [visibleWhen]; { group, columns } OK; buildDefaultDetails output for a grouped object → invalid_type at every sections.N.fields.M (pre-existing). NOT MEASURED: live before/after on a running backend, reason: shared objectstack checkout has no built CLI (packages/cli/dist absent), a backend needs a fresh objectstack worktree plus a full app-showcase closure build under the shared verify lock, and the lock was saturated (first acquisition 49 min across five exit-99 waits per the lock ledger). CI on 37d6d62 at report time: in_progress (42 check runs, 0 failed so far).",
"mcp_calls": "0",
"api_writes": "3 REST writes, each one repository_dispatch through the objectstack-fleet relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls (draft forced; read-back 9853 bytes sent = stored); (2) label-write.mjs --issue 11641 --assign os-steve → POST /repos//issues/11641/assignees (read-back MATCHES, no label calls); (3) this os-dev-report comment via post-stamped.mjs --comment=11630 → POST /repos//issues/11630/comments. Plus git push of the branch (not REST). REST reads only otherwise (issue + comments, PR read-back, check-runs).",
"files_changed": [
".changeset/11630-detail-group-visible-when.md",
"packages/plugin-detail/src/synth/buildDefaultPageSchema.ts",
"packages/plugin-detail/src/renderers/record-details.tsx",
"packages/plugin-detail/src/synth/tests/buildDefaultPageSchema.groupVisibleWhen-11630.test.ts",
"packages/plugin-detail/src/renderers/tests/record-details.groupVisibleWhen-11630.test.tsx"
],
"line_budget": "git diff --numstat c4c506b 37d6d62: changeset +15/-0; buildDefaultPageSchema.ts +15/-0; record-details.tsx +127/-2; synth test +77/-0; renderer test +301/-0. Total +535/-2 (source +142/-2, of it mostly comments).",
"gates": [
{
"command": "pnpm exec vitest run (2 new files)",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm exec vitest run packages/plugin-detail/",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm exec turbo run build --filter=@object-ui/plugin-detail^... --concurrency=2",
"exit": 0,
"head": "69eac22"
},
{
"command": "pnpm --filter @object-ui/plugin-detail type-check",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm --filter @object-ui/plugin-detail lint",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:phantom-deps",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:installed-pin-claims",
"exit": 0,
"head": "37d6d62"
},
{
"command": "pnpm check:esm-specifiers",
"exit": 0,
"head": "37d6d62"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"head": "37d6d62"
},
{
"command": "live backend before/after",
"exit": null,
"head": "NOT MEASURED"
}
],
"deviations": [
"packages/types gates not run: the diff does not touch packages/types (the dispatch listed them conditionally).",
"Docs (AGENTS.md commandment #2) not updated: content/docs/plugins/plugin-detail.mdx and the plugin-detail README have no fieldGroups section, so no published statement became false; adding one is outside the claimed file surface (stop-on-breach). Carrier: PM.",
"Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md, not the harness reminder's model-named Co-Authored-By line; the merge commit was amended locally before its first push to carry the same pair.",
"PR body ends with the os-dev session-URL footer form, not the harness reminder's emoji footer.",
"Gate scripts ran in the background under the verify lock with a foreground tail --pid wait, because the queued wait plus run time exceeded one foreground call; no narrowing of any gate."
],
"open_questions": [
{
"question": "Contract: the synthesized default page (and every Studio-seeded record page, via createSeed) now writes visibleWhen onto an ENUMERATED record:details section, a key @objectstack/spec RecordDetailsProps.sections[] refuses on parse (unrecognized_keys, measured with control). record:details honours it on any section, so a hand-authored sections[].visibleWhen also renders gated while the spec refuses it. How should the contract converge?",
"options": [
"A: objectstack declares visibleWhen on the record:details section entry (same EvaluatedExpression input as FormSectionSchema.visibleWhen) and adds it to that surface's derivedKeys, so it is refused beside group and inherited through it; objectui then mirrors it in RecordDetailsComponentProps.sections[] and the registered inputs. Cost: one spec key + mirror; follows the declare-what-the-renderer-honours rulings that declared hideEmpty/collapsible/showBorder/defaultCollapsed/icon/description/headerColor on this same entry.",
"B: the synthesizer and createSeed emit the spec-legal { group } reference for group-derived sections (the spec already says it inherits visibleWhen), keeping the predicate internal to the renderer's group resolution, and the renderer stops reading visibleWhen off enumerated sections. Cost: app-shell RecordDetailView change (outside this claim), seeded pages start tracking group membership, and a page body then depends on objectSchema being in record context; also retires the pre-existing rich-fields off-spec emission for grouped sections.",
"C: leave as delivered (undeclared key emitted by our own producer and honoured by the renderer)."
],
"recommendation": "A. Business need is measured (the QA checklist clause, and the form already honours the same predicate). Long-term: it converges on one declared contract with FormSection parity instead of a renderer-only reading. AI-safety: declared = enforced, and the AI-facing trap of C (renderer honours what the spec refuses) closes. Startup scope: it adds one key that already has a live producer and consumer, no new capability. B is the stricter no-new-key route but is larger and moves a seed shape; C leaves the drift."
},
{
"question": "Fault direction: @objectstack/spec's deriveFieldGroupLayout TSDoc says section gating is the renderer's contract with FALSE hiding the group 'fail-closed', while objectui's form (pinned by section-grouping-6236 FAULTED leg) and now the detail page fail OPEN on a predicate that cannot be evaluated. This PR follows the form per the card's ruling; which side should move?",
"options": [
"A: keep fail-open on both surfaces (ADR-0137 D3 render direction) and correct the spec TSDoc wording.",
"B: flip both surfaces to fail-closed for SECTION predicates (a shipped-behaviour change for the form, needs its own ruling)."
],
"recommendation": "No recommendation from this seat beyond keeping the two surfaces on one shared evaluator so either answer moves both; A is the smaller change, B matches the spec prose."
}
],
"out_of_scope_findings": [
"class: b · reach: named producer — Studio page-create createSeed (app-shell metadata-admin anchors.ts) stores buildDefaultPageSchema(objectDef).regions as a page row · evidence: for an object with fieldGroups, RecordDetailsProps.safeParse(buildDefaultDetails(def).properties) on installed spec 17.6.0 returns invalid_type at every sections.N.fields.M, because deriveFieldGroupDetailSections emits rich field descriptors where the spec declares z.array(z.string()); buildDefaultPageSchema.strictPayload.test.ts judges nodes with PageComponentSchema, which leaves properties unjudged, so no pin sees it · Seam: spec:RecordDetailsProps.sections[].fields → runtime:createSeed (Studio page create) | renderer:record:details · dedupe words: record:details sections fields rich descriptors; createSeed buildDefaultPageSchema RecordDetailsProps; synthesized page properties unjudged; strictPayload PageComponentSchema properties",
"carrier: PM / contract review of PR 11641 · noted, not filed — spec deriveFieldGroupLayout TSDoc says section gating is fail-closed while objectui's form and detail page fail open (open_questions 2); no public-door measurement made, so not a filing.",
"carrier: PM · noted, not filed — plugin-detail docs (plugin-detail.mdx, README) carry no fieldGroups section at all; a docs addition is outside this claim."
]
}objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsSeat ruling on PR objectui#11641's open question 1: route B, the spec-legal
{ group }reference. Patch round 1, file surface amendeddomain:uiexecution seat 1 @ objectui ·session_015W8GBu6sBiqus2L2xjMsAL(os-steve) · 2026-10-05T04:44Z. This answers the dev'sopen_questions[0]in report5988206775. ⛔ Not a decision card: the governing text below decides it, so the options that conflict with it are not options here.Governing text:
- objectstack
.claude/skills/pm-dispatch/SKILL.md: "协议为基准:spec 与代码不一致默认改代码对齐;改协议单独立卡,⛔ 不作缺陷卡的选项" (the protocol is the baseline: when spec and code disagree, change the code to match; a protocol change is its own card and never an option on a defect card). @objectstack/spec17.6.0RecordDetailsProps.sections[], read by the seat in the installeddist/ui/index.d.ts: the entry is$strict. It declaresgroup(a field-group key reference, #13855) andfields: string[], and novisibleWhen. The dev's parse probe agrees:unrecognized_keys [visibleWhen], with a control that parses.deriveFieldGroupLayout's TSDoc in the same spec: the derived section carries the group's predicate "verbatim", and "the renderer's section-gating contract evaluates it".
Reading of the three options:
- C (an enumerated section carries
visibleWhen, and the renderer honours a key the spec refuses) conflicts with the protocol. It does not land. - A (the spec declares
visibleWhenon the entry) changes the protocol. It is not an option on this defect card. Anyone who wants it files it on its own, in the spec lane. - B conforms. Group-derived sections are emitted as the spec's own
{ group: KEY }reference (with the declared presentation keys only).record:detailsresolves the reference against the object'sfieldGroupsand gates the section with the group's carriedvisibleWhen, using the form's evaluator, which this PR already uses. The renderer stops readingvisibleWhenoff an enumerated section.
Patch round 1 asks:
- Take route B, measured. Find every consumer of the synthesized sections (
buildDefaultPageSchema/deriveFieldGroupDetailSections/resolveDetailSections) and keep each one working. Name them in the PR body. - This also retires the off-spec rich field descriptors on grouped sections, the dev's class-b finding.
- The output shape of
buildDefaultPageSchemachanges for grouped objects, and it is public API. The changeset names that change and its migration line, graded by AGENTS.md §9. Clause-②: yesstays declared, and the at-tier contract review runs on the delivered head.- Update the PR body's route and test sections.
Claim file surface, amended: plus
packages/app-shell/src/views/RecordDetailView.tsxandpackages/app-shell/src/views/metadata-admin/(thecreateSeedpath inanchors.ts), each only if route B measurably needs it.open_questions[1](the fault direction) is not decided here. This PR keeps the detail page's verdict identical to the form's, as the card's ruling orders ("exactly as the form does"). The objectui form already failed open before this card. The gap between the specderiveFieldGroupLayoutTSDoc ("fail-closed") and ADR-0137 D3 is a cross-repo text question. It is carried to the seat's round report for the maintainer.
Generated by Claude Code
- objectstack
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 11630,
"status": "done",
"branch": "claude/issue-11630-detail-group-visible-when",
"pr": "#11641",
"session": "session_015W8GBu6sBiqus2L2xjMsAL (subagent; the parent PM seat's harness-stamped id, as on every commit's Claude-Session line)",
"premise_still_valid": true,
"summary": "Patch round 1, route B per seat ruling 5988275811; new head aeddcd7. The synthesizer (resolveDetailSections, and through it buildDefaultDetails / buildDefaultTabs / buildDefaultPageSchema) now emits each declared field group as the spec's { group: KEY, columns } reference and the ungrouped bucket as bare field names, so the node parses as RecordDetailsProps. That retires the round-0 class-b finding. The group's visibleWhen travels BESIDE the resolved section through the package-internal deriveFieldGroupDetailEntries (exported from its module, not from the package entry), never on any section. The public deriveFieldGroupDetailSections returns exactly its pre-card output. record:details resolves { group } and gates it with the form's evaluator: resolveFieldRuleState + usePredicateScope, the same record assembly as round 0, and fail-open parity with the form. It no longer reads visibleWhen off an enumerated section. app-shell RecordDetailView (the console default page, the card's repro path) writes { group: KEY, showBorder: true } per group; that change was measurably needed, because its enumerated copies could not carry the predicate. The createSeed path (metadata-admin anchors.ts) and PagePreview needed no code change. The record:details sections input description now says { group } inherits visibleWhen and that one written on a section is not read. Clause-② stays yes; the changeset (minor for plugin-detail and app-shell) names the changed buildDefaultPageSchema output for grouped objects and its migration lines.",
"tests": "Main gate run on 75c794c (route B plus a merge of origin/main 0abd4f9), under the verify lock: VERDICT command-exit 0, held 34m58s. Targeted six files (the two new files, buildDefaultPageSchema.test.ts, defaultFieldGroupsPage.sectionHeadings, recordDetailsInputs.spec-parity, buildDefaultPageSchema.strictPayload) → Test Files 6 passed (6), Tests 147 passed (147). vitest run packages/plugin-detail/ → Test Files 238 passed | 1 skipped (239), Tests 2382 passed | 8 skipped (2390). vitest run packages/app-shell/ → Test Files 1001 passed | 1 skipped (1002), Tests 9951 passed | 9 skipped (9960). turbo run build --filter=@object-ui/app-shell^... --concurrency=2 → exit 0. plugin-detail type-check → exit 0. plugin-detail lint → 0 errors (1138 warnings; 1139 at 37d6d62). app-shell lint → 0 errors. app-shell consumer pins (RecordDetailView.sectionHeadingsRenderPath-6190, anchors.page-seed, PagePreview) → Tests 15 passed (15). app-shell type-check on 75c794c → exit 2, TS2345: flatMap inferred its element type from the first return; fixed in 594b154 and aeddcd7 with a type-only annotation. Rerun on final head aeddcd7 under the lock (VERDICT command-exit 0): app-shell type-check exit 0; app-shell lint exit 0, 0 errors, 3486 warnings, equal to 75c794c; consumer pins Tests 15 passed (15); the two new files Tests 18 passed (18). The full suites were not rerun on aeddcd7, whose runtime code equals 75c794c (type annotations only). Unlocked root checks, exit 0 each: on 594b154, check:new-line-citations (0 new), control-bytes, changeset-claims, pending-changeset-literals, test-path-roots, phantom-deps, installed-pin-claims, esm-specifiers, spec-symbols and check-changeset-presence (7 source files of 2 released packages, 1 changeset); on aeddcd7, new-line-citations, control-bytes, changeset-claims, test-path-roots, phantom-deps and changeset presence. Spec legality (synth test, installed spec 17.6.0, RecordDetailsProps.safeParse): buildDefaultDetails(def).properties for a grouped object → OK; the record:details node inside buildDefaultPageSchema(def) → OK. Controls on the same parser: an enumerated section with visibleWhen → unrecognized_keys @ sections.0 [visibleWhen]; an enumerated section with a field object → invalid_type @ sections.0.fields.0. Parity matrix unchanged in content, now fed { group } references by buildDefaultDetails; form, detail and expected verdicts all agree. New legs: the runtime default page's { group, showBorder } shape → hidden/shown; an enumerated section carrying a spec-refused visibleWhen → shown/shown (not read). Ablation on 75c794c via ablation-replace.mjs: each anchor hit x1 → x0; each restore proven by blob == HEAD and an empty git diff HEAD; post-ablation diff 0 bytes; restored rerun 18/18; directions predicted before running. Drop the gate in resolveSectionGroup → 8 failed / 10 passed (predicted 8 gated legs red). Emit the old enumerated copy → 9 failed / 9 passed (3 synth plus 6 gated matrix legs). Read visibleWhen off an enumerated section → 1 failed (the enumerated-not-read leg only). previous unbound / no relation collapse / no null seed → 1 failed each, the targeted leg only. No HARNESS DEAD or partial-group error in any run. NOT MEASURED: live before/after on a running backend. Reason unchanged: no built objectstack CLI in the shared checkout, and a fresh worktree build of the showcase closure needs the saturated verify lock. CI on aeddcd7 at report time: in_progress (42 check runs, 0 failed so far).",
"mcp_calls": "0",
"api_writes": "2 REST writes this round, each one repository_dispatch through the objectstack-fleet relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) issue_patch → PATCH /repos//issues/11641, title and body (read-back 13429 bytes sent = stored, draft kept, footer intact); (2) this os-dev-report comment via post-stamped.mjs --comment=11630 → POST /repos//issues/11630/comments. Plus git pushes of the branch (not REST). Round 0 spent 3: pr_create, assign, report. REST reads only otherwise: card comments, PR read-back, check-runs.",
"files_changed": [
".changeset/11630-detail-group-visible-when.md",
"packages/app-shell/src/views/RecordDetailView.tsx",
"packages/plugin-detail/src/index.tsx",
"packages/plugin-detail/src/synth/buildDefaultPageSchema.ts",
"packages/plugin-detail/src/renderers/record-details.tsx",
"packages/plugin-detail/src/synth/tests/buildDefaultPageSchema.groupVisibleWhen-11630.test.ts",
"packages/plugin-detail/src/synth/tests/buildDefaultPageSchema.test.ts",
"packages/plugin-detail/src/renderers/tests/record-details.groupVisibleWhen-11630.test.tsx"
],
"line_budget": "Whole PR, git diff --numstat 0abd4f9 aeddcd7: changeset +32/-0; RecordDetailView.tsx +13/-10; plugin-detail index.tsx +1/-1; buildDefaultPageSchema.ts +109/-27; record-details.tsx +145/-16; synth 11630 test +133/-0; buildDefaultPageSchema.test.ts +5/-3; renderer 11630 test +343/-0. Total +781/-57. This round alone (37d6d62..aeddcd7, merged-main files excluded): +346/-155.",
"gates": [
{
"command": "pnpm exec vitest run (6 targeted files)",
"exit": 0,
"head": "75c794c"
},
{
"command": "pnpm exec vitest run packages/plugin-detail/",
"exit": 0,
"head": "75c794c"
},
{
"command": "pnpm exec vitest run packages/app-shell/",
"exit": 0,
"head": "75c794c"
},
{
"command": "pnpm exec turbo run build --filter=@object-ui/app-shell^... --concurrency=2",
"exit": 0,
"head": "75c794c"
},
{
"command": "pnpm --filter @object-ui/plugin-detail type-check",
"exit": 0,
"head": "75c794c"
},
{
"command": "pnpm --filter @object-ui/plugin-detail lint",
"exit": 0,
"head": "75c794c"
},
{
"command": "pnpm --filter @object-ui/app-shell type-check",
"exit": 2,
"head": "75c794c"
},
{
"command": "pnpm --filter @object-ui/app-shell type-check",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm --filter @object-ui/app-shell lint",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm exec vitest run (3 app-shell consumer pins)",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm exec vitest run (2 new files)",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm check:phantom-deps",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"head": "aeddcd7"
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"head": "594b154"
},
{
"command": "pnpm check:installed-pin-claims",
"exit": 0,
"head": "594b154"
},
{
"command": "pnpm check:esm-specifiers",
"exit": 0,
"head": "594b154"
},
{
"command": "pnpm check:spec-symbols",
"exit": 0,
"head": "594b154"
},
{
"command": "live backend before/after",
"exit": null,
"head": "NOT MEASURED"
}
],
"deviations": [
"Full plugin-detail and app-shell suites, the plugin-detail type-check and lint, and the closure build ran on 75c794c, not on the final head aeddcd7. The two commits after it change only a TypeScript annotation in RecordDetailView.tsx (no runtime change), and the app-shell type-check, app-shell lint, consumer pins and new tests were rerun on aeddcd7.",
"The record:details registration's sections input description (plugin-detail index.tsx) was edited to say a { group } reference inherits visibleWhen and that a section-level one is not read. It is a prose change on a published registration, named in the changeset.",
"Docs (AGENTS.md commandment #2): content/docs and the plugin-detail README still have no fieldGroups section; slotted-pages.md mentions the sub-builders without describing section shape, so nothing published became false. Carrier: PM.",
"Commit trailers use the model-free pair per objectui AGENTS.md; the merge commit was amended locally, before its first push, to carry it. The PR body keeps the os-dev session-URL footer form.",
"Locked gate scripts ran in the background with foreground tail --pid waits, because one locked run (34m58s) exceeds a foreground call. No gate was narrowed."
],
"open_questions": [
{
"question": "Not decided here (carried by the seat): the spec's deriveFieldGroupLayout TSDoc calls section gating fail-closed, while the objectui form, and now the detail page, fail open on a predicate that cannot be evaluated. Which side moves?",
"options": [
"A: keep fail-open on both surfaces (ADR-0137 D3) and correct the spec TSDoc wording",
"B: flip both surfaces to fail-closed for section predicates (a shipped-behaviour change for the form)"
],
"recommendation": "I make no choice between A and B. Both surfaces share one evaluator (resolveFieldRuleState), so either answer moves both."
}
],
"out_of_scope_findings": [
"carrier: PM · noted, not filed — RecordDetailView's ungrouped primary and 'More details' sections (its own splitPrimarySecondary + toField) still carry rich field descriptors that RecordDetailsProps.sections[].fields refuses. That page is synthesized at runtime and never stored or parsed, route B does not need them changed, and changing them moves the default page of every object without fieldGroups.",
"carrier: PM · noted, not filed — a record page stored from a pre-route-B Studio seed keeps an enumerated copy of each group and is not gated by the group's visibleWhen; the changeset carries the migration line (FROM the enumerated copy TO { group: KEY }).",
"Round-0 class-b finding (synthesized grouped sections refused at every field object) is retired by this round: the synthesized node now parses, pinned with two controls in buildDefaultPageSchema.groupVisibleWhen-11630.test.ts."
]
}objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsACCEPT (content): PR objectui#11641, head
aeddcd7, after patch round 1 (route B). The contract review is still owed before landingdomain:uiexecution seat 1 @ objectui ·session_015W8GBu6sBiqus2L2xjMsAL(os-steve) · 2026-10-05T05:46Z. Reviewed against GitHub andorigin/main, not against the report's prose (report5988831078). The seat's ruling is5988275811.- Route B, as ruled. Each declared field group is emitted as the spec's own
{ group: KEY, columns }reference:buildDefaultPageSchema/buildDefaultTabs/buildDefaultDetails/resolveDetailSections. The console default page writes{ group: KEY, showBorder: true }.record:detailsresolves the reference and gates it with the form's evaluator (resolveFieldRuleStateunderusePredicateScope), failing open like the form. It no longer readsvisibleWhenoff an enumerated section. ThevisibleWhenkey is gone from every emitted section. - Spec legality is pinned with controls. On installed
@objectstack/spec17.6.0,RecordDetailsProps.safeParseaccepts the synthesized grouped node. On the same parser, an enumerated section withvisibleWhenis refused (unrecognized_keys), and so is one with a field object (invalid_type). That retires the dev's round-0 class-b finding, the off-spec rich descriptors on grouped sections. - The file surface stayed inside the amendment.
RecordDetailView.tsxwas measurably needed, because its enumerated copies could not carry the predicate. ThecreateSeedpath needed no change.plugin-detail/src/index.tsxchanges only therecord:detailsregistration'ssectionsinput description, prose on the renderer's own registration, and the changeset names it. - Changeset, sentence by sentence. It is
@object-ui/plugin-detail: minorand@object-ui/app-shell: minor, with the output-shape break stated and gradedminorunder objectui's release model (nomajor). The "Unchanged" bullet is true:deriveFieldGroupDetailSectionsreturns what it did, and no type member, zod member, registered input, prop or i18n key is added. Both migration lines match the diff. - Tests. The parity matrix is now fed
{ group }references, and the form, detail and expected verdicts agree. Ablation: dropping the gate turns 8 rows red, emitting the old copy 9, and readingvisibleWhenoff an enumerated section 1. Each restore was proven. The fullplugin-detailandapp-shellsuites are green on75c794c. Only type annotations changed after that, and the app-shell type-check, lint and consumer pins were rerun green onaeddcd7. - Live before/after: NOT MEASURED. The blocker is stated: the verify lock was saturated, and there was no built CLI in the shared checkout. The dispatch allowed that. The parity matrix is the evidence.
- Shape. The PR is a draft against
main, and its assignee isos-steve. Its first line isFixes #11630, the only line with a closing keyword next to an issue number.Clause-②: yesis on its own line.
Owed before landing: an isolated review at
CONTRACT_REVIEW_TIERon this head.needs:contract-reviewis added to the PR in this stroke. Carried to the maintainer, not decided here: the fault direction of section predicates. The specderiveFieldGroupLayoutTSDoc says "fail-closed", while ADR-0137 D3, the form and now the detail page fail open.
Generated by Claude Code
- Route B, as ruled. Each declared field group is emitted as the spec's own
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsLanded: PR objectui#11641 →
main76993f8, verified by contentdomain:uiexecution seat 1 @ objectui ·session_015W8GBu6sBiqus2L2xjMsAL(os-steve) · 2026-10-05T06:27Z.-
Merged through the merge queue as squash commit
76993f8(one parent,c000398), an ancestor oforigin/main. Its +/- lines are identical to the PR headaeddcd7diffed from its merge base: 8 files, +781/−57. The contract review PASS on that head is5989046218. -
Content check against the first parent:
reading c00039876993f8export function deriveFieldGroupDetailEntriesinplugin-detailsynth/buildDefaultPageSchema.ts0 1 resolveFieldRuleStateinplugin-detailrenderers/record-details.tsx0 4 record-details.groupVisibleWhen-11630.test.tsxexistsno yes export function deriveFieldGroupDetailSections(control)1 1 -
The card closed
completedthrough the PR'sFixesline. It was the only issue closed in that window.pm:dispatchedis removed in this stroke. -
Carried by the seat, not filed here:
- The contract review's non-blocking flag:
BuildPageOptions.sectionsdoes not namegroup. - plugin-detail has no field-group docs.
- The fault direction of section predicates (the spec
deriveFieldGroupLayoutTSDoc says "fail-closed", against ADR-0137 D3 and both objectui surfaces) goes to the maintainer in the seat's round report.
- The contract review's non-blocking flag:
Generated by Claude Code
-
- added a commit that references this issue
on Oct 7, 2026
QA-source: objectstack-ai/objectstack#21784 · records-forms.field-group-visible-when · acceptance[3]
A clause of
records-forms.field-group-visible-whenfails in the ObjectStack 17.7 pre-release checklist run objectstack-ai/objectstack#21784 (framework subject316be321e, console pin2e818d0b51ec). An independent verifier (VF2, RUNNER rule 7) confirmed it: P2. It predates the 17.6.0 console pin31971ff1e; no open duplicate was found. Owner: objectui.Reproduction
PUT /api/v1/meta/object/qa_group_gate4?package={pkg}with akindselect (basic / pro), two pro fields, andfieldGroups:[{key:'pro', label:'Pro details', fields:[…], visibleWhen:{dialect:'cel', source:"record.kind == 'pro'"}}](the bare-string form behaves the same).kind:'basic'and one withkind:'pro'./_console/apps/com.example.showcase/qa_group_gate4/record/{basic id}. Expected: no "Pro details" section. Actual: the section renders, header included, for both rows. Seen twice on fresh launches.Mechanism
objectui
plugin-detailbuildDefaultPageSchema.ts:717-729— the synthesized detail page does not apply the group predicate. The form (plugin-form/src/fieldGroups.ts) carries and evaluates it live at the pin (the clause was authored as an expected-fail; its form half is now fixed). Predates 17.6.0.Done when
The detail page evaluates
visibleWhenper group exactly as the form does.Generated by Claude Code