You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
components(data-table): a pending inline edit is keyed by page-local row index, so a re-read that reorders the rows moves a kept edit onto another record, and Save All writes it there #12310
Filing gate ① a product defect with a named landing point.reach: the grid's save, measured once through the real ObjectGrid + data-table in jsdom, against a fake server. Not reproduced in a live console. Found by the objectui#12292 dev (report 6139004493, out-of-scope finding 1) and filed by objectui domain:ui seat 1 (seat post objectui#5560) · session_01CsyMRsFJBKNEMa4oPY1b5z. ⛔ Not a claim. Lane and grade are triage's.
Reader who acts: the seat that owns @object-ui/components' data-table and @object-ui/plugin-grid.
Data integrity: a value the user typed on one record is written to another, with a 200 and no signal. The optimistic-concurrency token landing in PR objectui#12309 does not catch it, because the wrong row's own token is current.
Dedupe: MCP search_issues on objectui, closed included, query inline edit pending change wrong row after refetch data-table pendingChanges rowIndex reorder Save All writes edit to wrong record: 14 hits, none this defect. The nearest are objectui#7188 (a cell editor scoping a lookup by the saved row) and objectui#10740 (LineItemsPanel keeping another parent's lines); both have different mechanisms.
The defect (as measured)
A grid sorted most-recently-modified first has edits staged on two rows: Bravo (estimate 21) and Alpha (estimate 11).
The row button saves Alpha. The re-read that follows moves Alpha to the top.
Bravo's pending 21 is now drawn on Alpha's row, and Save All writes update('a', { estimate_hours: 21 }). Stored afterwards: a = 21, b = 20. Bravo's edit landed on Alpha.
Mechanism
data-table keys pending edits by page-local row index (PendingChanges is a Map keyed by number). Any re-read that reorders the page moves a kept edit onto whichever record now sits at that index:
a row save's refresh;
onMutation;
the data-invalidation bus.
This predates objectui#12292.
Wanted (for triage to grade and direct)
Pending edits are keyed by the record's identity (its id), not its position, so a re-read or reorder keeps each edit on its own record. Pin: stage edits on two rows, save one so the re-read reorders the page, then Save All writes each edit to its own record.
Dedupe words: inline edit pending change wrong row after refetch; data-table pendingChanges rowIndex reorder; Save All writes edit to wrong record; grid pending edits keyed by index.
Triage: first grade, bug · priority:p1 · domain:ui · area:records, into pm:queue. Taken out of turn (data integrity). Pending edits are keyed by record id
Triage seat (seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T05:58Z. ⛔ Not a claim, ⛔ not a dispatch.
data-table's PendingChanges is keyed by the record's id (the row id the grid already resolves), not the page-local index.
Every reader of the pending map (cell draw, dirty bar, Save All, single-row save) looks up by id.
A re-read that drops a record with a pending edit keeps the edit visible as pending against that id. ⛔ It never moves the edit onto the row now at that index.
Pins:
the card's sequence: edits on two rows, save one so the re-read reorders, then Save All writes each edit to its own record;
a re-read via the invalidation bus does the same;
a page change keeps a pending edit with its record.
Claim: PM loop round 5, next slot. The direction is triage 6139302398: a pending edit belongs to its record.
data-table's PendingChanges is keyed by the record's id (the row id the grid already resolves), not the page-local index.
Every reader of the pending map looks up by id: cell draw, the dirty bar, Save All and the single-row save.
A re-read that drops a record with a pending edit keeps the edit pending against that id. ⛔ It never moves the edit onto the row now at that index.
Session: session_01CsyMRsFJBKNEMa4oPY1b5z
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12310-pending-edits-keyed-by-record
Worktree: objectui-issue-12310
Domain: domain:ui
Seat: domain:ui#1
File surface: data-table's pending-edit state, read on objectui main3092aca (which carries PR objectui#12309) at 2026-10-13T07:22Z. Stop on a breach and explain it in the report.
The table:packages/components/src/renderers/complex/data-table.tsx.
type PendingChanges = Map<number, Record<string, any>> is keyed by page-local row index.
It is read by pendingRows.of(row, rowIndex), startEdit, the cell stage paths, saveRow, cancelRowChanges and saveBatch. saveBatch resolves each key to sortedData[...] at save time.
erroredRows is a Set<number> keyed the same way.
getRowId(row, index) already resolves row.id, falling back to row-${index}.
Co-located tests and a changeset.
packages/plugin-grid/src/ObjectGrid.tsx is read only, unless the measurement shows the producer of the id is there.
⛔ The published callback shapes (onCellChange, onRowSave, onBatchSave and its { failed }) keep their arguments unless the report justifies a change.
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: no
Clause-② reason:
The pending map's key is internal state of the data-table renderer.
The published callbacks keep receiving rowIndex (computed at call time) and row, so no published type, export or accepted input moves.
None of the three contract surfaces is touched.
Responsibility: @object-ui/components' data-table (pending edits keyed by page-local index) | none: the If-Match token from objectui#12309 cannot catch it, because the wrong row's own token is current | every console user who stages inline edits on two or more rows and then sees the page re-read in a new order (a row save's refresh, onMutation, the invalidation bus) or changes page; measured on the card in jsdom through the real ObjectGrid and data-table
Thread-read: 6139302398
Serial constraints cleared: read at 2026-10-13T07:22Z on objectui main3092aca.
Triage's serial predecessor, PR objectui#12309, merged as 3092aca.
This seat's in-flight devs share no file: objectui#12295 (data-objectstack, app-shelllayout/) and objectui#12305 (app-shellconsole/home/, views/app-launcher-renderer.tsx).
os-dev-report
{
"issue": 12310,
"status": "done",
"branch": "claude/issue-12310-pending-edits-keyed-by-record",
"pr": "#12320",
"head": "9d0e438",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — the seat's session (subagent = parent's)",
"premise_still_valid": true,
"summary": "data-table now keeps a pending inline edit, the open editor (editingCell) and a failed save's flag (erroredRows) under the record's key: row.id, or the row object for an id-less row, never a position. Each pending entry keeps the row it was last staged against. Every reader (cell draw, dirty bar, Save All, row save, row cancel, pendingRow, the stage paths, the error tint) resolves the record by key when it reads, against the rows the table holds now. The published callbacks keep their shapes: rowIndex is computed at call time, and an onBatchSave entry whose record the table no longer holds (a re-read dropped it, or it is on another server page) goes out with its staged-against row and a negative rowIndex (-1, -2, ...), so objectui#12292's { failed } outcome still maps back by key. ObjectGrid.tsx is untouched: the grid's rows already carry id (ObjectGrid's $select always includes id, and ObjectStack provisions id on every table).",
"tests": "HEAD 9d0e438. RED FIRST at 3092aca on the real ObjectGrid + data-table (plugin-grid inlineEditPendingKeyedByRecord-12310.test.tsx): Tests 5 failed (5). (a) Card sequence and bus re-read: 'Unable to find an element with the text: 21' within Bravo's row; the 21 was drawn on the row now at Bravo's index. (b) Row save on a reordered page: Received { id: 'b', body: { estimate_hours: 11 } }, Expected estimate_hours 21, so Alpha's edit was written to Bravo. (c) Save All from page 2: 0 PATCHes, because the handed-over row was undefined. (d) Drop: Charlie's cell showed Bravo's 21, not 30. GREEN after the fix, through the verify lock: pnpm exec vitest run --maxWorkers=2 over packages/components/, packages/plugin-grid/, packages/plugin-list/, plugin-view ObjectView.namedViewInlineEdit-10885 / tableEditable-10976 / namedViewEditInlineFold-5144, app-shell ObjectView.inlineEditSessionOnly-5144 / InterfaceListPage.editInlineFold-5144 / writeRefusalCensus-11583, plugin-form affordanceGrantMap-12082, and 13 suites re-derived by grep that read data-table.tsx or the dirty-bar strings. Result: 'Test Files 737 passed | 1 skipped (738)', 'Tests 7489 passed | 24 skipped (7513)', VERDICT command-exit 0. Type check: 'pnpm --filter @object-ui/components type-check && pnpm --filter @object-ui/plugin-grid type-check'; both echo 'tsc --noEmit && tsc -p tsconfig.test.json', and both include globs cover the new tests. Lock VERDICT batch-last-exit 0, where the last part is that && chain. The dependency closure was built first: turbo --filter=@object-ui/components^... --filter=@object-ui/plugin-grid^..., 13/13 tasks. ABLATION, one leg (restore index keying), run after the fix was committed: data-table.tsx checked out from 3092aca under a trap; no build needed because vitest aliases @object-ui/components to src. Mutation proof: on-disk blob 2457893 equals the base blob; pendingRowKeyOf count 4 to 0; index-keyed PendingChanges declaration 0 to 1. Result: both new files red, 'Tests 9 failed (9)'. Restore proof: on-disk blob c389e1d equals the HEAD blob; git diff HEAD empty; git status clean.",
"gates": [
"HEAD 9d0e438 · vitest union (lock): VERDICT command-exit 0 · Test Files 737 passed | 1 skipped (738) · Tests 7489 passed | 24 skipped (7513)",
"HEAD 9d0e438 · pnpm --filter @object-ui/components type-check && pnpm --filter @object-ui/plugin-grid type-check (lock): VERDICT batch-last-exit 0, the last part being the && chain",
"HEAD 9d0e438 · eslint --format json on the 3 touched TS files: exit 0 · 3 files · 0 errors · 49 warnings (data-table.tsx 34 at base, 39 now: +5 no-explicit-any on row slots typed like the existing ones; lint.yml sets no --max-warnings) · narrowing proof: (1) population read from the root eslint.config.js, the only config, since neither package has its own; (2) count from the JSON output; (3) invariance: tseslint.configs.recommended, not a type-checked preset, with no parserOptions.project or projectService, and no rule in eslint-rules/ reads the file system, so this diff cannot move a verdict on an untouched file",
"HEAD 9d0e438 · exit 0: node scripts/check-changeset-presence.mjs · check:changeset-claims · check:pending-changeset-literals · check:new-line-citations (VERDICT 0 new citation(s)) · check:control-bytes · check:test-path-roots · check:vi-mock-specifiers · check:handler-key-reads · check:metadata-write-doors · check:i18n-keys · check:doc-types · check:doc-json · check:doc-fences · scripts/check-doc-links.mjs",
"Not run: the rest of the check:i18n-* family, because no string or key was added",
"NOT MEASURED: check:readme-exports (exit 1) and check:doc-snippets (exit 2). Reason: both stopped on 'type entry ./dist/index.d.ts is not on disk' for packages outside the built closure, a missing prerequisite, not a verdict. The README and .mdx edits add no code fence",
"NOT MEASURED: a live console run. Reason: the pins run in jsdom through the real components",
"Governed surface: node scripts/check-governed-queue-guard.mjs --test on the 6 paths reads NOT GOVERNED",
"CI at report time on 9d0e438: 32 check runs: 21 in_progress, 8 success, 3 skipped, 0 failed. Read once and not polled; convergence is the seat's"
],
"line_budget": "n/a: no skills/** file and no ledger with a line ceiling is touched",
"files_changed": [
".changeset/12310-pending-edits-keyed-by-record.md",
"content/docs/components/complex/data-table.mdx",
"packages/components/src/tests/data-table-pending-by-record-12310.test.tsx",
"packages/components/src/renderers/complex/data-table.tsx",
"packages/plugin-grid/README.md",
"packages/plugin-grid/src/tests/inlineEditPendingKeyedByRecord-12310.test.tsx"
],
"deviations": [
"File surface: added packages/plugin-grid/src/tests/inlineEditPendingKeyedByRecord-12310.test.tsx, because Zone 2.4 and the triage pins need the real ObjectGrid; ObjectGrid.tsx itself is untouched. Docs-driven edits per AGENTS.md commandment 2: content/docs/components/complex/data-table.mdx and packages/plugin-grid/README.md. Neither file is in the claim's named surface.",
"The ablation mutation was a whole-file 'git checkout 3092aca -- data-table.tsx' under a trap 'restore' EXIT INT TERM, proven by blob hashes and marker counts. objectstack's scripts/ablation-replace.mjs was not used: objectui has no copy, and this leg restores a file rather than replacing an anchor.",
"I stopped my own first background union run (process group 4116, launched by me) because a type-only edit to data-table.tsx landed while it ran, and re-ran the union at the final HEAD 9d0e438.",
"Commit trailers are the model-free pair objectui AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body ends with the role file's session-URL footer, not the harness's footer form."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, all through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectui/pulls (draft PR #12320; body read back 10187 bytes sent = 10187 stored, identical); POST /repos//issues/12320/assignees (zhuangjianguo); POST /repos//issues/12310/comments (this report). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches, once per stroke (2). Plus git push of the branch (not REST). No label write.",
"open_questions": [
{
"question": "Does the new negative rowIndex for a record the table no longer holds (onBatchSave entries -1, -2, ...) keep this PR at Clause-② no? The type shape { rowIndex: number; changes; row } is unchanged and @object-ui/types is not touched, but it is a value a host could not receive before. Before this fix, the same case handed over the index of whatever row sat at the edit's old position, or undefined.",
"options": [
"A: keep Clause-② no. No type, export, prop or accepted input moves. The sentinel is documented in data-table.mdx and the changeset. Cost: none.",
"B: declare Clause-② yes, and add the sentinel to the onBatchSave doc comment in packages/types/src/data-display.ts (doc-only), with @object-ui/types added to the changeset. Cost: about 15 lines, another package in the release notes, and a PR-body edit the seat would write."
],
"recommendation": "A. By the four axes: (1) real need: no in-repo host reads rowIndex for anything but echoing it into { failed }, and ObjectGrid's default does exactly that; (2) long-term: the shape is unchanged and the value tells the truth where the old one named the wrong row; (3) AI-error-proofing: a host that indexes its own array with -1 gets undefined, not another record's row; (4) startup scope: B widens the published-type surface for a doc line. If the seat reads Clause-② as covering value domains, B is cheap."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. An inline editor whose record a re-read drops stays open with its record and is not drawn anywhere; it reappears when the record returns. Until then, Enter-to-edit, Ctrl+C and arrow navigation stay suspended, as they do whenever an editor is open, and the next cell click starts a new edit. Before this change the editor moved onto another record. Read in the code, not reproduced in a browser. Also in the PR's Acceptance notes.",
"carrier: 承接者:无 · noted, not filed. saveRow and saveBatch build the next pending map from the map captured before their await, so an edit staged on another row while a save is in flight would be dropped when it settles. A reading of the code, not reproduced, and it predates this change. Dedupe words: inline edit lost during in-flight save; data-table saveRow stale pendingChanges closure; Save All drops edit staged while saving.",
"carrier: 承接者:无 · noted, not filed. getRowId still falls back to 'row-' plus the row's index for selection and React keys on id-less rows, so a selection on id-less rows follows position across a client-side sort. Grid rows always carry id. A reading of the code; there is no consumer in the grid."
]
}
os-dev-report
{
"issue": 12310,
"status": "done",
"round": "REWORK 1, per seat comment 6141569782 on PR objectui#12320",
"branch": "claude/issue-12310-pending-edits-keyed-by-record",
"pr": "#12320",
"head": "e58bf60",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — the seat's session (subagent = parent's)",
"premise_still_valid": true,
"summary": "One commit this round, e58bf60, on top of 9d0e438. It touches only packages/types/src/data-display.ts (doc comments) and the changeset. In the DataTableSchema docblocks, onCellChange and onRowSave no longer quote calls with a globalIndex argument. Each says rowIndex is computed when the call is made: the row's global index while the table holds the record. onCellChange gets -1 when a re-read or a client-side search dropped the record while its editor was open. onRowSave is never negative, because its button is drawn only on a held row. onBatchSave now says each entry is resolved by record: a record the table no longer holds is handed over with the row the edit was staged against and a negative rowIndex (-1, -2, …, distinct within one batch), and a { failed } entry names its row by the rowIndex it was handed, negatives included. The changeset adds '@object-ui/types': patch with one sentence on the sentinel, and corrects its own onCellChange sentence, which had said onCellChange always gets the row's index. No type, export or zod change; Clause-② stays no, per the seat's ruling A. Finding 2 measured once in a scratch run and confirmed: an edit staged on another row while a row save or Save All is in flight is silently dropped when the save settles. The scratch file was never committed.",
"measurement_finding_2": {
"how": "Scratch file packages/components/src/tests/scratch-12310-inflight-save.test.tsx, copied in only for the run and removed by an EXIT trap: 'SCRATCH REMOVED: yes; git status --short=[]'. It renders the real data-table through ComponentRegistry.get('data-table') with rows a/Alpha qty 10 and b/Bravo qty 20, and onRowSave or onBatchSave returning a promise held open. Steps: stage Alpha 11; press Alpha's Save row (or Save All); while the promise is pending, stage Bravo 21; read the dirty bar and Bravo's cell; release the promise; read again.",
"command": "OS_VERIFY_LOCK_SLOT=issue-12310-dev bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c 'bash scratchpad/issue-12310/r2-run.sh', which runs pnpm exec vitest run --maxWorkers=2 packages/components/src/tests/scratch-12310-inflight-save.test.tsx at HEAD e58bf60; the readings were written to a scratch jsonl file",
"output": [
{
"case": "row-save",
"calls": [
[
0,
{
"qty": "11"
}
]
],
"during": {
"dirty": "2 rows modified",
"bravo": "21"
},
"after": {
"dirty": "(no dirty bar)",
"bravo": "20",
"alpha": "10"
}
},
{
"case": "save-all",
"calls": [
[
{
"rowIndex": 0,
"changes": {
"qty": "11"
},
"row": {
"id": "a",
"name": "Alpha",
"qty": 10
}
}
]
],
"during": {
"dirty": "2 rows modified",
"bravo": "21"
},
"after": {
"dirty": "(no dirty bar)",
"bravo": "20",
"alpha": "10"
}
}
],
"reading": "Both cases are dropped. During the held save the table read '2 rows modified' and Bravo drew 21. After release there was no dirty bar and Bravo drew 20: Bravo's edit was neither sent (calls hold only Alpha's change) nor kept pending. Cause: saveRow builds 'new Map(pendingChanges)' minus the saved key from the map captured before its await, and saveBatch's success path sets 'new Map()'. isSaving disables only the save and cancel buttons, not cell editing. Alpha drawing 10 afterwards is the static data prop, which was not re-read; it is not part of the defect."
},
"tests": "HEAD e58bf60 (doc-comment and changeset round). pnpm --filter @object-ui/types type-check, run through the lock: echoed 'tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json', TYPES-TYPECHECK-EXIT=0, lock VERDICT command-exit 0. The scratch measurement passed 2 of 2; its expectations only pin the 'during' state, and its readings are above. The 737-file union was not re-run locally: this round changes doc comments and the changeset only, and the seat's gate list for this round does not name it. CI on e58bf60 is complete instead: 33 check runs, 30 success and 3 skipped (the coverage pair and dependabot); Test (shards 1-12), Type Check, Lint and Repo Gates all succeeded.",
"gates": [
"HEAD e58bf60 · pnpm --filter @object-ui/types type-check (lock): exit 0 (TYPES-TYPECHECK-EXIT=0; lock VERDICT command-exit 0)",
"HEAD e58bf60 · node scripts/check-changeset-presence.mjs: exit 0 · '4 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'",
"HEAD e58bf60 · pnpm check:changeset-claims: exit 0 · 'No pending changeset names a file this change touches.' (the self-contradiction reading passes with @object-ui/types now declared)",
"HEAD e58bf60 · pnpm check:pending-changeset-literals: exit 0",
"HEAD e58bf60 · pnpm check:new-line-citations: exit 0 · 'VERDICT new-cross-file-line-citations: 0 new citation(s)'",
"HEAD e58bf60 · pnpm check:control-bytes: exit 0",
"HEAD e58bf60 · pnpm check:handler-key-reads: exit 0 · '152 arm(s), 184 registration(s) … 56 judged … every judged read is a declared member of its arm'",
"HEAD e58bf60 · eslint --format json packages/types/src/data-display.ts: exit 0 · 1 file · 0 errors · 30 warnings, all no-explicit-any, identical to 9d0e438 (30, read with --stdin-filename) · narrowing proof: (1) population: the only config is the root eslint.config.js, since packages/types has none; (2) count: 1 file, from the JSON output; (3) invariance: tseslint.configs.recommended, not a type-checked preset, with no parserOptions.project or projectService, and no rule in eslint-rules/ reads the file system, so a doc-comment edit to this file cannot move a verdict anywhere else",
"CI read once: on e58bf60, 33 runs, 30 success and 3 skipped, 0 failed. On 9d0e438 the Test aggregate reads failure because its 11 shards (and Spec Main Shape Gate) were cancelled, superseded by the e58bf60 push"
],
"line_budget": "n/a: no skills/** file and no ledger with a line ceiling is touched",
"files_changed": [
".changeset/12310-pending-edits-keyed-by-record.md",
"content/docs/components/complex/data-table.mdx",
"packages/components/src/tests/data-table-pending-by-record-12310.test.tsx",
"packages/components/src/renderers/complex/data-table.tsx",
"packages/plugin-grid/README.md",
"packages/plugin-grid/src/tests/inlineEditPendingKeyedByRecord-12310.test.tsx",
"packages/types/src/data-display.ts"
],
"files_changed_this_round": [
".changeset/12310-pending-edits-keyed-by-record.md",
"packages/types/src/data-display.ts"
],
"deviations": [
"The first lock call for the scratch measurement and the types type-check timed out in the queue: exit 99 after 540s, holder pid 12086 running scratchpad/issue-12306/verify.sh. That is NOT MEASURED. The retry used the same slot and acquired after 284s.",
"The first successful scratch run printed its readings with console.log, which did not appear in the run output. It was re-run once, writing the readings to a scratch jsonl file; that second run is the measurement above. Neither run's file was committed.",
"The worktree removed after round 1 was recreated on the unchanged branch head 9d0e438. origin/main was not merged in: this round did not ask for it, and the merge queue rebuilds on main."
],
"pr_body_edits_for_the_seat": [
"In the 'Why Clause-② no' paragraph, replace 'The diff does not touch @object-ui/types.' with 'In @object-ui/types the diff changes doc comments only (the DataTableSchemaonCellChange, onRowSave and onBatchSave docblocks): no type, export or zod schema.'",
"In the same paragraph, replace its last sentence ('One value is new, and it is named here so the seat can rule on it: …') with 'One value is new, and the seat ruled it Clause-② no (REWORK 6141569782, option A): a negative rowIndex for a record the table no longer holds, now documented on the onBatchSave and onCellChange docblocks.'",
"In 'What changed', change the Changeset bullet to '.changeset/12310-pending-edits-keyed-by-record.md, @object-ui/componentspatch and @object-ui/typespatch.', and add the bullet '@object-ui/types: the three DataTableSchema callback docblocks describe rowIndex by record, and the negative rowIndex of a record the table no longer holds (doc comments only).'",
"In 'Acceptance notes', replace the second bullet's sentence 'This is a reading of the code, not reproduced, and it predates this change.' with 'Measured once in a scratch run on the real data-table (REWORK round). During a held row save, and during a held Save All, the dirty bar read 2 rows modified with Bravo drawing 21. After release it read nothing and Bravo drew 20, and the edit was neither sent nor kept. It predates this change; the seat files it.'",
"In 'Gates', add the line 'REWORK round, HEAD e58bf60: pnpm --filter @object-ui/types type-check exit 0; changeset-presence, changeset-claims, pending-changeset-literals, new-line-citations, control-bytes and handler-key-reads all exit 0; eslint on data-display.ts 0 errors, 30 warnings, unchanged from 9d0e438.'"
],
"mcp_calls": "0",
"api_writes": "1 REST write this round, through the fleet-write relay as objectstack-fleet[bot]: POST /repos//issues/12310/comments (this report). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches (1). Plus git push 9d0e438..e58bf60 (not REST). No label write and no PR body edit. Reads: the REWORK comment, check runs for both heads, and the comment read-back.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: ObjectGrid's defaultRowSave and defaultBatchSave await dataSource.update over the network while every cell stays editable (isSaving disables only the save and cancel buttons). Measured once on the real data-table in jsdom (measurement_finding_2), not in a live console. · evidence: an edit staged on Bravo while Alpha's row save, or a Save All, is in flight is silently dropped when the save settles. During the save the bar read '2 rows modified' and Bravo drew 21; after release there was no dirty bar, Bravo drew 20, and only Alpha's change was sent. saveRow rebuilds the map from the pendingChanges captured before its await, and saveBatch's success path sets an empty Map. Predates objectui#12310. · dedupe words: inline edit lost during in-flight save; data-table saveRow stale pendingChanges closure; Save All drops edit staged while saving; isSaving does not disable cell editing",
"carrier: 承接者:无 · noted, not filed. An editor whose record a re-read drops stays an Acceptance note, per the seat.",
"carrier: 承接者:无 · noted, not filed. getRowId's index fallback for selection on id-less rows stays an Acceptance note, per the seat."
]
}
Merged through the merge queue at 2026-10-13T09:10Z. This card closed completed by the PR's Fixes #12310. No other lane card closed at the merge.
Landed by content, not by sha. All 7 of the PR's files on maina8051d6 are blob-identical to the accepted head e58bf60: REWORK 6141569782, ACCEPT 6141972553.
pm:dispatched was removed and read back. domain:ui, area:records, the type and the priority stay.
The changeset rides the next objectui release: @object-ui/componentspatch and @object-ui/typespatch.
What is on main:data-table keeps a pending inline edit, the open editor and a failed save's flag under the record's key (row.id, else the row object), never a position.
A re-read that reorders the rows, a bus re-read, or a page change leaves each edit on its own record. Save All writes it there.
An edit whose record the held rows no longer include stays pending. It goes out with the row it was typed on and a negative rowIndex, as @object-ui/types now documents on onBatchSave and onCellChange.
Filed from this card: objectui#12322. An edit staged on another row while a save is in flight is dropped when the save settles. This was measured in the REWORK round, and it predates this card.
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·priority:p1·domain:ui·area:records, intopm:queue. Taken out of turn (data integrity). Pending edits are keyed by record idTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T05:58Z. ⛔ Not a claim, ⛔ not a dispatch.data-table'sPendingChangesis keyed by the record's id (the row id the grid already resolves), not the page-local index.ObjectGrid's save functions. This card's fix is in@object-ui/components'data-table. If the claim finds it must touch the sameObjectGridlines, it lands after fix(plugin-grid,plugin-list,components,types): the grid's inline-edit saves send each row's If-Match, and Save All reports per row (objectui#12292) #12309. ⛔ Not folded into it.packages/components/src/(data-table) (domain:ui); rationale: an edit belongs to its record.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 5, next slot. The direction is triage
6139302398: a pending edit belongs to its record.data-table'sPendingChangesis keyed by the record's id (the row id the grid already resolves), not the page-local index.Session:
session_01CsyMRsFJBKNEMa4oPY1b5zAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12310-pending-edits-keyed-by-recordWorktree:
objectui-issue-12310Domain:
domain:uiSeat:
domain:ui#1File surface:
data-table's pending-edit state, read on objectuimain3092aca(which carries PR objectui#12309) at 2026-10-13T07:22Z. Stop on a breach and explain it in the report.packages/components/src/renderers/complex/data-table.tsx.type PendingChanges = Map<number, Record<string, any>>is keyed by page-local row index.pendingRows.of(row, rowIndex),startEdit, the cell stage paths,saveRow,cancelRowChangesandsaveBatch.saveBatchresolves each key tosortedData[...]at save time.erroredRowsis aSet<number>keyed the same way.getRowId(row, index)already resolvesrow.id, falling back torow-${index}.packages/plugin-grid/src/ObjectGrid.tsxis read only, unless the measurement shows the producer of the id is there.onCellChange,onRowSave,onBatchSaveand its{ failed }) keep their arguments unless the report justifies a change.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: no
Clause-② reason:
data-tablerenderer.rowIndex(computed at call time) androw, so no published type, export or accepted input moves.Responsibility:
@object-ui/components' data-table (pending edits keyed by page-local index) | none: the If-Match token from objectui#12309 cannot catch it, because the wrong row's own token is current | every console user who stages inline edits on two or more rows and then sees the page re-read in a new order (a row save's refresh, onMutation, the invalidation bus) or changes page; measured on the card in jsdom through the real ObjectGrid and data-tableThread-read: 6139302398
Serial constraints cleared: read at 2026-10-13T07:22Z on objectui
main3092aca.3092aca.data-table.tsxorplugin-grid/src(objectui#12313, chore(deps): bump next from 16.3.6 to 16.3.8 #11845, build(deps): Bump next from 16.3.6 to 16.3.8 in /apps/site in the npm_and_yarn group across 1 directory #11844 and chore: release packages (next) #11600 read).data-objectstack,app-shelllayout/) and objectui#12305 (app-shellconsole/home/,views/app-launcher-renderer.tsx).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 12310,
"status": "done",
"branch": "claude/issue-12310-pending-edits-keyed-by-record",
"pr": "#12320",
"head": "9d0e438",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — the seat's session (subagent = parent's)",
"premise_still_valid": true,
"summary": "data-table now keeps a pending inline edit, the open editor (editingCell) and a failed save's flag (erroredRows) under the record's key: row.id, or the row object for an id-less row, never a position. Each pending entry keeps the row it was last staged against. Every reader (cell draw, dirty bar, Save All, row save, row cancel, pendingRow, the stage paths, the error tint) resolves the record by key when it reads, against the rows the table holds now. The published callbacks keep their shapes: rowIndex is computed at call time, and an onBatchSave entry whose record the table no longer holds (a re-read dropped it, or it is on another server page) goes out with its staged-against row and a negative rowIndex (-1, -2, ...), so objectui#12292's { failed } outcome still maps back by key. ObjectGrid.tsx is untouched: the grid's rows already carry id (ObjectGrid's $select always includes id, and ObjectStack provisions id on every table).",
"tests": "HEAD 9d0e438. RED FIRST at 3092aca on the real ObjectGrid + data-table (plugin-grid inlineEditPendingKeyedByRecord-12310.test.tsx): Tests 5 failed (5). (a) Card sequence and bus re-read: 'Unable to find an element with the text: 21' within Bravo's row; the 21 was drawn on the row now at Bravo's index. (b) Row save on a reordered page: Received { id: 'b', body: { estimate_hours: 11 } }, Expected estimate_hours 21, so Alpha's edit was written to Bravo. (c) Save All from page 2: 0 PATCHes, because the handed-over row was undefined. (d) Drop: Charlie's cell showed Bravo's 21, not 30. GREEN after the fix, through the verify lock: pnpm exec vitest run --maxWorkers=2 over packages/components/, packages/plugin-grid/, packages/plugin-list/, plugin-view ObjectView.namedViewInlineEdit-10885 / tableEditable-10976 / namedViewEditInlineFold-5144, app-shell ObjectView.inlineEditSessionOnly-5144 / InterfaceListPage.editInlineFold-5144 / writeRefusalCensus-11583, plugin-form affordanceGrantMap-12082, and 13 suites re-derived by grep that read data-table.tsx or the dirty-bar strings. Result: 'Test Files 737 passed | 1 skipped (738)', 'Tests 7489 passed | 24 skipped (7513)', VERDICT command-exit 0. Type check: 'pnpm --filter @object-ui/components type-check && pnpm --filter @object-ui/plugin-grid type-check'; both echo 'tsc --noEmit && tsc -p tsconfig.test.json', and both include globs cover the new tests. Lock VERDICT batch-last-exit 0, where the last part is that && chain. The dependency closure was built first: turbo --filter=@object-ui/components^... --filter=@object-ui/plugin-grid^..., 13/13 tasks. ABLATION, one leg (restore index keying), run after the fix was committed: data-table.tsx checked out from 3092aca under a trap; no build needed because vitest aliases @object-ui/components to src. Mutation proof: on-disk blob 2457893 equals the base blob; pendingRowKeyOf count 4 to 0; index-keyed PendingChanges declaration 0 to 1. Result: both new files red, 'Tests 9 failed (9)'. Restore proof: on-disk blob c389e1d equals the HEAD blob; git diff HEAD empty; git status clean.",
"gates": [
"HEAD 9d0e438 · vitest union (lock): VERDICT command-exit 0 · Test Files 737 passed | 1 skipped (738) · Tests 7489 passed | 24 skipped (7513)",
"HEAD 9d0e438 · pnpm --filter @object-ui/components type-check && pnpm --filter @object-ui/plugin-grid type-check (lock): VERDICT batch-last-exit 0, the last part being the && chain",
"HEAD 9d0e438 · eslint --format json on the 3 touched TS files: exit 0 · 3 files · 0 errors · 49 warnings (data-table.tsx 34 at base, 39 now: +5 no-explicit-any on row slots typed like the existing ones; lint.yml sets no --max-warnings) · narrowing proof: (1) population read from the root eslint.config.js, the only config, since neither package has its own; (2) count from the JSON output; (3) invariance: tseslint.configs.recommended, not a type-checked preset, with no parserOptions.project or projectService, and no rule in eslint-rules/ reads the file system, so this diff cannot move a verdict on an untouched file",
"HEAD 9d0e438 · exit 0: node scripts/check-changeset-presence.mjs · check:changeset-claims · check:pending-changeset-literals · check:new-line-citations (VERDICT 0 new citation(s)) · check:control-bytes · check:test-path-roots · check:vi-mock-specifiers · check:handler-key-reads · check:metadata-write-doors · check:i18n-keys · check:doc-types · check:doc-json · check:doc-fences · scripts/check-doc-links.mjs",
"Not run: the rest of the check:i18n-* family, because no string or key was added",
"NOT MEASURED: check:readme-exports (exit 1) and check:doc-snippets (exit 2). Reason: both stopped on 'type entry ./dist/index.d.ts is not on disk' for packages outside the built closure, a missing prerequisite, not a verdict. The README and .mdx edits add no code fence",
"NOT MEASURED: a live console run. Reason: the pins run in jsdom through the real components",
"Governed surface: node scripts/check-governed-queue-guard.mjs --test on the 6 paths reads NOT GOVERNED",
"CI at report time on 9d0e438: 32 check runs: 21 in_progress, 8 success, 3 skipped, 0 failed. Read once and not polled; convergence is the seat's"
],
"line_budget": "n/a: no skills/** file and no ledger with a line ceiling is touched",
"files_changed": [
".changeset/12310-pending-edits-keyed-by-record.md",
"content/docs/components/complex/data-table.mdx",
"packages/components/src/tests/data-table-pending-by-record-12310.test.tsx",
"packages/components/src/renderers/complex/data-table.tsx",
"packages/plugin-grid/README.md",
"packages/plugin-grid/src/tests/inlineEditPendingKeyedByRecord-12310.test.tsx"
],
"deviations": [
"File surface: added packages/plugin-grid/src/tests/inlineEditPendingKeyedByRecord-12310.test.tsx, because Zone 2.4 and the triage pins need the real ObjectGrid; ObjectGrid.tsx itself is untouched. Docs-driven edits per AGENTS.md commandment 2: content/docs/components/complex/data-table.mdx and packages/plugin-grid/README.md. Neither file is in the claim's named surface.",
"The ablation mutation was a whole-file 'git checkout 3092aca -- data-table.tsx' under a trap 'restore' EXIT INT TERM, proven by blob hashes and marker counts. objectstack's scripts/ablation-replace.mjs was not used: objectui has no copy, and this leg restores a file rather than replacing an anchor.",
"I stopped my own first background union run (process group 4116, launched by me) because a type-only edit to data-table.tsx landed while it ran, and re-ran the union at the final HEAD 9d0e438.",
"Commit trailers are the model-free pair objectui AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body ends with the role file's session-URL footer, not the harness's footer form."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, all through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectui/pulls (draft PR #12320; body read back 10187 bytes sent = 10187 stored, identical); POST /repos//issues/12320/assignees (zhuangjianguo); POST /repos//issues/12310/comments (this report). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches, once per stroke (2). Plus git push of the branch (not REST). No label write.",
"open_questions": [
{
"question": "Does the new negative rowIndex for a record the table no longer holds (onBatchSave entries -1, -2, ...) keep this PR at Clause-② no? The type shape { rowIndex: number; changes; row } is unchanged and @object-ui/types is not touched, but it is a value a host could not receive before. Before this fix, the same case handed over the index of whatever row sat at the edit's old position, or undefined.",
"options": [
"A: keep Clause-② no. No type, export, prop or accepted input moves. The sentinel is documented in data-table.mdx and the changeset. Cost: none.",
"B: declare Clause-② yes, and add the sentinel to the onBatchSave doc comment in packages/types/src/data-display.ts (doc-only), with @object-ui/types added to the changeset. Cost: about 15 lines, another package in the release notes, and a PR-body edit the seat would write."
],
"recommendation": "A. By the four axes: (1) real need: no in-repo host reads rowIndex for anything but echoing it into { failed }, and ObjectGrid's default does exactly that; (2) long-term: the shape is unchanged and the value tells the truth where the old one named the wrong row; (3) AI-error-proofing: a host that indexes its own array with -1 gets undefined, not another record's row; (4) startup scope: B widens the published-type surface for a doc line. If the seat reads Clause-② as covering value domains, B is cheap."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. An inline editor whose record a re-read drops stays open with its record and is not drawn anywhere; it reappears when the record returns. Until then, Enter-to-edit, Ctrl+C and arrow navigation stay suspended, as they do whenever an editor is open, and the next cell click starts a new edit. Before this change the editor moved onto another record. Read in the code, not reproduced in a browser. Also in the PR's Acceptance notes.",
"carrier: 承接者:无 · noted, not filed. saveRow and saveBatch build the next pending map from the map captured before their await, so an edit staged on another row while a save is in flight would be dropped when it settles. A reading of the code, not reproduced, and it predates this change. Dedupe words: inline edit lost during in-flight save; data-table saveRow stale pendingChanges closure; Save All drops edit staged while saving.",
"carrier: 承接者:无 · noted, not filed. getRowId still falls back to 'row-' plus the row's index for selection and React keys on id-less rows, so a selection on id-less rows follows position across a client-side sort. Grid rows always carry id. A reading of the code; there is no consumer in the grid."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 12310,
"status": "done",
"round": "REWORK 1, per seat comment 6141569782 on PR objectui#12320",
"branch": "claude/issue-12310-pending-edits-keyed-by-record",
"pr": "#12320",
"head": "e58bf60",
"session": "session_01CsyMRsFJBKNEMa4oPY1b5z — the seat's session (subagent = parent's)",
"premise_still_valid": true,
"summary": "One commit this round, e58bf60, on top of 9d0e438. It touches only packages/types/src/data-display.ts (doc comments) and the changeset. In the DataTableSchema docblocks, onCellChange and onRowSave no longer quote calls with a globalIndex argument. Each says rowIndex is computed when the call is made: the row's global index while the table holds the record. onCellChange gets -1 when a re-read or a client-side search dropped the record while its editor was open. onRowSave is never negative, because its button is drawn only on a held row. onBatchSave now says each entry is resolved by record: a record the table no longer holds is handed over with the row the edit was staged against and a negative rowIndex (-1, -2, …, distinct within one batch), and a { failed } entry names its row by the rowIndex it was handed, negatives included. The changeset adds '@object-ui/types': patch with one sentence on the sentinel, and corrects its own onCellChange sentence, which had said onCellChange always gets the row's index. No type, export or zod change; Clause-② stays no, per the seat's ruling A. Finding 2 measured once in a scratch run and confirmed: an edit staged on another row while a row save or Save All is in flight is silently dropped when the save settles. The scratch file was never committed.",
"measurement_finding_2": {
"how": "Scratch file packages/components/src/tests/scratch-12310-inflight-save.test.tsx, copied in only for the run and removed by an EXIT trap: 'SCRATCH REMOVED: yes; git status --short=[]'. It renders the real data-table through ComponentRegistry.get('data-table') with rows a/Alpha qty 10 and b/Bravo qty 20, and onRowSave or onBatchSave returning a promise held open. Steps: stage Alpha 11; press Alpha's Save row (or Save All); while the promise is pending, stage Bravo 21; read the dirty bar and Bravo's cell; release the promise; read again.",
"command": "OS_VERIFY_LOCK_SLOT=issue-12310-dev bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c 'bash scratchpad/issue-12310/r2-run.sh', which runs pnpm exec vitest run --maxWorkers=2 packages/components/src/tests/scratch-12310-inflight-save.test.tsx at HEAD e58bf60; the readings were written to a scratch jsonl file",
"output": [
{
"case": "row-save",
"calls": [
[
0,
{
"qty": "11"
}
]
],
"during": {
"dirty": "2 rows modified",
"bravo": "21"
},
"after": {
"dirty": "(no dirty bar)",
"bravo": "20",
"alpha": "10"
}
},
{
"case": "save-all",
"calls": [
[
{
"rowIndex": 0,
"changes": {
"qty": "11"
},
"row": {
"id": "a",
"name": "Alpha",
"qty": 10
}
}
]
],
"during": {
"dirty": "2 rows modified",
"bravo": "21"
},
"after": {
"dirty": "(no dirty bar)",
"bravo": "20",
"alpha": "10"
}
}
],
"reading": "Both cases are dropped. During the held save the table read '2 rows modified' and Bravo drew 21. After release there was no dirty bar and Bravo drew 20: Bravo's edit was neither sent (calls hold only Alpha's change) nor kept pending. Cause: saveRow builds 'new Map(pendingChanges)' minus the saved key from the map captured before its await, and saveBatch's success path sets 'new Map()'. isSaving disables only the save and cancel buttons, not cell editing. Alpha drawing 10 afterwards is the static data prop, which was not re-read; it is not part of the defect."
},
"tests": "HEAD e58bf60 (doc-comment and changeset round). pnpm --filter @object-ui/types type-check, run through the lock: echoed 'tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json', TYPES-TYPECHECK-EXIT=0, lock VERDICT command-exit 0. The scratch measurement passed 2 of 2; its expectations only pin the 'during' state, and its readings are above. The 737-file union was not re-run locally: this round changes doc comments and the changeset only, and the seat's gate list for this round does not name it. CI on e58bf60 is complete instead: 33 check runs, 30 success and 3 skipped (the coverage pair and dependabot); Test (shards 1-12), Type Check, Lint and Repo Gates all succeeded.",
"gates": [
"HEAD e58bf60 · pnpm --filter @object-ui/types type-check (lock): exit 0 (TYPES-TYPECHECK-EXIT=0; lock VERDICT command-exit 0)",
"HEAD e58bf60 · node scripts/check-changeset-presence.mjs: exit 0 · '4 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'",
"HEAD e58bf60 · pnpm check:changeset-claims: exit 0 · 'No pending changeset names a file this change touches.' (the self-contradiction reading passes with @object-ui/types now declared)",
"HEAD e58bf60 · pnpm check:pending-changeset-literals: exit 0",
"HEAD e58bf60 · pnpm check:new-line-citations: exit 0 · 'VERDICT new-cross-file-line-citations: 0 new citation(s)'",
"HEAD e58bf60 · pnpm check:control-bytes: exit 0",
"HEAD e58bf60 · pnpm check:handler-key-reads: exit 0 · '152 arm(s), 184 registration(s) … 56 judged … every judged read is a declared member of its arm'",
"HEAD e58bf60 · eslint --format json packages/types/src/data-display.ts: exit 0 · 1 file · 0 errors · 30 warnings, all no-explicit-any, identical to 9d0e438 (30, read with --stdin-filename) · narrowing proof: (1) population: the only config is the root eslint.config.js, since packages/types has none; (2) count: 1 file, from the JSON output; (3) invariance: tseslint.configs.recommended, not a type-checked preset, with no parserOptions.project or projectService, and no rule in eslint-rules/ reads the file system, so a doc-comment edit to this file cannot move a verdict anywhere else",
"CI read once: on e58bf60, 33 runs, 30 success and 3 skipped, 0 failed. On 9d0e438 the Test aggregate reads failure because its 11 shards (and Spec Main Shape Gate) were cancelled, superseded by the e58bf60 push"
],
"line_budget": "n/a: no skills/** file and no ledger with a line ceiling is touched",
"files_changed": [
".changeset/12310-pending-edits-keyed-by-record.md",
"content/docs/components/complex/data-table.mdx",
"packages/components/src/tests/data-table-pending-by-record-12310.test.tsx",
"packages/components/src/renderers/complex/data-table.tsx",
"packages/plugin-grid/README.md",
"packages/plugin-grid/src/tests/inlineEditPendingKeyedByRecord-12310.test.tsx",
"packages/types/src/data-display.ts"
],
"files_changed_this_round": [
".changeset/12310-pending-edits-keyed-by-record.md",
"packages/types/src/data-display.ts"
],
"deviations": [
"The first lock call for the scratch measurement and the types type-check timed out in the queue: exit 99 after 540s, holder pid 12086 running scratchpad/issue-12306/verify.sh. That is NOT MEASURED. The retry used the same slot and acquired after 284s.",
"The first successful scratch run printed its readings with console.log, which did not appear in the run output. It was re-run once, writing the readings to a scratch jsonl file; that second run is the measurement above. Neither run's file was committed.",
"The worktree removed after round 1 was recreated on the unchanged branch head 9d0e438. origin/main was not merged in: this round did not ask for it, and the merge queue rebuilds on main."
],
"pr_body_edits_for_the_seat": [
"In the 'Why Clause-② no' paragraph, replace 'The diff does not touch
@object-ui/types.' with 'In@object-ui/typesthe diff changes doc comments only (theDataTableSchemaonCellChange,onRowSaveandonBatchSavedocblocks): no type, export or zod schema.'","In the same paragraph, replace its last sentence ('One value is new, and it is named here so the seat can rule on it: …') with 'One value is new, and the seat ruled it Clause-② no (REWORK 6141569782, option A): a negative
rowIndexfor a record the table no longer holds, now documented on theonBatchSaveandonCellChangedocblocks.'","In 'What changed', change the Changeset bullet to '
.changeset/12310-pending-edits-keyed-by-record.md,@object-ui/componentspatchand@object-ui/typespatch.', and add the bullet '@object-ui/types: the threeDataTableSchemacallback docblocks describerowIndexby record, and the negativerowIndexof a record the table no longer holds (doc comments only).'","In 'Acceptance notes', replace the second bullet's sentence 'This is a reading of the code, not reproduced, and it predates this change.' with 'Measured once in a scratch run on the real data-table (REWORK round). During a held row save, and during a held Save All, the dirty bar read 2 rows modified with Bravo drawing 21. After release it read nothing and Bravo drew 20, and the edit was neither sent nor kept. It predates this change; the seat files it.'",
"In 'Gates', add the line 'REWORK round, HEAD
e58bf60:pnpm --filter @object-ui/types type-checkexit 0; changeset-presence, changeset-claims, pending-changeset-literals, new-line-citations, control-bytes and handler-key-reads all exit 0; eslint ondata-display.ts0 errors, 30 warnings, unchanged from9d0e438.'"],
"mcp_calls": "0",
"api_writes": "1 REST write this round, through the fleet-write relay as objectstack-fleet[bot]: POST /repos//issues/12310/comments (this report). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches (1). Plus git push 9d0e438..e58bf60 (not REST). No label write and no PR body edit. Reads: the REWORK comment, check runs for both heads, and the comment read-back.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: ObjectGrid's defaultRowSave and defaultBatchSave await dataSource.update over the network while every cell stays editable (isSaving disables only the save and cancel buttons). Measured once on the real data-table in jsdom (measurement_finding_2), not in a live console. · evidence: an edit staged on Bravo while Alpha's row save, or a Save All, is in flight is silently dropped when the save settles. During the save the bar read '2 rows modified' and Bravo drew 21; after release there was no dirty bar, Bravo drew 20, and only Alpha's change was sent. saveRow rebuilds the map from the pendingChanges captured before its await, and saveBatch's success path sets an empty Map. Predates objectui#12310. · dedupe words: inline edit lost during in-flight save; data-table saveRow stale pendingChanges closure; Save All drops edit staged while saving; isSaving does not disable cell editing",
"carrier: 承接者:无 · noted, not filed. An editor whose record a re-read drops stays an Acceptance note, per the seat.",
"carrier: 承接者:无 · noted, not filed. getRowId's index fallback for selection on id-less rows stays an Acceptance note, per the seat."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: objectui PR #12320 →
maina8051d6·domain:uiseat 1 ·session_01CsyMRsFJBKNEMa4oPY1b5z· 2026-10-13T09:11Zcompletedby the PR'sFixes #12310. No other lane card closed at the merge.maina8051d6are blob-identical to the accepted heade58bf60: REWORK6141569782, ACCEPT6141972553.pm:dispatchedwas removed and read back.domain:ui,area:records, the type and the priority stay.@object-ui/componentspatchand@object-ui/typespatch.main:data-tablekeeps a pending inline edit, the open editor and a failed save's flag under the record's key (row.id, else the row object), never a position.rowIndex, as@object-ui/typesnow documents ononBatchSaveandonCellChange.Generated by Claude Code