Skip to content

feat: classify recoverable engine refusals and bind the vault browser notice chrome - #1100

Merged
FSM1 merged 4 commits into
mainfrom
feat/1069-classify-engine-refusals-and-notice-chrome
Aug 6, 2026
Merged

feat: classify recoverable engine refusals and bind the vault browser notice chrome#1100
FSM1 merged 4 commits into
mainfrom
feat/1069-classify-engine-refusals-and-notice-chrome

Conversation

@FSM1

@FSM1 FSM1 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

What this does

Three adjacent vault-browser resilience surfaces, one owner set.

#1069 — classify a recoverable engine refusal

FileBrowser.tsx computed settled = !isLoading && error === null and gated the
listing, the drop target, and the empty state on it, so any failure blanked
the vault browser — a recoverable stream-ceiling refusal wiped it exactly as a
fatal one did.

  • snapshotStore.isRecoverable(error) classifies on the engine's stable code —
    tooManyStreams, the one ceiling that is not a verdict. It fails closed: an
    unrecognised code and a codeless transport fault are both fatal, so a new
    EngineError variant can never render as recoverable without someone adding
    it. trustViolation is fatal by construction.
  • A recoverable refusal renders as a role="status" notice over the
    last-known-good rows, with a [retry] control. A fatal one keeps the existing
    role="alert" blanking behaviour.
  • Retry drives SnapshotStore.refresh().

#808 — offline / notice chrome bound to engine events

The staleness ladder and the dead-letter notice already landed, so this is only
the residual:

  • stores/notification.store.ts — the warning-notice surface. Deduplicates by
    key and caps its list, so an engine that escalates on every resolve tick
    cannot grow it without bound. Cleared with the engine that raised the notices.
  • components/NotificationToast.tsx — renders them as a distinct warning class
    (data-notice-class="warning", role="alert"), dismissed by hand rather than
    on a timer: a trust warning that expired unread would read as "nothing was
    wrong".
  • withheldUpdateEscalation and attributableAbuse are projected onto that
    surface from the snapshot store's existing subscription — the stream's only
    listener. Never the staleness ladder (blueprint/web-client.md "Staleness
    ladder rendering", AGENTS.md rule 6); a test asserts the ladder's rung is
    untouched when an escalation lands, and that a stalenessChanged raises no
    warning.
  • components/layout/OfflineBanner.tsx — the ladder's bottom rung at banner
    scale, rendered from the engine's rung alone.
  • hooks/useOnlineStatus.ts, hooks/useVisibility.ts,
    engine/useRefreshHints.ts — each edge back into on-screen-and-online drives
    one nocache refresh. Only the transition refreshes, never the mount.
  • facade.manualRefresh gets its first apps/web consumer: the footer status
    indicator is now a button that drives it.

#1079 — the snapshot store's focus window

useFolderPicker restored facade.setFocus(openOn) on unmount, which could land
after a route change had moved the store's focus — leaving the engine on the
old folder while the store's cached focus said the new one, with setFocus's
sameNode short-circuit unable to re-assert. The store now lends the window out
and takes it back: SnapshotStore.refocus() re-asserts the cached focus
unconditionally, and the picker calls it instead of restoring the folder it
opened on. The regression test fails against the previous implementation.

Defect found by runtime verification

Driving the built app against a live stack turned up a real regression in the
first commit: the engine does not implement manualRefresh yet and answers
unimplemented, which the store committed as a fatal snapshot error — so
clicking the status indicator, or simply coming back online, blanked the whole
listing. The unit fake resolved the command, so no test saw it.

Fixed in the second commit: the nocache hint is best-effort and only the pull it
precedes sets error. Covered by a regression test that refuses the command and
asserts the listing survives.

Review gates

/simplify (four parallel passes: reuse, simplification, efficiency, altitude)
and /security-review, both against git diff main...HEAD, folded into the
third commit.

/security-review returned no findings at confidence >= 8. It traced and
cleared the fail-closed classification (Engine::snapshot cannot return a trust
verdict at all), the notice surface for key material / PII / storage / logging,
cross-session leakage of the module-level notice store, XSS, and focus-window
misdirection.

Real findings that were folded in:

  • Divergent classifier. packages/client already ships
    isRecoverableEngineError (tooManyStreams only). Adding overBudget in
    apps/web gave the repo two answers to one question, and overBudget's
    DeviceFull / StagingLimit causes do not clear on their own. Narrowed to
    agree.
  • Missed cold-start escalations. useEngineNotices subscribed from
    AppShell, a render after the engine starts and only on authenticated routes,
    so every escalation emitted during start -> pointer resolve -> root adoption
    fell in the gap and read as "nothing was wrong" — the exact failure the
    distinct-warning-class rule exists to prevent. The projection moved into the
    snapshot store's own subscription and the hook was deleted.
  • Second authority over the ladder. The banner OR'd navigator.onLine into
    a rendered rung, so a VPN or captive portal could show OFFLINE over a footer
    reading fresh. The blueprint puts online in the RefreshHintSource seam,
    not the ladder; the banner now renders from the rung alone.
  • Smaller: stable accessible name on the refresh control, flattened error
    branch, dropped a single-valued data attribute and its tautological assertion,
    simplified the notice store's publish path, shared the jsdom online/visibility
    stubs, and trimmed the comments this repo's comment law rejects.

Deliberately not done, because the fix lives outside this PR's owner set:

  • Consolidating onto packages/client's isRecoverableEngineError — it is not
    in that package's barrel, so exporting it means editing packages/client.
    Both predicates now agree, so nothing is divergent in the meantime.
  • Routing the online/visibility edges through QueueRefreshHintSource
    (packages/client/src/seams/refreshHint.ts), which is the seam that properly
    owns them; EngineClient exposes no hint API today, so useRefreshHints
    drives store.refresh() instead.
  • Extracting a shared .text-button reset and a shared warning-box class — the
    repo already carries five copies of each across untouched stylesheets.
  • A shared renderWithEngine test helper — testFakes.ts is a .ts file and
    would have to become .tsx, on a file a sibling PR may also touch.

Also worth knowing: Event::WithheldUpdateEscalation has no production emit
site
in the engine today (it is constructed only in tests), so that branch is
dead until the engine slice lands. Event::RenewalFailed remains the one engine
event with no rendering home — out of scope here.

Issue-body corrections

Verified against the code, not the issue text.

Files outside the owner set

Two, both additive and both needed by the tests for files I own:

  • apps/web/src/engine/testFakes.tsfakeEngine gained manualRefresh, a
    refreshes() counter, refuseRefresh(error), and the shared
    setOnline/setVisible jsdom stubs.
  • apps/web/src/styles/file-browser.css / layout.css — the styles for the new
    surfaces, added to the existing sheets so main.tsx needs no new import.

Nothing in FileBrowserActions.tsx, DetailsDialog.tsx, FilePreviewDialog.tsx,
useFilePreview.ts, or useFileDownload.ts was touched.

Verification

pnpm typecheck, pnpm lint, pnpm lint:tracker-refs, pnpm test all green
(apps/web: 26 files, 215 tests).

Runtime, against a live stack — API on :3000 over Postgres, the hermetic
/routing/v1 store on :3001, a real WASM engine, a real cold-started vault:

  • cold start reaches a settled vault; a folder is created and lists
  • the status indicator renders as a <button> with the right rung and a stable
    Refresh now tooltip; clicking it drives the manual refresh without
    blanking the listing
  • forcing the browser offline no longer raises the banner while the engine reads
    fresh — the rung is the one authority (this is the post-gate behaviour; the
    earlier browser-driven banner was verified before that change)
  • the online edge fires the refresh hint with the listing intact
  • the recoverable notice, the offline banner, and the warning toast were verified
    for computed style and layout by injecting their markup into the live document
    — the engine cannot be driven into tooManyStreams, the offline rung, or an
    escalation from the page, since the introspection hook is read-only. Their
    behaviour is unit-tested.

Not driven end to end in a browser: the recoverable-refusal path, the offline
rung, and the escalation warning. By hand: open more than MAX_OPEN_STREAMS file
previews at once and confirm the rows stay while the notice appears; pull the
network and wait for the ladder to reach offline for the banner.

Closes #1069
Closes #808
Closes #1079

Summary by CodeRabbit

  • New Features
    • Added offline status messaging when the app loses connectivity.
    • Added dismissible warning notifications for important engine updates.
    • Made the status indicator clickable to manually refresh data.
    • Automatically refreshes data when connectivity and visibility are restored.
  • Bug Fixes
    • Recoverable file-browser interruptions now preserve listings and offer retry.
    • Terminal errors continue to clearly suppress unavailable listings.
  • Tests
    • Expanded coverage for offline behavior, notifications, refresh actions, retries, and folder navigation.

Note

Classify recoverable engine refusals and add vault browser warning notices

  • Adds isRecoverable to classify tooManyStreams errors as recoverable; FileBrowser now keeps the current listing visible and shows a retry notice instead of blanking the screen on these errors
  • Adds a global notificationStore with deduplication and bounded size; snapshotStore posts warning notices for withheldUpdateEscalation and attributableAbuse engine events
  • Adds NotificationToast and OfflineBanner components rendered from AppShell; the staleness StatusIndicator is now a clickable button that triggers store.refresh
  • Adds useRefreshHints hook that calls store.refresh when the app transitions to both online and visible; useFolderPicker now delegates focus restoration to store.refocus
  • snapshotStore gains refocus and refresh methods and a disposed guard to prevent late commits after engine teardown

Macroscope summarized c0114e8.

FSM1 and others added 2 commits August 6, 2026 01:19
The vault browser gated its listing on `error === null`, so any failure
blanked it — a stream-ceiling refusal wiped the rows exactly as a fatal
error did. `snapshotStore` classifies on the engine's stable code (fail
closed: an unrecognised or absent code is fatal), and the browser renders
a recoverable refusal as a retryable notice over the last-known-good rows.

Adds the residual notice chrome: an offline banner, a warning-notice store
and toast bound to `withheldUpdateEscalation`/`attributableAbuse` as a
class distinct from staleness, online/visibility refresh hints, and a
manual refresh driven from the status indicator.

The snapshot store also takes the focus window back from the folder
picker, so a route change during a move cannot strand the engine on the
folder the picker opened on.

Closes #1069
Closes #808
Closes #1079

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WegkkQ3uhNREerTW4MMeY2
The engine does not implement `manualRefresh` yet and answers `unimplemented`, which the store committed as a fatal snapshot error — clicking the status indicator or coming back online blanked the whole listing. Caught driving the built app against a live stack; the unit fake resolved the command, so no test saw it.

The nocache hint is best-effort: only the pull it precedes sets `error`. Adds the regression test and the folder-picker's own focus-handback test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@FSM1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c23901e-a021-4330-b987-48eae7b7d165

📥 Commits

Reviewing files that changed from the base of the PR and between db4a530 and c0114e8.

📒 Files selected for processing (3)
  • apps/web/src/engine/snapshotStore.test.ts
  • apps/web/src/engine/snapshotStore.ts
  • apps/web/src/hooks/useFolderPicker.test.tsx

Walkthrough

The web application now classifies recoverable snapshot failures, supports manual refresh and focus restoration, displays offline and warning status surfaces, and coordinates refreshes after connectivity or visibility changes.

Changes

Web event and recovery surfaces

Layer / File(s) Summary
Notification state and rendering
apps/web/src/stores/notification.store.ts, apps/web/src/components/NotificationToast.tsx, related tests
Adds bounded, deduplicated warning notifications with dismissal and toast rendering.
Snapshot recovery and engine notices
apps/web/src/engine/snapshotStore.ts, apps/web/src/engine/testFakes.ts, related tests
Adds recoverable-error classification, manual refresh, refocus, engine warning projection, and disposal cleanup.
Connectivity and visibility refresh hints
apps/web/src/hooks/useOnlineStatus.ts, apps/web/src/hooks/useVisibility.ts, apps/web/src/engine/useRefreshHints.ts, related tests
Refreshes the snapshot store when the application becomes both online and visible.
Folder picker focus restoration
apps/web/src/hooks/useFolderPicker.ts, related test
Restores focus through SnapshotStore.refocus() when the picker unmounts.
Recoverable file-browser failures
apps/web/src/components/file-browser/FileBrowser.tsx, related tests, apps/web/src/styles/file-browser.css
Preserves listings for tooManyStreams, provides retry behavior, and keeps fatal or unknown errors on the alert path.
Offline and notification application chrome
apps/web/src/components/layout/*, apps/web/src/styles/layout.css, apps/web/src/components/NotificationToast.tsx
Mounts refresh hints, offline status, notification toasts, and a refreshable status indicator in AppShell.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant useRefreshHints
  participant snapshotStore
  participant EngineFacade
  Browser->>useRefreshHints: online or visibility event
  useRefreshHints->>snapshotStore: refresh() when online and visible
  snapshotStore->>EngineFacade: manualRefresh()
  snapshotStore->>EngineFacade: pull focused snapshot
  EngineFacade-->>snapshotStore: snapshot result
Loading
sequenceDiagram
  participant Engine
  participant snapshotStore
  participant notificationStore
  participant NotificationToast
  Engine->>snapshotStore: emit warning event
  snapshotStore->>notificationStore: warn with keyed notice
  notificationStore-->>NotificationToast: notify subscribers
  NotificationToast->>notificationStore: dismiss notice
Loading

Possibly related PRs

  • FSM1/cipher-box#899: Extends the same snapshot-store and test-fake infrastructure with refresh, recoverability, focus, and notification behavior.
  • FSM1/cipher-box#945: Introduced focus-window behavior used by the snapshot-store focus and refocus changes.
  • FSM1/cipher-box#971: Shares changes to the file browser, application shell, status indicator, snapshot store, and test fakes.

Suggested labels: release:web:feat

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy the coding objectives for recoverable errors, notice and refresh chrome, and SnapshotStore focus handback [#1069, #808, #1079].
Out of Scope Changes check ✅ Passed The code, tests, styles, and test fakes directly support the linked issue objectives without unrelated changes.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: recoverable engine refusal handling and vault browser notice chrome.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1069-classify-engine-refusals-and-notice-chrome

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

/simplify and /security-review, plus a manual reuse/altitude pass.

- Narrow `isRecoverable` to `tooManyStreams`, agreeing with the client's own
  `isRecoverableEngineError`. `overBudget` was a divergent second answer, and
  its device-full and staging-limit causes do not clear on their own.
- Project the trust warnings from the snapshot store's subscription instead of
  a second one mounted from `AppShell`. A second subscription opens a render
  after the engine starts, so every cold-start escalation fell in the gap and
  read as "nothing was wrong". Drops `useEngineNotices` entirely; the notices
  now clear with the store that raised them.
- Render the offline banner from the engine's rung alone. `navigator.onLine`
  reports the link, not whether anything answers over it, and the blueprint
  puts it in the refresh-hint seam, not the ladder.
- Stable accessible name on the manual-refresh control; flatten the browser's
  error branch; drop a single-valued data attribute and its tautological
  assertion; simplify the notice store's publish path; share the jsdom
  online/visibility stubs; trim the comments the repo's comment law rejects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@FSM1
FSM1 marked this pull request as ready for review August 6, 2026 05:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/web/src/hooks/useOnlineStatus.ts (1)

17-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the server snapshot callbacks from these SPA hooks.

apps/web/src/hooks/useOnlineStatus.ts#L17-L22 and apps/web/src/hooks/useVisibility.ts#L9-L14 both add getServerSnapshot callbacks. This Vite application does not render on the server.

  • apps/web/src/hooks/useOnlineStatus.ts#L17-L22: call useSyncExternalStore(subscribe, () => navigator.onLine).
  • apps/web/src/hooks/useVisibility.ts#L9-L14: call useSyncExternalStore(subscribe, () => document.visibilityState === 'visible').
Proposed change
-    () => navigator.onLine,
-    () => true
+    () => navigator.onLine
-    () => document.visibilityState === 'visible',
-    () => true
+    () => document.visibilityState === 'visible'

Based on learnings, “the SPA at apps/web uses Vite + React with no SSR. For useSyncExternalStore hooks, do not provide getServerSnapshot unless you are rendering on the server.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/hooks/useOnlineStatus.ts` around lines 17 - 22, Remove the
unnecessary server snapshot callback from useSyncExternalStore in
apps/web/src/hooks/useOnlineStatus.ts lines 17-22, leaving subscribe and the
navigator.onLine client snapshot. Apply the same change in
apps/web/src/hooks/useVisibility.ts lines 9-14, retaining only subscribe and the
document.visibilityState client snapshot.

Source: Learnings

apps/web/src/components/file-browser/FileBrowser.test.tsx (1)

21-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove or replace the setup narration.

listedThenFailed already states the helper behavior. Keep a comment only if it explains a test constraint or failure-mode rationale. As per coding guidelines, comments must “explain why rather than what.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/file-browser/FileBrowser.test.tsx` at line 21, Remove
the setup narration comment above listedThenFailed, since the helper name
already describes its behavior. Add a replacement only if it documents a
specific test constraint or failure-mode rationale rather than restating what
the helper does.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/engine/snapshotStore.ts`:
- Around line 191-195: Update the snapshot store disposal lifecycle to set a
disposed flag and increment generation in dispose(), then guard assertFocus(),
refresh(), pull(), and the coalesced finally callback so no facade calls occur
after disposal. Add a test covering disposal before ackFocus() and verify that
no pull begins.

In `@apps/web/src/engine/useRefreshHints.test.tsx`:
- Around line 25-71: There is no named required CI check executing the web
Vitest suites. Add a required web-unit (or equivalent) job/check in
.github/workflows/ci.yml that runs the relevant tests, including
apps/web/src/engine/useRefreshHints.test.tsx,
apps/web/src/components/layout/OfflineBanner.test.tsx, and
apps/web/src/components/layout/StatusIndicator.test.tsx; the listed test files
require no direct changes.

In `@apps/web/src/hooks/useFolderPicker.test.tsx`:
- Around line 34-45: Strengthen the unmount behavior test around the Probe
rerender: record the engine focus-write count immediately after
store.setFocus(NEXT), then after rerendering without the picker require the
count to increase and verify the newly written value is NEXT. Keep the existing
focus assertions while ensuring the test fails if store.refocus() is removed.

---

Nitpick comments:
In `@apps/web/src/components/file-browser/FileBrowser.test.tsx`:
- Line 21: Remove the setup narration comment above listedThenFailed, since the
helper name already describes its behavior. Add a replacement only if it
documents a specific test constraint or failure-mode rationale rather than
restating what the helper does.

In `@apps/web/src/hooks/useOnlineStatus.ts`:
- Around line 17-22: Remove the unnecessary server snapshot callback from
useSyncExternalStore in apps/web/src/hooks/useOnlineStatus.ts lines 17-22,
leaving subscribe and the navigator.onLine client snapshot. Apply the same
change in apps/web/src/hooks/useVisibility.ts lines 9-14, retaining only
subscribe and the document.visibilityState client snapshot.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a98a3aa6-1016-4126-9cd9-b43c67a75dc4

📥 Commits

Reviewing files that changed from the base of the PR and between 3d087d6 and db4a530.

📒 Files selected for processing (22)
  • apps/web/src/components/NotificationToast.tsx
  • apps/web/src/components/file-browser/FileBrowser.test.tsx
  • apps/web/src/components/file-browser/FileBrowser.tsx
  • apps/web/src/components/layout/AppShell.tsx
  • apps/web/src/components/layout/OfflineBanner.test.tsx
  • apps/web/src/components/layout/OfflineBanner.tsx
  • apps/web/src/components/layout/StatusIndicator.test.tsx
  • apps/web/src/components/layout/StatusIndicator.tsx
  • apps/web/src/engine/engineNotices.test.tsx
  • apps/web/src/engine/snapshotStore.test.ts
  • apps/web/src/engine/snapshotStore.ts
  • apps/web/src/engine/testFakes.ts
  • apps/web/src/engine/useRefreshHints.test.tsx
  • apps/web/src/engine/useRefreshHints.ts
  • apps/web/src/hooks/useFolderPicker.test.tsx
  • apps/web/src/hooks/useFolderPicker.ts
  • apps/web/src/hooks/useOnlineStatus.ts
  • apps/web/src/hooks/useVisibility.ts
  • apps/web/src/stores/notification.store.test.ts
  • apps/web/src/stores/notification.store.ts
  • apps/web/src/styles/file-browser.css
  • apps/web/src/styles/layout.css

Comment thread apps/web/src/engine/snapshotStore.ts
Comment thread apps/web/src/engine/useRefreshHints.test.tsx
Comment thread apps/web/src/hooks/useFolderPicker.test.tsx
@FSM1
FSM1 marked this pull request as draft August 6, 2026 06:00
A logout rebuild disposes the store and the client while the tab stays
live, so an in-flight setFocus or manualRefresh could resolve afterwards
and drive a pull into a closed facade. Latch a disposed flag and
supersede the generation so no continuation reaches it.

Also strengthen the folder-picker unmount test: it asserted only the last
focus write, which the store's own setFocus already satisfied, so it
passed with the picker's refocus removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WegkkQ3uhNREerTW4MMeY2
@FSM1
FSM1 marked this pull request as ready for review August 6, 2026 09:24
@FSM1
FSM1 merged commit 67de38d into main Aug 6, 2026
31 of 32 checks passed
@FSM1
FSM1 deleted the feat/1069-classify-engine-refusals-and-notice-chrome branch August 6, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant