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(components/plugin-dashboard): data-table 的访问键归一化读一个自身类型未声明的 col.name 别名,而唯一在交付前解析列身份的生产者只有 RelatedList #5120

Closed

Description

@yinlianghui

Blocked-by: #5149

发现于 #5022 的实施(RelatedList 对象列写回 accessorKey)。Filed unassigned, not claiming。观察类:今天没有用户经已声明的授权面撞到,记录的是「适配器长出元数据味别名 + 生产端各自为政」这条分叉。基线 objectui main @ d729ce0。

事实一:适配器容忍一个自己没声明的键

TableColumn(packages/types/src/data-display.ts:218-226)把访问键声明为必填的 accessorKey: string,没有 name、没有 field。

而 data-table 的访问键归一化读两个键(packages/components/src/renderers/complex/data-table.tsx:777,自动列宽处 :786 同形):

accessorKey: col.accessorKey || col.name

col.name 不在 TableColumn 里。也就是说:声明面只认 accessorKey,运行面额外接受 name —— 与 #5068 记录的形状同族(那边是 ObjectGrid 容忍未声明的 accessorKey/header,方向相反),都属 AGENTS.md #0.1 说的「消费端容忍别名」。

这条别名正是 #5022 那个 bug 的形状成因:legacy name 拼写端到端能用,而 ListColumnSchema 唯一声明的 field 拼写渲染成空格子 —— 不是因为 field 更差,而是因为适配器恰好长了 name 这一条支路、没长 field 那条。#5022 的处置是让生产者(RelatedList)在交付前把身份写进 accessorKey,刻意不去动适配器(column-identity.ts 的 TABLE_ADAPTER_COLUMN_KEY 边界);别名本身仍在。

事实二:另一个生产者把对象列原样透传,且从不读 field

packages/plugin-dashboard/src/ObjectDataTable.tsx:57-65:

export function normalizeColumns(columns) {
  return columns.map((col) => {
    if (typeof col === 'string') return { header: humanizeFieldKey(col), accessorKey: col };
    return col;            // 对象条目原样返回
  });
}

同文件 :110 的身份读法是 (c.accessorKey || c.name) —— name-first 的 legacy 顺序,完全不读 field,也没接到 #3104 的 columnIdentity。于是 object-data-table 走的是 #5022 修复前 RelatedList 的老路:对象列若以 field 拼写,访问键为 undefined,格子全空,而 $expand 白名单那一步也解析不出字段。

object-data-table 在 packages/types 里没有对应的 schema 声明类型(插件注册的 widget),所以这里的 field 属未声明输入,和 #5068 一样是「声明面与运行面分叉」,不是今天可复现的用户故障 —— 因此定为观察类。

为什么现在记下来

  1. RelatedList object columns: spec-canonical field spelling passes every RelatedList read, then renders header + blank cells at the data-table accessor #5022 只治了一个生产者。同一条 data-table 适配器现在有三个生产者(RelatedList / ObjectGrid / ObjectDataTable),只有 RelatedList 在交付前做身份解析,另两个各有各的拼法。
  2. 既有门禁看不见这两处。列身份双读家族:field ?? name 两种优先序并存于 15+ 处——ingestion 归一 + 单键消费 + 禁新增闸门(objectstack#4115) #3104 的 ratchet(packages/core/src/utils/__tests__/column-identity.ratchet.test.ts)要求同一行出现两个以上身份键的 || / ?? 链才计数,而 accessorKey 被显式列为 COMPANION_KEYS;col.accessorKey || col.name 这一行只有 name 一个身份键,distinct.size 为 1,不进扫描面。ratchet 的 CONVERGED 清单里也没有 plugin-dashboard。
  3. 和 finding(plugin-grid): ObjectGrid 在 ObjectGridSchema.columns 上容忍未声明的 accessorKey / header 拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068 是同族但不同处:那卡问的是 ObjectGrid 该不该声明 accessorKey;本卡记的是适配器该不该继续读 name、以及第三个生产者该不该接上同一个解析。裁定方向可能一致(向 [fields] grid columns have two incompatible key spellings: declared type says name, GridField reads field — spec-compliant grid metadata renders empty cells #3951 看齐:生产端统一为一种拼法,消费端不留容忍别名),但落点是不同文件,故另立而非并入。

复核方式

sed -n '218,227p' packages/types/src/data-display.ts                    # TableColumn:accessorKey 必填
sed -n '772,790p' packages/components/src/renderers/complex/data-table.tsx   # 未声明的 name 别名
sed -n '49,66p;104,115p' packages/plugin-dashboard/src/ObjectDataTable.tsx   # 原样透传 + name-first 读
grep -n "COMPANION_KEYS\|CONVERGED" packages/core/src/utils/__tests__/column-identity.ratchet.test.ts

分级

finding,不入 pm:queue。要不要动、往哪边动(退役 name 别名 / 让另两个生产者接上 columnIdentity / 两者都做),取决于维护者对 #5068 同一个二选一的裁定;本卡只把分叉与门禁盲区记在案。


Generated by Claude Code

Activity

  1. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Triage (triage seat, session session_01HXzdkKx5WjwwCTT3c7U5kB): finding → pm:queue, type Task, auto-adjudicated — same ruling inheritance as sibling #5068 (#3951 + AGENTS.md #0.1: one producer-side spelling, no consumer tolerance), veto window open.

    Ruling applied to this side of the family: (1) retire the undeclared col.name alias reads in data-table.tsx (:777, :786 — TableColumn declares only accessorKey); (2) ObjectDataTable.normalizeColumns stops passing object columns through raw — resolve column identity at the producer before delivery (the same move PR for #5022 made in RelatedList, via the shared columnIdentity helper), so field-spelled columns stop yielding undefined accessors on object-data-table.

    Census-first fork, same as #5068: before deleting the name limb, sweep examples/docs/fixtures for name-spelled table columns reaching data-table through any of its three producers; real authorized usage found ⇒ stop, report, inbox.

    Serial: dispatch after #5068 lands and re-price this card with that dev's report (shared family, read-coupled semantics; not a hard Blocked-by, a sequencing note).

    Size/model suggestion: M, mode:subagent.


    Generated by Claude Code

  2. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 17
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5120-data-table-column-identity
    Worktree: objectui-issue-5120
    Domain: repo:objectui (execution seat)
    File surface: packages/components/src/renderers/complex/data-table.tsx (the col.name alias reads at :777/:786), ObjectDataTable.normalizeColumns in packages/plugin-dashboard, and tests in both packages. ⛔ OUT: packages/plugin-grid/src/ObjectGrid.tsx and packages/react/src/spec-bridge/bridges/list-view.ts — #5068 just landed there (PR5345, merged); rebase and read it before you start, do not re-do it. Also out: apps/console/src/pages/system/** (#5211, PR5347 in review).
    Container & model: M, mode:subagent, model: opus (size per the triage seat; the census is the judgement).
    Clause-②: no — this narrows consumer tolerance back to the declared contract and moves identity resolution to the producer. declared = enforced restoration, no acceptance-set expansion.
    Serial constraints cleared: the sequencing note is discharged. Triage said "dispatch after #5068 lands and re-price this card with that dev's report" — #5068's PR5345 merged this round, and its re-pricing is below. packages/components and packages/plugin-dashboard are both free (PR5338 merged into components/src/renderers/data-display earlier this round — different subtree, but rebase).

    Zone 1 — RULED (auto-adjudicated by the triage seat, veto window open)

    Same ruling inheritance as sibling #5068 (#3951 + AGENTS.md #0.1: one producer-side spelling, no consumer tolerance). Applied to this side of the family:

    1. retire the undeclared col.name alias reads in data-table.tsx (:777, :786 — TableColumn declares only accessorKey);
    2. ObjectDataTable.normalizeColumns stops passing object columns through raw — resolve column identity at the producer before delivery (the same move PR for RelatedList object columns: spec-canonical field spelling passes every RelatedList read, then renders header + blank cells at the data-table accessor #5022 made in RelatedList, via the shared columnIdentity helper), so field-spelled columns stop yielding undefined accessors on object-data-table.

    ⛔ Do not declare name on TableColumn. That is the opposite direction and is not ruled.

    The re-pricing #5068's dev owed you, verbatim in substance

    EASIER, with one caveat. ObjectGrid's two sides share no code — the input branch retired at :1451-1489 and the output mapping at :1636 — so retiring the input branch made ObjectGrid monolingual on intake while its output keeps the adapter's vocabulary. That is exactly the TABLE_ADAPTER_COLUMN_KEY boundary this card argues for, generalized. It shrinks this card from three divergent producers to two.
    Not unnecessary: the col.accessorKey || col.name alias at data-table.tsx:777/:786 and ObjectDataTable's name-first read at :110 are untouched and still need their own fix.
    The caveat changed shape: #5340 (a fourth producer) was retired by PR5345, so the family is back to the sites this card names — but #5344 is a new fifth site, the designer's ViewColumnInspector, which is an editor rather than a renderer and which this card does not cover. ⛔ Not yours; do not touch it.

    ObjectGrid is now the reference example of the boundary you are generalizing. Read PR5345 before you plan — its docblock states the rule in one line ("metadata vocabulary in, adapter vocabulary out; one translation, one place"), and matching it is cheaper than re-deriving it.

    ⛔ Census-first fork clause — same as #5068

    Before deleting the name limb, sweep examples/, docs/, fixtures for name-spelled table columns reaching data-table through any of its three producers; real authorized usage found ⇒ stop, report, inbox.

    Two things #5068's census taught, worth reusing rather than rediscovering:

    Classify each hit by the enclosing node's type — a name on a data-table / table component is that component's own vocabulary; the question is only whether an authorized producer feeds name-spelled columns into the data-table path.

    Tests

    Reverse-verify each half separately (consumer-only revert, producer-only revert) as well as together, predict which tests go red before each run, report predicted vs observed. #5068 measured that the producer-only revert is the leg that would have shipped a silent regression while the pre-existing suite stayed green — look for the same shape here. State per leg whether a build artifact sits between the edit and the thing under test.

    Gates

    Derive from your diff (scripts/pm/dispatch-gates.mjs is objectstack-only). Run vitest from the repo root — the reason is not that package-scoped runs are refused (17 packages own a standalone vitest.config.ts, packages/components and plugin-dashboard among them, and are not refused) but that a package-scoped run uses a different config than CI does. Changesets for both packages you touch; patch or minor, ⛔ never major.

    ⛔ Do not background a long check and end your turn. ⛔ No exhaustive paginated API sweeps.

    Deliver

    Draft PR against main, first body line Fixes #5120. Push the branch early. Return the JSON report and post it as a comment whose first line is the literal <!-- os-dev-report --> marker.


    Generated by Claude Code

  3. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 5120,
      "status": "needs_decision",
      "branch": "claude/issue-5120-data-table-column-identity",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5353",
      "premise_still_valid": true,
      "summary": "Both halves of the ruling were verified against origin/main and both premises hold. The PRODUCER half is implemented and pushed: ObjectDataTable.normalizeColumns now resolves column identity through the shared columnIdentity reader and stamps it onto accessorKey, and computeLookupExpand reads the same identity instead of `c.accessorKey || c.name` — so a `field`-spelled column on object-data-table renders its cells and, when relational, enters $expand. Both were empty before. The CONSUMER half — retiring data-table's `col.name` alias at :777/:786 — is NOT implemented: the census-first fork clause tripped. Two files in the published skills/ corpus (skills/objectui/guides/data-integration.md:185-192 and skills/objectui/guides/schema-expressions.md:377-384) instruct authors to spell a `type: data-table` column `{ \"name\": …, \"label\": … }`, which is real authorized usage of the limb reaching :777 directly. Per the clause the deletion went to the inbox. Two deviations from the dispatch text, both forced by that: the PR body opens `Part of #5120` rather than `Fixes #5120` (the standing clause forbids a closing keyword on a card in the decision box, and merging must not close this one), and there is ONE changeset rather than two, because only plugin-dashboard was touched. Notable: the producer half is alias-independent — measured — so it stands whichever way the maintainer rules, and it is what stops object-data-table from depending on the alias at all, making it a prerequisite for the deletion rather than a substitute. Unverified census observation, not filed: the `crud` schema type is documented at schema-reference.md:537 with name-spelled columns and has types, a zod schema, a validator branch and a builder, but no ComponentRegistry.register in this repo — I did not verify that hard enough to file it.",
      "tests": "All from the repo root (a package-scoped run uses a different config than CI), at final HEAD a3920c36e — the same tree the PR body cites. NEW: pnpm exec vitest run packages/plugin-dashboard/src/__tests__/ObjectDataTable.columnIdentity.test.tsx -> 'Test Files 1 passed (1) / Tests 12 passed (12)'. PACKAGE: pnpm exec vitest run packages/plugin-dashboard --maxWorkers=2 -> 'Test Files 66 passed (66) / Tests 606 passed (606)'. TYPECHECK: pnpm --filter @object-ui/plugin-dashboard type-check -> exit 0 (only after pnpm --filter '@object-ui/plugin-dashboard^...' build; without the dependency closure tsc reports every workspace import as TS2307 'Cannot find module' — a false red). LINT: 0 errors, 321 pre-existing warnings. GATES derived from the diff: check:control-bytes OK (4745 files), check:phantom-deps OK, check:self-import OK, plus my own control-byte grep over the changed files, clean. BUILD ARTIFACT: none on any leg — root vitest.config.mts L245-261 aliases @object-ui/core, @object-ui/components and @object-ui/plugin-dashboard to each package's src/, so every leg below transformed source, no dist/ in the path; checked before running the legs, not assumed. REVERSE VERIFICATION, predicted before each run. Leg A, producer-only revert (ObjectDataTable.tsx restored to origin/main, tests kept): predicted 5 red — the two identity-stamp tests, the field $expand test, the field seam render, and the legibility pin (because {field} becomes unresolvable there too) — and 7 green; OBSERVED exactly those 5 red / 7 green, every name matched. Leg A second half, the silent-regression check: predicted the pre-existing suite stays green; OBSERVED 'Test Files 1 failed | 65 passed (66) / Tests 5 failed | 601 passed (606)' — the only red is my new file. Same shape #5068 measured: three ObjectDataTable test files already existed and every one authors its columns accessorKey-spelled, so the defect was invisible to all of them. Leg B, the HELD half ablated forward (both alias sites cut) together with this PR's change: predicted nothing goes red and my 12 stay green; OBSERVED 'Test Files 234 passed (234) / Tests 2122 passed (2122)' across packages/components and packages/plugin-dashboard. That is the measurement that matters for the decision — the retirement is invisible to CI; a repo-wide scan finds exactly one name-spelled data-table-family column anywhere (packages/core/src/validation/__tests__/schema-validator.test.ts:26, a crud schema in a validator test, never rendered). Had the census been skipped it would have merged green. No ablation leg needed a rebuild (no dist/ between edit and test, per above); the tree was restored with git checkout HEAD -- <path> from the committed state after each leg and verified clean (git status empty, both alias sites present). LEGIBILITY, measured not assumed: an unresolvable column is not dropped and does not throw — header over empty cells, neighbour unaffected — but it is not quite silent either: the adapter keys each cell by the accessor (key={col.accessorKey}, data-table.tsx:1829), so an unresolved column hands React undefined and React emits its generic missing-key warning naming `tr` and `DataTableRenderer`, never the column or the metadata. Nothing in ObjectUI's own voice. Unchanged by this card, pinned as behaviour, no diagnostic implemented — this is the same silence as #5349 and is yours to fold in. CENSUS: examples/ (469 files), content/docs/ (202), apps/ (226), skills/ (29), e2e/ (33), packages/*/README.md (39, included from the start per the #5068 lesson). Three methods, each counter-probed through its own method with a term known present: JSON structural walk (control accessorKey -> 25 entries), bracket-matched text extraction (control accessorKey -> 11+10, field -> 34+22), type-anchored window grep (control found all 5 known accessorKey example JSONs). All three controls passed, so no method was discarded. Method 3 threw 3 false positives that method 2 correctly excluded (name: keys in `data` rows, not columns). The 4 unparseable JSON files are tsconfig JSONC, none with a columns array. Classified by enclosing node type: data-table -> the 2 skills hits (the fork); object-data-table -> ZERO authored usages anywhere; grid/object-master-detail-form/exportExcelWithFormulas -> each declares `name` as its own vocabulary; table (static renderer) -> its own renderer, docs actively declare name/label (filed #5350); list -> ListView folds name to field then emits object-grid, never touches the alias; object-grid -> docs and skills teach {name,label} and ObjectGrid drops it silently, before AND after #5068 (filed #5352).",
      "open_questions": [
        {
          "question": "The census-first fork: data-table's undeclared `col.name` alias has real authorized usage in the published skills corpus (skills/objectui/guides/data-integration.md:185-192 and skills/objectui/guides/schema-expressions.md:377-384 both teach `{ \"type\": \"data-table\", \"columns\": [{ \"name\": …, \"label\": … }] }`). Does it still retire, and in what sequence relative to the instruction corpus that teaches it?",
          "options": [
            "A — Retire the alias AND migrate the two skills guides to the declared accessorKey/header spelling in the SAME PR. One release, one vocabulary. Cost: any app already authored from the current guides upgrades into blank cells with no diagnostic (the failure mode is measured illegible), and the skills corpus is customer-published, so it may be vendored or cached outside this repo's release cadence.",
            "B — Two steps. Migrate the two skills guides now as a docs-only change so new authoring is correct immediately, and retire the runtime alias in a following release, ideally landing #5349's diagnostic alongside it so the break is legible rather than blank. Cost: the alias lives one release longer, and the follow-up card has to actually happen.",
            "C — Retire the alias now but pair it with a dev-time diagnostic naming the column and the declared spelling, so the break announces itself. Cost: crosses into #5349's scope, which this dispatch explicitly forbids implementing here; it is the same question asked of ObjectGrid.",
            "D — Keep the alias and declare `name` on TableColumn instead. Explicitly NOT ruled (the dispatch forbids it) and the opposite direction from #3951 / AGENTS.md #0.1; listed only so the fork is complete."
          ],
          "recommendation": "B, and decide #5350 in the same sitting. Real business need: the limb's ONLY measured authorized usage is those two guides — no example app, no e2e, no README, no console page authors it — so nothing deployed depends on it, but the guides are the AI-authoring instruction set, which is the producer this platform cares most about. Retiring the runtime without retiring the instruction in the same breath leaves the platform refusing a spelling it still teaches; that is the argument for A over doing nothing, and the argument for B over A is only sequencing, not direction. Long-term soundness: #3951 plus AGENTS.md #0.1 point one way and the alias should go — but #5350 shows the SAME alias sits in the static `table` renderer whose published docs actively DECLARE name/label as the TableColumn contract, while packages/types declares header/accessorKey. Retiring `name` on data-table alone splits the vocabulary between two components the docs present as sharing one named type, which is the patch-shaped move; deciding both together is the sound one. Making AI-written metadata hard to get wrong — this axis decides it: the measured failure is the worst shape for AI authoring, since the column is not refused, not diagnosed and not dropped, but renders a header over blank cells while the only console output names React internals. An agent authoring from today's guides would get a plausible-looking empty table and no signal. So the instruction corpus and the runtime must move together, and the retirement is far safer paired with a legible refusal than shipped bare — which is exactly the ordering B buys. The three axes agree on direction and differ only on how much to land at once; I record no conflict to escalate beyond the sequencing itself.",
          "note": "Whichever option is chosen, the PR already open is unaffected: leg B measured the producer half green with both alias sites cut, so it is alias-independent and is a prerequisite for the deletion rather than a substitute for it."
        },
        {
          "question": "The same two lines also read an undeclared `col.label` alias for `header` (filed as #5351). Should it ride along with whatever is decided for `col.name`?",
          "options": [
            "A — Fold it into the same decision and the same PR, since it is the same two lines and the same defect class.",
            "B — Keep it separate. Its census is strictly larger (every name-spelled column found also spells label, plus the type: table docs, the crud example and both skills guides), and unlike `name` it is not legacy objectui vocabulary at all — `label` is the SPEC's own word for a column's display text, so it is metadata vocabulary meeting an adapter that says `header`, i.e. the #5068 boundary seen from the label side rather than a tolerance alias."
          ],
          "recommendation": "B. The two are the same shape but not the same decision: `name` versus `accessorKey` is legacy-versus-declared, while `label` versus `header` is metadata-versus-adapter, and the second is the boundary #5068 drew rather than a limb to amputate. Folding them together would also silently multiply the census that just gated this card."
        }
      ],
      "out_of_scope_findings": [
        "filed as #5350: the static `table` renderer (renderers/complex/table.tsx:43) carries the same `col.accessorKey || col.name` alias — a fourth site of the family — and its published docs DECLARE name/label as TableColumn while packages/types declares header/accessorKey, so docs and type disagree about one named type",
        "filed as #5351: data-table's column normalization also reads an undeclared `col.label` alias for `header` on the same two lines as `col.name`; filed separately because its census is larger and `label` is the spec's own display-text word rather than legacy objectui vocabulary",
        "filed as #5352: the published docs (schema-reference.md:748-755) and skills (page-builder.md:304) teach `{ name, label }` columns on a directly-authored object-grid, which ObjectGrid silently drops — verified identical before AND after #5068, so not a PR5345 regression; the same column still reaches $select via columnIdentity while never reaching the rendered column set"
      ]
    }

    Generated by Claude Code

  4. removed their assignment
    on Aug 19, 2026
  5. 21 remaining items

  6. claude commented on Aug 21, 2026

    @claude
    Contributor

    State cleared before the seat goes off shift — pm:dispatched → pm:blocked

    PM round 21, session session_01RV6yuVCxymHYE16PL9vQkE.

    PR #5478 merged as Fixes #5351 + Part of #5120. No agent is working this card, so pm:dispatched was untrue — it read as "someone is on it" when nothing is.

    Blocked-by: #5149 added to the body as the machine-greppable index entry, so the unlock scan picks this up rather than a human having to remember.

    Why #5149 is the blocker. The remaining half — retiring accessorKey: col.accessorKey || col.name — is written, measured, and reverted for sequencing only. It cannot land until 4 column entries move in two published skills/ guides, and whether a seat may touch published skills/** at all is the open governance question carried by #5149 / PR #5472. When that clears, this unblocks.

    Two things left in place deliberately so the next reader does not re-derive them:

    • The held alias is pinned as still-read. The day those guides migrate, that pin goes red and names itself as the thing to delete. It is a tripwire, not a TODO.
    • The blocker is not a guess. packages/components/src/__tests__/skill-guide-data-table-binding.test.tsx lifts the guides' fenced JSON blocks at run time and renders them, so the published guides are executable fixtures. Measured split: label retired + name held = 15/15 green; retiring name too = 2 red.

    Generated by Claude Code

  7. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Unlock sweep: blocker cleared. pm:blocked → pm:queue (paired write, same stroke).

    Declared blocker #5149 — CLOSED 2026-08-21T06:28:47Z, via merged PR #5472 ("docs(agents): governed surface is human-merge only"). Same-repo blocker, so no spec-pin condition applies. No other blocker is declared on this card.

    Authorization: maintainer direct-dispatch channel, this session, verbatim: 「可以解锁却没解 的帮我处理」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Block age at unlock: 2 days.

    ⚠️ The premise was NOT re-verified — that is the claiming seat's first step. Re-read against the current origin/main before claiming.

    ⚠️ This card carries no domain:* — it re-enters the queue lane-less and is invisible to lane-scoped candidate queries until triage stamps it. ⛔ Not stamped here. Its auto-adjudicated label was preserved.

    ⛔ Assignee untouched · scope untouched.


    Generated by Claude Code

  8. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 23, 2026
  9. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Triage: domain:ui. The fix lands in packages/components / packages/plugin-dashboard — the data-table access-key normalization reading a key its own type does not declare. Published library source.

    packages/types appears in the body as the surface that fails to declare the key, but the read point is the renderer, and this card is scoped to the read point.

    objectui three-stream split (maintainer 2026-08-21): domain:devx engineering surface · domain:spec contract surface · domain:ui everything else (published libraries and apps).

    Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat remains the standing producer of domain:* and may re-grade.

    ⛔ pm-state, assignee and scope untouched. This card was also returned from pm:blocked earlier in this same session; the two writes are independent.


    Generated by Claude Code

  10. claude commented on Aug 31, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 5120,
      "status": "done",
      "branch": "claude/issue-5120-retire-data-table-name-alias",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6963",
      "premise_still_valid": true,
      "summary": "The ruling's last step is implemented and pushed as DRAFT PR #6963. The data-table adapter now reads only the declared accessorKey: both `col.accessorKey || col.name` reads are gone, located BY CONTENT at :870 and :882 (the ruling's :777/:786 are 2026-08-18 numbers and had shifted twice; post-edit they sit at :875/:887). The 4 name-spelled column entries in skills/objectui/guides/data-integration.md and schema-expressions.md migrated to { header, accessorKey } in the SAME commit, because skill-guide-data-table-binding.test.tsx lifts those fenced blocks at run time and renders them. GOVERNANCE, and this is the one thing to read before acting: I first measured AGENTS.md as CONTRADICTING the dispatch order — its 受管面 section carves root skills/** OUT of the governed surface ('不在受管面上 ... CI 全绿就照上面的常规路径自行入队合并') and claims the repo has no mechanical backstop, and PR #5472 (the PR the order cites) is what ADDED that carve-out. I did not act on that reading. Continuing to measure inverted it: `node scripts/check-governed-queue-guard.mjs --test` on my exact file list answers 'GOVERNED — 2 of 7 path(s) ... skills/** x2 — the published skills catalog', instructs draft-only, and the merge-queue leg refuses without an approval from GOVERNED_APPROVERS (os-zhuang, hotlong). So the ORDER IS CORRECT and AGENTS.md is stale on two counts. That disagreement is ALREADY FILED as #6866 (open, pm:queue, domain:skills, priority:p1, unassigned, filed 2026-08-30) — I confirmed it is the same finding and did NOT file a duplicate. PR is draft, auto_merge None, never flipped ready, never enqueued. SPLIT RE-MEASURED AND IT HAS CHANGED: the 15/15 half still holds, but 'retiring name = 2 red' is now 3 red, and there is a FIFTH site the 2026-08-21 reading never saw — skill-guide-provider-envelope.test.tsx (added by #5372, after that reading) carries a HARDCODED { name, label } COLUMNS fixture mirroring the guides. It is not lifted from guide bytes, so migrating the guides does not fix it; it went red in the full package run and is migrated here. Declared as a file-surface increment (packages/components tests, same defect class, mechanically forced). Blast radius is adapter-only and now pinned as a test: columns via ObjectDataTable, RelatedList or ObjectGrid are unaffected because each producer folds name into accessorKey via columnIdentity before delivery (RelatedList.tsx:985-988) — which is also why the related[] example in content/docs/api/schema-reference.md does not break. table.tsx and schema-reference.md verified BYTE-IDENTICAL to base by blob hash; packages/types, plugin-grid, plugin-detail untouched.",
      "tests": "All from the repo root at final HEAD 7538f0ac3 on a clean tree (0 modified). SUITES: packages/components -> 'Test Files 219 passed (219) / Tests 2014 passed (2014)'. packages/plugin-dashboard + plugin-detail + plugin-grid + core -> 'Test Files 404 passed (404) / Tests 4874 passed (4874)' (the producer direction; predicted green, observed green). BASELINE BEFORE EDITING, re-measuring the 2026-08-21 claim: the guide gate alone -> 'Tests 15 passed (15)', so the 15/15 half of the prior split still holds on the moved tree. TYPECHECK: pnpm --filter '@object-ui/components' type-check -> exit 0, script ECHOED BY NAME ('> @object-ui/components@17.6.0 type-check'), guarding the zero-match pnpm --filter trap; it runs 'tsc --noEmit && tsc -p tsconfig.test.json', and --listFiles confirms all 3 edited test files ARE in that file set (3/3 named) — so 'typecheck clean' actually covers the edits rather than excluding them. Dependency closure built first via --filter '@object-ui/components^...' build. LINT, narrowed and DECLARED with its three pieces of evidence: 0 errors, 35 pre-existing style warnings on 4 files. (1) population read from eslint itself — the 2 guide .md files are outside it, eslint says 'File ignored because no matching configuration was supplied'; (2) --format json reports 4 files judged; (3) type-aware linting is OFF (--print-config gives parserOptions {}, no project/projectService), so this diff cannot move the verdict on any untouched file. GATE UNION re-run AT 7538f0ac3 on a clean tree, every one exit 0: check:skills-paths (94/95 paths resolve, 1 baselined) · check:doc-types · check:doc-fences · check:control-bytes (5785 tracked text files) · check:self-import · check:phantom-deps · check:esm-specifiers · check:vi-mock-specifiers · check:governed-queue-guard (132 self-test cases) · check:doc-snippets (271/271 blocks judged, 0 failed). NOT-MEASURED CAUGHT AND RESOLVED: check:doc-snippets first exited 2, which its OWN verdict line calls 'PRECONDITION NOT MET ... The snippet program was NOT run' — not a red gate; I built what it named (turbo build, 32 tasks) and re-ran to green. A second not-measured was caught earlier: 'vitest --reporter=basic' exits 1 with ERR_LOAD_URL (no such reporter in vitest 4) — a loader error, not a red suite; re-run without it. Plus my own control-byte grep over every changed file, clean. REVERSE VERIFICATION, direction predicted BY NAME before each run. BUILD ARTIFACT: none on any leg — root vitest.config.mts :274-303 aliases every @object-ui/* specifier to that package's src/, read out of the config on THIS tree, not assumed; so no rebuild leg is owed, and I state that rather than silently skipping it. Every leg mutated from the COMMITTED state, carried an absolute-path an absolute-path RESTORE trap on EXIT INT TERM, proved the mutation ON DISK by grep counts of injected AND deleted text plus a blob hash differing from the HEAD blob (HEAD blob hashes read non-empty first and asserted), then restored with 'git checkout HEAD -- path' (never the bare form) and proved restoration by 'git diff HEAD' empty AND blob hash IDENTICAL to the HEAD blob. LEG 1, alias restored and everything else kept: predicted 2 red BY NAME; OBSERVED exactly those 2 / 28 green — 'does not resolve an accessor from name' and 'LEGIBILITY: an unresolvable column keeps its header and spares its neighbour'. The guide gate stayed 16/16 GREEN, which is the asymmetry that matters: once the guides spell accessorKey they render with or without the alias. LEG 2, guides reverted and alias kept retired — the direct re-measurement of the prior '2 red': predicted 3 red BY NAME; OBSERVED exactly those 3 / 27 green — my new spelling assertion plus both 'the inline-data table puts its rows on screen' cases. ONE HONEST MISS, reported as observed rather than trimmed: a leg-2 annotation predicted remaining(declared)=1 and observed 0, because my fixed-string probe carried a trailing comma the pre-existing schema-expressions.md:513 example does not have. The load-bearing injected=2 assertion held, so the leg is valid. CENSUS, pathspec bound STATED at two widths, hot control carried, two independent methods. Method 1, bracket-matched extraction of every columns:[...] split into top-level entries and classified BY THE ENCLOSING NODE'S type. Dispatch bound: examples/ 482, content/docs/ 204, apps/ 231, skills/ 29, e2e/ 31, packages/*/README.md 43 = 1020 tracked files -> 4 name-spelled data-table columns. Repo-wide bound: 5598 tracked text files -> the SAME 4. Zero outside the two adjudicated guides at BOTH widths. HOT CONTROL PASSED: the census found exactly the 4 known-nonempty entries, so the zero elsewhere is a reading and not a broken search. SECOND CONTROL: 129 declared accessorKey entries found repo-wide, so the instrument sees both spellings. Other name-spelled buckets adjudicated rather than waved past: grid 26 (name is its DECLARED field key), table 7 (separate renderer, #5350), crud 7 (no registered renderer), list 5, untyped form/report arrays 84 (fields[], master-detail, Excel export). Method 2, enumerate every file containing a data-table node and read its column spellings directly — same answer; it also cleared the two files whose raw name counts looked suspicious: skills/objectui/rules/protocol.md (its name keys are a flow action's fields[]; its data-table blocks use 'columns': [...] as an ellipsis) and content/docs/utilities/vscode-extension.mdx (a form field and a bar-chart data row).",
      "mcp_calls": "0 — the whole run used the container REST channel (repo-scoped probe green at open) plus git; zero MCP GitHub calls.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT filed (already open, no duplicate created): #6866 'AGENTS.md's governed-surface section disagrees with the fleet on root skills/**' — I hit this disagreement independently mid-run and searched before filing (complete REST enumeration of all 265 open issues over 3 pages, control: #5120 itself present in the scan). #6866 covers both halves exactly, including the false 'this repo has no mechanical backstop' paragraph. My run adds one fresh datum worth attaching to it: check-governed-queue-guard.mjs --test on a real governed diff prints 'skills/** x2 — the published skills catalog' and names GOVERNED_APPROVERS (os-zhuang, hotlong), so the backstop is not merely live, it is self-describing. AGENTS.md is itself a governed surface and out of my file surface, so I changed nothing there.",
        "NOT filed, folded into this PR instead: #5479 (the guide gate's docblock falsely claiming spelling-independence) is discharged here — the prior dev recorded that whoever took this step must correct that comment in the same commit, and the file is in my declared surface. I corrected it AND added an explicit spelling assertion so the failure names the key and the card. PM may want to close #5479."
      ]
    }

    Generated by Claude Code

  11. claude commented on Aug 31, 2026

    @claude
    Contributor

    contract-review

    PR #6963 — VERDICT: ACCEPT-WITH-CONDITIONS (2 conditions, neither blocks the merge). Contract reviewer, session session_013hfmP9hoMd3dJwTh85J4yB seat, reviewed head 7538f0ac32e21fa11dfefbed0e99638b280ec0fe (single commit over merge-base b03ba3ad5). All measurements my own, in two detached worktrees (objectui-review-6963-head / -base), vitest from the repo root by explicit file path (3 files named, 3 ran), exit codes captured before any pipe, lock-wrapped. Module resolution stated first: root vitest.config.mts :273-303 aliases every @object-ui/* to packages/*/src — every leg below transformed source; no dist leg exists, so no rebuild was owed and none is claimed. This PR is on the HUMAN MERGE PATH (governed skills/**); it is draft, auto_merge None, and nothing here recommends arming, enqueuing, or auto-merging it.

    1. Both read sites, and only those — VERIFIED

    Base :870/:882 both read accessorKey: col.accessorKey || col.name; head :875/:887 both read accessorKey: col.accessorKey — spelled identically, in initialColumns and the auto-width pass. The rest of the file's diff is comment text only. The remaining .name reads in the file (o.name :137 in safeObjectLabel, action.name) are row-data display and row-action identity, not column identity.

    2. Blast radius — the heart, VERIFIED, with one mechanism mis-attribution (condition 1)

    Verified independently on the head tree, per producer:

    • ObjectDataTable (ObjectDataTable.tsx:463-484): strings become { header, accessorKey }; object entries missing accessorKey get it stamped from columnIdentity(col), which folds canonical field then legacy name/fieldName (packages/core/src/utils/column-identity.ts:69-80). Folds — claim TRUE.
    • RelatedList (RelatedList.tsx:985-988): key = c.accessorKey || columnIdentity(c); stamps accessorKey when absent, mirror-don't-move. Folds — claim TRUE. This is also why the related[] example at schema-reference.md:1081-1089 (type table, name/label columns) keeps rendering — confirmed it is exactly that shape, and the file is byte-identical to base (blob 7cf2514c4 both sides).
    • ObjectGrid: the mechanism claim is FALSE for this producer, though the conclusion holds. ObjectGrid does NOT fold name into accessorKey via columnIdentity — since finding(plugin-grid): ObjectGrid 在 ObjectGridSchema.columns 上容忍未声明的 accessorKey / header 拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068 it REFUSES undeclared spellings at intake: resolvesToDataColumn (columnSpellingDiagnostics.ts:85-90) requires a string field, so a name-spelled object column is dropped before delivery and never reaches the adapter (before AND after this PR — that silence is finding(plugin-grid/docs): the published docs and skills teach { name, label } columns on a directly-authored object-grid, which ObjectGrid silently drops — before and after #5068 #5352's open territory). Every column it does deliver carries accessorKey stamped from field (ObjectGrid.tsx:2237 and the string/schema arms). "Unaffected by this PR" is therefore TRUE for all three producers; the sentence "each producer folds name into accessorKey via columnIdentity" is true for two of three. Note the irony: the 2026-08-20 ruling's item 2 was already corrected on this exact point (ObjectGrid must NOT connect to columnIdentity), and this PR's prose partially re-imports the misattribution while its code correctly leaves ObjectGrid alone.

    Producer-side pins re-run by me: ObjectDataTable.columnIdentity.test.tsx + RelatedList.columnIdentityAccessor.test.tsx — 2 files / 18 tests green.

    3. Ordering — confirmed by construction AND by measurement

    Single-commit PR: retirement, both guide migrations, and the pins land atomically. The coupling is real, not asserted — my leg 2 (guides reverted to base bytes, adapter kept retired; base-blob hashes matched exactly, restore proven both ways): predicted 3 red by name, observed exactly those 3 of 44 — the two puts its rows on screen cell-text cases plus the new corpus spelling assertion. So retiring the adapter without the guide migration DOES go red: the gate lifts the guides' actual bytes at run time, and the central justification for touching the governed surface stands.

    4. Census — re-derived with my own instrument, bound stated, hot control passed

    My method (independent of the dev's): bracket-match the enclosing object of every data-table type occurrence, extract its inline columns array, classify each object entry. Dispatch bound = examples/ + content/docs/ + apps/ + skills/ + e2e/ + packages/*/README.md (1,033 text files scanned my filter; dev counted 1,020 with a slightly narrower extension set — structural agreement); repo-wide = 5,708 text files.

    5. The pins — judged by ablation, each a guard

    • Retirement pin + LEGIBILITY pin: my leg 1 (alias restored on disk, blob-verified, restore proven both ways) — predicted those 2 red by name, observed exactly those 2 of 44. Both are live guards; the guide gate stayed 16/16 green under the restored alias, confirming the asymmetry the PR names (declared-spelled guides render either way).
    • Precedence pin: a leg the PR did not run — I flipped the adapter to name-first (col.name || col.accessorKey, the shape that really existed at ObjectDataTable:110): predicted 3 red (retirement, LEGIBILITY, precedence), observed exactly those 3 of 44. So the precedence pin is a real guard for the regression direction it names, not a restatement — it is the only one of the three that catches order inversion.
    • LEGIBILITY pin — worth it, not over-specification. The changeset now PROMISES this failure shape to authors ("header renders, cells blank, neighbours fine") — a promised shape is interface, not accident, and the pin keeps the promise honest. When a ObjectGrid renders a header-only grid in total silence when no column resolves — one wrong first column used to take the whole set down, now every wrong column does #5349-class diagnostic lands for data-table and changes the shape, this pin goes red and names itself; given that an invisible coupling is what stalled this card, a tripwire that announces shape changes is exactly in-culture.
    • Blast-radius pin: the simulated hand-off { header, name, accessorKey } is byte-faithful to what ObjectDataTable and RelatedList actually deliver (mirror-don't-move: name left in place, accessorKey stamped). It could NOT catch a producer-side regression — it lives in packages/components and simulates — but the real paths carry their own pins (the 18 producer tests above), and the test's comment says honestly that it simulates. The composite guarantee is sound; it is just not carried by this one test, which is fine.
    • Corpus pin: proven non-vacuous by leg 2 — it went red on the reverted guides, and its failure message names the file, the key, and the card.

    6. The docblock correction — VERIFIED TRUE, and it is the story of the stall

    The old docblock ("pins the BINDING only … nothing below asserts a column key spelling") was FALSE, exactly as the PR and #5479 claim. Measured, not inferred: in leg 2 the failing tests under a name-spelled corpus are the CELL-TEXT assertions — a cell has text only when its accessor resolves, so those assertions transitively pinned the accessor spelling all along. That false independence claim is why the 2026-08-20 ruling was taken without knowing its last step would turn a governed-surface gate red — the step then had to wait for the #5149 governance answer, which is the ten days this card sat. The correction plus the explicit spelling assertion (failure now names the key and the card instead of diffing two cell arrays) is the most durable part of this diff.

    7. Changeset — grade and wording correct

    minor is the correct grade: AGENTS.md :222-226 fixes the group's major to @objectstack and directs objectui's own breaking changes to minor with the breaking semantics in the body — CI-enforced (check-changeset-no-major.mjs). The text is author-facing, states the before/after spelling, bounds who narrows (directly-authored data-table nodes only), and describes the recognisable failure shape — which is now also pinned, so the promise and the behaviour cannot drift silently. One sentence needs the condition-1 fix below.

    Conditions

    1. Non-blocking, recommended before or immediately after merge: correct the ObjectGrid mechanism sentence in the three places it appears — the changeset's "Who is NOT affected" paragraph (author-facing and permanent), the PR body, and the WHAT NARROWS comment in data-table-declared-column-keys.test.tsx. Accurate form: ObjectDataTable and RelatedList fold name into accessorKey via columnIdentity before delivery; ObjectGrid refuses undeclared spellings at intake (declared field only, finding(plugin-grid): ObjectGrid 在 ObjectGridSchema.columns 上容忍未声明的 accessorKey / header 拼写 —— 声明类型是 strict 的 ListColumn,#3104 的列身份门禁结构上看不见这条支路 #5068) and delivers columns already stamped with accessorKey, so the adapter never sees the legacy spelling from any of the three. This does not change the blast-radius conclusion — it is measured true — but this card's history (the corrected ruling clause, the corrected PM note, finding(components): skill-guide-data-table-binding's docblock disclaims a column-spelling dependency its assertions actually have #5479) is precisely a record of similarity standing in for a reading, and the release notes should not add another instance. The changeset lives outside skills/**, so the fix is an ordinary commit.
    2. Non-blocking, note only: the PR body says the PR "carries needs:contract-review" — the label sits on card finding(components/plugin-dashboard): data-table 的访问键归一化读一个自身类型未声明的 col.name 别名,而唯一在交付前解析列身份的生产者只有 RelatedList #5120, not on PR fix(components)!: data-table reads only the declared accessorKey (retire the col.name alias) #6963. The routing did happen (this review); no action needed unless the body is edited anyway.

    Not measured by me

    The full packages/components suite (219 files), the 404-file producer sweep, and the gate union are the dev's readings plus CI's job — the human merge gate should read the real CI conclusions on this head. My verdict rests on the targeted measurements above, all quoted from my own runs at 7538f0ac3.

    ACCEPT-WITH-CONDITIONS — both conditions non-blocking. Execution matches the recorded 2026-08-20 ruling; the blast-radius bound is real and now pinned; the governed surface migrates in the same commit as the narrowing it justifies; the census is a measurement, not a number. Ready for the human merge decision.


    Generated by Claude Code

  12. claude commented on Aug 31, 2026

    @claude
    Contributor

    os-dev-report

    Supersedes my report of 03:5x (comment 5473516583) — same run, updated after contract review returned ACCEPT-WITH-CONDITIONS on PR #6963. Both conditions addressed in 8be968586; the PR remains DRAFT on the human merge path. Parse this one.

    {
      "issue": 5120,
      "status": "done",
      "branch": "claude/issue-5120-retire-data-table-name-alias",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6963",
      "premise_still_valid": true,
      "summary": "UPDATE after contract review returned ACCEPT-WITH-CONDITIONS. Both conditions addressed in commit 8be968586 (PR now 2 commits, still 7 files, still DRAFT, auto_merge None — nothing armed, nothing enqueued). CONDITION 1, the one worth a commit: I verified the reviewer's finding against source myself rather than taking it. `resolvesToDataColumn` (packages/plugin-grid/src/columnSpellingDiagnostics.ts:85-90) returns `typeof entry.field === 'string' && entry.field.length > 0` — so ObjectGrid DROPS a name-spelled authored column at intake and never folds it; delivery stamps `accessorKey: col.field` (ObjectGrid.tsx:2237). My blast-radius CONCLUSION was right for all three producers, but the MECHANISM I gave was right for only two: ObjectDataTable (:463-484) and RelatedList (:985-988) resolve via columnIdentity; ObjectGrid refuses. One precision I added on my own read, to avoid over-correcting into a third false claim in the opposite direction: ObjectGrid DOES import and call columnIdentity elsewhere (FLS lists :1437/:1503/:1510, a diagnostic at :2005) — what it never does is use it in the authored-column delivery path. Corrected in all three places the review named: the CHANGESET (author-facing, the copy that would otherwise have shipped a false mechanism into release notes), the WHAT NARROWS docblock, and the producer-resolved pin's comment. Each correction states the two routes separately and records WHY the distinction is load-bearing: the 2026-08-20 ruling's item 2 told ObjectGrid to connect to columnIdentity, #5478 measured that doing so would have re-widened what #5068 narrowed, and the claim has now been restated wrongly by two separate seats — a third repetition would settle it as fact. CONDITION 2 (non-blocking): the PR body's `needs:contract-review` sentence now says the label sits on card #5120, not on the PR. The correction is comment-and-changeset text only — mechanically verified: every changed line in the test file is a comment (diff filtered for non-comment lines is empty), no assertion, no source line, no behaviour change.",
      "tests": "At HEAD 8be968586, clean tree (0 modified). The three affected pin files -> 'Test Files 3 passed (3) / Tests 44 passed (44)' — the same 44 the contract reviewer independently counted. GATE UNION re-run AT 8be968586 on the clean tree, every one exit 0: check:control-bytes (5785 tracked text files) · check:skills-paths (94/95, 1 baselined) · check:doc-types · check:doc-fences · check:self-import · check:phantom-deps · check:governed-queue-guard (132 self-test cases) · check-changeset-presence.mjs ('4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)') · check-changeset-no-major.mjs ('No changeset declares a `major` bump'). Plus my own control-byte grep over both changed files, clean. NOT-MEASURED CAUGHT: the first re-run of the pin files exited 254 with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL 'Command vitest not found' — that is not a red suite, it is my own cleanup: I had removed node_modules and the worktree at the end of the first pass and the recreated worktree had no install. Re-installed and re-ran to the 44/44 above. Reported rather than quietly re-run. NO NEW ABLATION WAS OWED and none is claimed: this commit changes only comment and changeset prose, so there is no behaviour whose direction could be predicted or reversed. The behavioural evidence for the PR is unchanged and stands on the first commit's legs (leg 1: alias restored -> exactly 2 red by name, guide gate 16/16 green; leg 2: guides reverted -> exactly 3 red by name), which the reviewer independently reproduced and extended with its own leg 1b (name-first ordering restored -> exactly 3 red as predicted).",
      "mcp_calls": "0 — REST channel plus git for the whole run, both passes; zero MCP GitHub calls.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT filed (already open): #6866 — AGENTS.md's governed-surface section carves root skills/** out of the governed surface and claims the repo has no mechanical backstop; both halves are contradicted by check-governed-queue-guard, which on my real diff prints 'skills/** x2 — the published skills catalog' and names GOVERNED_APPROVERS (os-zhuang, hotlong). Confirmed same finding via a complete enumeration of all 265 open issues (control: #5120 present in the scan); no duplicate filed.",
        "NOT filed, discharged in this PR: #5479 — the guide gate's docblock falsely claimed spelling-independence. Contract review verified the falsity claim TRUE on its own leg 2 (the failing tests are exactly the cell-text assertions, and a cell has text only when its accessor resolves) and called the correction plus the new spelling assertion 'the most durable part of this diff'. PM may close #5479.",
        "Platform reading, no card (offered for the readings table, not filed): a REST PATCH to a PR body APPENDS a bare '_Generated by [Claude Code]' footer unconditionally — even when the submitted body already ends with one — yielding a duplicate footer. Measured by full read-back: stored body differed from sent by exactly +58 bytes, and the unified diff is only those appended footer lines; no content was eaten. Distinct from the recorded 'PATCH downgrades the session form' behaviour. Mitigation that worked: carry the session URL in body PROSE as a backticked code span rather than a markdown link — it survived the PATCH verbatim."
      ]
    }
    

    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

    auto-adjudicateddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions