Repository navigation
[finding] disabled has no vocabulary at all on FormSectionSchema / PageComponentSchema — no readonly/disabled/disabledWhen key to point an author at #7887
Description
Activity
Triage: routed
domain:spec(the question is an acceptance-surface one — any disabled-ish key onFormSectionSchema/PageComponentSchema/SelectOptionSchemawidens the accepted metadata set); hold asfinding. Zero measured author pull today (the gap surfaced from alias-row curation, not from anyone trying to author a disabled section), and the #7884 tripwire pins already make the owed alias rows visible the moment any of the three shapes gains such a key — the guard exists, so waiting costs nothing.Startup-scope reading: default is defer, don't declare — vocabulary added without pull is a permanent maintenance obligation. If the spec seat instead wants to rule it a deliberate boundary ("sections/components gate visibility only; disabling is field-level"), that ruling is a one-sentence prose addition on the shapes (that half re-routes
domain:spec-surface) and would close this card.Restart conditions: (a) measured author demand (a real app or Studio flow needing a disabled section/component); (b) the spec seat electing to write the boundary ruling; (c) either sibling decision (#7816 canonical spelling) landing in a way that touches this vocabulary family.
Generated by Claude Code
Finding-grading round: escalated to
needs-user-decision(domain:spec) — the zero-pull assessment is stale by one day: #7816 is queued work whose ask 1 names the section/component shapes for adisabled → disabledWhenalias, a row those shapes would reject (alias-integrity.test.tsfails outright, and §3 ofvisible-when-alias-guidance.test.tspins the rejection). #7816 has been markedpm:blockedon this card.Question: is the absence of
disabled/readonlyonFormSectionSchema/PageComponentSchemaa boundary (sections/components gate visibility only — document it, alias rows stay off) or a gap (add the slot + alias)?Four-prong analysis:
- Platform long-term coherence: sections/components have no editability semantics today — a
readonlyslot on them would be declared-but-unenforced from day one, the exact ADR-0049 class the repo is retiring elsewhere. - Measured business pull: One intent, two spellings:
visible(actions) vsvisibleWhen(fields / sections / userActions) — and the alias guard only covers one direction #7816's ask is author-confusion-driven (better error messages), not capability-driven; no author has asked for section-level disabling itself. - AI-agent error-resistance: loud rejection already works; ruling "boundary" + a guidance string telling authors where the key DOES belong is strictly clearer than accepting a key the runtime ignores.
- Startup scope discipline: boundary = one sentence of prose per shape; gap = new enforced surface across two schemas plus renderer work.
Recommendation: boundary — document on both shapes ("visibility only; editability lives on fields"), which re-routes the fix
domain:spec-surface, keeps acceptance unchanged, and unblocks #7816 immediately (itsdisabledarm becomes guidance-text-only; notefield.zod.ts:464already records fields takereadonlyWhen, so #7816's alias target needs that correction either way). When ruled, return #7816 topm:queuein the same action.
Generated by Claude Code
- Platform long-term coherence: sections/components have no editability semantics today — a
- added and removed
on Aug 12, 2026 Maintainer ruling — 2026-08-12 (live PM chat,
session_01GxKQfv3k8b6a2d2QrZU411; maintainer sam, verbatim: 「接受你的全部建议。」 accepting the decision-box analysis in full). Moved out of the decision box →pm:queue, re-routeddomain:spec→domain:spec-surface.Ruling: boundary, not gap.
FormSectionSchema/PageComponentSchemagate visibility only; editability lives on fields. Nodisabled/readonly/disabledWhenslot is added to those shapes, and no alias row is registered for them — adding one would declare a key the runtime does not honour, the exact ADR-0049 class being retired elsewhere.Deliverable (text-face only, acceptance byte-identical ⇒ surface lane, changeset patch): document the boundary on both shapes and give the rejection a guidance string naming where the key does belong, so an author who writes
disabledon a section is told about field-levelreadonlyWheninstead of getting a bare unknown-key error. The existing rejection pins invisible-when-alias-guidance.test.ts§3 stay green by construction.#7816 is unblocked and returned to
pm:queuein the same action. Its ask 1 narrows accordingly: guidance text for the section/component shapes (not alias rows), plus the field-arm correction —field.zod.ts:464already records that fields carryreadonlyWhen, so #7816's originaldisabled → disabledWhenalias target is wrong for fields too and must be corrected rather than copied.
Generated by Claude Code
Claim:
domain:spec-surfacePM seat (#6298), round 6 of the 2026-08-12 shift. Dispatched on the maintainer's boundary ruling above.- Session:
session_016YBUGvukaeVu9DjKdsHJa9(os-zhuang) - Branch:
claude/issue-7887-section-component-editability-boundary - Worktree:
../objectstack-issue-7887(dedicated, per-repo) - Domain:
domain:spec-surface— text face only. No key is added to any shape, so acceptance stays byte-identical; the rejection set is unchanged and only the message an author reads changes. - File surface:
packages/spec/src/ui/view.zod.ts(FormSectionSchema),packages/spec/src/ui/page.zod.ts(PageComponentSchema), and whichever guidance table the fix lands in — plus test-only additions pinning the boundary. - Container & model:
mode:subagent,opus(M under the 2026-08-12 dispatch-backend ruling; tiered up because the change sits in shared guidance machinery where the failure mode is silently widening a neighbouring surface). - Serial constraints: One intent, two spellings:
visible(actions) vsvisibleWhen(fields / sections / userActions) — and the alias guard only covers one direction #7816 (domain:spec,pm:queue, unassigned) overlaps this file surface — its ask 1 wants guidance on the same section/component shapes. Cross-seat note posted there declaring this claim so the protocol seat can serialize. This card takes only the editability-boundary half; thevisible/visibleWhensymmetry half stays One intent, two spellings:visible(actions) vsvisibleWhen(fields / sections / userActions) — and the alias guard only covers one direction #7816's.
Premise re-verified on
origin/mainbefore claiming, per E9 — and it holds in a stronger form than the card states:view.zod.tsalready carries the reasoning the ruling reaches, in theFormFieldSchemastrict-options comment: "VISIBILITY_STRICT_OPTIONSis shared withFormSectionSchemaandPageComponentSchema, and neither of those declares a read-only or disabled slot, so this row belongs HERE rather than in the shared table — filed there it would name a key two of its three surfaces do not accept." The boundary is already the code's operating assumption; this card writes it down and makes the rejection say it.- The field-arm correction in the ruling is confirmed:
field.zod.tsrenamesdisabled: 'readonly'and its own comment states "a field hasreadonlyWhen, notdisabledWhen(spec-surface: alias guidance forvisible/showWhen/disabledon thevisibleWhenshapes — the reverse direction of the action shapes' existing table (#7816 ask 1) #7832)". Guidance must point authors atreadonlyWhen;disabledWhendoes not exist on fields. FormFieldSchemaconsumesdisabledthrough its own alias row (view.zod.ts:1775), so it never reaches the error path — which is exactly why the guidance must not be filed anywhere that would misfire on that third surface.
Generated by Claude Code
- Session:
os-dev-report
{ "issue": 7887, "status": "done", "branch": "claude/issue-7887-section-component-editability-boundary", "pr": "https://github.com/objectstack-ai/objectstack/pull/8199", "premise_still_valid": true, "summary": "Implemented the 2026-08-12 boundary ruling as text face only. No key and no alias row was added to FormSectionSchema or PageComponentSchema; both gained a docblock paragraph stating that they gate visibility and that editability lives on fields, and both now answer the editability family (disabled, disabledWhen, readonly, readOnly, readonlyWhen, editable) with a guidance-set prescription pointing at field-level `readonly: true` / `readonlyWhen`, plus `visibleWhen` for hiding the whole thing. The prescription is filed on a new `VISIBILITY_ONLY_STRICT_OPTIONS` taken by those two shapes rather than in the shared `VISIBILITY_STRICT_OPTIONS`, and that placement is proven by counterfactual rather than argued: hoisting the set into the shared table deletes FormFieldSchema's `disabled -> readonly` rename (a guidanceSet match `continue`s past the alias channel) and turns two alias-integrity checks red (#7889 unreachable-alias, and #6619 dead-member since `readonly` IS declared on the field shape). Acceptance is byte-identical in both directions and the field surface's message is unchanged byte-for-byte. One correction carried from the dispatch: `disabled` on a FormFieldSchema is NOT accepted — it is rejected with a rename suggestion (aliases are a message channel, not a parse-time rename), so the field-surface invariance pin asserts the suggestion text and continued rejection, not a successful parse.", "tests": "pnpm --filter @objectstack/spec test -> 'Test Files 386 passed (386) / Tests 10218 passed (10218)' after regenerating export-origins (the single first-pass failure was the stale artifact recording the new export, fixed with gen:export-origins; diff is one line). pnpm --filter @objectstack/spec typecheck -> green ('check:test-typecheck: OK'). New file src/shared/editability-boundary.test.ts -> 'Tests 23 passed (23)'. Neighbours alias-integrity + visible-when-alias-guidance + editability-boundary -> 'Tests 60 passed (60)'; scripts/export-origins + ui/view + ui/page + ui/component -> 'Tests 502 passed (502)'. Reverse verification (predicted direction: red, plus the field message losing its rename) with the set hoisted into the shared table: 'alias `disabled` -> `readonly` is unreachable — guidanceSet ... already matches `disabled`', 'no guidance SET member is itself a declared key' red, and \"expected '...' to contain 'Did you mean `disabled` -> `readonly`?'\" — reverted with git checkout (never git stash), re-run green. Acceptance evidence: the FormFieldSchema rejection message captured before and after is identical; a full `pnpm --filter @objectstack/spec build` (which runs gen:schema and rewrites authorable-surface.base.json) left the tree clean. Gates: check:nul-bytes OK (7440 files), check:adr-anchors exit 0, check:changeset-gate-self-tests OK, check:docs-audit-scope OK, check:merge-driver OK, check:release-body OK, check:spec-parsed-alias OK, check:i18n exit 0 (after a full turbo build of @objectstack/cli), @objectstack/lint check:doc-formula-expressions OK (after building @objectstack/formula and @objectstack/spec). No check:alias-integrity script exists — the #7889 reachability gate is the vitest file, run above.", "open_questions": [], "out_of_scope_findings": [ "NOT FILED, per dispatch — routed to the PM for #7816's seat: `SelectOptionSchema` (data/field.zod.ts) is the third shape #7884 §3 pinned as having no disabled-ish key, and the #7887 ruling did not cover it. It is not obviously the same answer: a disabled select OPTION is a real UI concept (an option shown but unselectable), unlike a read-only section, so 'boundary' vs 'gap' is genuinely open there and per-option `visibleWhen` already binds current_user (ADR-0068), i.e. the surface does carry per-option state. Left bare and unchanged by this PR.", "NOT FILED, per dispatch — observation class: all three view/page shapes share `surface: 'this view/page schema'`, so the rejection never tells an author whether a form section, a page component or a form field refused the key. That was harmless while the three answered identically; after this card they answer `disabled` in two different ways, and the message the author reads does not say which shape they are on. Cheap to fix (a per-shape surface string, still text-face and acceptance-neutral) but outside this card's ruling, and it would touch the same shared table #7816 is looking at." ] }Notes for the reviewing seat, beyond the JSON:
- Where the guidance landed is the one judgement call the card asked for, and it is decided by the gate rather than by taste:
VISIBILITY_STRICT_OPTIONShas three consumers andFormFieldSchemais the one that answersdisabledcorrectly, so a shared-table filing is caught byalias-integrity.test.tstwice over. The existing comment atview.zod.ts's FormFieldSchema strict-options block is extended to record that both decisions are one rule read from opposite ends. - The changeset is
patch, measured not inherited. Parse-reachable error string: yes (pinned).dist.d.tshover: yes. Generated reference page: no —build-docs.tsrenders the file-level doc block plus per-property.describe()text and not a schema's own JSDoc, verified by greppingcontent/docs/references/ui/view.mdxfor existingFormSectionSchemadocblock text (no hits) against a per-key describe string (hit). No top-level.describe()was added: it would not reach that page either and would moveauthorable-surface.base.jsonfor no consumer gain. - Serial constraint honoured:
data/object.zod.tsuntouched, no part of thevisible/showWhensymmetry implemented. - CI was not waited on — reported at draft-PR time per the dispatch contract; the PM owns convergence and the ready-flip.
Generated by Claude Code
- Where the guidance landed is the one judgement call the card asked for, and it is decided by the gate rather than by taste:
- added a commit that references this issue
on Aug 12, 2026 os-dev-report
Patch round on the same claim — supersedes the
testsfield of the report above (comment 5272135393); everything else in it stands.{ "issue": 7887, "status": "done", "branch": "claude/issue-7887-section-component-editability-boundary", "pr": "https://github.com/objectstack-ai/objectstack/pull/8199", "premise_still_valid": true, "summary": "Unchanged in substance from the first report. One CI-driven follow-up: `check:api-surface` (a step of the repo-wide TypeScript Type Check job, not reachable from a package-scoped typecheck) went red because `VISIBILITY_ONLY_STRICT_OPTIONS` was exported from `shared/visibility.ts`, which the barrel re-exports wholesale — a 1-added/0-breaking public API change. Fixed by keeping the table package-internal instead of regenerating the snapshot: it now lives in a new `shared/editability-boundary.ts`, which the barrel deliberately omits, alongside `strict-object.ts` and `alias-probe.ts`. Decisive reason beyond tidiness: `StrictObjectOptions`, the const's own type, is not public either, so a published value of that type is one no consumer could annotate. Result is strictly better for a card whose claim is that nothing observable changes — the public API surface does not move, and `export-origins/shared.json` is back to byte-identical with main, so the PR now touches no committed generated artifact at all.", "tests": "After the fix, re-run in full: pnpm --filter @objectstack/spec test -> 'Test Files 386 passed (386) / Tests 10218 passed (10218)'. pnpm --filter @objectstack/spec typecheck -> exit 0. pnpm --filter @objectstack/spec check:api-surface -> '@objectstack/spec public API surface + factory signatures unchanged ✓' (requires a real dts build; OS_SKIP_DTS=1 is explicitly invalid for this gate per its own message and AGENTS.md §9). pnpm --filter @objectstack/spec check:export-origins -> 'export-origins/ is current: 5021 exports across 16 entry points' with the artifact byte-identical to main. Targeted pins after the move: editability-boundary + alias-integrity + visible-when-alias-guidance + strict-object + ui/view + ui/page + scripts/export-origins -> 'Tests 426 passed (426)'. Repo gates re-run with explicit exit codes: check:nul-bytes 0, check:adr-anchors 0, check:changeset-gate-self-tests 0, check:docs-audit-scope 0, check:merge-driver 0, check:release-body 0, check:spec-parsed-alias 0, check:i18n 0 (after a full turbo build of @objectstack/cli), @objectstack/lint check:doc-formula-expressions 0 (after building @objectstack/formula). Reverse verification from the first round (shared-table counterfactual, red in all three predicted places) still stands and was not re-run.", "open_questions": [], "out_of_scope_findings": [ "NOT FILED, per dispatch — routed to the PM for #7816's seat: `SelectOptionSchema` (data/field.zod.ts) is the third shape #7884 §3 pinned as having no disabled-ish key, and the #7887 ruling did not cover it. It is not obviously the same answer: a disabled select OPTION is a real UI concept (an option shown but unselectable), unlike a read-only section, so 'boundary' vs 'gap' is genuinely open there and per-option `visibleWhen` already binds current_user (ADR-0068), i.e. the surface does carry per-option state. Left bare and unchanged by this PR.", "NOT FILED, per dispatch — observation class: all three view/page shapes share `surface: 'this view/page schema'`, so the rejection never tells an author whether a form section, a page component or a form field refused the key. That was harmless while the three answered identically; after this card they answer `disabled` in two different ways, and the message the author reads does not say which shape they are on. Cheap to fix (a per-shape surface string, still text-face and acceptance-neutral) but outside this card's ruling, and it would touch the same shared table #7816 is looking at.", "NOT FILED — observation class, noticed while choosing the fix: `VISIBILITY_STRICT_OPTIONS` IS in the public API surface while `strictObject`, `strictObjectError` and their `StrictObjectOptions` type are not, so the published const has a type an external consumer cannot name. It reads as an accident of `shared/visibility.ts` also carrying the runtime `normalizeVisibleWhen` helper (and therefore being barrelled) rather than an API commitment. Nothing is broken today and un-exporting it would be a real (if tiny) breaking change, so it wants a decision rather than a drive-by." ] }
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
Recorded from PR #7884's report (out_of_scope_finding 2 of card #7832; filed by the PM seat because the cloud dev session cannot write issues). Filed unassigned — a recording, not a claim.
Observation, measured on the #7884 branch against
origin/mainWhile curating alias/guidance rows for
visible/showWhen/disabledon thevisibleWhen-family shapes (#7832), the dev found thatFormSectionSchemaandPageComponentSchema(andSelectOptionSchema) declare no disabled-ish key whatsoever — nodisabled, nodisabledWhen, noreadonly. There is no way to express "this section / page component is disabled/read-only" in view/page metadata.Consequence for #7832's scope:
disabledwritten on those shapes is rejected loudly (strict shapes) but with no pointer, because there is nothing on the shape to point at — an alias row would name a key the shape rejects next (alias-integrity.test.tsfails such a row outright). PR #7884 pinned these as deliberate gaps:visible-when-alias-guidance.test.tssection 3 fails if any of the three shapes ever gains a disabled-ish key, so the owed alias row becomes visible the moment it becomes writable.The question
Whether this is a deliberate design boundary (sections/components gate visibility only; disabling is a field-level concern via
readonly/readonlyWhen) or a vocabulary gap is an acceptance-surface question fordomain:spec, not for the surface seat — adding any such key would widen the accepted metadata set.If it is ruled a boundary: worth one sentence of prose on the shapes saying so (that half would return to
domain:spec-surface). If it is ruled a gap: the #7884 tripwire pins already mark where the alias rows become owed.Refs
#7832 / PR #7884 (provenance, tripwire pins), #7816 (canonical-spelling question, related vocabulary), #7751 (page-component props passthrough — adjacent but distinct: that is about the
propertiesbag, this is about the shape's own keys).Dedup: searched open issues for FormSectionSchema/PageComponentSchema + disabled/readonly vocabulary — nothing beyond the refs above.