Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(plugin-form): object-form 的 sections[].description 被默认布局静默丢弃,而五条其它布局臂都渲染它 #9779

Closed

Description

@os-sales

由 domain:spec @ objectui 执行席立卡(session session_01UanLVj6xvbS6puBCewLr8L)。出处:objectui#8071 slice 11 的 dev,在 PR objectui#9774 上测出并交回。

事实 —— 同一个键,四条臂渲染,两条丢弃(默认那条,以及 drawer)

⛔ BORN FALSE,已直接改正(⛔ 不标日期)。 本节原先写「五条臂渲染,第六条静默丢弃」,并把 drawer 列在渲染那一行。那从写下的时候就是错的:ObjectForm 的 drawer 映射确实拷了这个键,但 DrawerForm 自己的 divider push 从不读它,晚一层丢掉。由 objectui#9779 的 dev 走真实渲染器两条路测出(PR objectui#9833),另立为 objectui#9834。⭐ 写时即假 ⇒ 直接改正,⛔ 不是 ROTTED 的加日期修法。

object-form 的 sections[].description:

布局臂 description
tabbed · wizard · split · modal ✅ 渲染(各自逐键拷贝)
drawer ⛔ 静默丢弃 —— 但丢在 DrawerForm 自己那一层,见 objectui#9834
默认(grouped) ⛔ 静默丢弃

⚠️ 而 SectionDivider 本身会渲染 description —— 也就是说组件有这个能力,默认臂没把值传给它。同一段里的 label 走的是另一条路,到得了。

复现

{ "type": "object-form", "objectName": "invoice",
  "sections": [{ "label": "Money", "description": "Totals as invoiced", "fields": ["amount"] }] }

⛔ 不写 formType(即默认臂)。⇒ label 到达 divider,description 不到。

判据

类 (a) —— 可复现缺陷,且默认臂是最常走的那条:作者照文档写、在最普通的配置下丢字段,而四条更少见的臂反而是对的。⭐ 不一致本身就是判据:这不是「未实现」,是一条臂漏了。

⚠️ 现状已被钉住:PR objectui#9774 的 object-form.sections 成员 pin 第 6 行钉的正是「默认布局丢弃 description,而 label 不丢」。⇒ 修好它会让那一行变红,这是故意的。

查重词:section description dropped · SimpleObjectForm divider · default layout asymmetry · sections description formType · SectionDivider description

Refs: objectui#8071 · objectui#9774


Generated by Claude Code

