Skip to content

[finding] disabled has no vocabulary at all on FormSectionSchema / PageComponentSchema — no readonly/disabled/disabledWhen key to point an author at #7887

Description

@os-zhuang

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/main

While curating alias/guidance rows for visible/showWhen/disabled on the visibleWhen-family shapes (#7832), the dev found that FormSectionSchema and PageComponentSchema (and SelectOptionSchema) declare no disabled-ish key whatsoever — no disabled, no disabledWhen, no readonly. There is no way to express "this section / page component is disabled/read-only" in view/page metadata.

Consequence for #7832's scope: disabled written 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.ts fails such a row outright). PR #7884 pinned these as deliberate gaps: visible-when-alias-guidance.test.ts section 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 for domain: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 properties bag, this is about the shape's own keys).

Dedup: searched open issues for FormSectionSchema/PageComponentSchema + disabled/readonly vocabulary — nothing beyond the refs above.

Activity

  1. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Triage: routed domain:spec (the question is an acceptance-surface one — any disabled-ish key on FormSectionSchema/PageComponentSchema/SelectOptionSchema widens the accepted metadata set); hold as finding. 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

  2. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    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 a disabled → disabledWhen alias, a row those shapes would reject (alias-integrity.test.ts fails outright, and §3 of visible-when-alias-guidance.test.ts pins the rejection). #7816 has been marked pm:blocked on this card.

    Question: is the absence of disabled/readonly on FormSectionSchema / PageComponentSchema a 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 readonly slot 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) vs visibleWhen (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 (its disabled arm becomes guidance-text-only; note field.zod.ts:464 already records fields take readonlyWhen, so #7816's alias target needs that correction either way). When ruled, return #7816 to pm:queue in the same action.


    Generated by Claude Code

  3. added theissue type on Aug 12, 2026
  4. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    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-routed domain:spec → domain:spec-surface.

    Ruling: boundary, not gap. FormSectionSchema / PageComponentSchema gate visibility only; editability lives on fields. No disabled / readonly / disabledWhen slot 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 disabled on a section is told about field-level readonlyWhen instead of getting a bare unknown-key error. The existing rejection pins in visible-when-alias-guidance.test.ts §3 stay green by construction.

    #7816 is unblocked and returned to pm:queue in 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:464 already records that fields carry readonlyWhen, so #7816's original disabled → disabledWhen alias target is wrong for fields too and must be corrected rather than copied.


    Generated by Claude Code

  5. self-assigned this
    on Aug 12, 2026
  6. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor

    Claim: domain:spec-surface PM seat (#6298), round 6 of the 2026-08-12 shift. Dispatched on the maintainer's boundary ruling above.

    Premise re-verified on origin/main before claiming, per E9 — and it holds in a stronger form than the card states:

    • view.zod.ts already carries the reasoning the ruling reaches, in the FormFieldSchema strict-options comment: "VISIBILITY_STRICT_OPTIONS is shared with FormSectionSchema and PageComponentSchema, 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.ts renames disabled: 'readonly' and its own comment states "a field has readonlyWhen, not disabledWhen (spec-surface: alias guidance for visible/showWhen/disabled on the visibleWhen shapes — the reverse direction of the action shapes' existing table (#7816 ask 1) #7832)". Guidance must point authors at readonlyWhen; disabledWhen does not exist on fields.
    • FormFieldSchema consumes disabled through 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

  7. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor

    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_OPTIONS has three consumers and FormFieldSchema is the one that answers disabled correctly, so a shared-table filing is caught by alias-integrity.test.ts twice over. The existing comment at view.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.ts hover: yes. Generated reference page: no — build-docs.ts renders the file-level doc block plus per-property .describe() text and not a schema's own JSDoc, verified by grepping content/docs/references/ui/view.mdx for existing FormSectionSchema docblock 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 move authorable-surface.base.json for no consumer gain.
    • Serial constraint honoured: data/object.zod.ts untouched, no part of the visible/showWhen symmetry 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

  8. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    ContributorAuthor

    os-dev-report

    Patch round on the same claim — supersedes the tests field 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

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions