Skip to content

fix(pwa,a11y,deps): land Plan 161/162 — offline precache, restorable backups, hydration-safe reduced motion, React 19.3.0 - #925

Open
d-oit wants to merge 11 commits into
mainfrom
fix/offline-lazy-view-precache
Open

d-oit wants to merge 11 commits into
mainfrom
fix/offline-lazy-view-precache

Conversation

@d-oit

@d-oit d-oit commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

What ships in this batch

Nine commits rebased onto current main (next 16.3.8, react-day-picker 10.0.2, @types/node 26.6.3 all kept at main's versions):

  1. fix(pwa): precache every emitted chunk — a first offline visit into any of the six lazy views no longer rejects its dynamic import (61 URLs: 21 document + 36 chunks + 17 media).
  2. feat(studio): truthful, restorable pre-import backup — the backup taken before an OKF import now actually restores what it claims (closes the recovery/backup-outcome gap recorded in plans).
  3. chore(deps): React runtime 19.3.0 (+ @types 19.3.0, eslint-config-next 16.3.7) — supersedes closed PR fix(deps): bump react and @types/react #915; lockfile regenerated, every override/peer rule retained.
  4. fix(a11y,offline): offline banner no longer covers the topbar — measured --offline-banner-height published by the indicator, reserved by the shell; controls are hit-testable at every viewport while offline.
  5. fix(a11y): useReducedMotion hydration-safe — useSyncExternalStore pins the server answer; 8 React mismatch warnings per Home load under prefers-reduced-motion → 0, pinned by a new real-browser spec.
  6. ci: production offline suite behind an offline paths filter — pnpm build && pnpm run test:e2e:offline runs where the defect can be found, not on every UI edit. Also clears the two pre-existing workflow lint nits (yamllint comments-indentation, actionlint SC2002) recorded in plans/161 — both files now lint clean.
  7. plans/ — Plan 161 follow-ons documented as fixed with measured evidence; ADR 041 updated; Plan 162 roadmap record.

Verification (this tree, this machine)

  • scripts/minimal_quality_gate.sh (lint + typecheck + full unit suite) green on the rebased tree — via pre-commit on every commit.
  • pnpm run build clean; regenerated manifest matches the tree (27 hashes moved).
  • Production offline suite 6/6 against that build (e2e/offline-views.spec.ts).
  • hydration-mismatch.spec.ts + accessibility.spec.ts (incl. axe + new Offline banner suite) + touch-targets.spec.ts 31/31 on chromium.
  • yamllint + actionlint: zero warnings on both touched workflows.

Merge order note

Depends on nothing. PR #922 (sharp) and #924 (source-map-js override) touch the lockfile; whichever lands last gets an @dependabot rebase / lockfile regen. Closing #915 is justified by item 3 above.

🤖 Generated with Codebuff


📝 Summary by GitNexus

Summary

This appears to be a broad product and maintenance change spanning offline behavior, recovery, accessibility, dependencies, tests, and roadmap documentation. Its risk posture is elevated by wide graph reach and high-risk CI workflow files.

🔴 CRITICAL blast radius. A cross-cutting PWA, recovery, accessibility, and dependency change across the studio and project tooling, with 49 graph dependents.

The implementation centers on public/sw.js, public/precache-manifest.json, scripts/generate-precache-manifest.mjs, and src/lib/studio/recovery-helpers.ts, alongside studio components and end-to-end tests. The graph impact lands mainly in Studio and Views, with additional impact in Slices and Scripts. The PR also updates dependencies to React 19.3.0 and changes plans including plans/161-offline-lazy-view-precache-and-framework-refresh-2026-09-30.md and plans/162-roadmap-progress-and-next-work-2026-10-05.md.

Review the recovery helpers, service worker and precache generation path, and the offline and restore tests first. The HIGH risk files are .github/workflows/ci-and-labels.yml and .github/workflows/security-scan.yml.

Added by GitNexus for PR #925. Edit freely — this block is replaced on the next review, everything above it is left untouched.

do-ops885 and others added 10 commits October 10, 2026 17:20
…offline visit

The precache manifest was scraped from .next/server/app/index.html alone, so a lazily imported view — Graph, Mind Map, AI Harness, TRIZ, Export, Sync — appeared in no HTML and its chunk was never cached. A user who installed the app, went offline, then opened a view they had never visited online got a rejected dynamic import and '<View> failed to load'.

Measured on the pre-fix build: 17 of 36 emitted chunk files were precached. New e2e/offline-views.spec.ts reproduces all six views failing (the topbar renders an h1 with the view name, so assertions are scoped to main or two of them pass against the error fallback).

The generator now unions the document's URLs with every .js/.mjs/.css under .next/static/chunks and fonts/images under .next/static/media, and fails closed when a directory or the JS inventory is missing. 61 URLs (21 document, 36 chunks, 17 media); WASM/model weights/source maps stay out (~4 MiB total). STATIC_CACHE moves to dks-static-v3 because a manifest-only change never reinstalls an existing worker.

Fix proven load-bearing: dropping the chunk contribution returns the manifest to 17 chunks and the Graph case fails again.

Plans/ADR: plans/161, ADR 041.
Next/eslint-config-next 16.3.6/16.3.5 -> 16.3.7 (bug-fix release), react/react-dom 19.2.8 -> 19.3.0, @types/react(-dom) -> 19.3.0. TypeScript stays on 6.0.3: typescript-eslint supports >=4.8.4 <6.1.0, so TypeScript 7 is a separate side-by-side tooling migration. Zustand unchanged at 5.0.15.

Lockfile scope reviewed: six specifiers changed and only next, eslint-config-next, react, react-dom and their scheduler dependency re-resolved; every pnpm override, peer rule and ignored-build setting is retained.

Verified on a production build: 2790 unit tests, 0 vitest type-check errors, lint/typecheck/build clean with zero warnings, 642 production E2E tests green, and the six-view offline regression green. The Home hydration mismatch seen under prefers-reduced-motion is pre-existing — 8 warnings on both the old and new versions (plans/161).
Source inspection contradicted several completion claims across plans/,
so this reconciles the records against live source rather than
extending any implementation.

Add plans/162-roadmap-progress-and-next-work-2026-10-05.md: current
implementation progress, missing implementation, new feature priorities,
skills maintenance, documentation maintenance, execution order, and
verification evidence. Statuses use a fixed vocabulary
(Implemented -- source-confirmed / Partial / Not implemented --
source-confirmed / Recorded follow-on -- runtime not rechecked /
Candidate -- not scheduled / Not checked in this audit) so an unchecked
historical checkbox is never read as a live defect.

Recorded as missing, first implementation priority: recovery
reachability and backup outcome (restoreFromRecovery has no production
caller; persistRecoverySnapshot returns void and skips oversized writes
while importWithRollback still reports success). Also deletion/export
integrity, sync join/rejoin, heavy-leaf deferral, and Plan 161's three
recorded follow-ons. First new feature: AI request control.

Correct stale records rather than restating them:
- INDEX W2: withdraw "Closed, nothing to prune"; the ~26-dependency
  prune was an estimate, not a measured list
- INDEX W3: six React.lazy boundaries replace "zero next/dynamic"
- INDEX W4: bridge wiring done, join conflict surfacing and persistence
  re-init still open (ADR 027 is not complete)
- INDEX W5: sanitizer helpers exist, production integration not
  demonstrated; source-error gating is restored
- INDEX Plan 158: remediation complete (its own closure table already
  recorded P2-10/P2-11 Fixed)
- Plan 04/11: blanket COMPLETE statuses become historical; gesture and
  snapshot-compare criteria unchecked; 11.5 renamed to the real
  JSON + self-contained reader path
- ADR 037: Proposed -- not implemented (verified: no hash/popstate
  integration in src/ or e2e/)
- GOAL/ARCHITECTURE: add semantic search, CPU-first in-browser local
  provider, okf; Node >=22 and ESLint 10; DOCX is a static import inside
  a lazy view; fail-closed hydration
- GOAP: historical note -- the May ledger's Vite/SQLite/Orama/CLI
  instructions are not a current validation report

Verification: quality_gate.sh --scope docs exit 0 (57 files, 211 links,
0 broken). Source probes re-confirm 6 lazy boundaries,
ignoreSourceErrors: false, no restoreFromRecovery production caller, no
hash/history integration, and verify-before-asserting absent from both
generated catalogs. 48 introduced local links resolve, 0 unresolved.
validate-links.sh covers SKILL.md only, so the separate introduced-link
check is what covers plans/. No tests, builds, E2E, or remote queries
were run; Plan 161's figures stay dated 2026-09-30 records.
Plan 162 named recovery reachability the first implementation priority
from source inspection alone. The delivery lifecycle requires data-loss
work to start at production, so this drives a real browser against a
real corpus and records what actually happens. No implementation changes.

Reproduced (Chromium, dev build, throwaway spec since removed):
importing a >4 MiB single-entity corpus over the seed corpus replaces
the library and reports "Imported 1 entity and 0 claims", emits no
console warning, and leaves the *previous* corpus's snapshot in
localStorage unchanged. No restore affordance exists anywhere in the
Export view (RESTORE_UI_COUNT 0), for either a normal or an oversized
import.

Correction to my own earlier reading, now recorded in Plan 162: a
first probe reported the snapshot absent because it read the key
dks-recovery-snapshot, but the real key is do-knowledge-studio-recovery
(recovery-helpers.ts:16). With the correct key an ordinary import does
persist a ~19.7 KB snapshot. "Import never backs up" is therefore false.
The reproduced defect is narrower and specific: a skipped backup is
indistinguishable from a successful one, and a stale snapshot is left in
place that would restore the wrong state if a restore UI existed.

Still a missing reachability path plus a missing outcome report, not a
data-loss incident. Nothing was lost that exporting first would not
also prevent, and no restore was attempted because no UI offers one.
The restart-and-restore acceptance leg remains unproven and is left to
the implementation.

Docs gate: quality_gate.sh --scope docs exit 0, 0 broken links.
Plan 162 recorded that a JSON import could replace the entire library with
no way back and no indication anything was missing:

- persistRecoverySnapshot returned void and silently skipped the write
  above its 4 MiB guard, leaving the PREVIOUS import's snapshot in place.
- importWithRollback still returned { success: true }, so the UI showed a
  clean green import either way.
- restoreFromRecovery had no production caller at all. The snapshot
  existed and nothing could consume it.

Import now reports what actually happened and the backup is reachable.

Outcome reporting: persistRecoverySnapshot returns a discriminated
RecoveryPersistResult, threaded through a new ImportOutcome union to the
import toast, which warns "Imported -- but no backup was kept" instead of
claiming success. describeRecoverySnapshot exposes a count for the UI.

Reachability: new RecoveryBanner, mounted in RecoveryAlerts above the view
router so the offer is reachable from any view -- not just the Export view
the import happened in. Dismissal is session-only and never deletes the
snapshot, mirroring the quarantine banner's rule.

Two data-loss paths found while building this, both of which the new
banner would otherwise have made reachable, and both now guarded:

1. A refused write destroyed the surviving backup. Clearing in the
   storage-unavailable branch was wrong: localStorage.removeItem still
   succeeds when setItem is refused by a full quota, so it deleted the
   only copy of the corpus being replaced. Only the too-large branch
   clears now; a stale-but-real backup survives and is re-validated
   against schema and TTL on every read.

2. Restore could consume the backup without saving. clearRecoverySnapshot
   ran in a catch that also covered applying the snapshot, whose setState
   persists and can throw on quota *after* the in-memory swap -- and it
   was offered even in a hydration-refused session where every write is
   dropped. Restore now refuses while isSyncBlocked(), clears only after a
   confirmed apply, and never on a failure path.

readRecoverySnapshot is now contractually non-throwing and separates
unreadable storage (do not clear) from unusable bytes (clear), because
describeRecoverySnapshot runs from a shell effect where a throw would take
down the workspace over an unrelated leftover backup.

Evidence: 13 unit tests in recovery-backup-outcome.test.ts and 3 tests
across all four viewport projects in e2e/recovery-restore.spec.ts. Each
new test was driven against the unfixed code first: 6 fail without the
outcome fix, 4 without the restore-safety fix, and all 3 E2E cases without
the banner. Full gate on the final tree: 181 files / 2804 unit tests, 0
type errors, build clean, E2E 659 passed / 4 skipped / exit 0.

Deliberately unchanged: indexeddb-backup.ts still has no production
caller, so tiered backup remains not operational and no storage migration
is selected here. The graph revision-diff and remaining integrity gaps in
Plan 162 stay open.
… stops covering the topbar

The fixed top-0 z-50 banner sat over the topbar's hit points at every
configured viewport (measured with document.elementFromPoint: quick filter,
command palette and New entity at 1280/1920; menu and search triggers too
at 390x844) — while offline, the moment a local-first app most needs to
work, its primary controls could not be clicked.

OfflineIndicator now publishes its measured offsetHeight as
--offline-banner-height (ResizeObserver keeps a wrapped or zoomed banner
exact) and AppShell reserves that much paddingTop. Unset on the server, so
both renders agree at 0px. The new Offline banner suite in
e2e/accessibility.spec.ts pins the user-visible contract (controls are
hit-testable) and was re-verified to fail on the old layout before passing
here (plans/161).

Co-Authored-By: Codebuff <noreply@codebuff.com>
The hook read matchMedia synchronously on the client's first render while
the server answered false, so ~25 call sites branching
initial={reducedMotion ? false : {...}} hydrated different styles than the
HTML they were sent — 8 React mismatch warnings per Home load under
prefers-reduced-motion, on every framework version measured (plans/161 A/B:
8 on Next 16.3.6/React 19.2.8, 8 on 16.3.7/19.3.0).

useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot = () => false)
pins the server answer for the hydration pass and re-reads the real
preference immediately after. Module-scope callbacks keep identity stable so
the subscription survives renders. New e2e/hydration-mismatch.spec.ts pins
the contract in a real browser (the failure is invisible to screenshots and
mocked unit tests) and was confirmed red on the old hook before passing on
all four projects; touch-targets.spec.ts went from 8 logged mismatches to 0.

Co-Authored-By: Codebuff <noreply@codebuff.com>
…r; fix pre-existing workflow lint nits

The precache manifest and the worker's cache identity are build artefacts,
so e2e/offline-views.spec.ts only means something against `pnpm run start`.
The detect-changes job gains an `offline` filter (worker, manifest generator,
boot wiring, the offline spec, playwright config, package manifests) and the
e2e-tests job runs `pnpm run build && pnpm run test:e2e:offline` after the
dev-server suite when it trips — instead of on every UI edit (plans/161,
ADR 041). Nightly and manual runs force the filter on like the other outputs.

Also clears the two pre-existing CI-lint nits recorded in plans/161:
security-scan.yml's yamllint comments-indentation warning (comment block
misaligned with the following list item) and ci-and-labels.yml's actionlint
SC2002 (useless cat into jq). Both files now pass yamllint and actionlint
with zero warnings.

The precache manifest is regenerated from the current dependency tree so the
committed artefact matches what the offline suite builds.

Co-Authored-By: Codebuff <noreply@codebuff.com>
…gate in Plan 161 / ADR 041

Plan 161's follow-on section now documents the reduced-motion hydration
mismatch as fixed (mechanism, useSyncExternalStore fix, A/B evidence, the
deliberate first-mount residual) and the offline-banner obstruction as fixed
(with the measured obstruction table and the two false-pass traps the test
had to close before it could be trusted). ADR 041 records that the
production offline suite is wired into CI behind the `offline` paths filter.

Co-Authored-By: Codebuff <noreply@codebuff.com>
…cy tree

27 chunk hashes moved after the rebase onto main (next 16.3.8, react 19.3.0,
react-day-picker 10.0.2). The manifest is a build artefact of that exact
tree; regenerating it here keeps the committed list identical to what the
production offline suite builds. Verified: pnpm run build writes 61 URLs and
the full e2e/offline-views.spec.ts suite passes 6/6 against it.

Co-Authored-By: Codebuff <noreply@codebuff.com>
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
do-knowledge-studio Error Error Oct 10, 2026 4:01pm UTC

@github-actions github-actions Bot added documentation Documentation improvements ci config tests Related to automated/manual tests scripts labels Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Blocked merge diagnosis — blocked
⏳ Check run(s) still in progress: ["Codacy Static Code Analysis","YAML Syntax Validation","Detect Changes","Shell Script Security Analysis","Trivy Filesystem Security Scan","Secret Detection","Infrastructure as Code Security","commitlint","GitHub Actions Workflow Validation","Diagnose Blocked Merge State","Dependency Advisory Audit","Analyze (actions)","Analyze (javascript-typescript)","GitNexus"]

@codacy-production

codacy-production Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Not up to standards ⛔

🔴 Issues 3 high

Alerts:
⚠ 3 issues (≤ 0 issues of at least minor severity)

Results:
3 new issues

Category Results
Security 3 high

View in Codacy

🟢 Metrics 49 complexity · 0 duplication

Metric Results
Complexity 49
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Comment thread src/lib/studio/recovery-helpers.ts Outdated
Comment thread src/components/studio/offline-indicator.tsx Outdated
Comment thread src/lib/studio/recovery-backup-outcome.test.ts
Comment thread src/lib/studio/recovery-helpers.ts Outdated
Comment thread src/lib/studio/recovery-helpers.ts Outdated
Comment thread playwright.config.ts Outdated
Comment thread src/lib/studio/recovery-backup-outcome.test.ts Outdated
@nexus-check

nexus-check Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor
Akon Labs

GitNexus Review · PR #925

3 issues found across 2 files. (reviewed 18 of 19 reviewable files)

Summary

This appears to be a broad product and maintenance change spanning offline behavior, recovery, accessibility, dependencies, tests, and roadmap documentation. Its risk posture is elevated by wide graph reach and high-risk CI workflow files.

🔴 CRITICAL blast radius. A cross-cutting PWA, recovery, accessibility, and dependency change across the studio and project tooling, with 49 graph dependents.

The implementation centers on public/sw.js, public/precache-manifest.json, scripts/generate-precache-manifest.mjs, and src/lib/studio/recovery-helpers.ts, alongside studio components and end-to-end tests. The graph impact lands mainly in Studio and Views, with additional impact in Slices and Scripts. The PR also updates dependencies to React 19.3.0 and changes plans including plans/161-offline-lazy-view-precache-and-framework-refresh-2026-09-30.md and plans/162-roadmap-progress-and-next-work-2026-10-05.md.

Review the recovery helpers, service worker and precache generation path, and the offline and restore tests first. The HIGH risk files are .github/workflows/ci-and-labels.yml and .github/workflows/security-scan.yml.

🔀 Structural changes · fix/offline-lazy-view-precache → main

Both branches are separately indexed, so this compares their code graphs directly — what the diff cannot show.

Symbols added (35)

  • e2e/offline-views.spec.ts::OfflineViewCase
  • e2e/offline-views.spec.ts::assertOfflineFirstVisit
  • e2e/offline-views.spec.ts::assertSurface
  • e2e/offline-views.spec.ts::clearBrowserHttpCache
  • e2e/offline-views.spec.ts::waitForServiceWorkerControl
  • e2e/recovery-restore.spec.ts::entityNames
  • e2e/recovery-restore.spec.ts::hasSnapshot
  • e2e/recovery-restore.spec.ts::importJson
  • e2e/recovery-restore.spec.ts::seedCorpusA
  • e2e/recovery-restore.spec.ts::seedLargeCorpusA
  • e2e/recovery-restore.spec.ts::writeFixture
  • scripts/generate-precache-manifest.mjs::collectStaticAssetUrls
  • …and 23 more

Symbols removed (2)

  • src/components/studio/views/use-export-handlers.ts::ImportRollbackResult
  • src/lib/studio/use-reduced-motion.ts::handler

Full detail lives in the GitNexus check run for this commit.

@nexus-check

nexus-check Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Agent context for GitNexus Review · PR #925

This comment carries deterministic graph detail for coding agents and reviewers who want the receipts — the main review comment carries the human summary.

🔴 CRITICAL blast radius — this change reaches 49 downstream symbols across 4 modules; this lands on a critical surface, so review the dependents carefully before merging. (driven by dependent/module count, not file risk)

Blast Level Dependents Modules Files
🔴 CRITICAL 49 4 38

What changed

Symbol Changes (58)
Kind Symbol Location
Const API_CACHE public/sw.js:1
Const PRECACHE_MANIFEST_URL public/sw.js:27
Const FALLBACK_PRECACHE_URLS public/sw.js:28
Function main scripts/generate-precache-manifest.mjs:64
Const BUILD_HTML scripts/generate-precache-manifest.mjs:25
Const ALWAYS_INCLUDED scripts/generate-precache-manifest.mjs:29
Function GraphView src/components/studio/app-shell.tsx:29
Function MindMapView src/components/studio/app-shell.tsx:30
Function AIHarnessView src/components/studio/app-shell.tsx:31
Function TrizView src/components/studio/app-shell.tsx:32
Function ExportView src/components/studio/app-shell.tsx:33
Function SyncView src/components/studio/app-shell.tsx:34
Function RecoveryAlerts src/components/studio/app-shell.tsx:192
Function AppShell src/components/studio/app-shell.tsx:219
Function currentView src/components/studio/app-shell.tsx:220
Function editingEntityId src/components/studio/app-shell.tsx:221
Function OfflineIndicator src/components/studio/offline-indicator.tsx:17
Function handleOnline src/components/studio/offline-indicator.tsx:24
Function handleOffline src/components/studio/offline-indicator.tsx:25
Function renderUseExportHandlers src/components/studio/views/use-export-handlers.test.ts:137
Property resetStore src/components/studio/views/use-export-handlers.test.ts:146
Property error src/components/studio/views/use-export-handlers.ts:96
Interface ImportRollbackResult src/components/studio/views/use-export-handlers.ts:92
Function useExportHandlers src/components/studio/views/use-export-handlers.ts:217
Function handleConfirmImport src/components/studio/views/use-export-handlers.ts:401
Const translate src/lib/i18n/messages/announce.ts:73
TypeAlias RecoveryReadResult src/lib/studio/recovery-helpers.ts:89
TypeAlias ValidatedRecoverySnapshot src/lib/studio/recovery-helpers.ts:123
Property error src/lib/studio/recovery-helpers.ts:105
Property ok src/lib/studio/recovery-helpers.ts:105
Property error src/lib/studio/recovery-helpers.ts:111
Property ok src/lib/studio/recovery-helpers.ts:111
Property error src/lib/studio/recovery-helpers.ts:117
Property ok src/lib/studio/recovery-helpers.ts:117
Property data src/lib/studio/recovery-helpers.ts:119
Property ok src/lib/studio/recovery-helpers.ts:119
Property error src/lib/studio/recovery-helpers.ts:152
Property success src/lib/studio/recovery-helpers.ts:152
Property success src/lib/studio/recovery-helpers.ts:155
Property error src/lib/studio/recovery-helpers.ts:162
Function persistRecoverySnapshot src/lib/studio/recovery-helpers.ts:54
Function clearRecoverySnapshot src/lib/studio/recovery-helpers.ts:94
Function readRecoverySnapshot src/lib/studio/recovery-helpers.ts:103
Function applyRecoverySnapshot src/lib/studio/recovery-helpers.ts:129
Function restoreFromRecovery src/lib/studio/recovery-helpers.ts:149
Const RecoverySnapshotSchema src/lib/studio/recovery-helpers.ts:68
Property success src/lib/studio/slices/data-slice.ts:66
Function createDataSlice src/lib/studio/slices/data-slice.ts:46
Function importWithRollback src/lib/studio/slices/data-slice.ts:55
Interface StudioState src/lib/studio/store-types.ts:21
Function useReducedMotion src/lib/studio/use-reduced-motion.ts:11
Function handler src/lib/studio/use-reduced-motion.ts:27
Const originalPersistStorage src/lib/studio/store-coverage.test.ts:27
Const STORE_KEY src/lib/studio/store-coverage.test.ts:26
Const RECOVERY_KEY src/lib/studio/store-coverage.test.ts:25
Function getItem src/lib/studio/store-coverage.test.ts:692
Function validPayload src/lib/studio/store-coverage.test.ts:618
Function setItem src/lib/studio/store-coverage.test.ts:581
Changed Files (38)
File Status
.github/workflows/ci-and-labels.yml 🟡 modified
.github/workflows/security-scan.yml 🟡 modified
e2e/accessibility.spec.ts 🟡 modified
e2e/hydration-mismatch.spec.ts 🟢 added
e2e/offline-views.spec.ts 🟢 added
e2e/recovery-restore.spec.ts 🟢 added
package.json 🟡 modified
plans/04-feature-roadmap.md 🟡 modified
plans/11-expansion-roadmap.md 🟡 modified
plans/130-goap-uiux-testpyramid-errorhandling-2026-08-22.md 🟡 modified
plans/131-goap-swarm-improvement-audit-2026-08-22.md 🟡 modified
plans/158-september-2026-best-practice-audit.md 🟡 modified
plans/161-offline-lazy-view-precache-and-framework-refresh-2026-09-30.md 🟢 added
plans/162-roadmap-progress-and-next-work-2026-10-05.md 🟢 added
plans/ADRs/037-url-addressable-view-state.md 🟡 modified
plans/ADRs/041-offline-precache-scope-and-upgrade-identity.md 🟢 added
plans/ARCHITECTURE.md 🟡 modified
plans/GOAL.md 🟡 modified
plans/GOAP.md 🟡 modified
plans/INDEX.md 🟡 modified
plans/PHASES.md 🟡 modified
playwright.config.ts 🟡 modified
pnpm-lock.yaml 🟡 modified
public/precache-manifest.json 🟡 modified
public/sw.js 🟡 modified
scripts/generate-precache-manifest.mjs 🟡 modified
src/components/studio/app-shell.tsx 🟡 modified
src/components/studio/offline-indicator.tsx 🟡 modified
src/components/studio/views/recovery-banner.tsx 🟢 added
src/components/studio/views/use-export-handlers.test.ts 🟡 modified
src/components/studio/views/use-export-handlers.ts 🟡 modified
src/lib/i18n/messages/announce.ts 🟡 modified
src/lib/studio/recovery-backup-outcome.test.ts 🟢 added
src/lib/studio/recovery-helpers.ts 🟡 modified
src/lib/studio/slices/data-slice.ts 🟡 modified
src/lib/studio/store-coverage.test.ts 🟡 modified
src/lib/studio/store-types.ts 🟡 modified
src/lib/studio/use-reduced-motion.ts 🟡 modified

What it affects

Architecture Impact

Module Hits Direct
Studio 7 ⚪
Views 5 ⚪
Slices 2 ⚪
Scripts 1 ⚪

Blast Radius

Depth Count
d1 (direct) 28
d2 (indirect) 21
d3 (transitive) 0
Direct dependents (d1)
  • src/lib/studio/store.ts · store.ts
  • src/components/studio/views/encrypt-export-dialog.tsx:23 · EncryptExportDialog
  • src/components/studio/views/home-view.tsx:145 · HomeView
  • src/components/studio/views/graph-view.tsx:31 · GraphView
  • src/components/studio/views/reset-confirm-dialog.tsx:17 · ResetConfirmDialog
  • src/components/studio/views/ai-harness-view.tsx:32 · useAIHarnessViewState
  • src/components/studio/views/mindmap-view.tsx:42 · MindMapView
  • src/components/studio/ui/skeleton.tsx:161 · ChatSkeleton
  • src/app/page.tsx:5 · Home
  • src/components/studio/shortcuts-dialog.tsx:109 · ShortcutsDialog
  • src/components/studio/views/triz-matrix-view.tsx:176 · TrizMatrixView
  • src/components/studio/views/chat-view.tsx:28 · ChatView
  • src/components/studio/views/triz-results-view.tsx:182 · TrizResultsView
  • src/components/studio/app-shell.tsx · app-shell.tsx
  • src/components/studio/views/export-format-grid.tsx:20 · ExportFormatGrid
  • scripts/generate-precache-manifest.mjs · generate-precache-manifest.mjs
  • src/components/studio/views/triz-subviews.tsx:141 · TrizPickView
  • src/app/layout.tsx:88 · RootLayout
  • src/components/studio/views/import-preview-dialog.tsx:17 · ImportPreviewDialog
  • src/components/studio/views/sync-helpers.tsx:339 · SyncStatusCard
  • (8 more)
Indirect dependents (d2)
  • src/components/studio/views/chat-view.tsx · chat-view.tsx
  • src/components/studio/right-panel.tsx · right-panel.tsx
  • src/components/studio/mobile-drawer.tsx · mobile-drawer.tsx
  • src/components/studio/topbar.tsx · topbar.tsx
  • src/components/studio/views/ai-harness-view.tsx:209 · AIHarnessView
  • src/lib/sync/bridge.ts · bridge.ts
  • src/lib/studio/recovery-helpers.ts · recovery-helpers.ts
  • src/components/studio/sidebar.tsx · sidebar.tsx
  • src/components/studio/views/editor-view.tsx · editor-view.tsx
  • src/components/studio/views/editor-claims-panel.tsx · editor-claims-panel.tsx
  • src/components/studio/voice-capture.tsx · voice-capture.tsx
  • src/components/studio/views/home-view.tsx · home-view.tsx
  • src/components/studio/views/timeline-view.tsx · timeline-view.tsx
  • src/components/studio/views/triz-view.tsx:15 · TrizView
  • src/components/studio/views/library-view.tsx:148 · LibraryView
  • src/components/studio/views/sync-view.tsx · sync-view.tsx
  • src/components/studio/views/graph-view.tsx · graph-view.tsx
  • src/app/page.tsx · page.tsx
  • src/components/studio/views/library-view.tsx · library-view.tsx
  • src/components/studio/views/ai-harness-view.tsx · ai-harness-view.tsx
  • (1 more)

What to check

File Risk (9)
File Risk Category
.github/workflows/ci-and-labels.yml 🟠 HIGH CI/CD
.github/workflows/security-scan.yml 🟠 HIGH CI/CD
package.json 🟡 MEDIUM Dependencies
plans/130-goap-uiux-testpyramid-errorhandling-2026-08-22.md 🟢 LOW Documentation
plans/131-goap-swarm-improvement-audit-2026-08-22.md 🟢 LOW Documentation
plans/161-offline-lazy-view-precache-and-framework-refresh-2026-09-30.md 🟢 LOW Documentation
plans/162-roadmap-progress-and-next-work-2026-10-05.md 🟢 LOW Documentation
plans/ADRs/041-offline-precache-scope-and-upgrade-identity.md 🟢 LOW Documentation
pnpm-lock.yaml 🟢 LOW Lock File
Prompt for AI agents (3 issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.

<file name="src/lib/studio/recovery-helpers.ts">

<violation number="1" location="src/lib/studio/recovery-helpers.ts:151">
P2: Oversized imports can leave a stale recovery snapshot when removal fails — The oversized path relies on 'clearRecoverySnapshot()' to prevent a later restore from returning the corpus replaced by a prior import. But 'clearRecoverySnapshot' catches a failed 'localStorage.removeItem' and returns without indicating failure (recovery-helpers.ts:196-203); this call then reports 'too-large' and the import proceeds. If removal is refused, the old snapshot remains available and can later restore the wrong corpus.
</violation>

</file>

<file name="e2e/accessibility.spec.ts">

<violation number="1" location="e2e/accessibility.spec.ts:238">
P2: Banner-position poll accepts the off-screen entry position — The condition rejects the banner only when its bottom is below its height (i.e. its top is positive). During the stated slide-in from above, the banner has a negative top and bottom less than its height, so this branch does not wait and the poll can proceed while the banner remains off-screen. That defeats the stated purpose of ensuring the geometric probe sees the resting layout; it should require the banner’s top/bottom to be at the resting position, rather than only reject a positive top.
</violation>

<violation number="2" location="e2e/accessibility.spec.ts:298">
P2: Release test can pass even if space is cleared during the exit animation — The test waits for 'paddingTop' to become zero, then separately waits for the offline status element to disappear. If a regression clears the reservation immediately when going online while the banner is still animating out, the first poll resolves early and the second simply waits for the banner to disappear; both assertions still pass. Thus this test does not verify the ordering its comment says it protects.
</violation>

</file>

recovery-helpers: containment + honesty fixes —
- notifyAvailability now contains each subscriber in its own try/catch
  (console.error) and runs outside the write try, so a listener bug can no
  longer be misreported as a storage failure or abort the import caller
- the size guard measures UTF-8 bytes via TextEncoder, not UTF-16 code
  units; Unicode-heavy content could previously pass a 4 MiB code-unit
  check and then fail in storage without the protective clear
- JSON.stringify failure returns its own 'unserializable' reason instead of
  'too-large'; the import toast gains matching copy so no user is told
  their library was too big when nothing was measured
- clearRecoverySnapshot notifies only after a successful removal

offline-indicator: the reserved height is now released by AnimatePresence's
onExitComplete instead of the effect cleanup, so returning online no longer
slides the topbar back under the still-animating banner; pinned by a new
real-browser e2e that asserts the padding clears after the exit.

tests: the restore-failure test now calls through before throwing so the
store is genuinely swapped when the write is refused (matching the
commented failure mode); refuseRecoveryWrite steps aside for non-recovery
keys instead of silently swallowing Zustand's persistence writes (jsdom's
proxy re-dispatches at call time, so a captured "original" recurses — the
mock restores, calls through, and reinstalls).

playwright.config: the production-offline comment no longer claims mobile
coverage the chromium-only run does not provide.

Co-Authored-By: Codebuff <noreply@codebuff.com>
@nexus-check

nexus-check Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

🔄 What's new in this push (ba9ad9b)

The 27 changed symbols appear to span studio UI and import/export recovery handling. In src/components/studio/offline-indicator.tsx, OfflineIndicator, handleOnline, and handleOffline changed; src/components/studio/views/use-export-handlers.ts changed ImportRollbackResult, useExportHandlers, and handleConfirmImport, while src/lib/studio/recovery-helpers.ts changed RecoveryReadResult and ValidatedRecoverySnapshot.

The main GitNexus review comment has the full, updated report.

// an oversized snapshot past a code-unit check (GitNexus on PR #925).
if (new TextEncoder().encode(serialized).length > MAX_RECOVERY_SIZE_BYTES) {
console.warn('Recovery snapshot exceeds size limit, skipping persistence')
clearRecoverySnapshot()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Warning — Oversized imports can leave a stale recovery snapshot when removal fails

The oversized path relies on clearRecoverySnapshot() to prevent a later restore from returning the corpus replaced by a prior import. But clearRecoverySnapshot catches a failed localStorage.removeItem and returns without indicating failure (recovery-helpers.ts:196-203); this call then reports too-large and the import proceeds. If removal is refused, the old snapshot remains available and can later restore the wrong corpus.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/studio/recovery-helpers.ts, line 151:

<comment>The oversized path relies on 'clearRecoverySnapshot()' to prevent a later restore from returning the corpus replaced by a prior import. But 'clearRecoverySnapshot' catches a failed 'localStorage.removeItem' and returns without indicating failure (recovery-helpers.ts:196-203); this call then reports 'too-large' and the import proceeds. If removal is refused, the old snapshot remains available and can later restore the wrong corpus.</comment>

<context>Enclosing symbol: persistRecoverySnapshot.</context>

Why this matters: GitNexus flagged this from your code graph — a caller or contract relies on what changed here. · llm-review

Comment thread e2e/accessibility.spec.ts
);
if (banner === undefined) return 'no banner';
const bannerRect = banner.getBoundingClientRect();
if (Math.round(bannerRect.bottom) > Math.round(bannerRect.height)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Warning — Banner-position poll accepts the off-screen entry position

The condition rejects the banner only when its bottom is below its height (i.e. its top is positive). During the stated slide-in from above, the banner has a negative top and bottom less than its height, so this branch does not wait and the poll can proceed while the banner remains off-screen. That defeats the stated purpose of ensuring the geometric probe sees the resting layout; it should require the banner’s top/bottom to be at the resting position, rather than only reject a positive top.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At e2e/accessibility.spec.ts, line 238:

<comment>The condition rejects the banner only when its bottom is below its height (i.e. its top is positive). During the stated slide-in from above, the banner has a negative top and bottom less than its height, so this branch does not wait and the poll can proceed while the banner remains off-screen. That defeats the stated purpose of ensuring the geometric probe sees the resting layout; it should require the banner’s top/bottom to be at the resting position, rather than only reject a positive top.</comment>

Why this matters: GitNexus flagged this from your code graph — a caller or contract relies on what changed here. · llm-review

Comment thread e2e/accessibility.spec.ts
// disappear: dropping it while the banner is still leaving would slide
// the topbar under it for the duration of the exit, and keeping it would
// leave a permanent gap (GitNexus on PR #925).
await expect

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Warning — Release test can pass even if space is cleared during the exit animation

The test waits for paddingTop to become zero, then separately waits for the offline status element to disappear. If a regression clears the reservation immediately when going online while the banner is still animating out, the first poll resolves early and the second simply waits for the banner to disappear; both assertions still pass. Thus this test does not verify the ordering its comment says it protects.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At e2e/accessibility.spec.ts, line 298:

<comment>The test waits for 'paddingTop' to become zero, then separately waits for the offline status element to disappear. If a regression clears the reservation immediately when going online while the banner is still animating out, the first poll resolves early and the second simply waits for the banner to disappear; both assertions still pass. Thus this test does not verify the ordering its comment says it protects.</comment>

Why this matters: GitNexus flagged this from your code graph — a caller or contract relies on what changed here. · llm-review

This branch had an error being deployed

1 failed deployment
Preview — ba9ad9b2 Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci config documentation Documentation improvements scripts tests Related to automated/manual tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants