Skip to content

Desktop app (Phase 2): Tauri sync control center + converter GUI, release CI, website - #1

Merged
cable729 merged 5 commits into
mainfrom
claude/desktop-app
Jul 11, 2026
Merged

Desktop app (Phase 2): Tauri sync control center + converter GUI, release CI, website#1
cable729 merged 5 commits into
mainfrom
claude/desktop-app

Conversation

@cable729

Copy link
Copy Markdown
Owner

What

Phase 2, shipped bigger than the roadmap's menu-bar-only plan — a full desktop app that is both a sync control center and a GUI over the converter, plus everything needed to distribute it:

  • core/src/inkterop/sync/ — multi-source/multi-sink engine generalizing the mirror: rules.py (per-note/folder allow/block, rename, destination, format overrides in rules.toml), sources.py (reMarkable cache, watched note-file folders, experimental GoodNotes/Notability container scanners with deadline-guarded walks), sinks.py (pdf/svg/png/inkz via the registry writers, atomic multi-file moves), engine.py (v2 state with v1 migration, per-doc error persistence, history log, watcher pid-lock), daemon.py (stdio JSON-RPC for the app — no network surface). mirror.py is now a thin back-compat wrapper; the launchd daemon keeps working.
  • app/ — Tauri 2 + React. Library browser (Finder-style columns / thumbnail grid / sortable details list, first-page previews, search + status filter), per-note/folder sync checkboxes with status icons and hover explanations, Sync History page with per-file diagnostics, drag-and-drop Convert view (also reachable from any library note), Settings, tray with sync controls, one-click migration off the legacy launchd agent. Release bundles run a PyInstaller-frozen sidecar; dev mode runs the live Python tree via uv.
  • Releasesrelease.yml builds macOS (signs+notarizes once APPLE_* secrets land — see app/RELEASING.md), Windows, Linux on v* tags into a draft GitHub Release. Homebrew cask recipe documented.
  • Websitewebsite/ landing page at the Pages root; mkdocs docs move under /docs/ (single workflow assembles both).

Why

Original goal #4/#5 (hands-off sync into normal PDFs) had no UI: no way to see what synced, pick notes, or diagnose failures. This adds that, and turns the one-off converter into something a non-CLI user can use.

Reviewer notes

  • Mirror behavior is pinned: golden tests untouched; real-library check after the rewrite was a clean 39/39 no-op with verbatim filenames (incl. : on APFS). State file migrates v1→v2 in place; old daemons rewriting v1 state self-heal on the next pass.
  • Annotated PDFs/EPUBs are hard-excluded (maintainer decision): grayed out with explanatory tooltips until the base-page merge exists.
  • Rule/config changes trigger a debounced settings sync pass — that plus persisted per-doc errors fixes the "pending forever with no explanation" class of bug.
  • Verified end-to-end on the real library by driving the release .app (columns/grid/list + previews, GoodNotes→PDF conversion through the GUI, allow-override, auto-sync, history diagnostics). Suite: 394 passed; mkdocs build --strict green.
  • Post-merge: add Apple signing secrets, drop a hero website/screenshot.png, pick an app icon, then tag v0.1.0.

🤖 Generated with Claude Code

cable729 and others added 5 commits July 10, 2026 20:04
- inkterop.sync package: rules (allow/block/rename/format overrides in
  rules.toml), sources (reMarkable cache, note-file folders, experimental
  GoodNotes/Notability container scanners with deadline-guarded walks),
  sinks (pdf/svg/png/inkz via the registry writers; atomic multi-file
  moves), engine (generalized mirror with v2 state + v1 migration),
  daemon (JSON-RPC over stdio for the desktop app; no network surface).
- mirror.py is now a thin back-compat wrapper; launchd daemon unchanged.
  Real-library check: pass after migration is a clean 39/39 no-op, and
  filenames keep the old verbatim naming (':' preserved on mac).
- config: [sync] default format, [sources.*]; tomlkit round-trip writes.
- CLI: inkterop sync + inkterop daemon.
- 23 new tests (engine/rules/sources/daemon incl. subprocess pipe smoke);
  full suite 389 green, goldens untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eviews

- Rust shell: sidecar daemon manager (stdio JSON-RPC bridge, auto-restart,
  clean EOF shutdown), tray menu (open/sync/pause/quit), close-to-tray with
  macOS accessory policy, dialog/opener/autostart plugins.
- Library view: Finder-style Miller columns (sources > folders > notes >
  preview pane), thumbnail grid, and details list — shared inspector with
  allow/block, rename, destination and per-note format controls; folder
  sync toggles at every level; breadcrumbs + view-mode toggle.
- Convert view: drag-and-drop (or picker) -> any writer format with
  fidelity choice; experimental writers routed through --experimental.
- Settings: output dir, scope, sources incl. watched folders, page
  sizing/pen style, autostart, close-to-tray. Activity: live event log.
- core fixes found by driving the real app: Library reload race under
  concurrent thumbnail RPCs (source-level lock), pdfium global lock
  (not thread-safe), watcher pid-lockfile so app + launchd daemon can't
  double-sync, root folder '.' -> ''.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… landing page

M3 (packaging/releases):
- core/packaging: PyInstaller spec + entry + build-sidecar.sh (freezes the
  engine into app/src-tauri/binaries/inkterop-daemon-<triple>; JSON-RPC
  ping smoke test). Local release verified end-to-end: the bundled
  Inkterop.app runs the frozen sidecar with no uv/python dependency;
  .dmg produced (35MB).
- .github/workflows/release.yml: v* tags -> macOS arm64 (signs+notarizes
  once Apple secrets exist), Windows + Linux community builds, draft
  GitHub Release via tauri-action. Secrets + cask recipe + version-bump
  checklist in app/RELEASING.md.
- app: dev placeholder sidecar auto-created (ensure-sidecar.mjs), real
  binaries gitignored; Settings gains legacy launchd-daemon migration
  (detect com.inkterop.watch, one-click disable).

M4 (website/docs):
- website/: static landing page (hero, features, downloads wired to the
  GitHub releases API, brew snippet, format matrix), light/dark.
- Pages restructure: landing at the root, mkdocs under /docs/ — docs.yml
  assembles both into one artifact; mkdocs site_url updated; verified
  locally (strict build green, combined tree served and spot-checked).
- CLAUDE.md: sync module/app/website layout + commands; 'no heavy deps'
  rule removed (rescinded by maintainer). ROADMAP Phase 2 marked done.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erywhere, search, icons, sort, convert/open actions

Per maintainer feedback:
- Sync History page: persisted pass table (when/result/changed/duration/
  trigger, expandable failures; history.json capped at 200) + live event
  feed. Sync Now navigates there; the bottom-left status is now a
  clickable 'synced Xm ago' link.
- 'blocked' renamed to 'excluded' and every status carries a hover
  explanation ('annotated PDFs are off in Settings', 'folder X is turned
  off', 'synced 5m ago', ...). Engine now returns machine reason codes.
- Explicit note/folder allows OVERRIDE the scope toggles, so checking a
  PDFs-only folder (Books/Music) just works — verified live: Books on ->
  2 book PDFs render; reason codes and folder-status aggregate icons all
  correct. (Root cause of the 'can't toggle Books' report.)
- Folders AND notes both get a sync checkbox (left) + status icon
  (right); custom SVG icon set replaces emoji (folder/tablet/note/book/
  status glyphs); kind is visible (book icon + 'annotated PDF' tooltip).
- Toolbar search + status filter (flat results across folders).
- List mode: sortable columns (name/status/pages/format/modified/output).
- Inspector: Convert-to-another-format (hands the note to the Convert
  view; works for library docs via uuid), Open synced output, Open
  original file (folder-source docs), Reveal in Finder.
- 392 tests green; sidecar rebuild + release bundle verified on the
  real library.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sync on rule changes

Root causes of the 'pending forever with no explanation' report, fixed:
- Rule/config changes now trigger a debounced sync pass (trigger:
  'settings') — previously only SOURCE file changes woke the watcher, so
  a toggled checkbox sat pending until the library happened to change.
- Per-doc failures are persisted (state entry keeps error + failed_mtime)
  and surface as a real 'failed' status whose tooltip and inspector show
  the actual error; retried automatically each pass, cleared on success.
- Sync History pass rows expand to per-file results: synced -> outputs
  (+ per-file duration), failed -> error message. Status icons and the
  inspector status badge are clickable links to Sync History.

Maintainer decision: annotated PDFs/EPUBs don't sync at all until the
base-page merge exists (handwriting-only output is useless). Engine
hard-excludes them (reason 'unsupported-kind', nothing overrides it),
UI grays them with disabled checkboxes + book icon + explanatory
tooltips, Settings drops the pdf/epub toggles. Folders containing only
PDFs (Books/Music) show a disabled checkbox explaining why.

Verified live on the release bundle: uncheck Calibration -> settings-
triggered pass removes the PDF in ~4s; recheck -> re-rendered; history
shows 'Calibration -> Calibration.pdf (0.03s)' in the expanded pass;
stale Books allow-rule cleaned up automatically. 394 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cable729
cable729 merged commit f16eb8d into main Jul 11, 2026
2 checks passed
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.

1 participant