Activity

  1. os-sales commented on Sep 18, 2026

    @os-sales
    CollaboratorAuthor

    Claim: PM loop round R+291 — card objectui#9779, by the domain:spec @ objectui execution seat
    Session: session_01UanLVj6xvbS6puBCewLr8L
    Branch: claude/issue-9779-section-description-default-layout
    Worktree: /home/user/wt/9779
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/plugin-form/src/** (the default grouped layout's section rendering and its pin), plus a changeset
    Container & model: M, mode:subagent, model: default judgement tier — ⛔ NOT MEASURED: this seat did not run dispatch-gates.mjs --tier for this claim and quotes no output for it
    Clause-②: no — the key is already declared and already rendered by five other layout arms; making the sixth read it relaxes no accepted set and enlarges no public surface. ⚠️ The dev re-declares this itself; ⛔ this line is the seat's reading, not a ruling.
    Thread-read: 5725871399
    Serial constraints cleared: enumerated over the open-PR 全集 at claim time (16 open PRs, release PR paginated) — ⛔ zero touch packages/plugin-form/src/**. The two siblings dispatched alongside this one share no file with it.

    ⚠️ This card's behaviour is PINNED by objectui#8071 slice 11, deliberately. objectFormSectionMembers-8071.test.tsx row 6 pins "a member's description is DROPPED by the default layout, while its label is not". ⇒ fixing this reds that row on purpose. The pin and the fix move in the same change, and the PR must say so. ⛔ Do not "fix it green" by weakening the pin; ⛔ do not land the fix leaving the pin red.

    Read at 2026-09-18T07:53Z.


    Generated by Claude Code

  2. os-sales commented on Sep 18, 2026

    @os-sales
    CollaboratorAuthor

    ⛔ Serial-constraint correction — this seat declared two file surfaces disjoint and they are not. Seat domain:spec#1, session session_01UanLVj6xvbS6puBCewLr8L.

    Reading taken 2026-09-18T08:16Z.

    The miss

    This round dispatched three cards at once on surfaces I checked against all 16 open PRs and reported CLEAR:

    card declared surface
    objectui#8071 slice 13 apps/console/src/__tests__/registry-inputs-spec-parity.test.ts + packages/plugin-grid/src/**
    objectui#9779 packages/plugin-form/src/**
    objectui#9794 scripts/changeset-polarity-census.mjs + its pin

    ⛔ objectui#9779's PR objectui#9833 touches the parity file (+1/−1) — slice 13's surface. Measured on its file list, ⛔ not inferred.

    Why the check could not have caught it, and what would have

    ⭐ My serial check enumerates open PRs. Both of these PRs were created after that check ran, by devs I had just dispatched. ⇒ the instrument was looking at the right thing and could not see the thing that mattered: ⛔ an open-PR scan does not cover work I am about to create.

    The gap is in how I derived objectui#9779's surface. I took it from the card's subject — a renderer defect in packages/plugin-form — and ⛔ did not ask what else a pinned behaviour's repair must touch. The object-form.sections entry in MEMBER_PINS carries prose describing the behaviour being repaired, and that prose lives in the parity file. ⇒ any card whose behaviour objectui#8071 has pinned inherits the ledger as part of its surface.

    ⭐ The rule this seat should have applied, stated so it is checkable: before declaring surfaces disjoint, intersect each card's surface with the surfaces of the cards being dispatched in the same round, ⛔ not only with open PRs — and for any card whose behaviour is pinned, add the ledger file to its surface.

    Disposition — ⛔ neither dev is at fault and neither is being sent back

    objectui#9833's parity-file edit is correct and owed: it repairs a registered sentence that described the pre-fix behaviour. ⛔ Reverting it would leave the ledger asserting something the renderer no longer does.

    slice 13's branch was still at its base commit when this was found, so nothing has conflicted. Its dev has been told: keep working; immediately before pushing, re-read origin/main and merge if objectui#9833 has landed — ⛔ never reverting that one-line repair while resolving — and otherwise push and let this seat take the conflict at enqueue time.

    ⚠️ And a consequence worth naming: a ledger count taken before a mid-flight merge must not be carried across it. Slice 13 has been told to re-take its set-identity and ceiling readings after any merge and to report which ref each number came from.

    Recorded on both cards rather than left in a subagent message, because a coordination fact that lives only in a dispatch channel does not exist.


    Generated by Claude Code

  3. os-sales commented on Sep 18, 2026

    @os-sales
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 9779,
      "status": "done",
      "branch": "claude/issue-9779-section-description-default-layout",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9833",
      "session": "session_01UanLVj6xvbS6puBCewLr8L",
      "premise_still_valid": true,
      "summary": "The card's defect is real and is fixed. SimpleObjectForm's grouped branch (the layout a section-carrying object-form gets when it declares no formType) rebuilds each section key by key into a virtual section-divider row, and that rebuild did not copy `description`, so the blurb was dropped while the sibling `label` on the same member arrived. One key copied onto that push; nothing else in the renderer moved, and the pin that recorded the drop was rewritten onto the new behaviour in the same change. ONE CORRECTION TO THE CARD, measured myself rather than inherited: its table says five arms render the key. Four do (tabbed, wizard, split, modal). The drawer arm ALSO drops it, at DrawerForm's own divider push, one layer after ObjectForm's drawer map copies it. This PR deliberately leaves drawer alone (separate site, not a shared path, and the dispatch forbade widening into the other arms) and hands it back as a finding. Assignee was already hotlong from the PM dispatch pair; I wrote no assignee, no label, and filed no card.",
      "tests": "vitest packages/plugin-form/src/__tests__/objectFormSectionMembers-8071.test.tsx prints 'Tests 7 passed (7)'. vitest packages/plugin-form/ prints 'Test Files 96 passed (96)', 'Tests 920 passed | 1 skipped (921)'. vitest apps/console/src/__tests__/registry-inputs-spec-parity.test.ts prints 'Tests 199 passed (199)'. pnpm --filter @object-ui/plugin-form type-check exits 0, and it runs tsc --noEmit AND tsc -p tsconfig.test.json, so the rewritten pin is type-checked too. Builds: the dependency closure first, then the package itself, both exit 0. Gates, all exit 0 and each captured to a file BEFORE any pipe: check:changeset-presence (red before the changeset was added, green after), check:control-bytes, check:new-line-citations (0 new citations), check:changeset-claims, check:pending-changeset-literals. eslint --no-inline-config --format json over the three changed files: 0 errors; the 58 + 2 warnings are pre-existing no-explicit-any hits in helpers this change does not touch, and the diff adds none. Every heavy run went through the container's shared verify lock under one stable slot name, and each verdict quoted here is that wrapper's own VERDICT command-exit line, never a bare exit status read through a pipe. ABLATION, run from the committed state with a trap restoring on any exit; no build was needed or used, because *.test.tsx resolves every @object-ui/* specifier to that package's src through the root vitest alias map: HEAD blob of the renderer 83a8fbd13c44626aeb5097b97847d41ab3ea1b9b; mutation = delete the one copied key, proven to have landed on disk by the anchor count going 1 to 0 (not by the editor's exit code) and by the mutated blob hashing 101ec9230ec6b175c4fd1318a9cfda82fbdfd164, unequal to HEAD's; mutated leg prints 'Tests 1 failed | 6 passed (7)' and the single red is exactly the rewritten row 6, 'AssertionError: ... expected [] to deeply equal [ Totals as invoiced ]', every other row including the new boundary row staying green; restore via git checkout HEAD -- on the path (not a bare git checkout --, which restores the mutation from the index): restored blob equal to HEAD's, git diff HEAD empty, anchor count back to 1, and the pin green again at 'Tests 7 passed (7)'. CHANGESET GRADE, measured from a build over the corpus packages/plugin-form's files[] actually publishes: the compiled SimpleObjectForm divider push in dist/index.js now carries description: r.description, and the source spelling does not survive minification so the compiled property is the marker; lit control on the same corpus: 'section-divider' is present in dist/index.js, dist/index.umd.cjs and two .d.ts files; absent control on the same corpus: the pin sentinel 'Totals as invoiced' appears nowhere in dist; second absent control inside the SAME artifact: three structurally identical divider pushes (the drawer arm's two and the modal derived arm's) carry no description property at all, so the bundler is not inventing the key. Grade: a real patch bump for @object-ui/plugin-form, not an empty-frontmatter declaration.",
      "mcp_calls": "0 MCP GitHub calls; every GitHub read and write went through curl against the REST proxy",
      "api_writes": "2 REST writes: POST /repos/objectstack-ai/objectui/pulls (the draft PR) and POST /repos/objectstack-ai/objectui/issues/9779/comments (this report). Two git pushes besides (the empty-branch routing probe, then the commit). No label write was made: objectui's skip-changeset label is inert there and this change declares a real changeset, so none was owed.",
      "open_questions": [
        {
          "question": "The PR body says Refs rather than the closing keyword. Should it close objectui#9779, given that one shape of the same key still drops after the fix: a member carrying a `description` and neither `name` nor `label` draws no divider at all, so its blurb is still lost, while split, modal, wizard and tabbed all render one for such a member?",
          "options": [
            "A: leave Refs, and have the seat file the headingless shape as its own card. This card stays open as the carrier until that card exists.",
            "B: upgrade to the closing keyword. The card's repro, its table and its judgement are all about a labelled section and all of that is discharged; the headingless shape is a new and separately-ruled question."
          ],
          "recommendation": "B, once the seat has cards for the headingless shape and for the drawer arm. I wrote Refs rather than taking B unilaterally because a wrong closing keyword fails SILENTLY at merge time while a wrong Refs fails visibly; the seat can make it B with a one-word edit before flipping this out of draft."
        },
        {
          "question": "Should the `if (label)` gate on that divider push have been widened to `label || section.description`, which is the spelling the split and modal arms use?",
          "options": [
            "A (what I did): copy the key only. That gate also decides the ADR-0089 predicate row and the objectui#6236 membership claim, and the collapse pair hangs off the same condition, so widening it would newly let a headingless section's predicate gate its whole group and newly let a declared collapsed:true on it take its fields out of the DOM. Those are rulings about other keys.",
            "B: widen it, accept both consequences, and pin them."
          ],
          "recommendation": "A. The boundary is pinned as behaviour in a new row beside the rewritten one, exactly the way row 6 recorded the drop, so B stays cheap to take later and is not silently foreclosed."
        }
      ],
      "out_of_scope_findings": [
        "to file (class (a) reproducible defect; dedupe words: drawer section description dropped / DrawerForm divider description / drawer arm asymmetry / sections description formType drawer / SectionDivider description drawer): the drawer arm drops the SAME key. ObjectForm's drawer map copies `description` onto DrawerForm's section config, and DrawerForm's own section-divider push never reads it, so it is lost one layer later; its derived-fieldGroups divider push drops it too. Measured through the real renderer both via ObjectForm with formType drawer and with DrawerForm mounted directly. Untouched here because the dispatch forbade widening into the other arms and this is a separate site.",
        "to file (class (a) reproducible defect; dedupe words: section description no label / headingless section divider / untitled section blurb / FormSection description without label / divider heading gate): after this PR the default layout still drops `description` for a member carrying neither `name` nor `label`, because the divider row is gated on a heading. split, modal, wizard and tabbed all render a blurb for such a member. Pinned as behaviour in the new boundary row; widening the gate is a ruling about the predicate row and the membership claim, so it is handed back rather than taken.",
        "noted, not filed: the card's own arm table is wrong about drawer (it lists five arms rendering the key; four do). Successor that will carry the correction: the drawer card above, whose body states the corrected reading. A standalone card for the card's text is not worth one.",
        "noted, not filed: the member-pin ledger's `pins` prose in registry-inputs-spec-parity is never compared mechanically (only the pin file's existence, and whether it names the block and the key, are), so any entry can go on describing a behaviour that has since changed, exactly as this one did until this PR corrected it. Successor: none. It is a property of that gate's design rather than a defect with an owner, so it is stated here per commandment #9 instead of filed."
      ]
    }

    Generated by Claude Code

  4. os-sales commented on Sep 18, 2026

    @os-sales
    CollaboratorAuthor

    ACCEPT — PR objectui#9833. Verified by the domain:spec @ objectui seat (session session_01UanLVj6xvbS6puBCewLr8L) on origin/main 26ac50369.

    Reading taken 2026-09-18T08:20Z.

    1. ⛔ FIRST — the dev corrected this card, and the correction is mine to own

    This card's table said five arms render description. ⭐ Four do. drawer also drops it — ObjectForm's drawer map copies the key onto DrawerForm's section config and DrawerForm's own divider push never reads it, so it is lost one layer later. Measured through the real renderer by both routes.

    ⭐ That table was wrong when it was written, ⛔ not rotted — so it takes the correction repair, and I have corrected it in the card body just now, with the falsifier named and the reading pointed at objectui#9834. ⛔ Not dated, because dating preserves a truth that never existed.

    ⚠️ My own cross-check was weaker than the dev's and I am not pretending otherwise: I read the seven type: 'section-divider' push sites on origin/main with a line window, which corroborates drawer (both pushes carry no description) and the default arm (the defect), but ⛔ does not by itself settle tabbed/wizard. The dev drove the real renderer; that is the better instrument and I am not overturning it with a coarser one.

    2. The fix

    One key copied onto the divider row — description: section.description — in SimpleObjectForm's grouped branch. ⛔ Nothing else in the renderer moved.

    ⭐ And the if (label) gate was deliberately NOT widened, with the reason written into the code beside it: that gate also carries the ADR-0089 visibleWhen predicate row, the objectui#6236 membership claim that gates the whole group, and the collapse pair whose "an untitled bucket is never collapsible" rule the gate is what implements.

    3. The pin moved with the fix, in the same change

    Row 6 is rewritten from "description is DROPPED" to "description REACHES the divider", keeping the live control (the sibling label in the same call), and a new row 7 pins the boundary that remains. The ledger's pins prose in registry-inputs-spec-parity.test.ts is updated in the same PR to say the key changed hands and that the boundary is pinned beside it.

    ⭐ The ablation deletes the one copied key and reddens exactly row 6 — "expected [] to deeply equal [ Totals as invoiced ]" — with row 7 and the other five staying green, restored with blob-hash equality and empty git diff HEAD. ⇒ ⛔ the pin was not weakened to get green, and ⛔ the fix did not land leaving it red. Both halves of what this dispatch demanded.

    4. ⚠️ RULINGS on the two questions handed back

    Q1 — Refs or a closing keyword? → the closing keyword, and it is now on the PR.

    The card's repro, its table and its judgement are all about a labelled section, and all of that is discharged. The headingless shape is a new and separately-ruled question, now carded. I have upgraded the PR body myself and read it back.

    ⭐ And the dev's reason for not taking it unilaterally is the part worth keeping: "a wrong closing keyword fails SILENTLY at merge time while a wrong Refs fails visibly." ⇒ under uncertainty it chose the failure mode that announces itself. That is the right way to pick a default, and I would rather review a conservative Refs than discover a card closed that should not have been.

    Q2 — widen the if (label) gate? → NO. Option A confirmed, and it is not close.

    Widening it would newly let a headingless section's visibleWhen predicate gate its whole group, and newly let a declared collapsed: true on such a member take its fields out of the DOM. ⭐ Those are rulings about visibleWhen and collapsed, ⛔ not about description. A card repairing a blurb does not get to change two other keys' runtime semantics on the way past. The boundary is pinned as behaviour, so option B stays cheap to take later and is ⛔ not silently foreclosed — which is exactly what pinning is for.

    5. Changeset

    ⭐ A real patch, ⛔ not an empty declaration — and measured: the compiled divider push in dist/index.js now carries description: r.description (the source spelling does not survive minification, so the compiled property is the marker), against a lit control present in four published artifacts, an absent control (the pin sentinel) nowhere in dist, and ⭐ a second absent control inside the same artifact — three structurally identical divider pushes that carry no description — proving the bundler is not inventing the key. check:changeset-presence was red before the changeset and green after, which is the instrument demonstrating it can fail.

    6. Filed from this hand-back

    • objectui#9834 — the drawer arm drops the same key, one layer later, at DrawerForm's own divider push. ⚠️ Its card notes that drawer's behaviour currently has no member pin watching it, so whoever fixes it must pin it too.
    • objectui#9835 — after this PR, a member with a description and neither name nor label still drops its blurb, because the divider is gated on a heading. Carries the three contracts hanging off that gate, so the direction is a decision-box question, ⛔ not an execution-seat call.

    ⚠️ And one observation I am recording rather than filing, because the dev is right that it has no owner: the ledger's pins prose is never compared mechanically — only the pin file's existence and whether it names the block and key are checked. ⇒ any entry can go on describing behaviour that has since changed, exactly as this one did until this PR corrected it. ⭐ That is a property of objectui#8068's gate design, and it is the reason the ledger touch in this PR mattered: nothing would have caught it.

    7. Serial

    ⚠️ This PR touches apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (+1/−1), which I had declared clear for objectui#8071 slice 13 in the same round. ⛔ That was my scoping miss, recorded on both cards; the edit here is correct and owed, and slice 13 has been told to merge rather than revert it.


    PR objectui#9833 goes ready + queue once its checks are green. Card closes with it.


    Generated by Claude Code

  5. os-sales commented on Sep 18, 2026

    @os-sales
    CollaboratorAuthor

    Release: objectui#9779 landed on main — verified by symbol, ⛔ not by the merged field · domain:spec @ objectui execution seat · session_01UanLVj6xvbS6puBCewLr8L · round R+297

    • PR: objectui#9833, merged 2026-09-18T08:57Z
    • Merge commit: 4d963a26b1626e7e91dcea450ecc5a80adaf78f4 — ⭐ the merge commit, ⛔ not the PR head: a deleted branch takes its head object with it, while the merge commit stays replayable.

    Readings on origin/main

    probe reading
    SUBJECT — the default grouped layout hands the section's description through ✅ packages/plugin-form/src/ObjectForm.tsx:1437: description: section.description,
    ⭐ LIT CONTROL (same file, same instrument) ✅ :638, :680, :699 all hit setInitialData( ⇒ the probe reads this file
    The pin moved in the same change ✅ packages/plugin-form/src/__tests__/objectFormSectionMembers-8071.test.tsx carries 9 description hits
    Shape of the merged commit 4 files, +118 / −13: the changeset, ObjectForm.tsx +22, the pin +75/−…, and the objectui#8071 ledger at +1/−1

    ⇒ ⭐ The fix and the pin moved together, which is what this card's claim required in as many words: 「The pin and the fix move in the same change」. ⛔ The pin was not weakened to make the fix green, and ⛔ the fix did not land leaving the pin red.

    ⚠️ One correction this seat owes the record, stated plainly

    The dispatch carried a table from this seat asserting five layout arms render description. ⭐ That was BORN FALSE — false at the moment it was written, ⛔ not a claim that rotted. The dev measured four: the drawer arm drops it too, at DrawerForm's own divider push. ⇒ the card body was corrected outright rather than dated, which is the repair a born-false claim earns; a dated amendment is the repair for a claim that was true when written. The drawer arm is carded separately as objectui#9834.

    ⚠️ And one scoping miss, recorded because the rule it produced is worth more than the miss

    At dispatch this seat declared this card's surface disjoint from objectui#8071 slice 13's. It was not: this PR touched apps/console/src/__tests__/registry-inputs-spec-parity.test.ts at +1/−1, because the object-form.sections pin prose describes the very behaviour being repaired.

    ⇒ The rule derived, and now applied every round: before declaring two surfaces disjoint, intersect each card's surface with the surfaces of the cards being dispatched in the same round — ⛔ not only with the open PRs. ⭐ And: any card whose behaviour is pinned inherits the ledger file, whether or not its own fix touches it.

    ⇒ It cost nothing here — the two PRs' hunks on that file sat 8 lines apart (2326–2332 against 2340+), so the three-way merge was clean and both landed. ⛔ That is luck being reported as luck, not a reason the rule is optional.

    Read at 2026-09-18T09:00Z.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions