Skip to content

0.4.0

Choose a tag to compare

@tomymaritano tomymaritano released this 11 Feb 19:47
· 178 commits to main since this release
e5cfa1b
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>