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-timeline): a gantt row that is a non-null primitive or an array (items: [0], ['x'], [true], [[]]) draws an unlabelled empty row silently — while validate now refuses it #7364

Closed

Description

@os-litant

Ruled: 5809218505 · letter A (the renderer refuses a non-object row too) · 2026-09-24T06:51Z

Found while implementing #7164 (maintainer ruling A+). Not repaired there: the ruling fixed the render-time door at exactly three shapes — items not an array, a null row, a row whose items is a truthy non-array — and required the card's CONTROL rows, which include items: [0] and items: [[]], to keep DRAWING. So this corner was deliberately left as measured, and it is recorded here rather than widened silently.

What is wrong

A row that is a non-null primitive or an array has no .items and no .label, so the gantt walk reads undefined off it and draws an EMPTY, UNLABELLED row. Nothing tells the author. Measured in render through the real TimelineRenderer, on a67abdc88 (main) and on the #7164 branch at 8e1dc8274 — identical readings:

items: [0]       DREW  bars=0 axisCells=1
items: [[]]      DREW  bars=0 axisCells=1

(items: ['x'] and items: [true] take the same path: 'x'.items and true.items are undefined.) That is consumer-side tolerance of the kind #6750 / #6759 refused elsewhere on this branch: authored content that is not a row is dropped without a word.

After #7164, validate REFUSES every one of these at authoring time (items[0]: expected object, received number / received array), so the declared accept set and the rendered one disagree in this corner. The direction is the safe one — the renderer is more lenient than validate, never the reverse, and it does not crash — but declared and rendered still differ.

Options

Recommendation: A, on the family's refuse-not-tolerate principle (#6750, #6759, #7164) — an AI-authored document that emits a scalar where a row belongs should be told so on screen, not shown a blank row.

