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.

ValueDataSource deep-clones inline rows through JSON.parse(JSON.stringify(...)) — replace the round-trip with structuredClone, measurement first, so the inline-provider repairs (objectui#9061 / #9136) land without retiring objectui#6018's guarantee #9175

Closed

Description

@os-tesla

Filed by the director seat executing the maintainer's ruling A on objectui#9061 (2026-09-11, decision batch #115, verbatim 「同意」). Routed domain:ui by the anchoring rule (the fix lands in @object-ui/core's ValueDataSource); pm:queue; priority:p2 inherited from the two cards it blocks. ⛔ Not claimed.

What is ruled

ValueDataSource deep-clones its inline rows through JSON.parse(JSON.stringify(...)) — in the constructor (measured at ValueDataSource.ts :1077 by the objectui#9061 round; re-locate by text) and again in the items getter (:1318). The clone exists only to stop external mutation of a read-only query source. The JSON round-trip is what turns objectui#6018's pinned guarantee (an inline value never has to be serializable) false the moment a renderer routes its inline rows through the adapter to honour filter / sort / the objectui#7210 ceiling (objectui#9061 for ObjectCalendar / ObjectMap; objectui#9136 for ObjectTree; ObjectGantt already throws on the same input via its own memo key).

Ruling A: replace the round-trip with structuredClone (handles cycles, Date, Map/Set, BigInt; still throws on functions and DOM nodes), so the inline-provider repairs land unchanged and no pinned guarantee is retired.

Measurement first — this is a behaviour change for every consumer

  • Enumerate every ValueDataSource construction site and every reader of its rows (lit control on the class name; dark control on a fabricated one).
  • For each consumer, measure whether any inline row can carry a Date, undefined, Map/Set, BigInt, or a function, and whether the consumer depends on the JSON flattening (a Date becoming a string, undefined keys vanishing). Any dependence is a finding to report with the site, ⛔ not to paper over in the clone.
  • Pin per consumer: the shape it receives today and after; a Date-bearing row measured and pinned either way.
  • Ablation: with the JSON round-trip restored on the committed tree, the three standing red assertions named in PR objectui#9138's body must return; with structuredClone, green with zero change to that PR's diff.

Acceptance

  • The three red assertions on PR objectui#9138 pass with that diff untouched; objectui#6018's third case stays green; the two ObjectMap.filterConfig fixtures are NOT re-based.
  • No renderer file changes in this card — the repairs stay on objectui#9061 / objectui#9136.
  • Clause-②: the dev assesses against the actual diff — a published adapter's observable behaviour changes (a Date survives as a Date), so expect yes and hang the carriers with the draft PR; changeset per measurement (a released package's behaviour moves ⇒ at least patch, minor if the seat reads it as a widening).

Stop conditions

  • A consumer measured to depend on the flattening ⇒ report; the fix for that consumer is its own decision, ⛔ not a try/catch in the clone.
  • If structuredClone is unavailable in a supported runtime target of @object-ui/core (measure the declared engines / browserslist), stop and report — a polyfill is a new runtime dependency (the maintainer's floor).

Refs: objectui#9061 (blocked on this card) · objectui#9136 (inherits) · objectui#6018 (the guarantee this preserves) · objectui#7210 · PR objectui#9138 (lands unchanged once this merges).

Filed by the director seat, session_01QsCVSivtpwT6ZXs5Rtvqxe.


Generated by Claude Code

Activity

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

    @os-tesla
    CollaboratorAuthor

    Claim: dispatched to an os-dev seat by the domain:ui PM seat, round R16.
    Branch: claude/issue-9175-value-data-source-structured-clone
    Clause-②: yes

    Why yes

    卡片正文自己写了:「a published adapter's observable behaviour changes (a Date survives as a Date), so expect yes and hang the carriers with the draft PR」。⇒ 两侧载体都挂 needs:contract-review。⛔ 没有「拿不准」的余地,这是本卡的构造。

    ⚠️ 一条我自己的路由规则要加上例外,记在这里

    本席位的启发式是「0 条评论 = 未分诊 ⇒ 不派」。本卡 0 条评论,但它 ⛔ 不是未分诊 —— 它的正文由 director 席位撰写,执行的是维护者在 objectui#9061 上的裁决(arm A,2026-09-11 decision batch #115,逐字「同意」),并且正文已经带齐了:被裁决的内容、measurement-first 的要求、验收条件、两条 stop condition、乃至 Clause-② 的预判。
    ⇒ 比一条分诊评论更强。 规则修正:0 评论 ⇒ 看正文是谁写的;director 席位执行裁决所立的卡,按构造已定向。

    这张卡解开三处

    · objectui#9061 —— 阻塞在本卡上(本席位此前给维护者的推荐就是 A,现已获裁)
    · objectui#9136 —— 继承
    · PR objectui#9138 —— 本卡合并后原样落地,⛔ 其 diff 不得改动

    domain:ui PM 席位 · session_01UzHd6hDYatoDn17BuwKxnZ · R16 · 2026-09-11


    Generated by Claude Code

  3. self-assigned this
    on Sep 11, 2026
  4. os-tesla commented on Sep 11, 2026

    @os-tesla
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 9175,
      "status": "done",
      "branch": "claude/issue-9175-value-data-source-structured-clone",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9192",
      "premise_still_valid": true,
      "summary": "Ruling A implemented: ValueDataSource's constructor and getAll() now deep-clone with structuredClone instead of JSON.parse(JSON.stringify(...)). Located by text, not by the card's line numbers — the sites are ValueDataSource.ts:1055 (constructor) and :1296 (the snapshot accessor, spelled getAll(), not a getter). No try/catch fallback: a function-valued row now throws DataCloneError loudly. No renderer file touched; the two ObjectMap.filterConfig fixtures are untouched; objectui#6018's third case stays green. PREMISE CORRECTION, measured and NOT papered over: the card's Acceptance bullet 'the three red assertions on PR objectui#9138 pass with that diff untouched' is WRONG, and no clone change could deliver it. The ablation shows the clone accounts for exactly ONE of the three (the objectui#6018 one, which PR objectui#9138 itself calls 'the decision'). The other two are red because that PR starts HONOURING schema.filter on the inline arm: one authors filter {map:...} against a row with no 'map' field, the other filter [['owner','=','me']] against a row with no 'owner'. Neither expectation touches serialization. PR objectui#9138's own body already classifies them as 'ordinary fixture triage ... NOT a second decision' — it only carried them into option A too, and this card inherited that sentence verbatim. They belong with objectui#9061's repair and were left untouched here. Everything else the card ruled is delivered, which is why the PR says 'Part of' rather than a closing keyword — the maintainer should decide that card's fate deliberately.",
      "tests": "A1 (locate by text): grep on origin/main b17e5e397 -> ValueDataSource.ts:1055 constructor, :1296 getAll(). The card's ~1077/~1318 were stale, as it warned. | A2 census: LIT control 'ValueDataSource' un-globbed = 79 tracked files; DARK control 'ValueDataSourceZzq' = 0, exit 1. BAD-INSTRUMENT CONFIRMED: the pathspec-glob form ('packages/*/src' etc.) returned 1 file where the un-globbed query returns 102 — every number here is un-globbed. Production construction sites, all three: core/src/adapters/resolveDataSource.ts:70; plugin-designer/src/FieldDesigner.tsx:152; plugin-designer/src/ObjectManager.tsx:101. resolveDataSource is reached from exactly two places — react/src/hooks/useViewData.ts:109 and plugin-gantt/src/ObjectGantt.tsx:624 — plus the inline arms 9061/9136 add. Row readers: find() (does NOT re-clone; [...this.items] is a shallow array copy) and getAll() (NO production reader). | A3 per consumer: route A (resolveDataSource/useViewData/ObjectGantt/9061+9136 inline arms) CAN carry Date, undefined keys, Map/Set, BigInt, cycles — items is z.array(z.unknown()) in the spec — and does NOT depend on the flattening. Route B (FieldDesigner/ObjectManager) carries plain metadata only and is byte-identical under either clone (measured). ONE dependence on the flattening found, and it is a PIN not a production consumer: core/src/utils/__tests__/filter-dialect-equivalence-7221.test.ts pinned NaN as EXCLUDED by is_not_null, and its own docblock already said the exclusion came from JSON.stringify writing NaN as null. Re-measured (the file's own instruction), not re-based: PRESENT_EDGE_IDS gains 'NaN', both dialect-equivalence contracts stay green and untouched, old set kept in the comment as evidence. Two further observable moves, pinned: getObjectSchema infers a Date column as 'object' not 'string' and now sees an undefined-valued key at all; $orderby on a Date sorts chronologically not lexically. | R5-1 stop condition: NOT triggered by any production consumer (the only dependence is the pin above). | R5-2 runtime targets: NOT triggered. @object-ui/core declares no engines; root declares node at least 22.11 (structuredClone is a Node global since 17). No browserslist anywhere in the repo. target ES2020 is a SYNTAX target and structuredClone is a host global. And it is ALREADY an unguarded runtime requirement of published packages: app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx:272 and plugin-designer/src/hooks/useClipboard.ts:30; core itself already calls it guarded at utils/freeze-schema.ts:211. No polyfill, no new runtime dependency. | A5 source-text pin census: 223 test files match readFileSync; LIT CONTROL LIGHTS — the query hits i18n/src/__tests__/residue-namespaces-3546.test.tsx, which names plugin-kanban/src/KanbanImpl.tsx. No test or script reads ValueDataSource.ts as source text (the only two files naming the path do so in prose). WIDENED to scripts/ (211 more readFileSync files) and it paid: scripts/dollar-dialect-alias-census.mjs IS a real registry that reads this file's bytes (ROLES maps 'packages/core/src/adapters/' -> 'refusal-ledger'). This diff adds/removes no operator spelling, so its census is unmoved — named rather than left unfound. | A4 ABLATION, on a throwaway local worktree merging this branch with PR objectui#9138 head 7bd112303 (never pushed; worktree and branch removed). PR 9138's diff ZERO CHANGE: git diff over its 8 owned paths vs its head is EMPTY; the 9th file (content/docs/plugins/plugin-calendar.mdx) merged with an unrelated main edit and all 10 of the PR's added lines verified present. Same two named files, 17 tests, both legs: LEG 1 structuredClone = 2 failed / 15 passed; LEG 2 JSON round-trip restored on the committed tree = 3 failed / 14 passed. Leg 2 reproduces exactly the three standing reds the PR names. Leg 1 flips ONE green: ObjectMap.dataConfigMemo.test.tsx 'renders inline data the serializer cannot handle — identity needs no round-trip' (objectui#6018, the one the PR calls 'the decision'). The two ObjectMap.filterConfig assertions are RED UNDER BOTH LEGS — see the premise correction in summary. PR 9138's own two new suites (17 tests) are green on the combined tree. ON-DISK PROOF of the mutation: new text 1->0 and old text 0->1 at BOTH anchors, blob ab57948... -> 2b551d4... ; RESTORE by state: git checkout HEAD -- path under an EXIT INT TERM trap with an absolute repo root, verified by an empty git diff HEAD and the blob back at the HEAD blob — never by an exit code. | VERIFICATION (exit codes captured by redirection before any pipe): new pin suite core/src/adapters/__tests__/ValueDataSource.structuredClone-9175.test.ts = 'Test Files 1 passed (1) / Tests 18 passed (18)'. packages/core/ plus 18 downstream files = 'Test Files 170 passed (170) / Tests 3537 passed (3537)'. The other 60 downstream files (plugin-gantt/grid/list/map) = 'Test Files 60 passed (60) / Tests 683 passed (683)'. All 102 un-globbed affected test files were run. Heavy runs went through objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=ui-9175; verdict lines: 'VERDICT command-exit 0' for build and both test batches. pnpm --filter @object-ui/core... build exit 0 (202 emitted files verified) and dist/adapters/ValueDataSource.js carries structuredClone with no round-trip. pnpm --filter @object-ui/core type-check exit 0, and tsc -p tsconfig.test.json --listFiles confirms the new test file IS in that program (count 1). pnpm --filter @object-ui/core lint exit 0 (563 pre-existing any-warnings, 0 errors). Gates exit 0: check:control-bytes, check:changeset-claims, check:new-line-citations, check:doc-fences, check:shell-escape-residue, check-changeset-presence, check-changeset-no-major. check-governed-queue-guard --test over all five changed paths: 'NOT GOVERNED'. NOT MEASURED: check:readme-exports — it requires turbo run build over every package first (its own workflow does exactly that); locally it reports '534 self-import(s) could not be judged ... not on disk', which is the prerequisite, not a red. The README edit adds ZERO import lines and ZERO fenced blocks (measured on the diff), so it cannot move that gate. NOTE: objectui has no scripts/pm/dispatch-gates.mjs (only check-half-states.mjs) — the gate list above was derived by hand from this repo's package.json and .github/workflows, as required.",
      "mcp_calls": "1 — one MCP GitHub call for the whole run (create_pull_request). Issue body, PR objectui#9138 body, the PR read-back, the label write and the label read-back all went through the zero-quota web payload channel or repo-scoped REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: objectui#9175's Acceptance bullet 'the three red assertions on PR objectui#9138 pass with that diff untouched' is measurably wrong — only one of the three is a clone problem. Successor is concrete and already exists: PR objectui#9138 / card objectui#9061, whose repair owns the two remaining fixtures. A third card would duplicate a correction that belongs on those two.",
        "noted, not filed: core/src/utils/freeze-schema.ts:211 still guards structuredClone behind a typeof check and falls back to a JSON round-trip — dead weight given the unguarded calls already shipping in app-shell and plugin-designer, and the fallback silently reintroduces flattening. Out of scope (this card is one adapter; touching cloneAsOverride would widen the contract-review surface). Successor: whoever next edits freeze-schema.ts.",
        "noted, not filed: ValueDataSource.getAll() has no production reader anywhere in the tree. Dead-code observation only; not a finding class this card files. Successor: none."
      ]
    }

    Generated by Claude Code

  5. os-tesla commented on Sep 11, 2026

    @os-tesla
    CollaboratorAuthor

    Landed at 729e851dc, verified by CONTENT — and closing as delivered

    PR #9192 merged. Maintainer ruling A on objectui#9061 is executed.

    reading parent 729e851dc^ main 729e851dc
    SUBJECT structuredClone in ValueDataSource.ts 0 5 (calls at :1074 and :1320)
    REVERSE, executable only — (= | return) JSON.parse(JSON.stringify 2 (:1055, :1296) 0
    getAll() (control, lit) 1 1
    this.idField (control, lit) 8 8
    mutationListeners (control, lit) 4 4
    matchesASTFilter (control, lit) 5 5

    ⭐ The reverse reading needed the discipline: a plain JSON.parse(JSON.stringify grep still returns 1 hit on main — at :1056, inside the comment that explains the removal ("structuredClone, NOT a JSON.parse(JSON.stringify(...)) round-trip"). A name survives in the comment explaining its own removal. Measuring the CALL rather than the NAME is what makes this 2→0 instead of a misleading 2→1.

    Why this card closes even though one acceptance bullet is unmet

    The bullet said the three red assertions on PR objectui#9138 would pass with that diff untouched. Only one of the three was ever a clone problem, and I verified that independently rather than accepting the report — reading the fixtures at source on that PR's head:

    data: { provider: 'value', items: ROWS },
    filter: [['owner', '=', 'me']],
    …
    expect(markers).toHaveLength(1);

    Once objectui#9138 makes the inline arm honour schema.filter, that predicate runs against rows with no owner field and the markers go to zero. Its sibling authors filter: { map: … } against rows with no map field. Neither expectation touches serialization, so no clone change of any kind could turn them green. That bullet was inherited verbatim from objectui#9138's own body, which already classified those two as "ordinary fixture triage … NOT a second decision".

    ⇒ the bullet is wrong, not unfinished. Everything this card's ruling actually required is delivered, so the card closes and the two fixtures stay with the repair that owns them (recorded on objectui#9061 and PR objectui#9138). ⚠️ If the maintainer reads that differently, reopening is one click — I would rather close it on a measured argument than leave it open against a claim that was false when it was written.

    pm:dispatched and the assignee stripped by hand.


    Generated by Claude Code

  6. removed their assignment
    on Sep 11, 2026
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:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpackage: corepriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions