Add DB migration framework and split initial archive schema into migrations#2
Closed
t41372 wants to merge 1 commit into
Closed
Add DB migration framework and split initial archive schema into migrations#2t41372 wants to merge 1 commit into
t41372 wants to merge 1 commit into
Conversation
t41372
added a commit
that referenced
this pull request
May 22, 2026
… i18n
Why
- Codex review flagged three Blocking/High issues against feat/v0.3-redesign-2:
(1) the ⌘K palette in shell.tsx sent `{ search, limit, offset }` to
query_history and tried to read `response.rows`, but the real
contract is `{ q, page, cursor, ... }` returning `items`. Result:
palette searches were silently empty on desktop, and the existing
tests had been mocking the wrong shape.
(2) Paper UI still leaked raw English copy (char/chars counter, "Remove
tag {tag}" aria, "Calendar" dialog label, "now"/"first" year-rail
footers, dashboard greetings). i18n is a shipping contract, not
polish.
(3) Theme was held twice — once by the shell as a private useState,
once by Settings → Appearance via applyPaperPreferences. Toggling
one didn't update the other, so the buttons drifted.
What
- shell.tsx: palette now calls `backend.queryHistory({ q, limit, sort })`
and maps `response.items` with the real HistoryEntry shape (id/url/
domain/title/visitedAt/visitTime). Removed the orphan PaletteRow
interface. shell.test.tsx mocks `backend.queryHistory` with valid
HistoryQueryResponse fixtures instead of the legacy rows shape.
- i18n: added 3-language entries for the paper detail panel's char
counter (singular + plural template), the remove-tag aria label, the
calendar dialog aria, the year-rail now/first footer captions, and
the dashboard morning/afternoon/evening greetings. The dashboard's
hand-rolled greeting map is gone — it now reads
`dashboard.greetingMorning/Afternoon/Evening` like every other copy
in that route. PaperDetailPanelCopy + PaperCalendarPopoverCopy +
PaperYearRailProps + paper-view.tsx copy interface all gained the
new keys; buildPaperDetailPanelCopy + buildPaperContactSheetCopy
thread them through. i18n parity 100% (2798 keys × 3 locales).
- paper-preferences.ts: applyPaperPreferences now dispatches a
`pathkeep.paperPreferencesChanged` CustomEvent (with the resolved
preferences in `detail.preferences`) after applying + persisting.
shell.tsx subscribes to that event so the topbar theme button stays
in sync with Settings → Appearance toggles; settings/appearance-
section.tsx subscribes too so flipping theme via the shell button
updates the radio without re-mount. shell.tsx's handleToggleTheme
now routes through applyPaperPreferences instead of mutating a
private useState — single source of truth, one persist call.
Context
- Codex findings #1, #5, #6. Findings #2 (paper pagination), #3
(og:image fetch trigger), #4 (coverage gate restoration) tracked
separately and remain pending in BACKLOG.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
t41372
added a commit
that referenced
this pull request
May 22, 2026
…age gate
Why
- Codex review flagged three more issues:
(2) Paper Explorer Browse exposed no pagination — the underlying
useExplorerUrlState had `goToHistoryPage` / `setHistoryPageSize`
handlers, but the paper UI dropped them. For a 1440 M-row archive
this caps the user at the first response page (≤200 rows).
(3) Card-mode og:image cache only loaded existing rows — there was no
production path that enqueued a fetch for cache-miss URLs, so a
fresh archive permanently rendered favicons.
(4) `check:coverage` ran `coverage:rust:quality` (a triage slice) not
the accepted full-scope `coverage:rust`. That's an unauthorised
gate weakening vs. docs/plan/program/quality-matrix.md.
What
- PaperContactSheet: new optional `pagination` prop renders a footer
with newer/older buttons (paper goes newest → oldest, so "next" is
the older direction), a "page X of Y · N rows" summary, and a
rows-per-page selector. Disabled buttons clamp at archive bounds.
Footer omitted entirely when route doesn't supply pagination (e.g.
the day-only insight surface).
- PaperExplorerView + Explorer route: thread the new pagination
descriptor through from useExplorerUrlState's existing
handleNextHistoryPage / handlePreviousHistoryPage / setHistoryPageSize
handlers. `visibleTimeResults` carries the authoritative page /
pageCount / total / hasNext / hasPrevious; the footer reads them.
- i18n: 5 new keys across all three locales — paginationOlder, Newer,
Summary, SummaryPending, PageSizeLabel. 2803 keys × 3 locales,
parity 100%.
- use-explorer-og-images.ts: after `loadHistoryOgImages` resolves,
the hook now diffs the response and enqueues
`triggerOgImageRefetch(batch)` for URLs that came back with a
non-`ok` status (missing or never-cached). Bounded at 20 URLs per
render so the worker pool can't be stampeded; `enqueuedFetchRef`
prevents re-enqueuing within the same cache epoch. The promise's
.catch swallows rate-limit / disabled-fetch rejections silently —
the Rust worker persists negative-cache rows on its own.
- og_images_fetch.rs: extracted `read_capped_bytes<R: Read>` from
`read_response_body` so the body-read fall-throughs (Io error,
TooLarge) can be unit-tested directly with synthetic Read impls,
closing line 336 of the production source. Three new cargo tests:
errors-mid-stream → Io, oversize → TooLarge, short stream → Ok.
- package.json: check:coverage now calls `coverage:rust` (full
scope), not the `:quality` triage slice. The Rust full gate still
has ~20 uncovered defensive lines (worker pool mutex poison,
sender disconnect, mid-stream Io in the live HTTP pipeline) — those
surface on `bun run check` so a release gate failure is honest.
Coverage delta
- JS: unchanged at 99.71 stmts / 98.89 branches / 99.77 funcs /
99.89 lines (existing threshold still 99/99/98/99 pending the
separate JS residual sweep).
- Rust full scope: 12 → 7 uncovered lines in og_images_fetch.rs.
archive_flows.rs worker pool internals (13 lines) still need an
integration harness — tracked in WORK-V03-RUST-COVERAGE-RESIDUAL.
Context
- Codex findings #2, #3, #4. Codex flagged the gate-weakening change
as a merge blocker — the check:coverage script is now restored to
full coverage. The remaining residual is documented in BACKLOG and
the gate fails until it closes, which matches the codex requirement
of "merge 前必須恢復".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
t41372
added a commit
that referenced
this pull request
May 27, 2026
Why Codex review finding C1: the user's Settings → Link previews copy promises "Off — No fetching anywhere." That promise was leaking. The Browse `useExplorerOgImages` hook fired `triggerOgImageRefetch` for every visible cache miss without consulting the effective fetch mode, and the matching backend IPC entry (`refetch_og_images_impl`) only checked the legacy `fetch_enabled` kill switch — not the modern `fetch_mode` tri-state. So a user who picked "Off" via the modern control while the legacy switch stayed `true` (default for installs upgrading from the pre-tri-state schema, or any directly-edited config.json) still saw outbound HTTP fetches every time a card scrolled into view. This is a data-sovereignty contract violation per AGENTS.md core principle #2, not a paper cut. What Backend (defence-in-depth, so a buggy caller can never bypass the policy): - Add `vault_worker::effective_og_image_fetch_mode()` that hydrates the unlocked config and returns `config.og_image.effective_mode()` (which already folds the legacy kill switch into the tri-state). - `worker_bridge::archive::refetch_og_images_impl` consults the effective mode first. `Off` → return `Ok(0)` without ever touching the worker pool. The explicit "Rebuild now" affordance lives behind `prefetch_og_images_impl` and is intentionally untouched (its docstring already declares that Rebuild ignores `fetch_mode` because clicking Rebuild *is* the explicit override). - New unit assertion in `worker_bridge::tests::annotations_and_og_image_impls_cover_local_desktop_flows` sets `fetch_enabled=true, fetch_mode=Off` and confirms the command short-circuits. Frontend (primary gate, so we don't even send the IPC for no-op work): - `useExplorerOgImages` takes a new optional `fetchMode: OgImageFetchModeConfig` prop (default `'background'` so older test callers behave unchanged). When `'off'` the hook still calls `loadHistoryOgImages` to hydrate cached rows from local SQLite, but skips the `triggerOgImageRefetch` enqueue branch entirely. - `pages/explorer/index.tsx` folds the legacy `fetchEnabled` switch into the effective mode (`fetchEnabled=false` ⇒ `'off'`) and threads it into the hook from `snapshot.config.ogImage`. - New hook test: with `fetchMode='off'` and a mix of cached + uncached rows, the cached row hydrates, the cache-miss URL is stored as null (so we don't re-issue the lookup), and `triggerOgImageRefetch` is never called. How Two layers of gating, not one. The hook is the fast path (skips the IPC for cases the user shouldn't pay for), the backend is the truth (the policy holds even when the frontend is bypassed by automation, scripts, the dev-IPC bridge, or future callers we haven't written yet). Each path tested independently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Description
schema_migrationstable and anapply_migrationhelper used bycreate_schemato run idempotent migrations.migrations/0001_initial_archive.sqlandmigrations/0002_import_recoverability.sqland wire them up viaMIGRATION_0001_SQL/MIGRATION_0002_SQLconstants.ensure_ai_schemaandensure_insight_schemaafter migrations.schema_migration_helpers_and_text_renderers_are_stable.Testing
schema_migration_helpers_and_text_renderers_are_stableandcreate_schema_upgrades_legacy_archives_without_baseline_stamping) viacargo testin thevault-corecrate, and they all passed.schema_migrationsduring tests. - All automated tests in the modified module succeeded.Codex Task