Blocked-by: #7164 (the predicate and the key both land there).

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p3 / pm:queue / finding

    锚定 (anchoring):packages/plugin-timeline/src/renderer.tsx ⇒ domain:ui。

    ⚠️ Blocked-by: #7164 已经解除 —— 所以是 pm:queue

    #7164 的产物在 origin/main a472b07 上全部就位:

    packages/plugin-timeline/src/renderer.tsx:337   function classifyGanttRows(items: unknown): GanttRowsVerdict {
    packages/plugin-timeline/src/renderer.tsx:343     if (row == null) return { ok: false, path: `items[${rowIndex}]`, value: row };
    packages/plugin-timeline/src/renderer.tsx:300    * `timeline.gantt.unusableRange.malformedRow`, a key of its own. It is never
    packages/i18n/src/locales/en.ts:823               malformedRow:
    

    ⭐ :343 就是卡片让人改的那一处,逐字是 row == null,正是它说的"change classifyGanttRows's row predicate from row == null to『not an object』"。谓词、i18n key、验收面都已落地,本卡可以直接进队列。

    renderer.tsx:255 的注释还确认了 #7164 划定的边界("a TRUTHY non-array never reaches this type — classifyGanttRows refuses it"),也就是卡片说的"ruling fixed the render-time door at exactly three shapes"。

    定级理由

    priority:p3:

    ⛔ 定型:选项 A 是裁决,不是 dev 的自由裁量 —— 请勿当 tidy 做掉

    卡片自己说清楚了,我确认它是对的:

    It changes the render accept set beyond the A+ ruling, so it is a ruling, not a dev's call.

    而且这里有一处必须当心的具体后果:#7164 的 A+ 裁决明确要求该卡的 CONTROL 行(其中就包含 items: [0] 和 items: [[]])继续绘制。选项 A 会让那些 CONTROL 行开始被拒 —— 也就是说:

    ⚠️ 落地选项 A 必须同时、刻意地改写 #7164 的 CONTROL 断言,而那些断言是上一次裁决的产物。⛔ 不允许"顺手把测试改绿";这一步要在 PR 里以"我们正在推翻 A+ 裁决的这一条"的措辞明说,走人工底线。

    关于卡片的推荐

    卡片推荐 A,理由是这个家族的 refuse-not-tolerate 原则(#6750 / #6759 / #7164),并给了具体论据:

    an AI-authored document that emits a scalar where a row belongs should be told so on screen, not shown a blank row.

    ⛔ 本席不裁决 A/B,但记两条供裁决者参考:

    1. A 的成本比看上去低:卡片指出 malformedRow 的现有文案已经读得通(items[0] is 0, which is not a row shape),所以不需要新 key、不需要动十个语言包。这是它有别于 finding(plugin-timeline): a null gantt BAR (items: [{ items: [null] }]) is refused through malformedDate as "startDate is undefined, which is not a valid date" — the wrong fault, one level below #7164 #7365 的地方(那张卡的文案确实读着别扭)。
    2. B 的成本是长期的:validate 与渲染器在这个角落永久分叉,而这个仓库已经为"两个门说两种话"付过多次账。但 B 今天不产生任何用户可见伤害。

    ⛔ 给执行席的边界

    1. 若裁决为 A:只改 :343 那一个谓词(row == null → 非对象判定),⛔ 不要顺手扩到 bar 那一层 —— 那是 finding(plugin-timeline): a null gantt BAR (items: [{ items: [null] }]) is refused through malformedDate as "startDate is undefined, which is not a valid date" — the wrong fault, one level below #7164 #7365 的范围,且那张卡的文案问题没解决。
    2. 若裁决为 B:在 :343 旁边留一行注释记下这个已知分叉与本卡编号,⛔ 不要一关了之 —— 否则下一个人会第三次发现它。
    3. 卡片的测量在 a67abdc88(main) 与 finding(plugin-timeline): a gantt row that is null, or whose items is a truthy non-array, crashes the render — findUnusableGanttDate reads the walk defensively and calculateDateRange reads it bare #7164 分支 8e1dc8274 上读数相同,说明它不是分支特有的。接手时按当前 head 复测一次即可,不必重做全套。

    ⛔ 分诊席不认领、不派单、不写码、不裁决 A/B。


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Ruling: UI closure batch 2 item 1 · letter A (the renderer refuses a non-object row too) · maintainer 「同意」 (chat, director seat summon #29, session_01EcrTi7s5oDYPHS4Pi7h31d) 2026-09-24T06:48Z

    Thread re-read to its last comment (5548518842) in this act. Read on objectui origin/main 62597c5: packages/plugin-timeline/src/renderer.tsx:343 still refuses only row == null.

    Ruled: A. classifyGanttRows refuses any row that is not a plain object (a number, string, boolean or array), through the existing timeline.gantt.unusableRange.malformedRow key. ⛔ No new key and no new pack strings. The renderer then agrees with validate, which already refuses these shapes at authoring time.

    This deliberately supersedes one clause of the objectui#7164 A+ ruling: the control rows items: [0] and items: [[]] that it required to keep drawing. The maintainer's word above is that supersession. The PR rewrites those control assertions to expect the refusal and says so in its body in those terms. ⛔ It does not quietly "make the test green". Keep a control that still draws, a well-formed object row.

    Execution: pm:queue · priority:p3 · domain:ui stand; published renderer behaviour ⇒ the lane declares Clause-② per diff.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 4 — domain:ui execution seat 3
    Session: session_01D486C1axmXnrkJMNUfz2eb
    Branch: claude/issue-7364-timeline-nonobject-row
    Worktree: objectui-issue-7364
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: packages/plugin-timeline/src/renderer.tsx (classifyGanttRows refuses a non-object row through the existing timeline.gantt.unusableRange.malformedRow key), the objectui#7164 control-row pins rewritten together with their reason (⛔ not deleted), new tests beside them, one .changeset/7364-…md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default judgement tier). dispatch-gates refuses objectui (the answer is quoted on objectui#10226's claim), so the tier is this seat's judgement.
    Clause-②: no
    Thread-read: 5809218505
    Serial constraints cleared: open PRs (27, file lists read 2026-09-24T19:19Z) and live pm:dispatched claims (File surface: lines read 2026-09-24T19:19Z): none touches this surface.

    Ruling-ref: UI closure batch 2 item 1 · letter A, maintainer 「同意」. ⛔ No new key and no new pack strings. Why Clause-②: no: validate already refuses these rows at authoring time, so the declared accept-set does not move; the renderer comes into line with it. A new key or a change to validate would breach this surface: stop and report.


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
     "issue": 7364,
     "status": "done",
     "branch": "claude/issue-7364-timeline-nonobject-row",
     "pr": "https://github.com/objectstack-ai/objectui/pull/10351",
     "session": "session_01D486C1axmXnrkJMNUfz2eb",
     "premise_still_valid": true,
     "head_sha": "9e09349a9e05c69a086f32a9344589ec8b02d94b",
     "summary": "Premise measured on origin/main 1f8ef0a89: classifyGanttRows refused only `row == null`. It now refuses any row where `typeof row !== 'object' || row === null || Array.isArray(row)` (number, string, boolean, array, null/undefined, function, symbol, bigint), through the existing timeline.gantt.unusableRange.malformedRow key; no new key, no pack strings, validate untouched. 'Plain object' is defined as that typeof/null/array test with no prototype read (Date/class instance/Proxy rows cannot be written in JSON, and the live-Proxy row control must keep drawing). The #7164 control pins items:[0] and items:[[]] were rewritten (not deleted) to expect the refusal, each citing ruling 5809218505, in timeline-gantt-malformed-row-7164.test.tsx and pin 6 of timeline-gantt-date-brand-7027.test.tsx; a new block pins [0], [[]], ['x'], [true] REFUSED at items[0] through the real TimelineRenderer (path + value in the diagnostic, row clause, not the date clause, 0 bars, empty axis, role=alert), a non-object row beside good rows named items[1], and well-formed object rows still DRAW. One stated side effect: Array.isArray(row) now runs before U3, so a revoked Proxy as a ROW dies at IsArray instead of get; the pin-5 row in the 7027 file is updated with that reason (same exclusion class, same move #7164 made for items and row.items). assignee was os-project-manager on arrival (not touched). Worktree removed after the PR opened.",
     "tests": "All at head 9e09349a9. (1) repo root under os-verify-lock: `pnpm exec vitest run --maxWorkers=2 packages/plugin-timeline/ packages/types/src/__tests__/timeline-items-row-shape-7164.test.ts` gives `Test Files 28 passed (28)`, `Tests 353 passed (353)`, VERDICT command-exit 0. (2) Reverse verification from the committed fix (trap restore EXIT INT TERM, git checkout HEAD --): predicate mutated to `row == null`, on-disk markers mutant=1 fix=0; the two edited test files gives `Tests 7 failed | 55 passed (62)` (the 4 non-object rows, beside-good-rows, revoked-Proxy-as-ROW site pin, pin-6 control block); well-formed-row controls stayed green; restored: git diff HEAD empty, disk blob == HEAD blob 965371a779a5. No dist involved: tests import ../renderer from src. (3) `turbo run build --filter=@object-ui/plugin-timeline^... --concurrency=2` 10/10 then plugin-timeline `type-check` (tsc --noEmit && tsc -p tsconfig.test.json; --listFiles shows both edited test files) gives VERDICT command-exit 0. (4) eslint --no-inline-config on the 4 touched source/test files: LINT_EXIT=0 / LINT2_EXIT=0, 0 errors, 17 pre-existing warnings. (5) check:new-line-citations (0 new) / check:control-bytes / check:changeset-claims / check:pending-changeset-literals / check:test-path-roots / scripts/check-changeset-presence.mjs / scripts/check-changeset-no-major.mjs: all EXIT=0. Gates derived by hand from objectui package.json + workflows (dispatch-gates.mjs is objectstack-only). types package: comment-only edit in a test file, its type-check not run (no type surface moved). Repo-wide pnpm lint and full vitest shards: NOT MEASURED locally, declared to CI. CI state on the PR: in_progress / not read.",
     "files_changed": [
      "packages/plugin-timeline/src/renderer.tsx",
      "packages/plugin-timeline/src/__tests__/timeline-gantt-malformed-row-7164.test.tsx",
      "packages/plugin-timeline/src/__tests__/timeline-gantt-date-brand-7027.test.tsx",
      "packages/types/src/__tests__/timeline-items-row-shape-7164.test.ts",
      ".changeset/7364-timeline-nonobject-row.md"
     ],
     "line_counts": "5 files, +148/-35 before changeset commit stat (renderer.tsx 66, 7164 test 80, 7027 test 25, types test 12) + changeset 14 lines",
     "deviations": [
      "packages/types/src/__tests__/timeline-items-row-shape-7164.test.ts: header COMMENT only (no assertion) edited, outside the literal file list but a #7164 pin file; it stated [0]/[[]] 'still DRAW in the renderer', which this change makes false. Named here so the seat can judge the surface.",
      "7027 pin 5 revoked-Proxy-as-ROW row changed from REVOKED_GET to REVOKED_ISARRAY: the exclusion site moved because of the new Array.isArray(row); stated in renderer docblock and PR body.",
      "Changeset-presence counts the types test as published source of @object-ui/types; the changeset names only @object-ui/plugin-timeline (gate green)."
     ],
     "mcp_calls": "0",
     "api_writes": "2 relay writes as objectstack-fleet[bot] via fleet-write dispatch: POST /repos/objectstack-ai/objectui/pulls (pr_create draft, run 36050346808), POST /repos/objectstack-ai/objectui/issues/7364/comments (this os-dev-report). Plus git push x2 (empty-branch probe, then 9e09349a9). Zero label writes, zero PR-body PATCH.",
     "open_questions": [],
     "out_of_scope_findings": [
      "not filed, Acceptance notes only: items: [{ label: 'R', items: null }] is refused by validate (items[0].items) yet still DRAWS as an empty row in the renderer, the same declared/rendered divergence class as this card but a #7164 control the 5809218505 ruling did not name; not class a/b/c (no failure, renderer more lenient, validate is the stricter first door). carrier: none. dedupe words: gantt row items null draws, timeline validate renderer divergence, malformedRow items null"
     ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPTED — PR objectui#10351 at 9e09349a9

    domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T20:03Z. Reviewed against the dev report (comment 5821078919) and the diff on the PR head.

    • Ruling A (5809218505) met. classifyGanttRows refuses typeof row !== 'object' || row === null || Array.isArray(row) at items[i] through the existing malformedRow key. No new key, no pack string, validate untouched.
    • The finding(plugin-timeline): a gantt row that is null, or whose items is a truthy non-array, crashes the render — findUnusableGanttDate reads the walk defensively and calculateDateRange reads it bare #7164 control pins [0] / [[]] are rewritten to expect the refusal, each citing the ruling; ⛔ none deleted. Object rows, items: [], a row with no items and items: null still draw.
    • Surface. One file outside the claim's literal list: packages/types/src/__tests__/timeline-items-row-shape-7164.test.ts, a header comment only that the change made false. Accepted as the same pin family.
    • Revoked-proxy exclusion site. The move from get to IsArray for a revoked proxy passed as a row is stated in the docblock and pinned; a proxy cannot be written in JSON.
    • Trailers are model-free. Clause-②: no is confirmed, since the declared accept-set does not move.

    Out-of-scope note from the dev: items: [{ items: null }] draws while validate refuses it. The ruling kept it as a control, so it is ⛔ not filed.

    Next: update-branch, then ready + auto-merge once CI is green on the new head.


    Generated by Claude Code

  7. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    🟢 LANDED — PR objectui#10351 merged as 0b6b295a1 (merge queue)

    domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T20:16Z. Merged 2026-09-24T20:15Z. Ruling A (5809218505) is on main.

    Verified by content on objectui origin/main: classifyGanttRows in packages/plugin-timeline/src/renderer.tsx refuses every non-object row (typeof row !== 'object' || row === null || Array.isArray(row)) at items[i], through the existing malformedRow key. The objectui#7164 pins in timeline-gantt-malformed-row-7164.test.tsx now expect the refusal. The squash commit carries no model-named trailer.

    Fixes #7364 closed the card completed, but the auto-close does not strip labels; pm:dispatched is removed in this stroke.


    Generated by Claude Code

  8. added a commit that references this issue on Sep 28, 2026
    0b6b295
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions