You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.4.0 (#83)
* docs: add Git Flow branching strategy to CLAUDE.md
- Document main/develop/feature branch structure
- Add workflow commands for starting work and creating PRs
- Include commit message conventions
- Add PR requirements checklist
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: integrate backend API with authentication and sync
Complete backend integration with desktop app.
## Backend API (@readied/api)
New Hono-based API for Cloudflare Workers:
- Magic link passwordless authentication
- JWT tokens with refresh (15 min / 7 day)
- End-to-end encrypted sync (AES-256-GCM)
- Stripe subscription management
- Turso (libSQL) database
Security features:
- Rate limiting (10 req/min auth, 100 req/min sync)
- Stripe webhook signature verification
- OS keychain token storage (safeStorage)
- HMAC-SHA256 verification
Sync architecture:
- Pull: Download server changes with conflict detection
- Push: Upload local changes (documented, Phase 3)
- Conflict resolution UI
- Device tracking
## Desktop App Integration
Auth flow:
- Magic link email → Deep link verification
- Token storage with OS encryption
- Auth state management (Zustand)
Sync UI:
- Sync status indicator
- Conflict resolver component
- Manual sync trigger
- Auto-sync (5-min interval)
Settings:
- Account section (auth status, logout)
- Backup section (manual backup)
- Enhanced UI components
## Deployment
Multi-environment setup:
- Development: localhost:8787
- Staging: readied-api-staging.workers.dev
- Production: api.readied.app
## Documentation
- BACKEND_INTEGRATION_COMPLETE.md (2,700+ lines)
- API setup guide (SETUP.md)
- Deployment guide (DEPLOYMENT.md)
- Rate limiting docs
- Stripe webhooks docs
## Known Limitations
- Sync push not yet implemented (Phase 3)
- Conflict resolution is UI-only
- No local change tracking
- Monitoring postponed (Sentry)
## Breaking Changes
- None (new features, backward compatible)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs: add ADR 001 for git-backed notes feature
Document architecture decision for product differentiation strategy.
Decision: Implement git-backed notebooks as core feature
Rationale:
- Differentiate from Inkdrop (no git) and Obsidian (weak sync)
- Justify $9/mo pricing (vs Inkdrop $4.99)
- Appeal to developers' trust in git
- Enable GitHub/GitLab collaboration
- Free backup via git push
Implementation approach:
- Opt-in per notebook
- Use isomorphic-git (pure JS, no binary)
- Auto-commit or manual commits
- Full history/diff/revert in UI
- Coexist with cloud sync
Consequences:
+ Unique selling proposition
+ Trust & security (user controls data)
+ Free backup & collaboration
- Complexity for non-technical users
- Performance overhead (.git storage)
- Sync complexity
Timeline: Phase 1 Sprint 2 (Semana 5-7)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: implement bidirectional sync with local change tracking
Implements Semana 2 Sprint 1 (local change tracking + push functionality).
Database Changes:
- Migration 008: Add sync tracking columns to notes table
- local_version: Increments on each local change
- needs_sync: Flag (1=needs push, 0=synced)
- last_synced_at: Timestamp of last successful sync
- Triggers: Auto-mark notes as needs_sync=1 on INSERT/UPDATE
- Index: Efficient querying of pending changes
Repository Methods (SQLiteNoteRepository):
- getPendingChanges(limit): Query notes where needs_sync=1
- markAsSynced(noteId): Mark note as synced after push
- markMultipleAsSynced(noteIds[]): Batch mark as synced
- getSyncStats(): Get count of pending notes + last sync time
- resetSyncTracking(noteId): Force re-sync (for conflict resolution)
Sync Service Enhancements:
- syncNow(): Now pulls AND pushes (was pull-only)
- Gets pending changes from repository
- Encrypts and pushes to server
- Marks successfully pushed notes as synced
- Handles push conflicts
- resolveConflict(): Real implementation (was stub)
- "local" resolution: Marks note for push via resetSyncTracking()
- "remote" resolution: Marks as synced to accept server version
- applyRemoteChange(): Marks notes as synced after pull
- Prevents re-pushing notes just received from server
What Works Now:
✅ Edit note on Device A → marked needs_sync=1
✅ syncNow() pushes change to server
✅ Device B pulls change → marked as synced
✅ Conflict detection on push
✅ Manual conflict resolution (choose local or remote)
Next Steps (Semana 2 Sprint 2):
- Multi-device testing with 2 instances
- Test conflict scenarios
- UI for visual diff of conflicts
Related:
- Phase 1 Sprint 1 of execution plan
- Addresses critical sync blocker from audit
- Enables real multi-device sync (was read-only before)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs: add multi-device sync testing guide
Comprehensive testing guide for Semana 2 Sprint 1.
Includes:
- 4 test scenarios (basic, conflict, rapid edits, delete)
- Migration verification steps
- Manual sync trigger options
- Debug queries for local + server
- Expected behavior documentation
- Success criteria
- Test log template
Ready for multi-device testing phase.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: enhance conflict resolution UI with visual diff
Adds visual diff highlighting and dual view modes to ConflictResolver.
Features:
- Dual view modes: Side-by-side and Unified diff
- Visual diff highlighting:
- Green background for additions
- Red background with strikethrough for deletions
- Neutral text for unchanged content
- Line-by-line diff using diff library
- Toggle between views per conflict
- Responsive layout (mobile-friendly)
UI Components:
- View toggle buttons (Side by Side / Unified Diff)
- DiffChange component for rendering highlighted changes
- UnifiedDiff component with line diff visualization
- actionsRow for resolution buttons in unified view
CSS Enhancements:
- .viewToggle - Toggle button group
- .toggleButton / .toggleActive - Toggle button states
- .unifiedDiff - Unified diff container
- .diffAdded / .diffRemoved / .diffUnchanged - Diff highlighting
- .actionsRow - Centered action buttons
- Mobile responsive breakpoints
Dependencies:
- Added `diff` library for diff computation
Integration:
- Already integrated in AccountSection (line 159)
- Shows automatically when conflicts.length > 0
- Stores expanded state and view mode per conflict
Next Steps:
- Multi-device testing to trigger actual conflicts
- User feedback on diff readability
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs: add Semana 2 completion summary
Comprehensive summary of bidirectional sync implementation.
Includes:
- Complete implementation details (migration, repo, service, UI)
- End-to-end flow diagrams
- Testing status and criteria
- Performance characteristics
- Critical blocker resolution
- File changes manifest
- Deployment checklist
Ready for multi-device testing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add git foundation for notebooks (Phase 1, Sprint 2)
Implements git-backed notebooks foundation (Differentiator #1).
Database Changes (Migration 009):
- Added git_enabled column to notebooks (INTEGER, default 0)
- Added git_auto_commit column (INTEGER, default 0)
- Added git_initialized_at column (TEXT, ISO 8601 timestamp)
- Index on git_enabled for efficient queries
GitService Implementation:
- Repository initialization with .gitignore
- File operations (write/read/delete note files)
- Git operations:
- commit() - Stage and commit changes
- status() - Get modified/added/deleted/untracked files
- log() - Get commit history (configurable limit)
- checkout() - Revert to specific commit
- diff() - Placeholder for future implementation
- Uses isomorphic-git (pure JS, no git binary required)
- Default author: "Readied User <user@readied.app>"
- Repo path: baseDir/notebooks/{notebookId}/
Architecture:
- Each notebook = independent git repository
- Notes stored as {noteId}.md files in repo
- Full git history tracked per notebook
- Optional (user enables per notebook)
Dependencies:
- Added isomorphic-git@1.x
Next Steps:
- Integrate GitService into main process
- Add IPC handlers (init, commit, log, checkout)
- UI toggle for enabling git on notebooks
- Auto-commit on save implementation
- Commit history UI with revert
Related:
- ADR 001: Git-backed notes decision
- Phase 1, Sprint 2 of execution plan
- Differentiator #1 (vs Inkdrop/Obsidian)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: integrate GitService into main process with IPC handlers
Wires up GitService to Electron main process with full IPC API.
Integration:
- Import and declare GitService
- Initialize on app ready (after database init)
- BaseDir: userData path for git repositories
- Repo path pattern: {baseDir}/notebooks/{notebookId}/
IPC Handlers Added (git:*):
- init: Initialize git repo for notebook
- isRepo: Check if notebook has git
- commit: Stage and commit changes
- log: Get commit history (configurable limit)
- status: Get modified/added/deleted files
- checkout: Revert to specific commit
- writeNote: Write note file to git repo
- readNote: Read note file from git repo
- deleteNote: Delete note file from git repo
Handler Pattern:
- All return {success: boolean, ...data, error?: string}
- Error handling with try/catch
- User-friendly error messages
Initialization Flow:
1. app.whenReady()
2. initDatabase() → creates GitService
3. registerGitHandlers() → registers IPC handlers
4. Git operations available to renderer
Next Steps:
- Add preload API bindings (window.readied.git.*)
- Update NotebookRepository with git_enabled methods
- UI toggle for enabling git on notebooks
- Auto-commit on save hook
Related:
- Builds on commit 78e52d7 (GitService foundation)
- Phase 1, Sprint 2 progress
- Enables UI to interact with git
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add preload API bindings for git operations
Exposes git operations to renderer process via window.readied.git
API Added (window.readied.git.*):
- init(notebookId) - Initialize git repo for notebook
- isRepo(notebookId) - Check if notebook has git
- commit(notebookId, message, files?) - Commit changes
- log(notebookId, limit?) - Get commit history
- status(notebookId) - Get repo status (modified/added/deleted/untracked)
- checkout(notebookId, commitSha) - Revert to commit
- writeNote(notebookId, noteId, content) - Write note file
- readNote(notebookId, noteId) - Read note file
- deleteNote(notebookId, noteId) - Delete note file
TypeScript Interface:
- Full type definitions in ReadiedAPI
- All methods return Promise<{success: boolean, ...data, error?: string}>
- Matches IPC handler signatures from main process
Implementation Pattern:
- Uses ipcRenderer.invoke('git:*', ...args)
- Direct pass-through to main process handlers
- No renderer-side logic (thin binding layer)
Integration Complete:
✅ GitService (main process)
✅ IPC handlers (main process)
✅ Preload API (this commit)
Ready for: UI components to call window.readied.git.*
Next Steps:
- Update NotebookRepository with git_enabled methods
- UI toggle for enabling git on notebooks
- Auto-commit on save hook
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add git methods to NotebookRepository with full IPC integration
Completes repository layer and IPC integration for git-backed notebooks.
NotebookRepository Changes:
- Updated NotebookRow interface with git columns
- Updated all SELECT queries to include git columns
- Added 6 git methods:
- enableGit(notebookId) - Enable git + set initialized_at
- disableGit(notebookId) - Disable git (keeps initialized_at)
- isGitEnabled(notebookId) - Check if git enabled
- getGitSettings(notebookId) - Get enabled/autoCommit/initializedAt
- setGitAutoCommit(notebookId, enabled) - Toggle auto-commit
- getGitEnabledNotebooks() - List all git-enabled notebooks
IPC Handlers Added (main/index.ts):
- notebooks:enableGit
- notebooks:disableGit
- notebooks:isGitEnabled
- notebooks:getGitSettings
- notebooks:setGitAutoCommit
- notebooks:getGitEnabled
Preload API Added (preload/index.ts):
- window.readied.notebooks.enableGit(notebookId)
- window.readied.notebooks.disableGit(notebookId)
- window.readied.notebooks.isGitEnabled(notebookId)
- window.readied.notebooks.getGitSettings(notebookId)
- window.readied.notebooks.setGitAutoCommit(notebookId, enabled)
- window.readied.notebooks.getGitEnabled()
Full Stack Complete:
✅ Database (migration 009)
✅ Repository (git methods)
✅ IPC Handlers (main process)
✅ Preload API (renderer access)
Ready for: UI components
Next Steps:
- UI toggle for enabling git on notebooks
- Auto-commit on save hook
- Commit history UI
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add git toggle UI to notebook items
- Add GitBranch icon from lucide-react
- Show git badge indicator when git is enabled
- Add git toggle button in notebook actions
- Check git status on component mount
- Handle git enable/disable with loading state
- CSS styles for git badge and git-enabled button state
* feat: implement auto-commit on note save
- Check git settings when note is updated
- Auto-commit to git if notebook has git enabled and autoCommit enabled
- Write note file to git repo using writeNote IPC method
- Commit with descriptive message (Update/Rename note: title)
- Fire-and-forget approach - doesn't block save flow
- Error handling without throwing (logs only)
- Applies to both content updates and title updates
* feat: add commit history UI for git-enabled notebooks
- Create CommitHistory component with modal UI
- Display commit list with message, author, timestamp
- Expandable commit details with full info and SHA
- Revert to commit functionality with confirmation
- History button in notebook actions (only visible when git enabled)
- Relative time formatting (minutes/hours/days ago)
- Loading, error, and empty states
- Animated modal with overlay and slide-up effect
- CSS module with dark theme styling
* chore(deploy): configure production infrastructure
- Update API base URL to https://api.readied.app
- Fix rate limiter for Cloudflare Workers compatibility (remove global setInterval)
- Update VitePress config for custom domain (docs.readied.app)
- Upgrade Wrangler to v4.58.0
- Add comprehensive RELEASES.md documentation
Deployed infrastructure:
- API staging: https://readied-api-staging.readied.workers.dev
- API production: https://api.readied.app
- Docs: https://docs.readied.app
- Marketing: https://readied.app
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(desktop): add window state persistence and enhanced editor settings
Window State Persistence:
- Save/restore window position, size, and maximized state
- Debounced state saving on resize/move events
- Persist state across app restarts
Editor Enhancements:
- Enhanced MarkdownEditor with configurable settings
- Expanded EditorSection with more customization options
- Improved GeneralSection with additional settings
Settings Sync:
- Add settings sync broadcasting across multiple windows
- Add manual update check handler (updates:checkNow IPC)
This restores settings that were previously in stash from main branch.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(desktop): add missing editor and general settings support
Settings Store:
- Add EditorSettings interface with editor configuration options
- Add GeneralSettings interface with app-level settings
- Add selectEditor and selectGeneral selectors
- Add updateEditor and updateGeneral actions
- Increment storage version to 2 for migration
Form Controls:
- Create controls.tsx with Toggle, NumberInput, TextInput, Select components
- Add CSS styling for all form controls
- Support disabled states and focus styles
SettingRow:
- Add optional htmlFor prop for label accessibility
- Wire up label to form controls
Fixes:
- Handle null defaultNotebookId in Select component
- Add rememberWindowPosition to GeneralSettings
- Resolve all TypeScript errors in settings sections
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(desktop): add default option to notebook select
- Add 'No default (ask each time)' option to notebook select
- Prevents empty select value issues
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(desktop): add QueryClientProvider to settings window
The settings window was showing a black screen because it was missing
the QueryClientProvider needed for useNotebooks hook.
Added:
- QueryClient instance with same config as main app
- QueryClientProvider wrapper around SettingsApp
Fixes: No QueryClient set error in GeneralSection
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(desktop): allow data URIs in settings CSP for SVG icons
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(desktop): polish settings UI and add Appearance section
UI Improvements:
- Add Lucide icons to settings sidebar
- Active indicator bar on selected section
- Improved spacing and typography throughout
- Polished form controls (toggle, select, input)
- Better hover states and transitions
- Custom scrollbar styling
- Centered content layout with max-width
Sidebar:
- Wider sidebar (220px) with better padding
- Icons with opacity states
- Active section with accent border indicator
- Smoother transitions
Controls:
- Larger, more modern toggle switches (44x24px)
- Better focus states with accent glow
- Improved hover feedback
- Consistent border radius (0.5rem)
- Subtle backgrounds and borders
SettingRow:
- Card-like appearance with borders
- Hover effect on rows
- Better spacing (1.125rem padding)
- Improved label/description hierarchy
AppearanceSection:
- Theme selection (Dark/Light/System)
- Zoom level control (80%-130%)
- Performance mode selector
- Proper integration with stores
All sections now have:
- Consistent spacing
- Better visual hierarchy
- Modern, polished appearance
- Matches main app design language
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add Share on Web feature
Allow users to publicly share notes via URL. Content is uploaded as
plaintext markdown to the API, which returns a slug. The public page
on the marketing site renders the markdown client-side.
- API: POST/GET/DELETE /share endpoints with upsert on userId+noteId
- Desktop: IPC handlers with auto-copy URL to clipboard
- UI: functional "Share on Web" button in ActionsPanel with toast feedback
- Marketing: standalone /shared page that fetches and renders via marked
- DB: shared_notes table with slug unique index
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: sync cursor skip and false conflict detection bugs
P1: Pull cursor no longer advances past failed changes. On failure,
processing stops and cursor stays at the last successfully applied
change version, ensuring failed changes are retried on next sync.
P2: Conflict detection now checks if the local note has unsynced
edits (needs_sync=1) instead of flagging every remote change from a
different device. Clean local notes accept remote updates without
creating unnecessary conflict copies.
Also fixes ESLint config: add caughtErrorsIgnorePattern for _error
catch vars, ignore .wrangler temp files, and fix unused catch var
in drizzle.config.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: apply Prettier formatting across codebase
Run pnpm format to fix style inconsistencies caught by format:check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): reject refresh tokens in auth and serialize sync versions
- Auth middleware now rejects tokens with type='refresh', preventing
refresh tokens from being used on protected routes as access tokens.
- Sync POST wraps version assignment + inserts in a transaction to
prevent concurrent requests from generating duplicate versions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: trigger CI re-run
* chore: regenerate pnpm-lock.yaml
Lockfile was out of sync with package.json specifiers, causing
frozen-lockfile install failures in CI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(marketing): remove unused getConfig import from subscribe page
The getConfig function doesn't exist in @readied/product-config, causing
the marketing-site build to fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add plugin system with filesystem discovery, command registry, and settings UI
Implements the full plugin architecture:
- Plugin API package (@readied/plugin-api) with EditorAPI, AppAPI, layout zones, config persistence
- Command registry package (@readied/command-registry) with keybindings and command palette
- Filesystem plugin discovery from ~/Library/Application Support/.../plugins/
- Plugin scanner (main process) + CJS evaluator (renderer via new Function())
- IPC bridge for scan/enable/disable with SQLite plugin_registry table
- Plugins section in Settings modal with toggle switches
- Built-in word count plugin as reference implementation
- Fix CommandRegistry.getAll() infinite re-render (cached snapshot for useSyncExternalStore)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add plugin-api unit tests and improve plugin system polish
- Add 50 unit tests for plugin-api (validation, registry lifecycle, loadPluginFromSource)
- Wire unused layout zones (editor-toolbar, modal) in NoteEditor
- Surface plugin load errors via Toast notifications
- Update PLUGIN_SYSTEM.md to mark Phase 4 as complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add typewriter mode plugin (proves registerExtensions API)
Built-in plugin that keeps cursor centered in the editor. Uses:
- registerExtensions() for CM6 ViewPlugin (first proof of this API)
- registerCommand() for Cmd+Shift+T toggle in Command Palette
- config API for persisting enabled state across sessions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: complete plugin system phases 3.3, 3.4, and extend phase 4
- Add Editor Decorations API (Phase 3.3): CM6 StateField-backed
line highlights and widget decorations with automatic cleanup
- Add 3 new layout zones (Phase 3.4): settings-section,
note-list-footer, command-palette-footer — wired into components
- Add cross-window plugin reload: IPC broadcast from settings
window triggers re-scan in main window without restart
- Add Plugins section to SettingsApp with enable/disable toggles,
reload button, and open folder action
- Add plugin config UI: auto-generated settings forms from
configSchema in manifest.json (boolean/string/number controls)
- Update PLUGIN_SYSTEM.md to reflect completed phases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move plugin reload from React hook to runtime store
Replace useDiscoveredPlugins hook with pluginRuntimeStore (Zustand
vanilla). Plugin scanning, loading, and reload now live in a runtime
controller — React observes state via useStore selectors.
Key changes:
- IPC listener for cross-window reload lives on the store, not in
React lifecycle
- Race protection via monotonic scan generation counter
- init() called once on boot, reload() callable from anywhere
- Eliminates reloadKey anti-pattern (React driving infrastructure)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: harden plugin system with active line plugin, auto-cleanup, and tests
- Add Active Line Highlight built-in plugin proving decorations API,
editor events (onSelectionChanged), and app events (onNoteSelected)
- Add event auto-cleanup in PluginRegistry.deactivate() via tracked
wrappers — leaked listeners are automatically unsubscribed
- Add 41 new tests: createAppAPI, createEditorAPI, layoutStore,
editorPluginStore, and registry auto-cleanup (91 total)
- Delete dead SettingsModal.tsx + CSS (replaced by SettingsApp)
- Update PLUGIN_SYSTEM.md with Phase 4.5 Hardening section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: bump version to 0.4.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: format all files with prettier
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>