Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pnpm install --frozen-lockfile # 安装依赖
pnpm run dev # 启动开发服务器(含热更新)
pnpm run build # 生产构建(执行 bin/build.sh)
pnpm run build:single # 打包为单个 HTML 文件
pnpm run lint:ts # oxlint + tsc --noEmit(CI 必跑)
pnpm run lint:ts # oxlint + workspace rebuild + tsc --noEmit(CI 必跑)
pnpm run format:check # prettier 格式检查(CI 必跑)
pnpm run test # 单元测试(Vitest)
pnpm run test:coverage # 带覆盖率的单元测试
Expand Down Expand Up @@ -204,7 +204,7 @@ test/setup/vitest.ts # 全局 mock:matchMedia、URL.createObjectURL
```

覆盖率统计 `lib/**/*.ts` 与 `packages/*/src/**/*.ts`,排除类型声明。
全局最低门槛保留在 `vitest.config.ts`,历史、shared、converter、agent-core 与 chat-ui
全局门槛为 lines/functions/statements 80%、branches 75%;Agent 应用层、历史、shared、converter、agent-core 与 chat-ui
另有独立模块门槛。实际数字以 `pnpm run test:coverage` 为准。

开发与 Vitest 通过 `bin/workspace-aliases.mjs` 从 workspace 的 `exports` 派生源码别名,
Expand Down Expand Up @@ -1140,3 +1140,10 @@ E2E 回归套件覆盖**(`pnpm run test:e2e` 的 embed-regression.spec:打
### AI assistant product entry

`lib/agent-entry.ts` owns the discoverable entry and browser-local default-off preference. URL `agent` flags do not enable it. Opening restores only UI/preferences; loading a model or connecting a service requires an explicit user action. Generated writing must be reviewed before applying. Developer transport and provider details belong in developer references, not product onboarding.

## Runtime and storage maintenance (2026-10-13)

- `bin/offline-assets.mjs` derives mandatory app dependencies from Vite's emitted manifest. Inference chunks/WASM are optional; the small assistant UI static graph stays in core for live-page compatibility. `PREPARE_ASSISTANT` follows explicit or persisted opt-in, and a prepared marker refreshes runtime assets before later worker activation. Model weights and external endpoints remain outside app caches. Preserve the vendor precache for first offline export.
- `pnpm run tsc` rebuilds workspace declarations before checking the app; `lint:ts` calls it. Do not rely on an earlier production build to refresh package contracts.
- History v3 uses key-only revision/size indexing for retention; latest snapshot reads only one value. Conversation storage keeps explicit opt-in and atomic revision conflicts while updating individual sessions.
- Panel context synchronization is lifecycle-owned: no periodic reads while closed, disabled or hidden, and BFCache restoration reinstalls observers. Keep extracted settings/context owners rather than adding them back to `panel.ts`.
59 changes: 54 additions & 5 deletions bin/offline-assets.mjs
Original file line number Diff line number Diff line change
@@ -1,18 +1,67 @@
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

/** Build outputs only: no model weights, documents, source maps or external URLs. */
const ASSISTANT_ENTRY = 'lib/agent-plugin/agent-plugin.ts';
const GENERATED_ASSET = /^assets\/[A-Za-z0-9_.-]+\.(?:js|css|wasm|woff2)$/;

/** Follow both static and lazy app edges, stopping at the optional assistant. */
function collectAssets(manifest, roots, excluded = new Set(), followDynamic = true) {
const visited = new Set();
const assets = new Set();
const visit = (key) => {
if (visited.has(key) || excluded.has(key)) return;
visited.add(key);
const chunk = manifest[key];
if (!chunk) throw new Error(`Missing offline manifest dependency: ${key}`);
for (const asset of [chunk.file, ...(chunk.css ?? []), ...(chunk.assets ?? [])]) {
if (!GENERATED_ASSET.test(asset)) throw new Error(`Unsupported offline asset: ${asset}`);
assets.add(`/${asset}`);
}
for (const imported of [...(chunk.imports ?? []), ...(followDynamic ? (chunk.dynamicImports ?? []) : [])])
visit(imported);
};
for (const root of roots) visit(root);
return assets;
}

/** Core app dependencies install first; optional inference assets need explicit opt-in. */
export async function stampOfflineAssets(directory) {
const chunks = (await readdir(join(directory, 'assets'))).filter((name) => /\.(?:js|css|wasm)$/.test(name));
const manifest = JSON.parse(await readFile(join(directory, '.vite', 'manifest.json'), 'utf8'));
const entries = Object.entries(manifest)
.filter(([, chunk]) => chunk.isEntry)
.map(([key]) => key);
const assistant = Object.keys(manifest).filter(
(key) => key === ASSISTANT_ENTRY || manifest[key].src === ASSISTANT_ENTRY,
);
if (!entries.length || assistant.length !== 1) throw new Error('Missing offline app or assistant manifest entry');
const core = collectAssets(manifest, entries, new Set(assistant));
// Retain the small UI graph for old live pages whose lazy import URLs must
// survive a deploy. Inference engines, Workers and WASM remain optional.
for (const asset of collectAssets(manifest, assistant, new Set(), false)) core.add(asset);
const optional = collectAssets(manifest, assistant);
// Worker outputs may be emitted outside the main manifest's dependency graph.
// They belong to the optional inference runtime, never to the first visit.
for (const name of await readdir(join(directory, 'assets'))) {
if (/\.(?:js|css|wasm)$/.test(name)) optional.add(`/assets/${name}`);
}
const fonts = (await readdir(join(directory, 'ran-fonts'))).filter((name) => name.endsWith('.woff2'));
const assets = [...chunks.map((name) => `/assets/${name}`), ...fonts.map((name) => `/ran-fonts/${name}`)].sort();
for (const name of fonts) core.add(`/ran-fonts/${name}`);
const assets = [...core].sort();
const assistantAssets = [...optional].filter((asset) => !core.has(asset)).sort();
const path = join(directory, 'sw.js');
const source = await readFile(path, 'utf8');
const marker = 'const APP_ASSETS_TO_CACHE = []; // BUILD_APP_ASSETS';
if (!source.includes(marker)) throw new Error('Missing offline app asset build marker');
const assistantMarker = 'const ASSISTANT_ASSETS_TO_CACHE = []; // BUILD_ASSISTANT_ASSETS';
if (!source.includes(marker) || !source.includes(assistantMarker))
throw new Error('Missing offline asset build marker');
await writeFile(
path,
source.replace(marker, `const APP_ASSETS_TO_CACHE = ${JSON.stringify(assets)}; // BUILD_APP_ASSETS`),
source
.replace(marker, `const APP_ASSETS_TO_CACHE = ${JSON.stringify(assets)}; // BUILD_APP_ASSETS`)
.replace(
assistantMarker,
`const ASSISTANT_ASSETS_TO_CACHE = ${JSON.stringify(assistantAssets)}; // BUILD_ASSISTANT_ASSETS`,
),
);
return assets;
}
Expand Down
52 changes: 52 additions & 0 deletions docs/explorations/2026-10-13-follow-up-performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Follow-up performance verification

This pass starts from 0131b526 and adds one commit per feature to PR #266. Operation counts establish reduced work; they do not imply a specific browser latency improvement.

## Browser baseline

Chrome DevTools inspected a production local editor preview on 127.0.0.1:4203. The reload trace was unthrottled and reported CLS 0.06; no LCP or field metrics were returned. The network snapshot confirmed native app/SDK/font resources, and the accessibility snapshot confirmed the real editor was interactive. This local reload is not a cold production bandwidth benchmark. No vendor-loading optimization is claimed from it.

## Selected improvements

- Chat rendering: repeated full-history button-state scans and per-row scroll measurements during restoration; repeated full streamed source attribute writes.
- History expiry: complete live metadata read/sort before expiry filtering despite an existing updatedAt index.
- GGUF deletion: per-shard SDK deletion repeats cache enumeration, including unrelated cached files.

Verification results will be recorded alongside each independently committed feature.

## Chat rendering

The new appendMany API restores completed messages through the existing renderer, then performs one final scroll update. Ordinary append keeps its immediate behavior. New action controls initialize within their row; existing controls update only when running or draft-emptiness changes. Streamed original source is published once at completion/interruption before the row becomes a completed answer.

| Deterministic fixture | Before | After |
| --------------------------------------------------- | -------------------------------: | ----: |
| Disabled-control writes for 100 agent messages | 5,050 | 100 |
| Disabled-control writes for 500 agent messages | 125,250 | 500 |
| Scroll calls for a 500-message restore | 500 | 1 |
| scrollHeight reads for a 500-message restore | 1,500 | 3 |
| data-source updates across 20 streaming frames | 20 | 0 |
| Final source publication on completion/interruption | Per-frame source already present | 1 |

Validated with 120 focused chat/controller/storage tests, package type generation, and a mounted Chromium case restoring 400 persisted messages, switching to a short conversation and back, checking source/action state and final scroll position. Operation counts are measured in deterministic DOM fixtures; no end-user milliseconds or memory reduction is asserted.

## Indexed history expiry

Use the existing updatedAt index to fetch only snapshots at or before the inclusive expiry cutoff. The precise expiresAt predicate still renews retention after reopening, and a stable descending sort of expired candidates preserves returned ID ordering, including equal timestamps. Deletions and save-target cleanup retain their original transactional behavior. No database schema change.

A fixture with 2,000 recent documents and five old metadata rows reads five candidates instead of all 2,005. Snapshot writes still perform the required full budget check: total metadata results fall from 4,008 to 2,008 (five expiry candidates and 2,003 budget rows); full metadata scans fall from two to one. Eleven focused history suites / 98 tests passed, including exact boundaries/ties, reopened retention, blob/handle cleanup and cancellation rollback.

## Batched GGUF deletion

Resolve the requested cache group, then delete its exact known filenames in one SDK deleteMany call. The installed SDK still owns body/metadata deletion and error propagation. Unrelated models, shared runtimes and the missing-group error remain unchanged.

Real installed SDK/storage-boundary tests show three-shard deletion uses two inventory scans instead of four. An eight-shard, four-unrelated-file small-body adapter probe shows nine scans become two, with identical deleted and remaining filenames; Blob reads fall from 240 to 72. These are aggregate operation counts on small fixtures, not a browser heap or transfer measurement. Eleven focused model-cache/storage tests pass, including metadata cleanup and failure propagation.

## Final assembled verification

- Full repository-pinned unit coverage: 5,394 tests / 181 files pass; 85.75% lines, 80.12% branches, all coverage gates pass.
- Thirty-eight impacted Chromium cases pass: long saved conversation restore, context notifications, assistant entry/native writes, history pagination/search/retention/export and autosave recovery.
- Lint, workspace declaration rebuild, application/package type checking, Docker Compose configuration, complete format check and production Vite build pass.
- Independent read-only review approves all three new feature scopes with no unresolved findings.
- New commits stay separate by feature. Earlier optional-cache/storage/UI changes remain in the previously reviewed commit; no dependencies, schemas, model defaults or vendor code changed in this pass.

The previous revision was separately checked with the user's pending upgraded dependencies. This follow-up was verified with repository-pinned dependencies; that distinction remains explicit in the PR. Real-user field metrics, production-network timing and deployment are outside the local evidence.
91 changes: 91 additions & 0 deletions docs/superpowers/plans/2026-10-13-runtime-storage-optimizations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Runtime and storage optimization implementation plan

> For agentic workers: execute the independent domains using dispatching-parallel-agents; the coordinator owns cache integration and final verification.

**Goal:** Implement all audit findings and deliver one reviewed GitHub PR.
**Architecture:** Preserve existing browser APIs and offline editor behavior. Defer optional assistant assets, reduce storage byte copies, and extract bounded UI lifecycles.
**Tech Stack:** TypeScript, Vite, IndexedDB, Service Worker, Vitest, Playwright.
**Spec:** docs/superpowers/specs/2026-10-13-runtime-storage-design.md

## Global constraints

No new dependencies or vendor changes. Preserve first offline save, stored data, explicit cloud/history opt-in, stale-tab conflicts, cancellation, public panel exports and BFCache. Do not include the root checkout's unrelated dependency edits.

## Review focus

- Offline reopen/export after one online editor initialization, with a cold landing that defers inference assets.
- Enabling an already cached assistant offline after reload.
- Database upgrades with old data and another tab open.
- Erasure/cancellation while a save is queued or in flight.
- Hidden panel, replaced editor and BFCache restoration.

### Task 1: Optional assistant precache

Files: bin/offline-assets.mjs, declaration; vite.config.ts; public/sw.js and public/sw-register.js; lib/agent-entry.ts; new lib/assistant-offline.ts; manifest/worker/entry/offline tests.

- [x] Assert the core manifest retains the static assistant UI graph and excludes inference runtime assets and includes static/dynamic editor dependencies.
- [x] Assert explicit worker preparation caches only the local allowlist, rejects failures, and preserves first native offline export.
- [x] Enable Vite manifest; derive graph closures and stamp two asset lists; request optional preparation after explicit enablement.
- [x] Run related unit tests and production build; measure core bytes before/after.

### Task 2: Bounded document history reads

Files: lib/history/{store,db,types}.ts and history unit tests.

- [x] Add regressions rejecting bulk blob reads while returning newest bytes/accounting correctly.
- [x] Implement reverse latest cursor and lightweight revision metadata/key trimming; migrate legacy records as needed.
- [x] Verify retention, quota, cancellation and source/handle cleanup tests.

### Task 3: Incremental conversation storage

Files: lib/agent-plugin/ui/{sessions,conversation-db}.ts and their unit tests.

- [x] Test burst coalescing, unchanged-session writes, v1 migration, stale-tab and erase races.
- [x] Implement a bounded pending save and per-session persistence under atomic index revision, keeping repository API.
- [x] Run the conversation/history controls/controller test suites.

### Task 4: Panel lifecycle and decomposition

Files: lib/agent-plugin/ui/panel.ts; extracted context/settings/history modules; related unit tests.

- [x] Test no hidden/disabled/background polling, immediate resume, SDK callbacks and BFCache cleanup.
- [x] Extract context scheduler and at least two focused presentation responsibilities; preserve public panel functions and request semantics.
- [x] Run all panel/composer/context/sidebar tests.

### Task 5: Quality gates and review

Files: failing iframe/focus tests; package.json scripts; vitest.config.ts; shared/root CLAUDE guides; workflow contract tests.

- [x] Fix synthetic iframe/focus fixtures without weakening production behavior assertions.
- [x] Make type checking rebuild declarations, verify from deleted stale package output.
- [x] Tighten useful application-owned coverage gates and update outdated guide statements.
- [x] Run full unit coverage, lint, format, production build and impacted E2E.
- [ ] Fresh review of the complete diff; resolve findings; commit, push and create PR.

## Progress

- Scope authorized by the user's instruction to implement all audit recommendations and submit a PR.
- Phase 1 branch prepared; PR phases 2-7 pending until implementation and validation complete.

## Implementation evidence

- History: 11 suites / 112 tests passed, including legacy v1/v2 upgrade, bounded reads, exact gapped-revision accounting, blocked upgrade retry and cancelled trim rollback.
- Conversation: 81 targeted tests passed; v2 migration, stale-tab conflicts/erasure and coalescing verified. Full-snapshot validation/serialization remains proportional to input size; unchanged records are neither cloned nor rewritten.
- Panel: 176 targeted tests passed; three modules extracted, panel reduced from 2905 to 2633 lines. SDK bursts coalesce and hidden/disabled/BFCache lifecycles release resources.
- Integrated upgraded-toolchain run: 5360 unit tests passed, coverage gates passed (85.39% lines, 79.84% branches), 34 impacted Chromium E2E passed.
- Default app precache: 1,453,207 bytes (~1.39 MiB), down from 57,481,875 bytes (~54.8 MiB); optional inference runtime: 56,063,311 bytes (~53.47 MiB). These are decoded/build file bytes, not network transfer measurements.
- Fresh converter declaration output was moved away before running the type-check script; the script rebuilt packages and passed.
- Independent review found and fixed prepared-runtime update migration/races. Final scoped review reports no unresolved Critical/Important findings.
- The first browser probe assumed a landing-only visit fully warms the editor. Missing native vendor assets disproved that assumption; the acceptance flow now initializes the native editor online before offline reopen/export, preserving the existing contract.

Final pinned-toolchain verification: 179 unit files / 5360 tests passed with coverage gates; 34 impacted browser cases plus the isolated worker-update case passed (35 total). Lint/type/build/format and fresh-declaration checks passed. The same pinned update case also passed against unmodified main. Upgraded Playwright update timing remains inconsistent in local probes; dependency updates are outside this PR.

## Follow-up: event-driven assistant UI

- Replace panel context intervals with disposable SDK/native-input subscriptions and explicit readiness/open/visibility notifications.
- Replace sidebar rail scans with filtered DOM/iframe-load/media-query observers.
- Replace pending-preview validity intervals with the context event lifecycle; preserve pre-apply validation.
- Refresh model release controls and translated context explicitly from existing runtime/language updates.
- Verify focused no-polling regressions, full quality checks, real editor sidebar/selection interactions, review and update PR #266.

Follow-up verification: 5378 tests across 179 files pass with coverage gates (85.51% lines, 79.90% branches); lint/type checks, format and production build pass. Twenty impacted assistant Chromium cases pass, including three new event-driven cases; the final rebuilt three-case suite additionally verifies a native sheet rename and same-selection history mutation. Independent read-only review found missing spreadsheet mutation/sheet notifications, now covered and rechecked. The tracking fixture models SDK multiple-listener ownership and checks complete pagehide cleanup. No recurring timers remain under lib/agent-plugin/ui.
Loading
Loading