Skip to content

fix(plugin-grid): a cancelled background import shows the rows the job committed, with Undo (objectui#11650) - #11657

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11650-import-cancel-counts
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11650-import-cancel-counts

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11650

Clause-②: no

Session: session_015W8GBu6sBiqus2L2xjMsAL (dispatched by PM seat domain:ui#1, claim comment 5992312538).

What changed

packages/plugin-grid/src/ImportWizard.tsx, one file of source.

  • The Cancel reads the job back. handleCancelImport used to write a result with importedRows: 0 right after cancelImportJob. It now reads the job through the data source's getImportJobProgress, which is the GET /api/v1/data/import/jobs/:id read the card names: the @objectstack/client data.getImportJobProgress fetches {data route}/import/jobs/{id}. The wizard reads until the outcome is final, at most IMPORT_JOB_CANCEL_SETTLE_READS (10) reads, the first at once and then one per poll interval (800 ms). It then shows the job's created and updated counts. No hand-rolled fetch, and no change to the cancel door or to the client's cancelImportJob answer.
  • One result builder and one settle rule for both paths. cancelledJobToImportResult is the poll loop's old cancelled result, moved out of the loop. isSettledJobRead decides when a read is final, and publishJobOutcome publishes it. The poll loop and the cancel handler share all three.
  • Undo on the cancelled result. When the settled read is undoable (isImportJobUndoable), the result shows an Undo import button. It runs the History list's own undo action: handleUndo moved into a module-private useImportJobUndo that both now use. After the undo the button gives way to "Undone".
  • onComplete once. A run token (jobRunRef) lets only the first publisher for a run publish. The poll loop also returns when a Cancel landed while its read was in flight. So the two paths never both set a result or call onComplete twice.
  • Undo refreshes the list it changed. After a successful undo, the shared undo action calls notifyDataChanged({ objectName }). That is the existing data-invalidation bus from @object-ui/react, which ObjectGrid already listens to. This is an in-place fix declared below.

Strings reused, none added: grid.import.importCancelled, grid.import.createdCount, grid.import.updatedCount, grid.import.undoImport, grid.import.undoing, grid.import.undoConfirm, grid.import.reverted. No export, prop, type member or i18n key is added to any @object-ui/* package.

Why the first read after a cancel is not always final

In @objectstack/rest's import-job routes, the cancel route writes status: 'cancelled' and completed_at to the job row itself, then answers. The worker keeps writing to its next progress boundary, 200 rows. Only then does it patch the final counts, the per-row results and, for a job it can undo, the undo log. Until that last write the job reads terminal, but its counts can still grow and undoable is still false. So isSettledJobRead takes a cancelled read as final only when it is undoable, or when it repeats the previous cancelled read's counts. succeeded and failed are written together with their final counts, so they stay final on first sight. The poll loop uses the same rule, so a job cancelled elsewhere, for example from the History list in another tab, is no longer shown short either.

If the bound is hit (no read settles within 10 reads, or every read fails), the result says "Import cancelled" and shows no count, because the wizard never read one. The old "0 imported" badge is gone from that path. onComplete does not fire there, as before.

Live before / after (own backend, showcase showcase_contact)

Backend: objectstack main at 2df3d13d, built in my own worktree, objectstack dev --seed-admin --fresh on a private port. Console: apps/console vite from each objectui worktree, proxied to that backend. A Playwright run uploads a 5000-row CSV, ticks background import, clicks Run, then Cancel after 2.5 s, and then reads the job through the API.

run console source result screen after Cancel job read after the run
before base f1a177c Import cancelled · 0 imported, no Undo cancelled, 1800 created, undoable: true
after d0bfaa4 Import cancelled · 1800 created · Undo import cancelled, 1800 created, undoable: true
final 02d9266 Import cancelled · 2000 created · Undo import cancelled, 2000 created, undoable: true

On the after and final runs, clicking Undo showed "Undone" and the job then read revertedAt set. A filtered contact read for that run's rows returned 0. The same filter returned 1800 for the before run's rows, which nobody undid. On the final run, one list read (GET .../data/showcase_contact) went out after the Undo click: that is the list refetch.

Direct API measurement against the same backend: six cancels of a 5000-row job, each after a 1.0 to 4.0 s delay. The cancel POST answered 5.8 to 9.4 s after it was sent. In the four sequential runs, the first read sent after the cancel answered was already cancelled with undoable: true and the final counts. In the two runs with a concurrent poller (one read every 50 ms plus the request time), no read ever showed a cancelled job that was not yet undoable. That was on this driver (SQLite). The window the settle rule guards is therefore a reading of the server source, not a live observation. The rule costs nothing when the first read is already undoable.

Tests

Head 02d9266 (after the one merge of origin/main at 4367146):

  • pnpm exec vitest run packages/plugin-grid/: Test Files 186 passed (186), Tests 1721 passed (1721). vitest list --filesOnly lists 186 files, the new file among them in the dom project.
  • pnpm --filter @object-ui/plugin-grid type-check exits 0. It runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFilesOnly shows the test project compiles the new test file.
  • pnpm --filter @object-ui/plugin-grid lint: 0 errors. Per file, ESLint JSON reports ImportWizard.tsx with 10 warnings, the same 10 rules as on base, and the new test file with 0.
  • Exit 0 for each of these root checks: check:i18n-keys, check:i18n-dead-keys, check:new-line-citations, check:control-bytes, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:handler-key-reads, check:unreferenced-sources, check:shell-escape-residue, check:vi-mock-specifiers, check:phantom-deps, scripts/check-changeset-no-major.mjs and scripts/check-changeset-presence.mjs.

New pins, in ImportWizard.cancelCounts-11650.test.tsx (10 tests, wizard driven end to end with real timers):

  • cancel mid-run: the summary equals the settled job counts (1950 created, 50 updated, not the early 1800), Undo is offered, and onComplete is called once;
  • the first read after the cancel is still running, and a later read settles;
  • a job that never reads undoable is final once two cancelled reads agree, with no Undo;
  • Undo on the result rolls the job back and reads "Undone";
  • Undo on the result, and Undo from the History list, announce the change on the bus, and a failed History undo announces nothing;
  • a Cancel clicked while a poll read is in flight publishes once;
  • the poll loop does not take a job cancelled elsewhere as final on its first cancelled read;
  • closing the wizard mid-run still reports the background job through onComplete (a behaviour on base that this change keeps);
  • the bound hit: no count shown, no onComplete.

Reverse verification, one-off and committed first. ImportWizard.tsx was swapped for base f1a177c under a restore trap, with blob hashes checked: 9 of 10 red, and the close-mid-run pin green, as a must-keep pin should be. Restore was proven by blob equals HEAD and an empty git diff HEAD.

Ablations, each through ablation-replace.mjs with the landing and the restore proven:

  • the settle rule reduced to "any terminal read is final": exactly 3 red (mid-run, never-undoable, cancelled elsewhere);
  • the notifyDataChanged call removed: exactly the 2 bus pins red;
  • the reset-token bump put back: exactly the close-mid-run pin red. The first attempt at this one was a no-op: the tool refused it because its replacement contained the anchor. It was re-run with a non-overlapping anchor.

Not run locally, left to CI: the full pnpm test farm, the repo-wide pnpm lint, and check:eager-closure. Consumers in app-shell were not re-tested: the public face of @object-ui/plugin-grid is byte-unchanged (no export, prop or type change).

In-place fix, declared

Undo now calls notifyDataChanged for the object (the History list and the new result button share it). This round makes the cancel call onComplete, so the console list refetches and shows the committed rows. An Undo that left the list showing those rows after deleting them would be a stale state this change makes newly reachable. The bus's own contract asks for exactly this call: its doc comment says "Call this from every WRITE path after the mutation succeeds". The fix is one line, in the claimed file, inside the same gate family. The pins and the ablation are above.

Behaviour changes for hosts

  • onComplete now fires once after a user Cancel whose job reads back, with cancelled: true and the committed counts. In the console this refreshes the list and raises the usual import toast ("Imported 1800 rows."). Before, it fired for a cancelled job only when the poll loop itself saw it end.
  • A poll loop from an earlier run no longer publishes into a newer run's screen.

Acceptance notes (observations, not filed)

  • Server event loop during an import. With a 5000-row job running on the SQLite showcase, every HTTP request to that server I timed took 4 to 9 s. The pre-cancel progress GET answered 4.1 to 6.4 s after it was sent (six runs), and the cancel POST 5.8 to 9.4 s. The import runner yields once per progress boundary (its yieldToEventLoop doc names framework#2824). These are latency readings, not a wrong answer. Carrier: none.
  • History-list undo error is cleared by its own reload. ImportHistoryPanel.load sets its error to null at the start, and the undo's reload runs right after a failure. So a failed History undo shows its message only until the reload begins. This change keeps that behaviour: the refactor kept it byte-equivalent, and the new result-screen Undo keeps its own error. This was read from the source and not measured live. Carrier: none.
  • The success result screen still offers Undo only through History. The card is about the cancel, so this is out of scope.

Generated by Claude Code

claude added 5 commits October 5, 2026 10:29
…b committed, with Undo (objectui#11650)

After the user cancels an async import job, the wizard reads the job back
(getImportJobProgress, the GET /api/v1/data/import/jobs/:id read) until its
outcome is final, then shows the committed created/updated counts and an Undo
button through the History list's own undo action. A cancelled read counts as
final once it is undoable or repeats the previous cancelled read's counts: the
server marks the row cancelled before its worker stops writing. The poll loop
shares the same result builder and settle rule, and a run token keeps the poll
loop and the cancel handler from both publishing or calling onComplete twice.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…t refetches (objectui#11650)

The cancel now hands its result to onComplete, so the host's list shows the
committed rows; an Undo then deleted them while the list kept showing them.
The shared undo action (History list and cancelled result) now calls
notifyDataChanged for the object after a successful undo, the bus every write
path is meant to use. A failed undo announces nothing.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…ete (objectui#11650)

The run token dropped any outcome that landed after reset(), which also
dropped the poll loop's completion of a background job the user had closed the
dialog on: the host no longer refreshed or toasted when it finished. Only a
newer run now moves the token; closing the wizard does not.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3318.0 KB 3330.4 KB
Main entry chunk (gzip) 151.9 KB 350 KB
Entry file index-CAA0y7Yl.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 574.72KB 137.94KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.58KB 38.81KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 247.21KB 65.03KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.09KB 45.89KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 238.48KB 65.51KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT: PR objectui#11657, head 02d9266. It lands when every check on this head is green

domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-05T11:24Z. Reviewed against GitHub and origin/main, not against the report's prose (report on objectui#11650).

  • Shape.

    • The PR is a draft against main, and its assignee is os-steve. Its first line is Fixes #11650, the only line with a closing keyword next to an issue number. Clause-②: no is on its own line.
    • The diff is 3 files, +560/−48, inside the claim's surface: ImportWizard.tsx, one new pin file and the changeset. No path is governed. Every commit carries the trailer pair.
  • The ruling, as written. After the cancel answers, the wizard reads the job back through the data source's existing getImportJobProgress (the GET /api/v1/data/import/jobs/:id read; no hand-rolled fetch). It then shows the job's created and updated counts, with an Undo button. The cancel door is unchanged.

  • The settle rule, a Zone 2 premise falsified and handled. The cancel route itself writes cancelled, before its worker's last write. So "terminal" is not "settled". isSettledJobRead accepts a cancelled read once it is undoable, or once it repeats the previous cancelled read's counts. Reads are bounded at 10, one per poll interval.

    • The poll loop's own cancelled branch now uses the same rule and the same builder (cancelledJobToImportResult).
    • If the bound is hit, the result reads "Import cancelled" with no count. It never says "0 imported".
  • Publish once. A run token makes the poll loop and the Cancel publish one result, so onComplete fires once. Closing the wizard mid-run still reports a background job through onComplete, as before.

  • The Undo. History's undo action moves into a module-private useImportJobUndo. The result screen and the History list both use it. Nothing is exported.

  • Two behaviour changes, both stated in the changeset, accepted.

    • onComplete now fires after a user cancel that reads the job back. This matches the poll loop's existing cancelled branch: the console list refreshes and toasts.
    • A successful Undo calls notifyDataChanged({ objectName }) (@object-ui/react's bus, as its own doc asks of every write path). Without that, the list the new onComplete refreshed would keep showing rows the Undo had deleted. A failed Undo announces nothing.
  • i18n. No key is added. The copy reuses importCancelled, createdCount, updatedCount, undoImport, undoing, undoConfirm and reverted. check:i18n-keys and check:i18n-dead-keys pass.

  • Tests. The new pin has 10 cases with real timers. The base ImportWizard.tsx turns 9 red, and the close-mid-run pin stays green, as it must on base. Ablations:

    • the settle rule reduced to "any terminal read" turns exactly 3 red;
    • no bus notify turns exactly the 2 bus pins red;
    • the token bump re-inserted turns exactly the close-mid-run pin red.

    packages/plugin-grid/ is green (186 files).

  • Live (showcase, a 5000-row CSV, Cancel after 2.5 s). Before: "Import cancelled · 0 imported", while the job read cancelled with 1800 created. After: "1800 created · Undo import", equal to the job. Undo reverted the run's rows (a filtered count of 0), and the list refetched.

  • Changeset, sentence by sentence. It is @object-ui/plugin-grid: patch. The read-back, the settle rule and its reason, the host behaviour change, the no-count bound case, the Undo bus paragraph and the Clause-②: no line with its reused-string list all match the diff.

Left as noted, not filed:

  • Server latency during a large SQLite import is a speed observation, not a wrong answer.
  • The REST cancel route writes cancelled before the worker's final write. The settle rule absorbs it, and it was not observed live.
  • ImportHistoryPanel.load clears a failed History undo's error on reload. That is unchanged by this PR.

Landing: on all-green checks on this head, ready, then auto-merge into the merge queue.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 11:38
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 11:38
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit 846f982 Oct 5, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11650-import-cancel-counts branch October 5, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants