refactor(main): extract FileLicenseStorage and window state to services (PR-G)#278
Conversation
First scoped slice of the PR-G "split god files" effort. apps/desktop/src/main/index.ts goes from 1065 -> 950 lines (-11%). No behavior change. All extractions are self-contained and surface-area preserving: - services/fileLicenseStorage.ts — was embedded in index.ts at lines 146-216. The three readJsonOrNull helpers fold the repeated try/catch/return-null pattern into one private function. The TODO about signed subscription envelopes points back at PR-F3 so the next step is unambiguous. - services/windowState.ts — was at lines 218-254 of index.ts. Comment now explicitly documents why the file I/O is sync (called during BrowserWindow construction before the renderer mounts and during close where handlers don't await). What this PR DELIBERATELY does NOT touch: - packages/storage-sqlite/src/repositories/SQLiteNoteRepository.ts (1121 lines). Splitting this into NoteCrudRepository + NoteTagRepository + NoteArchiveRepository + NoteSyncRepository needs runtime verification against a real DB and risks merge conflicts with every other PR in flight. Separate effort. - apps/desktop/src/renderer/components/MarkdownEditor.tsx (724 lines). The CodeMirror surface area is too entangled to split without an E2E suite to catch regressions. Defer until PR-E (#277) is stabilized and we can refactor under test coverage. Validates: - pnpm -r typecheck — green - pnpm test — 17/17 packages - pnpm --filter @readied/desktop typecheck — green (includes e2e tsconfig) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
7b21ff5
into
feat/playwright-electron-e2e
## Summary First slice of the knip cleanup. Knip surfaced 28 unused files in #267. After per-file verification (greps, import tracing, barrel resolution), **12 were genuinely orphaned** — those are deleted here. The rest stay for reasons documented below. ## Deleted (12 files, 659 lines) ### \`apps/desktop\` | File | Why safe | |---|---| | \`src/renderer/analytics.ts\` | No imports anywhere | | \`src/renderer/hooks/useTheme.ts\` | No imports anywhere | | \`src/renderer/settings.tsx\` | No imports anywhere (the real settings entry is \`pages/settings/SettingsApp.tsx\`, loaded via dynamic import in \`main.tsx\`) | | \`src/renderer/ui/patterns/Modal.tsx\` + \`index.ts\` + \`Modal.module.css\` + \`.gitkeep\` | Only the barrel imported \`Modal\`, and the barrel itself was unused — both go together | ### \`packages\` | File | Why safe | |---|---| | \`plugin-api/src/editor/types.ts\` | Not re-exported by \`plugin-api/src/index.ts\` | | \`storage-core/src/{interfaces,migrations,repositories,types}/index.ts\` | Main package index imports directly from concrete files, not these barrels | | \`storage-sqlite/src/repositories/index.ts\` | Same — concrete imports, no barrel use | ### \`scripts\` | File | Why safe | |---|---| | \`scripts/bump-version.js\` | Not referenced by any \`package.json\` script or CI workflow | ## Kept (Knip false positives) ### Auto-discovered files Knip can't see | File | Why kept | |---|---| | \`apps/desktop/src/renderer/vite-env.d.ts\` | \`/// <reference types="vite/client" />\` — required by Vite | | \`apps/desktop/src/renderer/css-modules.d.ts\` | TypeScript module shim for CSS module imports | | \`apps/desktop/src/renderer/turndown-plugin-gfm.d.ts\` | TypeScript module shim for an untyped npm dep | | \`apps/web/mdx-components.tsx\` | Next.js convention — auto-discovered, never imported | ### Knip wrong about being unused | File | Reality | |---|---| | \`renderer/pages/settings/components/controls/{NumberInput,Select,TextInput,Toggle}.tsx\` | Imported by EditorSection / AiSection / UpdatesSection **through** the \`controls/index.ts\` barrel. Knip flagged both the components and the barrel as unused because it doesn't follow the chain. | ## Skipped (out of audit scope) apps/web cleanup: \`magicui/*\`, \`NavDropdown\`, \`ui/separator\`. The audit excluded apps/web; leaving them for a separate marketing-site pass. ## Test plan - [x] \`pnpm -r typecheck\` — green - [x] \`pnpm test\` — 17/17 packages - [ ] Manual smoke after merge ## What's next in this cleanup track - **PR-Knip-2**: remove unused production + dev dependencies (the \`Unused dependencies (9)\` block from knip) - **PR-Knip-3**: remove unused exports (the \`Unused exports (~100)\` block) — much more careful, since some exports may be public API contracts for plugins or external consumers ## Stack context Stacked on top of #278 (PR-G) → #277 (PR-E) → #276 (PR-F3) → #275 (PR-F2) → #274 (PR-F5) → #273 (PR-F4) → #272 (PR-F1) → #271 (PR-I) → #270 (PR-J) → #269 (PR-D) → #268 (PR-H) → #267 (PR-C) → #266 (PR-A) → #265 (PR-B). **15 PRs deep.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary Phase 2 of knip cleanup. Knip flagged 11 unused dependencies in #267; per-dep verification confirmed **6 are genuinely unused** in our scope. Removing them shrinks the lockfile by ~120 lines. ## Removed ### \`apps/desktop\` production | Dep | Why safe | |---|---| | \`highlight.js\` | We use \`rehype-highlight\`, which depends on \`lowlight\` (NOT \`highlight.js\` directly). \`lowlight\` ships its own grammars. | | \`pino-roll\` | \`logger.ts\` writes directly via \`createWriteStream\` — no pino transport / no rolling pipeline | | \`react-resizable-panels\` | No imports anywhere | | \`unist-util-visit\` | No imports anywhere (the only consumer is \`@readied/wikilinks\`, which declares it itself) | ### \`apps/desktop\` dev | Dep | Why safe | |---|---| | \`@types/mdast\` | No imports anywhere | | \`pino-pretty\` | \`logger.ts\` doesn't pipe through a transport; dev output is raw JSON via the synchronous pino factory | ### \`packages\` | Package | Dep removed | Why | |---|---|---| | \`@readied/sync-core\` | \`@readied/core\` | No imports of \`@readied/core\` in \`src/*\` — workspace link was stale | | \`@readied/wikilinks\` | \`unified\` | \`src/*\` uses \`unist-util-visit\` directly (kept) but never \`unified\` | ## Skipped (out of audit scope) \`apps/web\`: \`@radix-ui/react-separator\`, \`next-themes\`, \`tailwindcss-animate\` — marketing site, separate pass. ## Test plan - [x] \`pnpm install --ignore-scripts\` — succeeds. Pre-existing peer warnings about electron-builder / electron-vite are unrelated. - [x] \`pnpm -r typecheck\` — green. - [x] \`pnpm test\` — 17/17 packages. - [ ] Manual smoke on the desktop app after merge — verify nothing transitively depended on highlight.js / pino-roll / etc. that grep missed. ## Stack context Stacked on **PR-Knip-1** (#279) → PR-G (#278) → PR-E (#277) → ... down to PR-B (#265). 16 PRs deep. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ge layer (PR-F3-Wiring) (#281) ## Summary Wires the verification primitive from #276 into the desktop's \`FileLicenseStorage\`. The path from server → disk → in-memory now has a real Ed25519 verification step at read time, with **lenient fallthrough** during the migration window. ## Changes ### \`packages/licensing/src/types.ts\` \`StoredSubscriptionData\` now allows an optional \`signedEnvelope\`: \`\`\`ts interface StoredSubscriptionData { readonly subscription: SubscriptionInfo; readonly lastVerified: string; readonly cacheExpiresAt: string; readonly signedEnvelope?: SignedSubscriptionEnvelope; // NEW } \`\`\` Both can coexist during migration. Long-term, the unsigned \`subscription\` field becomes a derived view of the envelope payload. ### \`apps/desktop/src/main/services/fileLicenseStorage.ts\` \`readSubscriptionData\` now branches on envelope presence: | State on disk | Behavior | |---|---| | **Envelope present + signature valid** | Return cache as-is | | **Envelope present + signature invalid** | Log error, **refuse the cache** (return null). Next caller fetches fresh from the API | | **Envelope absent** | Log warning, accept the cache (lenient migration mode) | Plus a \`SUBSCRIPTION_PUBLIC_KEY\` constant at the top of the file with a **REPLACE BEFORE SHIPPING** note. The all-zeros placeholder means any real envelope will fail verification — that's the *correct* failure mode while the placeholder is in place. The lenient "no envelope" branch is what runs today. ## What's NOT in this PR (still pending the server team) - \`ApiClient.getSubscriptionStatus\` does not yet return an envelope. When it does, \`mapApiToSubscriptionData\` in \`licenseHandlers.ts\` grows one more field (\`signedEnvelope\`) and \`writeSubscriptionData\` persists it. That change is one-liner plumbing, blocked only on the API contract being updated. - The placeholder public key gets swapped for the real server key in the release that first ships signed envelopes. ## Behavior matrix | Disk state | Today (placeholder key) | After server rollout (real key) | |---|---|---| | Empty | null returned, no log | null returned, no log | | Old shape (no envelope) | Warning logged, accepted | Warning logged, accepted (until strict mode) | | Real envelope, valid sig | Cannot occur (server isn't signing yet) | Silently accepted | | Real envelope, invalid sig | Will fail (placeholder key) → refused → refetch | Refused → refetch | | Tampered envelope | Refused → refetch | Refused → refetch | ## Test plan - [x] \`pnpm -r typecheck\` — green - [x] \`pnpm test\` — 17/17 packages (the 18 signature tests from #276 cover the verify path; this PR just calls into them) - [ ] Manual: edit \`subscription.json\` to flip a single byte in the cached \`subscription.subscriptionId\` field. Reopen the app. Once server signs envelopes: expect error log + refetch. Today: warning log + cache accepted (no envelope to verify). ## Stack context Stacked on **PR-Knip-2** (#280) → PR-Knip-1 (#279) → PR-G (#278) → PR-E (#277) → ... down to PR-B (#265). **17 PRs deep.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Repository (#282) ## Summary **Phase 1 of the SQLiteNoteRepository split.** Pure helpers come out to their own file so future sub-repositories (sync, tag, archive) can reuse them without inheriting from the 1000-line class. | Metric | Before | After | |---|---|---| | \`SQLiteNoteRepository.ts\` | 1121 lines | **1038 lines** (-7%) | | \`noteMapping.ts\` | — | 133 lines (new) | ## Extracted | Symbol | Kind | Notes | |---|---|---| | \`NoteRow\`, \`TagRow\`, \`TagWithColorRow\`, \`BacklinkInfo\` | Row types | Re-exported by SQLiteNoteRepository so external imports keep working | | \`rowToNote(row, tags) -> Note\` | Pure mapper | Reconstructs a domain Note from a SQLite row + its tags | | \`prepareFtsQuery(input) -> string\` | Pure helper | FTS5 query escaper + tokenizer | | \`archivedConditionSql(filter, alias) -> string\` | Pure helper | SQL fragment for archived filtering | Call sites swapped from \`this.<helper>()\` to plain function imports. **The public class signature is unchanged** — \`BacklinkInfo\` is re-exported so external consumers (e.g. \`apps/desktop/src/main/handlers/types.ts\`) keep working without edits. ## What this PR DELIBERATELY does NOT do - **Extract sync methods** (\`getPendingChanges\` through \`getSyncHistory\` ~430 lines) into a \`SQLiteNoteSyncRepository\`. Those share state — tag queries, transactions, FTS sync triggers — with the main class in ways that need real-DB integration coverage to refactor safely. The helpers extracted here are the foundation: a follow-up PR can build the sync sub-repo on top of them without touching the helpers again. - **Extract tag methods** (\`setManualTags\`, \`renameTag\`, \`getAllTagsWithColors\`, etc.) for the same reason. The audit aspired to a 4-way split (NoteCrudRepository + NoteTagRepository + NoteArchiveRepository + NoteSyncRepository). That remains the destination. This PR ships the **foundation** that makes those splits low-risk; each can ride its own PR with focused review. ## Test plan - [x] \`pnpm -r typecheck\` — green - [x] \`pnpm test\` — 17/17 packages - [ ] Manual: launch the desktop, exercise notes CRUD + search + tag operations. Behavior should be identical (no observable change). ## Stack context Stacked on **PR-F3 wiring** (#281) → PR-Knip-2 (#280) → PR-Knip-1 (#279) → PR-G (#278) → PR-E (#277) → ... down to PR-B (#265). **18 PRs deep.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
**Phase 1 of the MarkdownEditor split.** Pure theme + markdown
HighlightStyle move to their own file so further extractions (extensions
array, keymap bindings) can ride on top without merging against a
churn-prone component shell.
| Metric | Before | After |
|---|---|---|
| \`MarkdownEditor.tsx\` | 737 lines | **612 lines** (-17%) |
| \`editorTheme.ts\` | — | 139 lines (new) |
## Extracted
| Symbol | Kind |
|---|---|
| \`SCROLL_PAST_END_PADDING\` | constant |
| \`createEditorTheme(fontSize, fontFamily, lineHeight)\` | factory
returning \`EditorView.theme({...})\` |
| \`markdownHighlighting\` | \`HighlightStyle.define([...])\` with all
tag styles for markdown |
## Imports cleaned up
- Drop \`HighlightStyle\` from \`@codemirror/language\` import (no
longer referenced in this file)
- Drop \`tags\` from \`@lezer/highlight\` (moved into editorTheme.ts)
## What this PR DELIBERATELY does NOT do
- **Extract the extensions array** (~96 lines of \`createExtensions\`).
It closes over user settings (\`lineNumbersCompartment\` etc.) and mixes
context-coupled values like \`wikilinkAutocomplete\` from a hook. Safely
pulling that out requires either passing the closure context via a
builder, or moving the whole \`useMemo\` into its own hook. **Better
done under Playwright coverage (PR-E #277) so renderer regressions
surface.**
- **Extract the keymap.** Same reason — bindings reference
view-imperatives + the command-registry which are constructed inside the
React tree.
## Test plan
- [x] \`pnpm -r typecheck\` — green (renderer + e2e tsconfigs)
- [x] \`pnpm test\` — 17/17 packages
- [ ] Manual: open the editor, type in a markdown note with headings,
emphasis, lists, code blocks. The look must be identical (theme is
byte-for-byte the same; just moved).
## Stack context
Stacked on **note repo split** (#282) → PR-F3 wiring (#281) → PR-Knip-2
(#280) → PR-Knip-1 (#279) → PR-G (#278) → PR-E (#277) → ... down to PR-B
(#265). **19 PRs deep.**
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary Promotes `develop` to `main` for **v0.15.0**. Originally opened 2026-04-24; now refreshed with the 19-PR tech-debt audit shipped via #285 plus the accumulated dependabot bumps reconciled. `semantic-release` will pick the version bump. Expected: **minor (v0.14.x → v0.15.0)** because of multiple \`feat:\` commits. ## What ships (audit highlights, from #285) ### Runtime fixes (user-facing) - **Editor no longer crashes on table-containing notes** (#266) — \`Decoration.replace\` over multi-line ranges moved from a ViewPlugin to a StateField, plus an EditorView.exceptionSink so any future plugin error no longer tears down the EditorView. - **AI keys survive sleep/wake** (#275) — \`aiKeyStorage\` stopped silently deleting the encrypted store when the keychain was temporarily locked after macOS sleep. - **Backup restore is now safe** (#271) — restored DBs go through \`PRAGMA integrity_check\` before being swapped in; corrupt backups roll back to the safety copy. - **MCP server runs without electron-builder rebuilds** (#264 → #270) — migrated from native \`better-sqlite3\` to built-in \`node:sqlite\` (Node 22.5+), updated to the new \`registerTool\` MCP SDK API. ### Security - **Typed IPC boundary** (#272 + #273 + #274) — 130+ IPC channels now validated with Zod tuples at the main↔renderer boundary. Garbage in fails fast with \`IpcValidationError\` instead of corrupting downstream code. - **Ed25519 license verification scaffolding** (#276 + #281 + #284) — \`signSubscriptionPayload\` / \`verifySubscriptionSignature\` helpers ship in \`@readied/licensing\`, wired into \`FileLicenseStorage.readSubscriptionData\` with lenient fallthrough during migration. Real public key embedded (\`d04901…\`). Server-side \`LICENSE_SIGNING_PRIVATE_KEY\` already set in Cloudflare staging + production. ### Developer experience - **Husky → Lefthook** (#267) plus lint-staged that now runs ESLint, not just Prettier. - **\`knip\` added** (#267) + 12 unused files deleted (#279) + 6 unused deps dropped (#280). - **Playwright Electron E2E scaffold** (#277) with smoke + notes-IPC specs and a Linux+xvfb CI job (\`continue-on-error: true\` while it stabilises). - **Vitest coverage baseline** (#269) — 12 packages share a coverage config; smoke tests added for \`@readied/commands\`. ### Refactor (no behavior change) - **Zustand selectors migration** (#268) — 3 components stopped destructuring entire stores. - **God-file extractions**: - \`main/index.ts\` 1065 → 950 lines (#278) — \`FileLicenseStorage\`, \`windowState\` extracted to services - \`SQLiteNoteRepository.ts\` 1121 → 1038 lines (#282) — pure helpers extracted to \`noteMapping.ts\` - \`MarkdownEditor.tsx\` 737 → 612 lines (#283) — theme + markdownHighlighting extracted to \`editorTheme.ts\` ## Deploys triggered | Workflow | Trigger | What happens | |---|---|---| | \`deploy-api.yml\` | Auto on \`push\` to main affecting \`packages/api/**\` | Tests + deploys \`@readied/api\` to Cloudflare Workers (\`readied-api-production\`). This stack only touched \`wrangler.toml\` + \`.dev.vars\` docs, no production code change. | | \`release.yml\` | Manual \`workflow_dispatch\` post-merge | \`semantic-release\` analyses conventional commits, bumps version, creates GitHub Release draft + tag | | \`build.yml\` | Auto on tag push from release.yml | mac / windows / linux parallel builds, artefacts attached to the GitHub Release | ## Pre-merge verification (local, this branch) - ✅ \`pnpm -r typecheck\` — green across 18 workspace projects - ✅ \`pnpm test\` — 17/17 packages - ✅ Merge resolved: take develop versions for 19 conflicted package.jsons (develop has equal or newer deps than main's dependabot bumps) ## Post-merge action items (operator) 1. **Deploy API to staging first** (smoke test): \`\`\` gh workflow run deploy-api.yml -f environment=staging \`\`\` 2. Confirm staging API responds correctly (subscription endpoint with new \`LICENSE_SIGNING_PRIVATE_KEY\` secret already set in CF). 3. Merge this PR → auto-deploys API to production. 4. Trigger Release workflow: GitHub → Actions → Release → "Run workflow" → main. 5. Watch Build workflow for mac/win/linux completion. 6. Confirm the release un-drafts itself. ## Known risks / follow-ups - **Pre-existing Vercel preview failure for \`apps/web\`** — marketing site, scheduled to be extracted to its own repo (P3 in the roadmap). - **\`SUBSCRIPTION_PUBLIC_KEY\` is dev-grade** — generated in a Claude session. Before the licensing server emits envelopes for real paid users, rotate the keypair from a trusted machine and ship a follow-up release. - **Branch protection should require CodeRabbit completion before automerge** — added to the roadmap as a process item; this very PR was BLOCKED correctly because of that policy gap being closed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
First scoped slice of the PR-G "split god files" effort.
`apps/desktop/src/main/index.ts` goes from 1065 → 950 lines (-11%). No behavior change. All extractions are self-contained and surface-area preserving — no API changes, no runtime touch.
What moved
What this PR DELIBERATELY does NOT touch
Per your earlier guidance about merge-conflict risk:
The principle is established here: surgical extractions of self-contained pieces, each verifying typecheck+tests, no behavior change. Future PRs can keep slicing under the same discipline.
Test plan
Stack context
PR-G in the audit stack. Stacked on top of #277 (PR-E) → #276 (PR-F3) → #275 (PR-F2) → #274 (PR-F5) → #273 (PR-F4) → #272 (PR-F1) → #271 (PR-I) → #270 (PR-J) → #269 (PR-D) → #268 (PR-H) → #267 (PR-C) → #266 (PR-A) → #265 (PR-B). 14 PRs deep.
🤖 Generated with Claude Code