Skip to content

spec: RecordChatterProps.position declares a vocabulary no read point compares — sidebar/inline/drawer vs the renderer's bottom/right/left — and its collapsible default inverts the renderer's #8762

Description

@os-zhuang

Found while implementing #8744 (measuring the record:discussion / record:chatter pair together, as its triage note required). Filed as an out-of-scope finding, unassigned — #8744 wires record:discussion to this same shared row on purpose (one renderer, one accept face), so whatever fix lands here repairs both names at once.

The divergence, measured at the .objectui-sha pin (665661ab0932)

ComponentPropsMap['record:chatter'] (packages/spec/src/ui/component.zod.ts, RecordChatterProps) declares:

  • position: z.enum(['sidebar', 'inline', 'drawer']).default('sidebar')
  • collapsible: z.boolean().default(true)
  • defaultCollapsed: z.boolean().default(false)

The renderer chain reads none of those position values. RecordChatterPanel (objectui packages/plugin-detail/src/RecordChatterPanel.tsx:87-96) compares config.position against exactly right / left / bottom:

const position = config?.position ?? 'right';
const isSidebar = position === 'right' || position === 'left';
const isInline = position === 'bottom';

and the registration (plugin-detail/src/index.tsx, CHATTER_INPUTS) declares enum: ['bottom', 'right', 'left'], defaultValue: 'bottom' — agreeing with the panel, not with the spec row. record-chatter.tsx merges { position: 'bottom', collapsible: false, … , ...schema }, so the renderer-side fallbacks are bottom / false.

Consequences today:

  1. A spec-valid position: 'sidebar' (the schema's own default, materialized onto every parsed node that says nothing) matches neither branch and falls into the inline/bottom render — the declared value is a silent no-op, on the very surface whose row exists to make undeclared keys loud. The spec-invalid position: 'right' is what actually docks the panel.
  2. The collapsible schema default (true) inverts the renderer merge's fallback (false): "the author said nothing" parses into "the author asked for collapsible", which is also not what the renderer would have done on its own.

What a fix needs to decide

Blocked-by: nothing, but lands cleanly after #8744's PR (which touches the neighbouring map rows and pins the pair's shared identity).


Generated by Claude Code

Activity

  1. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Triage: lands in packages/spec/src/ui/component.zod.ts (RecordChatterProps) — domain:spec, the semantic lane, because every candidate fix moves the accept face. Typed Bug: the row declares a vocabulary nothing reads, which is declared-≠-enforced on a published contract, not a new capability.

    State: needs-user-decision. Reasoning below, including the half that does not need a ruling.

    Why not straight to pm:queue — the boundary test, applied

    The card is two defects sharing a schema object, and they grade differently:

    So one ruling on the vocabulary releases the whole card, defaults included.

    Four-facet card face

    • Platform long-term coherence — the only outcome to avoid is a mapping layer; that is special-case growth papering over two contracts. One vocabulary, and the loser's spellings get an ADR-0087 conversion. Note drawer has no renderer counterpart at all, so convergence toward the renderer is also a removal question, not a pure rename.
    • Measured business pull — today the authoring path is broken in both directions: the schema's own default (sidebar) renders inline, and the value that actually docks the panel (right) is spec-invalid and refused at publish. The Studio designer's CHATTER_INPUTS emits bottom/right/left, so authored metadata most likely carries the spec-invalid set. ⚠️ Unmeasured and decisive: which spelling stored record:chatter / record:discussion components actually carry. That count should pick the direction; it is a survey, not a judgement.
    • AI-agent error-resistance — one closed enum, loud refusal, no schema default. The current shape is the worst case for an AI author: the spec-valid value silently does nothing and the working value is refused.
    • Startup scope discipline — converge and delete; do not add a translation shim, and do not keep three declared spellings alive because they are already published.

    Recommendation (not a ruling): converge the spec on the renderer's bottom/right/left, drop both defaults, drop drawer unless a renderer branch is wanted for it. Rationale: the renderer's set is the only one with a reader and the only one the designer emits, so it is the de-facto contract; declaring it converts a silent no-op into a working declaration without breaking anyone who has something working today — nobody can be relying on sidebar, since it renders inline either way. Conditioned on the stored-corpus survey above not contradicting it.

    Serial constraint (post-ruling)

    ⚠️ Not dispatchable in the same batch as #8744 — same ComponentPropsMap object, and #8744 pins the record:chatter / record:discussion shared identity this fix has to preserve. Sequence after it, per the filer's own note.

    Index hygiene note

    The body carries a prose line reading Blocked-by: nothing, but lands cleanly after #8744's PR. The Blocked-by: reverse index is grepped mechanically and does not validate that the token after the colon is an issue reference — this card has no upstream and must not be read as blocked. Recorded here rather than edited into someone else's body.

    本评论来自分诊座位 Routine,不构成认领。


    Generated by Claude Code

  2. added theissue type on Aug 14, 2026
  3. os-zhuang commented on Aug 15, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling (2026-08-15, verbatim: 「接受全部建议。」, item 2 of the five-item list posted in the live PM session):

    1. The row's vocabulary converges on the renderer's — position: z.enum(['bottom', 'right', 'left']) — one vocabulary, ⛔ no mapping layer. The renderer side is already self-consistent in three places (panel branches, registration enum, merge fallback); the spec row was the isolated wrong party.
    2. The three old spellings (sidebar/inline/drawer) take an ADR-0087 conversion (value-level rewrite; the breaking classification is the post-cut class, same as today's five precedents).
    3. Schema defaults are dropped (position, collapsible, defaultCollapsed) per the maxVisible principle — renderer fallbacks stay the renderer's facts. .default('sidebar') was itself half of defect 1.
    4. The row stays one shared schema object for record:chatter AND record:discussion (the spec: record:alert / record:quick_actions / record:history have no ComponentPropsMap row — same silent no-op mechanism #8691 closed for the rail #8744 pairing).

    needs-user-decision → pm:queue in the same stroke. Queue position under SERIAL mode: first (Bug class). Dispatch follows immediately.


    Generated by Claude Code

  4. self-assigned this
    on Aug 15, 2026
  5. os-zhuang commented on Aug 15, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 7 (SERIAL slot)
    Session: session_01E5tUwGM3LQoqErTfkvRW7W
    Branch: claude/issue-8762-chatter-position-vocabulary
    Worktree: objectstack-issue-8762
    Domain: domain:spec
    File surface: packages/spec/src/ui/component.zod.ts (RecordChatterProps — the shared chatter/discussion row), its pin tests, ADR-0087 conversion entry + protocol-18 semantic entry + regenerated registry/projections, changeset (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-fable-5 (mandatory: value-level accept-face change)
    Serial constraints cleared: SERIAL mode — this is the only card in flight; no other claim touches packages/spec/src/ui/**. Registry hot file: main at 89 semantic; if anything lands ahead, this branch runs the os-regen relay.

    Ruling being implemented (maintainer 2026-08-15 「接受全部建议。」 item 2, recorded above): vocabulary converges on the renderer's (bottom/right/left, no mapping layer); old spellings take an ADR-0087 conversion; schema defaults dropped per the maxVisible principle; the row stays one shared schema object for chatter + discussion.


    Generated by Claude Code

  6. removed their assignment
    on Aug 15, 2026
  7. os-zhuang commented on Aug 15, 2026

    @os-zhuang
    ContributorAuthor

    Claim withdrawn — maintainer stop (spec seat, session session_01E5tUwGM3LQoqErTfkvRW7W).

    Provenance: maintainer directive in the live PM session, 2026-08-15, verbatim: 「8762 停掉,不再派发spec,没token了」. The dev agent was stopped during its read-only measurement phase — no code written, no branch pushed, nothing to salvage or discard. Assignee cleared and pm:dispatched → pm:queue in the same stroke.

    The card is fully ready for the next dispatch: the maintainer ruling of 2026-08-15 (four binding points — renderer vocabulary, ADR-0087 conversion for the old spellings, defaults dropped, one shared row for chatter/discussion) is recorded above and stands. Whoever picks this up next starts from that comment plus the claim-shaped file surface in the withdrawn claim.


    Generated by Claude Code

  8. os-project-manager commented on Aug 15, 2026

    @os-project-manager
    Collaborator

    Claim: PM loop round 1 (spec seat; concurrency raised to 3 by maintainer instruction 2026-08-15, verbatim: 「优先处理 bug 和 v17 任务。然后才是其他任务,并发加到3」— supersedes both the 2026-08-14 SERIAL directive and the 2026-08-15 「8762 停掉,不再派发spec,没token了」stop, which was a token-budget stop, not a scope objection)
    Session: session_01Fgvh1iEJfxetei7aNVdtJt
    Branch: claude/issue-8762-chatter-position-vocabulary
    Worktree: objectstack-issue-8762
    Domain: domain:spec
    File surface (region-level): packages/spec/src/ui/component.zod.ts (RecordChatterProps row + the shared chatter/discussion pairing, lines ~1028-1040 region), its pin tests, ADR-0087 conversion entry + regenerated migrations/registry.ts projections, changeset (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-fable-5 — mandatory tier: value-level accept-face change
    Serial constraints cleared: #8744 precondition MET — PR #8773 merged, record:discussion pairing present on origin/main (verified this fire, component.zod.ts:2161); in-flight #8778 (data/object.zod.ts + platform-objects + plugin-audit) and #8337 (data/driver/** + services redaction) are disjoint source regions. Shared generated surfaces (migrations/registry.ts @ ~95 semantic — moved from 89 since the seat note, relay discipline live; reference docs) under the four waiver disciplines: region-level declaration (this comment), merge main before opening the PR, re-merge after any sibling lands, conflicts to the merge queue + os-regen relay.

    Ruling being implemented (maintainer 2026-08-15 「接受全部建议。」, four binding points recorded in comment 5299771841): vocabulary converges on the renderer's bottom/right/left, ⛔ no mapping layer; old spellings take an ADR-0087 conversion; all three schema defaults dropped per the maxVisible principle; the row stays ONE shared schema object for record:chatter + record:discussion.


    Generated by Claude Code

  9. os-project-manager commented on Aug 15, 2026

    @os-project-manager
    Collaborator
    {
      "issue": 8762,
      "status": "done",
      "branch": "claude/issue-8762-chatter-position-vocabulary",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8914",
      "premise_still_valid": true,
      "summary": "Implemented the four-point ruling (comment 5299771841): RecordChatterProps.position is now z.enum(['bottom','right','left']) with no mapping layer; the three old spellings refuse at parse with per-value prescriptions via the enum's own error map (the #8010 'pdf' precedent); all three schema defaults dropped per the maxVisible principle with three declared entries in the default-changes ledger (check:authorable-surface required them); the row stays ONE shared schema object for record:chatter + record:discussion. ADR-0087: mechanical conversion record-chatter-position-vocabulary (toMajor 18, retiredFromLoadPath — post-cut class, sidebar→right / inline→bottom / drawer→right, intent-preserving mappings justified in the PR body) plus semantic entry record-chatter-position-vocabulary-converged; gen:spec-changes/upgrade-guide/api-surface came back byte-identical because projections exclude protocol-18 steps until the cut (same as the field-scale precedent) — flagged in the PR body. Renderer facts re-measured at pin 665661ab0932: the card's numbers verified, with the nuance that the panel's own ??'right'/??true fallbacks are unreachable for authored nodes (the renderer merge supplies bottom/false first); described precisely in the PR. Main moved three times under the branch; merged twice (scripts/turbo, then lint+metadata-protocol — no spec overlap either time, no regen debt); the newest two main commits touch no shared surface so no third merge. PR carries a changeset so skip-changeset does not apply.",
      "tests": "All at final head ca7f562 (union re-run AFTER the last commit, the post-merge re-merge included). packages/spec full suite: 402 files / 10650 passed (at pre-merge 820b414; targeted re-run at ca7f562: component.test + component-record-blocks.test + conversions.test = 371 passed). Consumers at merged heads: lint 2039 passed, metadata-protocol 1493, metadata 603, metadata-core 162, metadata-fs 51, sdui-parser 6 — all downstream-consumer suites (prefix direction: these packages consume @objectstack/spec). Example apps validate: app-showcase/app-crm/app-todo all exit 0 (warnings pre-existing, unrelated). Gates at ca7f562: check:generated 'All 13 generated artifacts up to date'; check:type-check-debt --re-measure OK (33 entries, none above ceiling); check:type-check-coverage OK; check:query-options-erasure OK (240 sites, no new files); check:nul-bytes OK; check-adr-0087-registration OK (marker registered record-chatter-position-vocabulary, record-chatter-position-vocabulary-converged); check:merge-driver, check:spec-parsed-alias, check:type-source-resolution, check:cross-package-test-inputs, check:dev-prereqs, check:changeset-no-major, check:empty-changeset, check:doc-formula-expressions, check:docs-audit-scope, check:objectui-changeset, check:quick-reference-counts, check:role-word, check:changeset-gate-self-tests — all OK. Gate list re-derived from actual changed paths via dispatch-gates.mjs; it added 7 families beyond the prompt's list (cross-package-test-inputs, docs-audit-scope, objectui-changeset, quick-reference-counts, role-word, changeset-no-major, empty-changeset), all run and green. Spec typecheck green (incl. test-typecheck ledger). Reverse verification (direction decided first: red expected): with component.zod.ts restored to origin/main, exactly the 10 new/changed pins went red (no-default pins, new-vocabulary positives, 3 per-value rejection pins, pair pin); fix restored from the committed state via git restore --source=HEAD, re-run green (371/371). Conversion fixture auto-driven by conversions.test.ts (before→after + 3 notices). No dogfood ablation in scope — the schema-level reverse verification above is this card's counterfactual. CI on PR #8914: reported at draft-PR time per contract; convergence wait is the PM's.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  10. os-project-manager commented on Aug 15, 2026

    @os-project-manager
    Collaborator

    ACCEPT — PR #8914 (Fixes #8762), reviewed by the spec seat (session session_01Fgvh1iEJfxetei7aNVdtJt) against the actual diff, not the report's self-description. Held in draft for the generated-surface serial relay (see below) — the ACCEPT and the queueing are deliberately decoupled.

    Review findings, per the four-point ruling (comment 5299771841):

    1. Vocabulary — z.enum(['bottom','right','left']), no mapping layer anywhere; the three retired spellings refuse with per-value prescriptions via the enum's own error map keyed on issue.input (the spec: ListView.exportOptions is typed as a bare array, but the only renderer reads exportOptions.formats #8010 'pdf' precedent), and the never-legal 'modal' is pinned to get zod's plain refusal, NOT a retirement prescription — the error map does not overclaim.
    2. ADR-0087 — conversion record-chatter-position-vocabulary (toMajor: 18, retiredFromLoadPath), fixture carries the scoping negative (a non-chatter component with position: 'sidebar' survives byte-identically) and expectedNotices: 3; semantic entry + regenerated registry both present; the sidebar→right / inline→bottom / drawer→right intent mappings are stated consistently in three places, and the acceptance criteria honestly flag that sidebar/drawer authors will SEE the layout change to the docked panel they originally asked for.
    3. Defaults dropped — all three keys .optional() with renderer defaults stated as prose; authorable-defaults/ui.json lost exactly the three rows; the default-changes ledger carries the three declared entries with deployed-behaviour analysis; the old "accept empty with defaults" pins are REPLACED by no-default pins ('position' in result === false).
    4. Pair identity — one shared schema object preserved; the new pair pin asserts both names refuse a retired spelling with the identical message.

    CI: all 27 check-run jobs completed: success at head ca7f562. Consumer readings in the report: spec 10650, lint 2039, metadata-protocol 1493, metadata 603+162+51, sdui-parser 6, three example apps validate exit 0. Reverse verification red on exactly the 10 predicted pins. No objectui edits (renderer unchanged, correct — it keeps its own vocabulary).

    Landing hold — serial relay on migrations/registry.ts: PR #8925 (issue #8337) is in the merge queue ahead of this one and also registers a protocol-18 entry in the same generated registry. One generated-surface PR lands at a time. Once #8925 is MERGED, the relay runs here: merge main, regenerate (gen:migration-registry + check:generated), assert BOTH cards' entries and the sibling's implementation survive, push, then this PR flips ready + auto-merge. ⛔ Not queued before that.


    Generated by Claude Code

  11. added a commit that references this issue on Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions