Skip to content

Store project summaries for the picker (#434) - #472

Merged
alex-rawlings-yyc merged 5 commits into
mainfrom
434-store-project-summaries
Oct 20, 2026
Merged

alex-rawlings-yyc merged 5 commits into
mainfrom
434-store-project-summaries

Conversation

@alex-rawlings-yyc

@alex-rawlings-yyc alex-rawlings-yyc commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Part of #424; closes #434.

Summary

Saved projects are listed from one stored record of summaries, which every project write keeps current. The picker and the Paratext 9 lookups read only that record, never a project record.

 interlinearizer.getProjectsForSource
-  projectStorage.getProjectsForSource
-    projectIds -> every project:{id} in full -> filter by source -> summarize
+  projectStorage.getProjectSummariesForSource
+    ProjectSummaries.listForSource -> projectSummaries
create / Paratext 9 import / editable copy    -> record, then ProjectSummaries.add (unlisted and rolled back on failure)
updateAnalysis / updateProjectMetadata / sync -> record, then ProjectSummaries.replace (resolves undefined when unlisted)
deleteProject                                 -> ProjectSummaries.remove, then record
  • Record. projectSummaries holds { modelVersion, summaries } in creation order and replaces projectIds. Each summary carries books and tokenAnalysisCount, computed at write time. It also carries pt9Import with its file hashes, which the WebView compares when it opens an import.
  • Guards. A record from a newer build is refused on read and never overwritten. Invalid JSON or a malformed entry throws rather than listing fewer projects.
  • ProjectSummaries imports nothing from PAPI and works over KeyValueStorage, like DraftRepository. The perf seeder lists its saved projects through it.
  • getPt9ImportForSource returns a summary, since its callers use only the id. listProjects is removed.
  • Perf harness. wireBytes.projectsForSource is now the measured size of the getProjectsForSource response. It used to be estimated as draft size × saved projects. updateAnalysis:summary is a new host step timing the summarizing alone, done before the summaries lock is taken.

Evidence

  • Unit suite: passes at 100% coverage. A test asserts that the lookup by source reads only the projectSummaries key.
  • perf:app: packaged app, one run, on this branch, against the Phase 0 baseline (10 runs at 10d81320):
    tier            saved  picker response          host getProjectsForSource
    small           5      3.9 MB est. -> 1,528 B   15.7 ms -> 1.1 ms
    large-loadable  1      83.9 MB est. -> 951 B    408 ms  -> 1.1 ms
    
    The baseline response sizes were the harness's estimate, not measurements. The WebView-side projects-fetch went from 422 to 139 ms on large-loadable, but from 60 to 160 ms on small; a single run on this machine is too noisy to read those.
  • Not run: the e2e suites.

Deviations from the issue and its comment

  • No Node-safe split of projectStorage. The comment planned a core plus a thin PAPI wrapper, with the seeder calling the core. Only the summaries moved into a Node-safe class. The seeder still writes the project record itself and lists it through that class. Nothing else needs project records in Node yet.
  • Kept from the comment: summaries aren't cached in the host, so a view close has nothing to drop.
  • fetchSummary still pulls the analysis. After an import, a sync or an editable copy, the WebView builds the summary from getProject's full record. Phase 1's surface adds no WebView calls, so this waits until a later phase trims getProject.

Decisions with a strong alternative

Reviewer push-back is welcome.

  • One global record
    • Alternative: one record per source, or one per project.
    • Why not: delete knows only the id, and create's rollback would span three records. There are tens of projects at under 1 KB each, up to about 15 KB for an import's file hashes.
  • A failed summary refresh after an update is logged, not thrown
    • Alternative: reject, or restore the prior project record as create does.
    • Why not: the record holds the write, so the only damage is a stale picker row until that project's next write.
  • No migration from projectIds
    • Alternative: rebuild the summaries once from the full records.
    • Why not: there are no deployed users, and testers know saved projects don't survive builds yet.

Merge Danger

Door: one-way for saved projects

Projects saved before the merge stop listing, and a revert would stop listing those saved after it. Their records stay on disk.

Blast Radius: saved projects

This touches listing, create, save, metadata edits, delete, and Paratext 9 import and sync.


This change is Reviewable

@alex-rawlings-yyc alex-rawlings-yyc self-assigned this Oct 19, 2026
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1cf2a136-331d-4e5c-a344-85532e9b2fd2
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@alex-rawlings-yyc
alex-rawlings-yyc force-pushed the 434-store-project-summaries branch from cbb80f6 to 979f23c Compare October 19, 2026 22:57

@jasonleenaylor jasonleenaylor left a comment

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.

Reviewed with reproductions. Findings 1 to 5 were each confirmed by a throwaway jest test against this branch (finding 2 also against main, for comparison); the three nits are from reading. Sorted by severity below and inline.

  1. Medium: a Save after a half-failed delete reports success, then the save is lost at the next startup sweep.
  2. Medium: a listed summary whose record is missing is shown and trusted; each Paratext 9 sync then creates another import (regression from main).
  3. Medium: a failed summary refresh after the record landed reports the Save, metadata edit or sync as failed.
  4. Low: the sweep's comment states an invariant deleteProject now breaks.
  5. Low: every Save summarizes the whole analysis while holding the global index lock.

Nits: a missing @throws on createEditableCopy, a test helper that writes a summary shape this build never writes, and a silently skipped perf measurement.

The overall design (one record, Node-safe class, summaries written on every project write) looks right to me; these are all about the places where the listing and the records can disagree.

Comment thread src/services/projectStorage.ts
Comment thread src/services/projectStorage.ts
Comment thread src/services/projectStorage.ts Outdated
Comment thread src/services/projectStorage.ts Outdated
Comment thread src/services/projectSummaries.ts Outdated
Comment thread src/services/projectStorage.ts
Comment thread src/__tests__/test-helpers.ts
Comment thread e2e-tests/tests/perf/app-perf.spec.ts Outdated

@jasonleenaylor jasonleenaylor left a comment

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.

Verified c21fd43: reran the reproductions from my review and all five findings now behave correctly (Save to an unlisted project returns gone and the sweep cleans up; a ghost import is unlisted and two syncs leave one import; a failed summary refresh logs and the save resolves; the sweep comment and warning read right; the summary is built outside the lock). Also checked a create whose summaries write lands but reports failure: it rejects and leaves no listed-but-missing project. Full suite at 100% coverage and lint pass. The two narrower choices (write then check the listing; staleKept can still return a ghost id until the next converting sync) look fine to me.

Heads-up: this and #475 conflict in three small hunks (two import lines and the updateAnalysis doc comment). I'll take the rebase if #475 lands second.

@jasonleenaylor jasonleenaylor left a comment

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.

@jasonleenaylor reviewed 14 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).

@alex-rawlings-yyc
alex-rawlings-yyc merged commit d5cb6b8 into main Oct 20, 2026
10 checks passed
@alex-rawlings-yyc
alex-rawlings-yyc deleted the 434-store-project-summaries branch October 20, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[1e] Store project summaries for the picker

2 participants