Files
Marcos Paulo cac6b0ed39
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: registry consumers (grammar, theme, pack), seed reader, wasm plugin host with approval, wanted scan, STIX and Lottie store lookups, plugin auto-disable persistence
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 18:35:47 -03:00

22 KiB

Tasks: registry and debloat

One task = one agent (specs/agents/opencode-runbook.md). [ ] open. Spec: spec.md (sections D1 to D13). Numbers: measurements.md. Rules: AGENTS.md, docs/CODE-MAP.md. No new crate: everything new is crates/engine/src/addons/ (not registry, that name is the command registry). First tool call rule: after at most three reads, write the first OWNS file in your SECOND tool call: a compiling stub with the GOAL signatures and ONE failing test, then make it pass. Standing VERIFY: export PATH="$HOME/.cargo/bin:$PATH"; cargo test -p <pkg>; cargo clippy -p <pkg> --all-targets -- -D warnings; rustfmt --edition 2024 <owned files>; UI/i18n tasks add cargo xtask i18n; the last task of a phase adds cargo xtask layers and cargo xtask tree. Standing DO NOT: edit Cargo files (tasks marked LEAD do), files outside OWNS, git state, cargo fmt --all; add allow attributes; use unwrap/expect/panic/[i]/[a..b] on input-derived data; add a dependency; write outside <data dir>/registry/ and .notescraft/registry.json. Blocked on owner: task 3 and everything that installs (7 onward) wait for open question 1 (ed25519). Tasks 1, 2, 4, 5, 6 and Phase 0 do not.

Phase 0 - size fixes without a registry (do these first, they are 25 % of the binary)

  • 0.1 LEAD: release profile. OWNS: root Cargo.toml ([profile.release] only), packaging/macos/*.sh and packaging/windows/* only if they strip separately. GOAL: add strip = "symbols"; confirm Linux package.sh strip is now redundant but harmless. VERIFY: CARGO_TARGET_DIR=target/agent-size cargo build --release -p notescraft, ls -l is at most 34.9 MB (measured 34,851,464), the app still starts (measure.py in measurements.md), cargo xtask ci. DO NOT: set panic = "abort" (the never-crash policy and tests rely on unwinding).
  • 0.2 Share fonts (brief briefs/29-font-dedupe.md).
  • 0.3 Drop egui default_fonts. OWNS: apps/notescraft/Cargo.toml (eframe feature list only, LEAD edit), crates/ui-egui/src/design/fonts/mod.rs, assets/fonts/NotoEmoji-Regular.ttf (copy from epaint_default_fonts, with its OFL text), crates/ui-egui/tests/fonts_emoji.rs. FILES TO OPEN: crates/ui-egui/src/design/fonts/mod.rs (install), ~/.cargo/registry/src/*/epaint_default_fonts-*/fonts/. GOAL: eframe without default_fonts, FontDefinitions::empty() plus Inter, JetBrains Mono, STIX, NotoEmoji (last in every chain), the system fallbacks as today. TESTS: kittest renders 😀 ★ → ✓ in proportional and monospace with no missing-glyph box (compare against the egui replacement glyph); the snapshot of the Settings and Explorer screens differs only by sub-pixel noise. VERIFY: standing + cargo run -p notescraft-ui-egui --example snapshot -- --vault <v> --out a.png --size 1440x900 before and after, read both PNGs; binary shrinks by at least 0.9 MiB. DO NOT: change any font size or weight.
  • 0.4 LEAD: opt-level = "s" decision. GOAL: measure on target/agent-size with the profile env vars: index build of the 6500-note vault (CPU seconds, measure.py --large-ok), cargo test --release -p notescraft-ui-egui --test perf_code_blocks -- --ignored --nocapture, a graph timelapse frame time; adopt only if none is worse than 10 %. Write the numbers in measurements.md section 1.3. DO NOT: adopt without numbers.
  • 0.5 Lazy system fallback fonts. OWNS: crates/ui-egui/src/design/fonts/mod.rs, .../scripts.rs (add Hebrew and Arabic to the on-demand table if missing), crates/ui-egui/src/design/fonts/tests.rs. FILES TO OPEN: same, note_text / apply_pending. GOAL: remove SYSTEM_FALLBACKS eager read at install; Hebrew and Arabic load through note_text like other scripts, so launch does not read SFHebrew.ttf and SFArabic.ttf. TESTS: a note with Hebrew shows no box after one frame loop (existing test style in tests.rs); a note without them never reads the files (counter in the injected file_size). VERIFY: standing. DO NOT: change the Latin chain.

Phase 1 - pure registry core (engine, no IO unless stated)

  • 1. Index model, validation, version compare (brief briefs/30-registry-index-model.md).
  • 2. Fileset guard: path and size rules (brief briefs/31-registry-fileset-guard.md).
  • 3. Signature canonical text and verify. OWNS: crates/engine/src/addons/verify.rs, crates/engine/src/addons/keys.rs, crates/engine/tests/addons_verify.rs. FILES TO OPEN: spec.md D3, crates/engine/src/addons/model.rs (task 1), crates/syntax/src/validate.rs (sha256_hex). GOAL: pub trait Verifier { fn verify(&self, entry: &Entry) -> Result<()>; }, pub fn canonical_text(entry: &Entry) -> String, pub struct Ed25519 { keys } implementing it with the dependency the owner approved, TRUSTED_KEYS with the owner's public key (placeholder key in tests only), a FakeVerifier for other tasks. TESTS: known-answer signature generated by the xtask tool (task 18) verifies; one flipped hash byte fails; unknown key_id fails; wrong kind or version in the text fails; files in a different order produce the same text. VERIFY: standing. DO NOT: put a private key anywhere; accept an entry without signature.
  • 4. State and store layout. OWNS: crates/engine/src/addons/state.rs, crates/engine/src/addons/store.rs, crates/engine/tests/addons_state.rs. FILES TO OPEN: spec.md D4, crates/engine/src/activity/outbox.rs (versioned JSON pattern), crates/engine/src/grammars/mod.rs (grammar_dir, settings dir), craft-libs/crates/atomic/src/lib.rs (atomic_write, write_new). GOAL: State { installed: BTreeMap<String, Installed> } with parse/to_json (version 1, cap 512, unknown version Err), Store::open(data_dir), Store::version_dir(kind, id, version) -> PathBuf built from validated components, Store::gc(&State) removing version dirs not in state and staging/, commit(&State) via atomic_write, VaultFile { enabled, wanted } with parse/to_json (cap 256 each). TESTS: round trip; corrupt JSON returns Err and leaves the file byte-identical; gc keeps version and previous and deletes the rest; commit is the only writer of state.json. VERIFY: standing. DO NOT: touch the vault; delete anything outside <data dir>/registry/.
  • 5. Transport and bounded download. OWNS: crates/engine/src/addons/net.rs, crates/engine/tests/addons_net.rs. FILES TO OPEN: crates/syntax/src/fetch.rs (Transport, Curl), crates/syntax/src/validate.rs (check_url), crates/engine/src/sync/ (how ureq is used), spec.md D5, D7. GOAL: pub trait Transport { fn get(&self, url: &str, max_bytes: u64, if_none_match: Option<&str>) -> Result<Response>; }, Response { status, etag, bytes } (304 allowed), Ureq implementation (https only, no redirects, total timeout, reads at most max_bytes + 1 and fails above), FakeTransport for tests, download_file(&dyn Transport, &File, cap) -> Result<Vec<u8>> that checks host allow-list, size equals entry size, sha256 equals entry. TESTS: host not allow-listed; http://; credentials; IP literal; redirect status; body one byte too long; one byte short; wrong sha256; 304 keeps cache; error maps to offline. VERIFY: standing; no real network in tests. DO NOT: spawn curl; follow redirects; add a header with an identifier beyond NotesCraft/<version>.
  • 6. Per-kind content checks. OWNS: crates/engine/src/addons/kinds.rs, crates/engine/tests/addons_kinds.rs. FILES TO OPEN: spec.md D8, crates/syntax/src/validate.rs (check_grammar), crates/themes/src/load.rs (Theme::from_toml diagnostics), crates/plugins/src/manifest.rs, crates/plugins/src/lib.rs. GOAL: check(kind, &Entry, &[(path, bytes)]) -> Result<()> dispatching to grammar, theme, plugin (manifest equals entry: id, version, permissions, command ids; wasm magic \0asm), font (magic bytes), snippet (UTF-8, no NUL), pack (each file by its subfolder). TESTS: one good and one bad fixture per kind; plugin manifest with extra permission fails; theme reusing a built-in id fails; .tmLanguage renamed to .sublime-syntax fails. VERIFY: standing. DO NOT: instantiate wasm here (task 12 does the dry load).
  • 7. BLOCKED on 3. Install, update, remove, rollback pipeline. OWNS: crates/engine/src/addons/install.rs, crates/engine/tests/addons_install.rs. FILES TO OPEN: spec.md D6, tasks 1 to 6 outputs, crates/engine/src/grammars/mod.rs (save). GOAL: install(&Ctx, &Index, kind, id, version) -> Result<InstallReport>, update, remove, rollback exactly as D6 steps 1 to 7; Ctx { store, transport: &dyn Transport, verifier: &dyn Verifier, app_version, seed: Option<&SeedDir> }; seed file used when its sha256 equals the entry. TESTS: the list in spec.md D13 IO bullet (hash mismatch, short and long body, crash before the state flip leaves the old state and gc removes the new dir, update keeps previous, rollback, remove, seed offline, revoked, app too old, bad signature, cap reached). VERIFY: standing. DO NOT: write anywhere but the store and state.json; delete the old version before the new state is committed.
  • 8. Commands, gate, command list. OWNS: crates/engine/src/addons/commands.rs, crates/engine/src/addons/mod.rs, one mod addons; in crates/engine/src/lib.rs, one register_addons(reg) call in crates/engine/src/commands.rs::register_core, crates/engine/src/ai/assistant_gate.rs, crates/engine/tests/core_commands.rs (CORE_IDS), crates/engine/tests/addons_commands.rs. FILES TO OPEN: spec.md D9, crates/engine/src/bases/commands.rs (group shape), crates/engine/src/ai/assistant_gate.rs (READ_COMMANDS, WRITE_COMMANDS, DELETE_COMMANDS), docs/CODE-MAP.md. GOAL: the nine commands of D9, error codes, session fields addons_transport and addons_verifier injectable like grammar_transport. TESTS: list joins state and cache; registry.install through the registry with a fake transport; read-only session refuses mutating ids; gate: list|info|suggest READ, enable|disable WRITE, the five others need allow_delete; offline check leaves the cache. VERIFY: standing + --test core_commands --test addons_commands. DO NOT: touch UI; change the grammar.* codes.
  • 9. CLI sugar. OWNS: apps/notescraft-cli/src/args.rs, apps/notescraft-cli/src/commands/addons.rs, apps/notescraft-cli/tests/registry.rs. FILES TO OPEN: apps/notescraft-cli/src/commands.rs, apps/notescraft-cli/src/commands/notes.rs, apps/notescraft-cli/src/output.rs. GOAL: notescraft registry list|info|check|install|update|remove|rollback mapped to the commands, --json like the rest, exit code 3 only for write commands while the app holds the lock (list and info use open_read_only). TESTS: parse table; list --json shape; install against a fake transport env var is not allowed: test the argument-to-params mapping only. VERIFY: standing, -p notescraft-cli. DO NOT: add network code to the CLI.

Phase 2 - consumers

  • 10. Grammar consumer and legacy adoption. OWNS: crates/engine/src/grammars/mod.rs (call the shared installer, keep codes), crates/engine/src/addons/consumers.rs, crates/engine/tests/grammars.rs (extend), crates/engine/tests/addons_grammars.rs. FILES TO OPEN: crates/engine/src/grammars/mod.rs, crates/engine/src/vault/session.rs (load_grammars), crates/syntax/src/load.rs. GOAL: Session::load_grammars feeds set_installed from state.json grammars plus legacy AppSettings::grammars once per generation; legacy entries show as source: legacy; grammar.install and grammar.remove call the registry code when the id is in the index. TESTS: legacy install still works; registry install appears in grammar.info; rebuild happens once for N installs in one frame; corrupt grammar file skipped. VERIFY: standing + --test grammars --test grammars_hostile. DO NOT: delete legacy files; change MAX_GRAMMARS.
  • 11. Theme consumer. OWNS: crates/engine/src/prefs/themes.rs, crates/engine/tests/addons_themes.rs. FILES TO OPEN: crates/engine/src/prefs/themes.rs, crates/themes/src/load.rs. GOAL: Session::user_themes also reads installed themes from the store (still capped by MAX_USER_THEMES, still refusing built-in ids); a selected id that is not installed resolves to the built-in of the same variant plus a diagnostic. TESTS: installed theme resolves; missing selected theme falls back with a diagnostic; duplicate id between settings folder and store: store loses. VERIFY: standing. DO NOT: change the built-ins.
  • 12. Plugin consumer: wire the wasm host into the app. OWNS: crates/engine/src/plugin_host/plugins.rs (scan installed plugins listed in the vault enabled), crates/engine/src/vault/session.rs (call load_plugins after open and after enable/disable; one line), crates/engine/tests/addons_plugins.rs. FILES TO OPEN: crates/engine/src/plugin_host/plugins.rs, plugin_run.rs, crates/engine/tests/plugins.rs, crates/engine/src/builtin/plugin_commands.rs, plugins/dice/. GOAL: load_plugins merges vault-local plugins (today's behaviour) with installed ones; commands plugin.<id>.<command> register; the approval flow is unchanged; a dry Plugin::load runs inside registry.install (task 6 left it). TESTS: install dice from a fake transport, enable, run plugin.dice.roll in a TempVault; disabled plugin not registered; permission not approved refuses; a plugin removed while enabled unloads. VERIFY: standing; then re-measure the binary and write the wasmi delta into measurements.md section 1.4. DO NOT: change ABI v1; give a plugin more permissions than approved.
  • 13. Pack consumer. OWNS: crates/engine/src/addons/consumers.rs (pack arm), crates/engine/tests/addons_packs.rs. GOAL: a pack entry's grammars/, themes/ files flow to the grammar and theme consumers; icons/ is read by the Lottie loader only when task 21 lands. TESTS: a pack with two grammars and one theme installs and removes atomically as one unit; one bad file fails the whole pack. DO NOT: install per-file.

Phase 3 - UI

  • 14. Registry browser view. OWNS: crates/ui-egui/src/views/registry_view/mod.rs, .../list.rs, .../detail.rs, crates/ui-egui/src/text/views/registry.rs, [ui.registry] at the END of crates/i18n/locales/en.toml, crates/ui-egui/tests/registry_view.rs. FILES TO OPEN: spec.md D10, crates/ui-egui/src/views/settings_view/categories.rs, crates/ui-egui/src/views/share_dialog/mod.rs (modal pattern), .claude/skills/notescraft-egui-screen/. GOAL: the view returns Actions only (Check, Install(kind,id), Update, Remove, Rollback, Enable(id)), draws rows by state, the drawer with permission sentences, the offline banner; no engine calls inside the view. TESTS: kittest: each state renders its button; Install emits one action; search filters; offline banner; permissions listed as sentences. VERIFY: standing + cargo xtask i18n. DO NOT: use raw egui buttons or inputs; hardcode strings; fetch from the view.
  • 15. Shell wiring: category, commands, snapshot flag. OWNS: crates/ui-egui/src/views/settings_view/categories.rs (one Registry entry), crates/ui-egui/src/shell/app/vault/registry.rs, crates/ui-egui/examples/snapshot.rs (--registry). GOAL: actions call the engine commands on a worker thread (poll like grammar_dialog.rs), results update the view data; registry.list on open reads the cache only; Check/Install are the network triggers. TESTS: kittest: opening the category makes zero network calls (counter on the fake transport); Install runs one command. VERIFY: standing + snapshot PNG read. DO NOT: block the UI thread.
  • 16. Code card, first-run card, update badge. OWNS: crates/ui-egui/src/views/note_view/blocks/code_card.rs (the Add-grammar link only), crates/ui-egui/src/shell/app/vault/first_run.rs, crates/ui-egui/src/text/views/registry.rs (append). GOAL: D10 bullets 5 and 6; card dismissal remembered in UiState. TESTS: code card shows "Install Twig grammar" when registry.suggest hits, the old dialog link otherwise; first-run card never fetches; badge only after a check. VERIFY: standing + cargo xtask i18n. DO NOT: install without a click, unless registry.auto_install_for_vault is on and the file comes from the seed.
  • 17. Vault wanted scan and banner. OWNS: crates/engine/src/addons/wanted.rs, crates/engine/tests/addons_wanted.rs, crates/ui-egui/src/shell/app/vault/wanted_banner.rs. FILES TO OPEN: crates/engine/src/vault/session.rs, the content-search code that uses ignore/grep-*, spec.md D11 step 4. GOAL: scan_wanted(session, budget_ms) -> Vec<String> over fence labels and $/$$ in the vault with a 3 s budget, run once per vault and registry generation, written to .notescraft/registry.json wanted; banner lists not-installed entries with size. TESTS: a vault with a ```twig fence yields grammar:twig; budget stops a 6500-note scan early without error; installed items are not listed; no network. VERIFY: standing. DO NOT: read notes over 2 MiB; write outside .notescraft/registry.json.

Phase 4 - first-party content and release

  • 18. Registry repo layout and signing tool. OWNS: xtask/src/registry_sign.rs, one subcommand line in xtask/src/main.rs, xtask/tests/registry_sign.rs; outside this repo (separate commit by the lead): notescraft-registry/README.md (schema v2, id not name), index.json, files/. FILES TO OPEN: spec.md D1 to D3, /Volumes/Storage/Projects/notescraft-registry/README.md, xtask/src/main.rs. GOAL: cargo xtask registry-sign --dir <registry checkout> --key <file> rebuilds index.json from files/<kind>/<id>/<version>/ plus a small entry.toml per item (title, license, aliases), fills size and sha256, signs; --check verifies an existing index. TESTS: sign then verify with the engine verifier; tampered file fails --check. VERIFY: standing, -p xtask. DO NOT: read the key from the repo; print it.
  • 19. Move the 25 grammars out. OWNS: crates/syntax/src/embedded.rs (shrinks to an empty list or removed), crates/syntax/grammars/ (moved to the registry repo and to the seed), crates/syntax/src/load.rs (drop the embedded loop), crates/engine/tests/addons_migration_grammars.rs. GOAL: only after 7, 10, 16, 17, 18, 23 are done; registry entries twig, blade, ... one per file; tests per D11 step 5; the TOML and TypeScript grammars stay if the owner wants highlighting out of the box (they are the two most common: decide with the owner, default keep). TESTS: for every moved id: suggest finds it, seed verifies, offline install from the seed makes is_known true; removal restores plain text. VERIFY: standing + measure load::current() (expect about 75 ms instead of 110 ms) and binary delta (about -69 KB). DO NOT: remove a grammar that has no seed copy.
  • 20. STIX Two Math as a font entry. OWNS: crates/ui-egui/src/design/fonts/mod.rs (STIX loaded from the store when present), crates/ui-egui/src/views/math_view.rs (Install button when absent), assets/fonts/STIXTwoMath-Regular.ttf (moves to the seed), tests. GOAL: after 7, 16, 18, 23; math without the font uses the existing Inter/JetBrains chain and shows "Install math font (376 KB)". TESTS: with and without the font; rendering never panics; the migration test of D11. VERIFY: binary delta about -0.36 MiB; snapshot of a note with display math both ways.
  • 21. Lottie icons as a pack. OWNS: crates/ui-egui/src/design/lottie_icon/files.rs (store lookup, static fallback), assets/icons-anim/ (moves to the seed), tests. GOAL: after 13, 18, 23; icons-animated pack; without it every animated icon draws its static Lucide icon (the skill notescraft-icon-motion rules still hold). TESTS: each icon id has a static fallback; motion tests run with the pack installed from a temp store. VERIFY: binary delta about -0.14 MiB. DO NOT: change any animation JSON.
  • 22. (this repo's side only: test via the signing tool with a throwaway key built at test time, nothing committed; the registry repo's files/plugin/dice/ and the four themes are still to publish) First plugins and themes in the registry. OWNS (registry repo, plus this repo's tests only): files/plugin/dice/1.0.0/, files/theme/{catppuccin-frappe,catppuccin-macchiato,solarized-light,solarized-dark}/1.0.0/ (themes copied from crates/themes/src/builtin.rs and kept built-in until a migration test passes), plugins/dice/README.md (point to the registry), crates/engine/tests/addons_first_party.rs. GOAL: dice builds with plugins/dice/build.sh, its manifest.json equals the entry; citations follows when roadmap 10 starts. TESTS: dice installs offline from the seed, rolls, 2d6+3 result in range. DO NOT: remove built-in themes in this task.
  • 23. Seed packaging. OWNS: packaging/macos/*, packaging/windows/*, packaging/linux/package.sh (copy registry-seed/ next to the binary), crates/engine/src/addons/seed.rs, crates/engine/tests/addons_seed.rs. FILES TO OPEN: packaging/README.md, packaging/macos/. GOAL: SeedDir::locate() finds Contents/Resources/registry-seed (macOS), registry-seed/ beside the exe (Windows, Linux), verified signed snapshot of moved items only. TESTS: locate with a fake exe path; missing seed is fine (network install still works); tampered seed file rejected. DO NOT: put anything unsigned in the seed.
  • 24. Docs, numbers, status. OWNS: ROADMAP.md, docs/ARCHITECTURE.md, docs/CODE-MAP.md (addons rows), SPEC.md sections 6.1 and 7 (a "Registry" subsection, community store no longer deferred), specs/registry/measurements.md (new numbers), specs/plugins-parity/roadmap.md (add the rows). VERIFY: cargo xtask ci. DO NOT: add features.

Order and parallelism

Phase 0 in parallel with 1, 2, 4, 5, 6 (disjoint folders). 3 starts when the owner answers question 1. 7 after 3 to 6. 8 and 9 after 7. 10 to 13 are disjoint after 8. 14 to 17 after 8 (UI can start on a fake command layer). 18 can start at any time and unblocks 3's known-answer test. 19 to 22 only after their dependencies, one at a time, each with its migration test. The first three agent briefs are specs/plugins-parity/briefs/29..31.