Repository navigation
fix(plugin-form,fields): a superseded load or save commits nothing in the line-items panel; useRecordQuery resets when it can no longer query (objectui#10712) - #10773
Conversation
… the line-items panel; `useRecordQuery` resets when it can no longer query (objectui#10712) `LineItemsPanel`: a load a newer one has superseded commits neither its rows nor the end of the loading state (the run number `loadSeqRef` now guards the rows commit and the `finally`). The save reloads through the panel's latest `load` only while the parent it saved is still on screen, and writes its failure only then; a save whose parent has left the screen re-reads nothing and reports nothing. `useRecordQuery`: the reset is keyed on `canQuery` (`enabled`, the data source and the object together) and is the same `reset()` a consumer calls. It moves the run number, so a read in flight commits nothing, ends `loading`, drops a pending debounced search and clears the query state. A pending debounced search is also dropped whenever `runQuery` changes, since the fetch effect re-runs the current query with the typed term then. Pins: `LineItemsPanel.supersededRun-10712.test.tsx` and `useRecordQuery.cannotQuery-10712.test.tsx`; one `patch` changeset per package. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…le it has unsaved edits (objectui#10712 R3) A change to a load input other than the parent (sort, limit, filter) is held while the panel holds unsaved edits for the current parent: dirty, the rows held for `parentId`, and the last run's parent (and adapter, child object, relationship field) equal to the current ones. A held change issues no read and is not numbered. The next read that runs carries it: the post-save reload, a parent move, or the commit of a same-parent read that was in flight at the hold, which replays one read. The precedent is the default form's bus hold (`formDirtyRef` / `heldChangeRef` in ObjectForm.tsx). The objectui#10740 control row "a RE-load of the SAME parent that fails keeps the held edits" keeps its assertions and changes its trigger to a post-save reload that fails, since a limit change no longer reads while edits are held. Pin: `LineItemsPanel.dirtyReloadHold-10712.test.tsx`; the plugin-form changeset covers R3, frontmatter unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Setup: ① Derived judgmentsR1–R6 base readings reproduced. Pure-base leg (base panel and hook blobs under the head pins): Interleavings tried at head (throwaway probes), all as the fix predicts: a save landing after a hold with the parent moved meanwhile (p2 read once with the new input, no third read, no replay); two quick input changes while dirty (one post-save read carrying both); a held flag across a parent swap (p2 read once, its commit replays nothing); an in-flight same-parent read, a hold, a parent move before its commit (stale p1 commit refused, no replay, four reads); unmount with a held flag and a read in flight (no throw, no read after); held change, failed save, landed save (one read carrying the change); refused filter while dirty (no read, notice drawn, Save stays ON over the hidden edited rows exactly as at base, and the later fix of the filter is held too); the in-flight read FAILS after a hold (no replay, edits kept, the flag survives, the post-save reload carries it); StrictMode mount (double read pre-existing, the last commits, hold and carry unchanged); relationshipField change while dirty is read; parentId to undefined and back is read. Hook: canQuery flapping with a read in flight (stale answer commits nothing), reset() mid-debounce then a new term (only the new term fires), unmount mid-debounce (nothing fires), StrictMode filter change in the window (exactly one more read), pageSize change in the window (one read, carries the term), refetch while disabled (nothing), total not reset (body observation confirmed). R3 against the ruling. The three clauses are present verbatim ( objectui#10740 row edit. Trigger moved from a Hooks and closures. No ref written during render; Pins and ablations. No pin tautological: every non-control row of the three new pins is red on its base leg on the asserted Type-check. Finding, class (a), R6's defect by a sibling trigger, at head. The cleanup effect is keyed on ② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL What stops the landing:
|
…ort or page change too (objectui#10712) The debounce cleanup effect was keyed on `runQuery`, whose identity does not move on a `sort` or `page` change, so a `setSearch` timer armed before such a change stayed live and ran last with the older sort or page: the records shown were unsorted while `sort` read `name asc`, or page 1 while `page` read 2. The cleanup is now keyed on what the fetch effect is keyed on: `runQuery`, `page` and `sort`. Two pin rows (sort-in-window, page-in-window), each red at the previous head on its `find` count. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…ectui#10712) The pin's header described the debounce defect through a filter, object or data-source change only, and the fix as keyed on `runQuery`. It now names the sort and page triggers the two round-3 rows pin, and the fix as keyed on what the fetch effect is keyed on. Comment only. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: A narrow round-3 review, run in-seat by the dispatching seat, of the delta ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10712
Clause-②: no
PR 2 of 2 for objectui#10712. PR objectui#10723 (merged
4345558,Refs #10712) carried the six record forms anduseRecordQuery'srunQuery. This PR carries the six rows seat 5's release note5852404775listed for PR 2:LineItemsPanel's load and save (R1–R2), its dirty-reload hold (R3, per the seat's ruling5855609171, option A, in round 2), anduseRecordQuery's reset and debounce (R4–R6, with R6's sort / page sibling, found by the round-2 contract review5856013809, fixed in round 3). No export, prop, schema key or registration moves;@object-ui/plugin-formand@object-ui/fieldspublish the same surface. Dispatched by thedomain:uiseat 2, sessionhttps://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN(mode:subagent), executing triage5839162848, release note5852404775and ruling5855609171.What changed
packages/plugin-form/src/LineItemsPanel.tsx(+120 / −8 against base25c7d584e4; round 2 added +76 / −4).setRows/setOriginal/setDirty/setRowsHeldFor) sits behindif (!isCurrent()) return;, taken right after the awaitedfind, and thefinallyreleasesloadingonly for the current run (if (isCurrent()) setLoading(false)). ThesetError(null)after the commit keeps its rule (cleared on a commit, objectui#10682) and no longer needs its ownisCurrent().savedParent) and aparentStillShown()predicate read fromlatestLoadRef(the panel's latestloadandparentId, written from an effect keyed on both, never during render); after the batch it reloads throughlatestLoadRef.current.load('save')only while that parent is still on screen, and writes its failure only then.save's dependency list dropsload, which it no longer reads.loadtakes anorigin('inputs'from the fetch effect,'save'from the post-save reload, the default). An'inputs'read is HELD when the panel holds unsaved edits for the CURRENT parent:dirty, the rows held forparentId, and the last run's parent, adapter, child object and relationship field equal to the current ones. A held read is not numbered (it supersedes nothing) and setsheldReloadRef; every run that does start clears that flag, since every run reads the panel's current inputs. A same-parent read that was in flight at the hold replays it once on its commit through aheldReplaystate bump the fetch effect is keyed on (the panel is clean after that commit, so the replay is not held). The valuesloadreads to decide this live in refs, so an edit does not moveload:heldEditsRefmirrorsdirty/rowsHeldForfrom an effect declared ahead ofload;lastRunRefis written where a run is numbered. The precedent is the default form's bus hold,formDirtyRef/heldChangeRefinObjectForm.tsx(unsaved input holds the re-read; one is replayed when the form is pristine again or its save lands), named in the comment.dirty,rowsHeldForequalsparentId, the last run's parent equalsparentId). The adapter / child object / relationship-field identity is added to it so the hold covers exactly the inputs the ruling names (sort, limit, filter): rows loaded from another adapter or another object are not the same rows, so a change of either is read. Pinned as controls.loadstill releasesloadingbefore any await, so a declining run is never superseded at thefinally.packages/fields/src/widgets/useRecordQuery.ts(+52 / −30 against base; round 3 added +13 / −7). Oneresetcallback (the consumer'sreset()and the reset effect's body) bumpsrunSeqRef, clears the debounce timer, endsloading, and clears the query state. The reset effect is keyed oncanQuery(enabledAND a data source withfindAND an object name) instead ofenabled. AclearDebouncecallback replaces the three inlineclearTimeoutspellings. The debounce cleanup effect (formerly unmount only) is keyed on what the fetch effect is keyed on:runQuery(moved byenabled, the data source, the object,pageSize,paginate, the filter,expandandsearchFields) and, since round 3,pageandsort, which move without movingrunQuery. So a pending debounced search is dropped whenever the fetch effect is about to run a query with the typed term, and on unmount. The header bullet about clearing is reworded.runQueryalone (round 2), the cleanup let asetSearchtimer armed before atoggleSort/setSortorsetPageinside the window stay live:setSearcharms it with page 1 and the sort in scope, the fetch effect ran the query with the new sort or page and the typed term, and the timer then ran as the latest call with the older sort or page, so its answer won. The reviewer offered two fixes:clearDebounce()at the top of the fetch effect body, orpageandsortin the existing cleanup's key. The second is one dependency-list edit on the effect that already exists and already covers unmount, so it is the fix.refetch()is not a clearing point (source reading, not pinned): called in the same event as asetSearch, its closure still holds the previous term, so dropping the armed timer there would drop the typed one.savedropsload(it readslatestLoadRef), thelatestLoadRefeffect is keyed[load, parentId], theheldEditsRefmirror effect[dirty, rowsHeldFor], and the fetch effect goes from[load]to[load, heldReplay];load's own list is unchanged. In the hook, the reset effect goes from[enabled]to[canQuery, reset], the debounce cleanup effect from[]to[runQuery, page, sort, clearDebounce](round 2 had[runQuery, clearDebounce]),setSearchgainsclearDebounce, and the two new callbacks are keyed[](clearDebounce) and[clearDebounce](reset). The hook's fetch effect ([canQuery, page, sort, runQuery]) andrunQueryare unchanged.packages/plugin-form/src/LineItemsPanel.supersededRun-10712.test.tsx(10 rows, R1–R2),packages/plugin-form/src/LineItemsPanel.dirtyReloadHold-10712.test.tsx(14 rows, R3, round 2),packages/fields/src/widgets/useRecordQuery.cannotQuery-10712.test.tsx(12 rows, R4–R6: 10 in round 1, the sort-in-window and page-in-window rows in round 3, whose header now names both triggers).LineItemsPanel.heldForAnotherParent-10740.test.tsx, the row "a RE-load of the SAME parent that fails keeps the held edits: the edited line is drawn editable, Save is on, and the save carries l1 under p1". Exactly what changed: its TRIGGER. It re-readp1through a host re-render withlimit: 200whilep1's edit was held; under the hold that re-render issues no read, so the row would have waited forever for its secondfind. It now presses Save, lands the batch, and fails the post-save reload (the secondfind, stillp1's). Every assertion is unchanged: the edited line drawn, inputs enabled, no placeholder, Save on, and the save (now the row's second save) carryingupdate l1 { label: 'p1 line edited', po: 'p1' }. The property it keeps — a same-parent re-read that fails leaves the author's lines drawn, editable and saveable under that parent — is the same, exercised through the one same-parent re-read that still runs while edits are held. The row's comment and the pin header's bullet say so. The other eight rows are byte-identical and green.supersededRun-10712's "the reload after a save reads the panel's CURRENT inputs, not the ones captured at the click" re-rendered withlimit: 200while the save was in flight and expected an immediate secondfind; under the hold it now asserts no read is issued for the change (findstill 1), and that the post-save reload (now the secondfind) carries$top: 200. Ablation A5 (the capturedload) still reddens it.patchchangesets, one per package, no new changeset in round 3. The plugin-form one gained an R3 paragraph in round 2; its frontmatter is byte-identical. The fields one is unchanged since round 1: its closing sentence 「Now a pending debounced search is dropped when the query changes.」, which the round-2 review measured false atbef026bdc5for a sort or page change, is now true for every inputrunQueryreads pluspageandsort(pinned for filter, sort and page; the other inputs moverunQuerythe same way the filter does).The six rows, measured first
Every measurement is the new pin run on the UNTOUCHED base sources (
25c7d584e4for R1–R2 and R4–R6; the round-1 head5cd02243bbfor R3), before the source edit, through the realSchemaRendererandrecord:line_items(panel) orrenderHook(hook), everyfindandbatchTransactiona deferred promise settled by hand.loadingscoped to the runp1's read held, host moves top2,p2answersp2 line, thenp1answers late: drawn lines[](the superseded rows were committed, then refused asp1's behind the objectui#10740 placeholder;p2's answer gone either way). Superseded read landing FIRST as a failure: loading paragraph gone and the grid drawn whilep2's read was pending; landing first as a success: loading gone, placeholder drawn.finallyreadisCurrent()p1edited, Save (batch held), host moves top2,p2answers; the batch lands: a THIRDfindwithpo: 'p1'(findcalled 3 times, expected 2), and the save never ends. Withp2's load having FAILED instead: the same third read, whose commit would clearp2's own failure banner.savedParentis on screen, through the panel's latestloadp1 save failed: the banner is drawn overp2's panel.savedParentis on screenp1(batch held), host re-renders withlimit: 200; the batch lands: the save's reload carries$top: 500, the click's row cap.latestLoadRef.current.load('save')5cd02243bb): before, drawn['p1 line edited'], Save on, 1find; the re-render issues a secondfindforp1(with$orderby,$top: 200, or the AND-merged filter) and the grid is replaced by the loading paragraph; after the answer, drawn['p1 line'], Save off, no banner. Silent.enabledgoes false with a read in flightrecords[],loadingTRUE; the late answer then commits:records['late'],loadingfalse.reset()behaves the same.runSeqRefand endsloadingobjectName/dataSourcegoing nullobjectNameto null afterfirstcommitted and arefetchin flight:recordsstay['first'],loadingstays TRUE, then['late']commits.dataSourceto null with a read in flight:loadingstays TRUE, the answer commits.canQuerysetSearchclosuresetSearch('a'), then a filter change inside the window: the fetch effect issues the current query ($filter closed,$search 'a'), and the timer then issues a THIRD query from the older closure (findcalled 3 times).enabledgoing false inside the window: the pending search still ran. Sibling, found by the round-2 review, re-measured onbef026bdc5by the round-3 base leg and StrictMode probe:setSearch('a')thentoggleSort('name')inside the window: the fetch effect's call carries$orderby name ascand$search a, and the timer then issues a THIRD call with$search aand no$orderby; withpaginate,setSearch('a')thensetPage(2): the second call carries$skip 10, the third$skip 0. Answered last, the third wins (the review's reading: recordsUNSORTEDwhilesortreadsname asc,PAGE1whilepagereads 2).runQuery,pageorsortmoves (the fetch effect's own key, round 3) and in the resetPins and ablations, round 1 (R1–R2, R4–R6)
Base leg (untouched
25c7d584e4, pins written first), one locked run:VERDICT command-exit 1,Tests 13 failed | 9 passed (22)over the two round-1 pins plus the R3 probe; each red row failed on its intended assertion (quoted in the round-1 report on the card,5855592309). Fix leg at5cd02243bb: the two pins plus every sibling pin (LineItemsPanel*×8,recordFormsErrorClears-10682,supersededReads-10712,useRecordQuery.supersededAnswer-10712,useRecordQuery.test.ts):VERDICT command-exit 0,Test Files 14 passed (14),Tests 178 passed (178)(with the 9-row hook pin, before itsreset()-in-window control).Ablations on committed
5cd02243bb, each through objectstack'sscripts/ablation-replace.mjs(anchor declared to hit once; anchor 1 to 0, blob changed, then blob == HEAD andgit diff HEADempty), under a shelltrapon EXIT INT TERM; panel legs over the R1–R2 pin plusrecordFormsErrorClears-10682andheldForAnotherParent-10740(88 rows), hook legs over the R4–R6 pin plussupersededAnswer-10712(18 rows). No build is on the resolution path (the pins import./index/./useRecordQueryrelatively and@object-ui/reactresolves tosrcthrough the root vitest alias).if (!isCurrent()) return;deletedfinallyreleasesloadingunconditionallyparentStillShown()parentStillShown()loadrunSeqRef.current += 1deleted fromresetenabled,reset(),objectName,dataSourcesetLoading(false)deleted fromresetenabledagainobjectName,dataSourceclearDebounce()deleted fromresetreset()-in-window controlPins and ablations, round 2 (R3)
LineItemsPanel.dirtyReloadHold-10712.test.tsx, 14 rows: for each of sort, limit and filter, a clean-change control (reloads with the change), the dirty row (no read, edits kept drawn and editable, Save on, no loading paragraph, no placeholder), and the post-save row (the reload after Save carries the changed input, exactly twofindcalls); plus five controls: a parent move while dirty is read and the held rows refused there (objectui#10740's shape), a swap back to the edited parent after a move is read (the last-run-parent clause), a change held while a same-parent read was in flight is replayed once on that read's commit (the fourthfindcarries$top: 200), a change of adapter is read, and a change of child object is read.Base leg on the round-1 head
5cd02243bb(the R3 pin, the edited 10740 pin and the edited R1–R2 pin, written before the source edit), one locked run:VERDICT command-exit 1,Tests 9 failed | 24 passed (33). The red rows, each on itsfindcount: the three dirty rows (「a same-parent reload ran over unsaved edits: called 1 times, but got 2 times」), the three post-save rows (1 expected, 2 before Save), the parent-move control (its pre-move hold assertion), the in-flight replay row (3 expected, 4), and the rewritten R2c row. Green on base: the three clean controls, the swap-back row, the adapter and child-object rows, and the re-judged 10740 row (its post-save trigger exercises the property at either source).Fix leg at
bef026bdc5, one locked run: the R3 pin, the R1–R2 pin, every siblingLineItemsPanel*pin (×8),recordFormsErrorClears-10682andsupersededReads-10712:VERDICT command-exit 0,Test Files 12 passed (12),Tests 165 passed (165).Ablations on committed
bef026bdc5(panel blobbbeb098240f7), each through objectstack'sscripts/ablation-replace.mjs(anchor declared to hit once; 「ok mutation landed: anchor 1 to 0」 with the blob change, then 「blob after restore」 == HEAD andgit diff HEADempty,git statusclean before and after), under a shelltrapon EXIT INT TERM, over the R3 pin, the 10740 pin and the R1–R2 pin (33 rows). Three legs (H2, H3, H7) were first spelled with a replacement that was a substring of its own anchor; the tool refused them as no-ops (「the replacement count moved N to N, a rise of 0」), nothing was written (restore proven), and they re-ran with a unique replacement token, readings below.if (false && …))dirtyclause droppedrowsHeldForequalsparentIdclause dropped'inputs'(so it can be held)Pins and ablations, round 3 (the R6 sibling)
Two rows added to
useRecordQuery.cannotQuery-10712.test.tsx: 「a sort change inside the debounce window: the fetch effect carries the typed term with the new sort, and the armed timer runs no older query」 (setSearch('a'),toggleSort('name'); the secondfindcarries$orderby name ascand$search a; the window is outwaited; exactly twofindcalls; the answer commits withsortreadingname asc) and 「a page change inside the debounce window (paginate): the fetch effect carries the typed term on the new page, and the armed timer runs no older query」 (pageSize 10,setSearch('a'),setPage(2); the secondfindcarries$skip 10and$search a; exactly two calls;pagereads 2).One locked chain at
ad6629e704(VERDICT command-exit 0, each leg's exit read from its own log; the hook swapped and restored bygit checkout, restore proven by blob == HEAD910e8d78a8e3andgit diff HEADempty after every leg):bef026bdc5(blobd010cbf927c7, the round-3 key spelled 0 times on disk, the round-2 key 1 time) under the threeuseRecordQuery.*pins: vitest exit 1,Test Files 1 failed | 2 passed (3),Tests 2 failed | 28 passed (30). The red rows are exactly the two, each on itsfindcount: 「the debounced timer ran the query it was armed with, without the sort: expected "vi.fn()" to be called 2 times, but got 3 times」 and 「the debounced timer ran the query it was armed with, for page 1: expected "vi.fn()" to be called 2 times, but got 3 times」.useRecordQuery.*pins plus the plugin-form panel set (LineItemsPanel*×10,recordFormsErrorClears-10682,supersededReads-10712): vitest exit 0,Test Files 15 passed (15),Tests 195 passed (195).scripts/ablation-replace.mjs(WRAP mode, anchor declared to hit once): the cleanup key back to[runQuery, clearDebounce]. 「ok mutation landed: anchor 1 to 0, blob 910e8d78a8e3 to e1a59b997873」 (the tool's arrows spelled as a word); over the threeuseRecordQuery.*pins: vitest exit 1,Tests 2 failed | 28 passed (30), the red rows exactly the two round-3 rows; then 「blob after restore」 and 「blob at HEAD」 both910e8d78a8e3, 「git diff HEAD empty」.renderHookprobe under aReact.StrictModewrapper (deleted, never committed; readings written to a file), the termaarmed and the change made inside the window, counting thefindcalls after the double-mounted initial reads: atbef026bdc5sort 2 (the second$search awithout$orderby), page 2 (the second$skip 0), filter 1, plainsetSearch1; atad6629e704sort 1, page 1, filter 1, plain 1, each carrying the term with the new sort, page or filter. AsetSearch('a')from page 2 thensetSearch('ab')reads the same at both heads:aat once through the fetch effect (the page moves to 1), thenabthrough the timer. StrictMode double-invokes effects on mount only, where no timer exists yet.Gates
At
5cd02243bb(round 1):pnpm turbo run build --filter="@object-ui/plugin-form^..." --filter="@object-ui/fields^..." --concurrency=2exit 0,Tasks: 11 successful, 11 total;pnpm turbo run type-check --filter @object-ui/plugin-form --filter @object-ui/fields --concurrency=2exit 0,Tasks: 14 successful, 14 total; the unionpnpm exec vitest run --maxWorkers=2 packages/plugin-form/ packages/fields/exit 0,Test Files 346 passed | 1 skipped (347),Tests 5000 passed | 8 skipped (5008); the round-1 light gates and the narrowed eslint (recorded in the round-1 report5855592309).At
bef026bdc5(round 2), one locked chain, each step's exit read from its own log: the round-2 ablations above (the tree restored, panel blob == HEADbbeb098240f7,git diff HEADempty,git statusclean);pnpm turbo run build --filter="@object-ui/plugin-form^..." --concurrency=2exit 0,Tasks: 11 successful, 11 total(the recreated worktree had nodist/; the closure was rebuilt from turbo's cache, which the type-check then resolved against);pnpm turbo run type-check --filter @object-ui/plugin-form --concurrency=2exit 0,Tasks: 13 successful, 13 total,tsc --noEmit && tsc -p tsconfig.test.jsonechoed for@object-ui/plugin-form. The fields package was byte-identical to round 1 at that head.At
ad6629e704(round 3, the fields package only; plugin-form is byte-identical tobef026bdc5), one locked chain, each step's exit read from its own log,VERDICT command-exit 0:pnpm turbo run type-check --filter @object-ui/fields --concurrency=2exit 0,Tasks: 12 successful, 12 total, with 「@object-ui/fields:type-check: cache miss, executing」 (run fresh, not replayed) andtsc --noEmit && tsc -p tsconfig.test.jsonechoed;--listFilesOnlyputs the pin in thetsconfig.test.jsonprogram and the hook in thetsconfig.jsonprogram (1 hit each). The fields package suite,pnpm exec vitest run --maxWorkers=2 packages/fields/: exit 0,Test Files 213 passed | 1 skipped (214),Tests 3439 passed | 7 skipped (3446)(the hook's three callers,LookupField, the people picker andRecordPickerDialog, live there).Light gates at
bef026bdc5and again atad6629e704, each exit 0 at both heads:check:control-bytes,check:new-line-citations(VERDICT new-cross-file-line-citations: 0 new citation(s)),check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:shell-escape-residue,check:i18n-keys,check:handler-key-reads,check:element-data-source-declaration,check:self-import,check:phantom-deps,check:unreferenced-sources,check:published-tsconfig-exclude,check:metadata-write-doors,lint:coverage,type-check:coverage,check:changeset-claims(「No pending changeset names a file this change touches」),check:pending-changeset-literals,check-changeset-presence(「6 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)」),check-changeset-no-major,check-changeset-overwrite,check-changeset-fixed;check-governed-queue-guard --testover the 8 paths: NOT GOVERNED.eslint at
bef026bdc5, narrowed to the panel and its three pins (eslint --no-inline-config --format json): exit 0, 4 files, 0 errors.LineItemsPanel.tsxcarries 18 warnings with the same rule multiset as its base blob (14no-explicit-any, 2set-state-in-effect, 2exhaustive-deps; the twoset-state-in-effectsites and the twoexhaustive-depssites are the base's own). The pins carry onlyno-explicit-any(14, 12, 10). eslint atad6629e704, narrowed to the hook and its R4–R6 pin: exit 0, 2 files, 0 errors;useRecordQuery.tscarries 8 warnings with the same rule multiset and the same four non-anysites as its base blob linted through--stdin --stdin-filename(4no-explicit-any, 2exhaustive-depsatrunQueryand the fetch effect, 2set-state-in-effectat the fetch and reset effects; the debounce cleanup's new key draws none), and the pin carries 8no-explicit-any. The narrowing evidence is the round-1 one: the population iseslint.config.js'sfiles: ['**/*.{ts,tsx}'],--print-configshowsparserOptions: {}and noproject/projectService, so no rule is type-aware and this diff cannot move an untouched file's verdict.NOT MEASURED locally, declared to CI: the repo-wide
pnpm lintand the rest of the lint and test farm.Serial
At claim time (2026-09-27T10:58Z) no open PR touched either file.
origin/mainis fetched into a private ref (refs/os/10712-main) before each push; the round-3 fetch, right before the round-3 push, reads17b323e5ca: five commits past the base25c7d584e4(63835d998f,35eb2f0ee3,b5b6f37531,805abe240c,17b323e5ca; 35 files), none a file this branch touches (overlap 0 of 8).git merge-tree --write-tree refs/os/10712-main HEADatad6629e704exits 0 with a clean tree (ca89b681fe). No merge commit was needed. The head isad6629e704(four commits on the branch:5cd02243bbround 1,bef026bdc5round 2,8451dc9709andad6629e704round 3, the second a comment-only pin header), and the remote branch is at the same sha.Acceptance notes
dirtystays true, the failure banner is drawn over the edited lines (unchanged), and the held change stays pending until a read runs.findis issued either way (at base the load declined before the wire), so nothing observable moves.reset()fixed in place (round 1): a consumer'sreset()with a read in flight had the R4 gap. No caller in this repository callsreset(); it is the exported API's own guard.totalis not reset when the hook clears (base behaviour kept). Observation, not filed.savingtrue until its batch lands (base behaviour,disabled={saving}, objectui#10631); a save whose parent left the screen ends when the batch does, without a reload.Co-authored-by: ClaudeandClaude-Session:), and this body ends with the role file's session-URL footer; the harness reminder asked for a model-named trailer and another footer.latestLoadRefis a passive-effect mirror, left as is (the round-2 review's observation, not a regression): in the microtask between a non-discrete host re-render's commit and its passive flush a settling save reads the previousparentId, its re-read is superseded by the fetch effect's read in the same flush, and only its failure banner could land in that window, which base wrote unconditionally;useLayoutEffectwould close it.5856013809) and fixed here in round 3: R6's defect by a sibling trigger in the same hook, asetSearchtimer armed before asortorpagechange running last with the older sort or page (measurement in the R6 row, pins and ablation in the round-3 section). Fixed in-PR as the same defect class in the same file, a mechanical key change, and the fields changeset's closing sentence already claimed the property. No other class (a), (b) or (c) finding.Generated by Claude Code