Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
205 commits
Select commit Hold shift + click to select a range
9a48ab2
feat(api): add notebookSyncLog table to Drizzle schema
tomymaritano Mar 11, 2026
3d49703
feat(storage): add notebook sync tracking migration with triggers
tomymaritano Mar 11, 2026
b6c0d96
feat(desktop): add notebook sync methods to ApiClient
tomymaritano Mar 11, 2026
403642e
test(sync-core): add tree validation unit tests for notebook sync
tomymaritano Mar 11, 2026
628b16b
feat(api): add notebook sync pull/push endpoints with tree validation
tomymaritano Mar 11, 2026
a9fde10
feat(storage): add sync methods to SQLiteNotebookRepository
tomymaritano Mar 11, 2026
26401e3
feat(desktop): integrate notebook sync into SyncService
tomymaritano Mar 11, 2026
db02671
fix(desktop): fix Timestamp type casts and use static createNotebook …
tomymaritano Mar 11, 2026
535d53c
refactor(sync-core): extract shared tree validation to sync-core package
tomymaritano Mar 11, 2026
4055595
chore: format notebook sync code and fix unused variable
tomymaritano Mar 11, 2026
305bcb5
test(sync-core): add SyncQueue unit tests
tomymaritano Mar 11, 2026
fcd26b9
test(sync-core): add SyncEngine unit tests
tomymaritano Mar 11, 2026
a88f19d
style: format sync-core test files
tomymaritano Mar 11, 2026
488359e
feat: surface sync conflicts in status indicator
tomymaritano Mar 11, 2026
f34ab5e
feat(storage): add tag sync tracking migration (UUID + triggers)
tomymaritano Mar 11, 2026
4d2c4ac
feat(api): add tagSyncLog table to server schema
tomymaritano Mar 11, 2026
fff8cd8
feat(api): add tag sync pull/push endpoints
tomymaritano Mar 11, 2026
0782ea8
feat(storage): add tag sync repository methods
tomymaritano Mar 11, 2026
1375fcd
fix(storage): remove double-invocation on transaction calls
tomymaritano Mar 11, 2026
51e8112
feat(desktop): add tag sync API client methods
tomymaritano Mar 11, 2026
2e60230
feat(desktop): add IPC bridge for tag sync
tomymaritano Mar 11, 2026
26f2757
feat(desktop): integrate tag sync into sync cycle
tomymaritano Mar 11, 2026
043148c
fix: address PR review feedback for notebook sync
tomymaritano Mar 11, 2026
e8250b8
Merge pull request #117 from tomymaritano/feature/notebook-sync
tomymaritano Mar 11, 2026
c913465
Merge pull request #118 from tomymaritano/feature/sync-tests
tomymaritano Mar 11, 2026
26bccb5
Merge pull request #119 from tomymaritano/feature/conflict-ui-polish
tomymaritano Mar 11, 2026
31a880e
Merge remote-tracking branch 'origin/develop' into feature/tag-sync
tomymaritano Mar 11, 2026
63610e4
Merge pull request #120 from tomymaritano/feature/tag-sync
tomymaritano Mar 11, 2026
73f9938
chore: add CodeRabbit configuration
tomymaritano Mar 11, 2026
cd01862
feat(plugin-api): add PluginHookOptions type for remark/rehype regist…
tomymaritano Mar 11, 2026
e1eb092
feat: add safePluginWrapper for graceful plugin failure handling
tomymaritano Mar 11, 2026
cb9a82e
feat: pass metadata through plugin registration in PluginRegistry
tomymaritano Mar 11, 2026
aa2bbf6
feat(desktop): add CSS for plugin error block boundaries in preview
tomymaritano Mar 11, 2026
46ae6de
feat: hot-reload preview when toggling plugins on/off
tomymaritano Mar 11, 2026
fbb5448
feat(plugin-api): upgrade remark/rehype stores with metadata, priorit…
tomymaritano Mar 11, 2026
4a087e7
test(plugin-api): update tests for new metadata and safePluginWrapper
tomymaritano Mar 11, 2026
3902345
docs: add remark/rehype hooks enhancement design plan
tomymaritano Mar 11, 2026
47fec79
docs: add theme system enhancement design
tomymaritano Mar 11, 2026
614b26f
docs: add theme system implementation plan
tomymaritano Mar 11, 2026
06a147d
feat(plugin-api): add theme types with token whitelist and ThemeRegis…
tomymaritano Mar 11, 2026
f5f8f25
feat(plugin-api): add useThemeOverrides hook and registerTheme contex…
tomymaritano Mar 11, 2026
5fe153e
feat: add nativeTheme IPC sync between main, preload, and renderer
tomymaritano Mar 11, 2026
56ba2a5
feat: add theme settings schema, UI selector, and startup restore
tomymaritano Mar 11, 2026
1f61ed7
test: add theme token validation and ThemeRegistry store tests
tomymaritano Mar 11, 2026
bf58f4a
docs: add Data Access API design document
tomymaritano Mar 11, 2026
7bda4c1
docs: add Data Access API implementation plan
tomymaritano Mar 11, 2026
b44fc75
feat(plugin-api): add DataAPI types, query options, and DataAccessError
tomymaritano Mar 11, 2026
9e16eec
feat(plugin-api): add DataAPI interface, bridge, and createDataAPI fa…
tomymaritano Mar 11, 2026
78a20cd
test(plugin-api): add comprehensive tests for createDataAPI
tomymaritano Mar 11, 2026
3d16297
feat(plugin-api): wire DataAPI into PluginRegistry and PluginHost
tomymaritano Mar 11, 2026
d8f8ca6
feat(plugin-api): export DataAPI types and factory from barrel
tomymaritano Mar 11, 2026
589fe17
feat: wire DataAPI bridge to IPC in App.tsx and fire data events
tomymaritano Mar 11, 2026
9f6f229
feat(api): add device list and rename endpoints
tomymaritano Mar 11, 2026
48195ab
feat(api): add device revoke and revoke-others endpoints
tomymaritano Mar 11, 2026
8ea9877
fix(api): reject token refresh for revoked devices
tomymaritano Mar 11, 2026
016a524
feat(desktop): add device management IPC handlers
tomymaritano Mar 11, 2026
0cd1214
feat(desktop): expose devices IPC bridge in preload
tomymaritano Mar 11, 2026
308c229
feat(desktop): add device management UI in settings
tomymaritano Mar 11, 2026
d1c3cb7
test(api): scaffold test infrastructure with vitest and helpers
tomymaritano Mar 11, 2026
2cb58bd
test(api): add note sync pull endpoint tests
tomymaritano Mar 11, 2026
fcc9d96
test(api): add note sync push and conflict detection tests
tomymaritano Mar 11, 2026
9f137d3
test(api): add notebook sync and tree validation tests
tomymaritano Mar 11, 2026
36e1a5b
test(api): add tag sync and sync status endpoint tests
tomymaritano Mar 11, 2026
d104dd1
test: add encryption round-trip tests for AES-256-GCM
tomymaritano Mar 11, 2026
a6b3558
docs: add sync hardening design and implementation plan
tomymaritano Mar 11, 2026
0e435e2
feat(storage): add sync_history migration for sync cycle metrics
tomymaritano Mar 11, 2026
080cb68
feat(storage): add sync history repository methods
tomymaritano Mar 11, 2026
3646cb2
feat(api-client): add bandwidth tracking to request method
tomymaritano Mar 11, 2026
57d4303
feat(sync): record sync history with per-cycle metrics and bandwidth
tomymaritano Mar 11, 2026
8153c78
feat(ipc): expose sync history via IPC and preload bridge
tomymaritano Mar 11, 2026
fc9ab96
feat(sync): auto-resume sync on network reconnect with debounce
tomymaritano Mar 11, 2026
99b3f56
feat(ui): add sync history section to Settings with bandwidth display
tomymaritano Mar 11, 2026
f4e904c
style: fix Prettier formatting across theme system and sync files
tomymaritano Mar 11, 2026
1e1a44c
docs: add Phase 2 completion design (2.4 + 2.6)
tomymaritano Mar 11, 2026
5af6419
docs: add Phase 2 completion implementation plan (2.4 + 2.6)
tomymaritano Mar 11, 2026
71bf473
fix(plugins): add enum and range to pluginScanner config schema type
tomymaritano Mar 11, 2026
249a2ad
feat(plugin-api): add validateConfigValue for config schema enforcement
tomymaritano Mar 11, 2026
801a738
feat(plugins): validate config values before persisting
tomymaritano Mar 11, 2026
17c9ff2
feat(plugins): track load timing per plugin in runtime store
tomymaritano Mar 11, 2026
3dabece
feat(plugins): add dev-mode Plugin Inspector with load timings and er…
tomymaritano Mar 11, 2026
38a5170
fix: address CodeRabbit review comments on remark/rehype hooks
tomymaritano Mar 11, 2026
c099dbd
fix: address CodeRabbit review comments on theme system
tomymaritano Mar 11, 2026
b8a4e81
docs: add theme system implementation plan
tomymaritano Mar 11, 2026
e013638
feat(plugin-api): add theme types with token whitelist and ThemeRegis…
tomymaritano Mar 11, 2026
c87de4e
feat(plugin-api): add useThemeOverrides hook and registerTheme contex…
tomymaritano Mar 11, 2026
2c753b3
feat: add nativeTheme IPC sync between main, preload, and renderer
tomymaritano Mar 11, 2026
6948a26
feat: add theme settings schema, UI selector, and startup restore
tomymaritano Mar 11, 2026
ff0e19b
test: add theme token validation and ThemeRegistry store tests
tomymaritano Mar 11, 2026
35c346e
style: fix Prettier formatting across theme system and sync files
tomymaritano Mar 11, 2026
35f3804
docs: complete documentation update for v0.8
tomymaritano Mar 11, 2026
09cf396
feat: add docs links, bug reporting, and contributor onboarding
tomymaritano Mar 11, 2026
d773955
Merge pull request #122 from tomymaritano/feature/theme-system
tomymaritano Mar 11, 2026
bc424cb
style: fix prettier formatting
tomymaritano Mar 11, 2026
c8d8833
style: fix prettier formatting
tomymaritano Mar 11, 2026
d8846d9
style: fix prettier formatting
tomymaritano Mar 11, 2026
188c7d8
style: fix prettier formatting
tomymaritano Mar 12, 2026
d86c2ec
style: fix prettier formatting
tomymaritano Mar 12, 2026
d096658
style: fix prettier formatting
tomymaritano Mar 12, 2026
65abbd6
style: fix prettier formatting
tomymaritano Mar 12, 2026
d6783af
fix: address CodeRabbit review comments on PR #127
tomymaritano Mar 12, 2026
df64ff5
fix: address CodeRabbit review comments on PR #123
tomymaritano Mar 12, 2026
f9766d3
style: fix prettier formatting in docs
tomymaritano Mar 12, 2026
452f3bd
Merge pull request #128 from tomymaritano/feature/docs-update
tomymaritano Mar 12, 2026
3f7b49d
Merge remote-tracking branch 'origin/develop' into feature/remark-reh…
tomymaritano Mar 12, 2026
5d112ff
style: fix formatting after merge
tomymaritano Mar 12, 2026
3d41f25
Merge pull request #121 from tomymaritano/feature/remark-rehype-hooks…
tomymaritano Mar 12, 2026
5e3af25
Merge remote-tracking branch 'origin/develop' into feature/phase2-com…
tomymaritano Mar 12, 2026
4b08f07
Merge pull request #127 from tomymaritano/feature/phase2-completion
tomymaritano Mar 12, 2026
7344281
Merge branch 'develop' into feature/data-access-api
tomymaritano Mar 12, 2026
b429ab3
Merge pull request #123 from tomymaritano/feature/data-access-api
tomymaritano Mar 12, 2026
3ed08b4
Merge branch 'develop' into feature/device-management
tomymaritano Mar 12, 2026
cdd2db0
Merge pull request #124 from tomymaritano/feature/device-management
tomymaritano Mar 12, 2026
177091d
Merge remote-tracking branch 'origin/develop' into feature/sync-tests
tomymaritano Mar 12, 2026
9f2ba75
Merge pull request #125 from tomymaritano/feature/sync-tests
tomymaritano Mar 12, 2026
cbe39c8
Merge remote-tracking branch 'origin/develop' into feature/sync-harde…
tomymaritano Mar 12, 2026
38b8128
Merge pull request #126 from tomymaritano/feature/sync-hardening
tomymaritano Mar 12, 2026
592d889
fix: resolve CodeRabbit review issues across merged PRs
tomymaritano Mar 12, 2026
09ac09a
chore: add Husky pre-commit hooks and auto-merge workflow
tomymaritano Mar 12, 2026
5cd5520
fix: use gh pr merge --auto for automerge workflow
tomymaritano Mar 12, 2026
2ef5a9a
ci: level up CI pipeline and Husky hooks
tomymaritano Mar 12, 2026
a8bf258
fix: remove process.env.NODE_ENV check from browser-only plugin-api
tomymaritano Mar 12, 2026
0999107
Merge pull request #129 from tomymaritano/fix/coderabbit-issues
tomymaritano Mar 12, 2026
40c5216
Merge pull request #130 from tomymaritano/feature/husky-automerge
tomymaritano Mar 12, 2026
d65eb5c
feat: unify docs-site and marketing-site into single Next.js app (#132)
tomymaritano Mar 12, 2026
8dc7079
feat: plugin auto-disable + CLI commands (Phase 2.5 & 3.1) (#131)
tomymaritano Mar 12, 2026
024e46c
fix: wrangler-action pnpm monorepo compatibility (#134)
tomymaritano Mar 12, 2026
193bbce
fix: create Cloudflare Pages project before deploy (#136)
tomymaritano Mar 12, 2026
d5cd8ed
fix: use npx wrangler for Cloudflare Pages deploy (#139)
tomymaritano Mar 12, 2026
cfe513d
fix: enable Next.js static export for Cloudflare Pages (#141)
tomymaritano Mar 12, 2026
21be0a5
feat(web): full website redesign with shadcn/ui + Magic UI (#142)
tomymaritano Mar 12, 2026
b959a25
feat(web,desktop,api): website redesign + auth UX rethink (#148)
tomymaritano Mar 13, 2026
00e25bf
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Mar 13, 2026
04e4e18
feat(desktop,api): complete Phase 1-3 roadmap implementation
tomymaritano Mar 13, 2026
21af479
feat(ai,plugins): complete Phase 4-5 — AI knowledge & extensibility
tomymaritano Mar 13, 2026
4ccab57
feat(ai): complete Phase 4-5 — AI command execution + plugin bridge (…
tomymaritano Mar 13, 2026
e908280
chore: Tailwind v4 canonical classes + tsconfig cleanup (#151)
tomymaritano Mar 13, 2026
8a1f5c8
feat(sync): connect auth → payment → sync flow with license gating (#…
tomymaritano Mar 13, 2026
a24a231
feat(ai-core): provider-agnostic AI architecture with streaming (#156)
tomymaritano Mar 14, 2026
64d7010
feat(release): automated release pipeline with semantic-release (#157)
tomymaritano Mar 14, 2026
f686ab6
fix(web): add cleanUrls to fix /auth/verify 404 (#164)
tomymaritano Mar 16, 2026
9d64ea9
feat(ai): add tool use (function calling) to AI assistant (#165)
tomymaritano Mar 18, 2026
a472580
feat(share): public notes API with metadata for portfolio consumption…
tomymaritano Mar 18, 2026
6e05279
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Mar 19, 2026
b2f65aa
fix(web,api): render shared notes as markdown + handle subscription.c…
tomymaritano Mar 19, 2026
01b51cb
Merge branch 'main' into develop
tomymaritano Mar 19, 2026
f6ddcad
feat(desktop): share store + note list and editor UI improvements
tomymaritano Mar 19, 2026
41c811c
feat: AI context scoping, newsletter unsubscribe page, create-note sc…
tomymaritano Mar 19, 2026
078c8f4
feat(mcp): add MCP server for Claude Code integration
tomymaritano Mar 20, 2026
7030149
fix(ui): modernize AI panel sidebar with cleaner chat design
tomymaritano Mar 20, 2026
ad325d8
feat(ai): OpenAI + Ollama providers, secure key storage, Connect flow UI
tomymaritano Mar 20, 2026
1680e01
fix(ui): settings visual refresh — card layout, tighter controls, cle…
tomymaritano Mar 20, 2026
31c5474
feat(admin): add dashboard with app metrics and admin API
tomymaritano Mar 20, 2026
7fd0ee1
feat(dashboard): standalone layout with sidebar, improved UI, admin e…
tomymaritano Mar 20, 2026
2bf9c1e
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Mar 20, 2026
20e101c
ci: add workflow_dispatch to build workflow
tomymaritano Mar 20, 2026
e80caf8
fix(mcp): add sql.js type declarations for CI build
tomymaritano Mar 20, 2026
e3b2ef8
fix(ci): bust corrupted electron-builder cache for Linux build
tomymaritano Mar 20, 2026
1e20308
fix(ci): clean fpm cache before Linux build to fix 7zip extraction error
tomymaritano Mar 21, 2026
fb0dafc
feat: fix cross-device sync with E2EE key hierarchy and docs cleanup …
tomymaritano Mar 29, 2026
3b01d4b
fix: address Codex review findings for sync and deep link (#178)
tomymaritano Mar 29, 2026
3f0bc7f
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Mar 29, 2026
578ad70
fix: move deep link handlers into primary instance block
tomymaritano Mar 29, 2026
ab4a486
chore(deps): bump pnpm/action-setup from 4 to 5 (#182)
dependabot[bot] Apr 2, 2026
dc5bc73
chore(deps): bump actions/labeler from 5 to 6 (#183)
dependabot[bot] Apr 2, 2026
075995d
chore(deps): bump github/codeql-action from 3 to 4 (#181)
dependabot[bot] Apr 2, 2026
0e85b24
fix: comprehensive project audit — CI, security, types, and dependencies
tomymaritano Apr 22, 2026
472bc4e
fix: floating promises, Electron upgrade, and renderer tests
tomymaritano Apr 22, 2026
4e36788
feat: design system primitives, save indicator, toast system, and UX …
tomymaritano Apr 23, 2026
d2d763f
refactor: design system consistency, onboarding, and sync progress
tomymaritano Apr 23, 2026
52a2bbe
feat: advanced search filters and functional plugin marketplace
tomymaritano Apr 23, 2026
68c56b6
feat: high-quality table rendering and improved document export
tomymaritano Apr 23, 2026
2672cdc
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Apr 23, 2026
4ace564
fix: address all PR review comments (security, a11y, UX, minor)
tomymaritano Apr 23, 2026
9bccc62
fix: resolve remaining PR review comments (encryption, sync, CI, prel…
tomymaritano Apr 23, 2026
a1f8422
fix: resolve CI failures (mcp-server build, ESLint projectService)
tomymaritano Apr 23, 2026
ef6c615
fix: address all inline and duplicate PR review findings
tomymaritano Apr 23, 2026
549339d
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Apr 23, 2026
19bf2ca
docs: enforce branch protection rules in CLAUDE.md (#199)
tomymaritano Apr 23, 2026
920cad9
fix: Modal hook order + plugin install slug check (#200)
tomymaritano Apr 23, 2026
321da87
fix: plugin install integrity check + branch rules (#201)
tomymaritano Apr 23, 2026
ebb4c17
fix: resolve main→develop merge conflicts (#203)
tomymaritano Apr 23, 2026
b0700b4
fix: address all PR #202 review findings (#204)
tomymaritano Apr 23, 2026
b5d2733
fix: sync main into develop for clean release (#205)
tomymaritano Apr 23, 2026
0dfe6db
fix(ci): remove broken tweet action, expand Linux cache cleanup (#210)
tomymaritano Apr 23, 2026
6328c4b
fix(ci): fix Linux build and Node.js 24 warnings (#212)
tomymaritano Apr 23, 2026
e366f10
fix: prevent 'Object has been destroyed' crash on update (#213)
tomymaritano Apr 23, 2026
a1a6679
refactor: split main/index.ts monolith into IPC handler modules (#214)
tomymaritano Apr 23, 2026
8deb73a
refactor: split preload, App.tsx, PluginsSection, and syncService mon…
tomymaritano Apr 23, 2026
1ab1400
fix: move single-instance lock before whenReady + branch protection (…
tomymaritano Apr 23, 2026
d58dbf8
feat: UX polish — onboarding, AiSection CSS, magic link resend (#218)
tomymaritano Apr 23, 2026
4f17e78
feat: auto-link titles, note animations, activity stats, revision tim…
tomymaritano Apr 23, 2026
807a8e4
chore(deps): bump the production group across 1 directory with 47 upd…
dependabot[bot] Apr 23, 2026
f0e2715
fix(ci): add HTTPS git rewrite to release workflow (#221)
tomymaritano Apr 23, 2026
49f5662
fix: handle @noble/ed25519 API rename (#223)
tomymaritano Apr 23, 2026
a63fad9
fix: React type compatibility after dep update (#225)
tomymaritano Apr 24, 2026
120ce15
fix: skip husky in release + React type compat (#227)
tomymaritano Apr 24, 2026
67a0831
fix: replace removed lucide brand icons (#229)
tomymaritano Apr 24, 2026
d4ce973
feat: local HTTP API, quick capture, mermaid/math plugins + ASAR fix …
tomymaritano Apr 24, 2026
bb88da2
fix: exclude @readied/* from ASAR to fix startup crash (#233)
tomymaritano Apr 24, 2026
550f1c2
fix: move @readied/* to devDependencies — fixes ASAR startup crash (#…
tomymaritano Apr 24, 2026
272c85f
feat: allow disabling built-in plugins + fix MCP server FTS5 crash (#…
tomymaritano Apr 24, 2026
1836b02
fix: gate built-in plugins until enabled state loads + fix IPC listen…
tomymaritano Apr 24, 2026
0514398
Merge remote-tracking branch 'origin/main' into develop
tomymaritano Apr 24, 2026
32f522a
fix: unwrap getToken IPC result + use exact range for auto-link paste…
tomymaritano Apr 24, 2026
3c7fab8
fix(mcp-server): non-destructive FTS5 check + FTS5 search (#241)
tomymaritano Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

### Features

* v0.14.0 — local HTTP API, quick capture, plugins, Vercel fix ([#232](https://github.com/tomymaritano/readide/issues/232)) ([19301a8](https://github.com/tomymaritano/readide/commit/19301a895f15fa9464d67b7e879502e751b08ab8))
- v0.14.0 — local HTTP API, quick capture, plugins, Vercel fix ([#232](https://github.com/tomymaritano/readide/issues/232)) ([19301a8](https://github.com/tomymaritano/readide/commit/19301a895f15fa9464d67b7e879502e751b08ab8))

### Bug Fixes

* v0.14.1 — ASAR startup crash fix ([#234](https://github.com/tomymaritano/readide/issues/234)) ([b051785](https://github.com/tomymaritano/readide/commit/b0517852c52241a633344907974e962a376f8dc1))
* v0.14.2 — ASAR startup crash fix (devDependencies) ([#236](https://github.com/tomymaritano/readide/issues/236)) ([17b951a](https://github.com/tomymaritano/readide/commit/17b951a634ea55992ce29afad1d969dd821f1f29))
- v0.14.1 — ASAR startup crash fix ([#234](https://github.com/tomymaritano/readide/issues/234)) ([b051785](https://github.com/tomymaritano/readide/commit/b0517852c52241a633344907974e962a376f8dc1))
- v0.14.2 — ASAR startup crash fix (devDependencies) ([#236](https://github.com/tomymaritano/readide/issues/236)) ([17b951a](https://github.com/tomymaritano/readide/commit/17b951a634ea55992ce29afad1d969dd821f1f29))

## [0.14.1](https://github.com/tomymaritano/readide/compare/v0.14.0...v0.14.1) (2026-04-24)

Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/preload/api/localServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export function createLocalServerApi(): LocalServerAPI {
start: (port?: number) => ipcRenderer.invoke('localServer:start', port),
stop: () => ipcRenderer.invoke('localServer:stop'),
status: () => ipcRenderer.invoke('localServer:status'),
getToken: () => ipcRenderer.invoke('localServer:getToken'),
getToken: async () => {
const result = await ipcRenderer.invoke('localServer:getToken');
if (!result.ok) throw new Error(result.error ?? 'Failed to get token');
return result.value as string;
},
};
}
5 changes: 3 additions & 2 deletions apps/desktop/src/preload/api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ export function createSettingsApi(): SettingsAPI {
export function createIpcApi(): IpcAPI {
return {
on: (channel: string, listener: (...args: unknown[]) => void) => {
ipcRenderer.on(channel, (_event, ...args) => listener(...args));
const handler = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => listener(...args);
ipcRenderer.on(channel, handler);
return () => {
ipcRenderer.removeAllListeners(channel);
ipcRenderer.removeListener(channel, handler);
};
},
};
Expand Down
34 changes: 33 additions & 1 deletion apps/desktop/src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,44 @@ function NotesApp() {
// Plugin runtime: init once, React observes
const discoveredPlugins = useStore(pluginRuntimeStore, s => s.plugins);
const pluginErrors = useStore(pluginRuntimeStore, s => s.errors);
const [builtInEnabledMap, setBuiltInEnabledMap] = useState<Record<string, boolean> | null>(null);

useEffect(() => {
void pluginRuntimeStore.getState().init();
// Load built-in plugin enabled states
void (async () => {
const stateList = await window.readied.plugins.listState();
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
})();
}, []);
Comment on lines 443 to +454

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Guard against setState after unmount.

The async IIFE in the mount effect has no cancellation guard. If NotesApp unmounts before listState() resolves (e.g., during fast test teardown or HMR), setBuiltInEnabledMap will fire on an unmounted component. Low risk in production, but trivial to harden:

🛡️ Proposed fix
   useEffect(() => {
     void pluginRuntimeStore.getState().init();
-    // Load built-in plugin enabled states
-    void (async () => {
+    let cancelled = false;
+    void (async () => {
       const stateList = await window.readied.plugins.listState();
+      if (cancelled) return;
       const map: Record<string, boolean> = {};
       for (const s of stateList) {
         map[s.pluginId] = s.enabled;
       }
       setBuiltInEnabledMap(map);
     })();
+    return () => {
+      cancelled = true;
+    };
   }, []);

The same guard is worth applying to the plugins:reload handler in the second effect.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
void pluginRuntimeStore.getState().init();
// Load built-in plugin enabled states
void (async () => {
const stateList = await window.readied.plugins.listState();
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
})();
}, []);
useEffect(() => {
void pluginRuntimeStore.getState().init();
let cancelled = false;
void (async () => {
const stateList = await window.readied.plugins.listState();
if (cancelled) return;
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
})();
return () => {
cancelled = true;
};
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/App.tsx` around lines 443 - 454, The async IIFE in
the first useEffect can call setBuiltInEnabledMap after NotesApp unmounts; add a
cancellation guard (e.g., a local "mounted" boolean or AbortController) inside
the effect and check it before calling setBuiltInEnabledMap after awaiting
window.readied.plugins.listState(); also apply the same guard to the
"plugins:reload" handler in the second effect by ensuring the handler is removed
or checks the mounted flag before calling setState, and clean up the flag/abort
in the effect's return cleanup so no setState runs after unmount.


// Re-check built-in enabled state when plugins reload
useEffect(() => {
const handler = () => {
void (async () => {
const stateList = await window.readied.plugins.listState();
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
})();
};
return window.readied.ipc.on('plugins:reload', handler);
}, []);
Comment on lines 443 to 469

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Refactor: deduplicate the built-in state loader.

The same listState → Record<string, boolean> mapping is implemented twice (initial load and plugins:reload handler). Extract a small helper to keep them in sync if the shape ever changes.

♻️ Proposed refactor
+  const loadBuiltInEnabled = useCallback(async () => {
+    const stateList = await window.readied.plugins.listState();
+    const map: Record<string, boolean> = {};
+    for (const s of stateList) {
+      map[s.pluginId] = s.enabled;
+    }
+    setBuiltInEnabledMap(map);
+  }, []);
+
   useEffect(() => {
     void pluginRuntimeStore.getState().init();
-    // Load built-in plugin enabled states
-    void (async () => {
-      const stateList = await window.readied.plugins.listState();
-      const map: Record<string, boolean> = {};
-      for (const s of stateList) {
-        map[s.pluginId] = s.enabled;
-      }
-      setBuiltInEnabledMap(map);
-    })();
-  }, []);
+    void loadBuiltInEnabled();
+  }, [loadBuiltInEnabled]);

   // Re-check built-in enabled state when plugins reload
   useEffect(() => {
-    const handler = () => {
-      void (async () => {
-        const stateList = await window.readied.plugins.listState();
-        const map: Record<string, boolean> = {};
-        for (const s of stateList) {
-          map[s.pluginId] = s.enabled;
-        }
-        setBuiltInEnabledMap(map);
-      })();
-    };
-    return window.readied.ipc.on('plugins:reload', handler);
-  }, []);
+    return window.readied.ipc.on('plugins:reload', () => {
+      void loadBuiltInEnabled();
+    });
+  }, [loadBuiltInEnabled]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
void pluginRuntimeStore.getState().init();
// Load built-in plugin enabled states
void (async () => {
const stateList = await window.readied.plugins.listState();
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
})();
}, []);
// Re-check built-in enabled state when plugins reload
useEffect(() => {
const handler = () => {
void (async () => {
const stateList = await window.readied.plugins.listState();
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
})();
};
return window.readied.ipc.on('plugins:reload', handler);
}, []);
const loadBuiltInEnabled = useCallback(async () => {
const stateList = await window.readied.plugins.listState();
const map: Record<string, boolean> = {};
for (const s of stateList) {
map[s.pluginId] = s.enabled;
}
setBuiltInEnabledMap(map);
}, []);
useEffect(() => {
void pluginRuntimeStore.getState().init();
void loadBuiltInEnabled();
}, [loadBuiltInEnabled]);
// Re-check built-in enabled state when plugins reload
useEffect(() => {
return window.readied.ipc.on('plugins:reload', () => {
void loadBuiltInEnabled();
});
}, [loadBuiltInEnabled]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/App.tsx` around lines 443 - 469, The built-in
plugin state mapping (window.readied.plugins.listState → Record<string, boolean>
→ setBuiltInEnabledMap) is duplicated in the initial effect and the
plugins:reload handler; extract a single async helper e.g. loadBuiltInEnabledMap
that calls window.readied.plugins.listState, builds the map from
s.pluginId/s.enabled and calls setBuiltInEnabledMap, then replace the inline
IIFEs in the effect that calls pluginRuntimeStore.getState().init() and in the
handler passed to window.readied.ipc.on('plugins:reload') to both invoke this
helper so the logic is centralized and stays in sync.


const allPlugins = useMemo(() => [...builtInPlugins, ...discoveredPlugins], [discoveredPlugins]);
const allPlugins = useMemo(() => {
// Don't mount built-in plugins until the enabled state is loaded
// to avoid activating disabled plugins on the initial render
const enabledBuiltIn = builtInEnabledMap
? builtInPlugins.filter(p => builtInEnabledMap[p.id] !== false)
: [];
return [...enabledBuiltIn, ...discoveredPlugins];
}, [discoveredPlugins, builtInEnabledMap]);

const configBridge = useMemo(
() => ({
Expand Down
18 changes: 11 additions & 7 deletions apps/desktop/src/renderer/components/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -575,22 +575,26 @@ export const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorPro
userEvent: 'input.paste',
});

// Fetch title in background and replace with markdown link
// Fetch title in background and replace with markdown link.
// Track the exact range where we inserted the URL so later
// edits don't cause us to rewrite the wrong occurrence.
const insertedFrom = from;
const insertedTo = from + plainText.length;
window.readied.editor
.fetchUrlTitle(plainText)
.then(({ title }) => {
if (!title) return;
// Find the URL in the document — it may have moved due to edits
// Verify the URL still sits at the expected position
const currentDoc = view.state.doc.toString();
const urlIndex = currentDoc.indexOf(plainText, from > 20 ? from - 20 : 0);
if (urlIndex === -1) return;
const textAtRange = currentDoc.slice(insertedFrom, insertedTo);
if (textAtRange !== plainText) return;
// Verify it's still a bare URL (not already wrapped in markdown link)
const charBefore = urlIndex > 0 ? currentDoc[urlIndex - 1] : '';
const charBefore = insertedFrom > 0 ? currentDoc[insertedFrom - 1] : '';
if (charBefore === '(' || charBefore === '<') return;
const mdLink = `[${title}](${plainText})`;
view.dispatch({
changes: { from: urlIndex, to: urlIndex + plainText.length, insert: mdLink },
selection: EditorSelection.cursor(urlIndex + mdLink.length),
changes: { from: insertedFrom, to: insertedTo, insert: mdLink },
selection: EditorSelection.cursor(insertedFrom + mdLink.length),
userEvent: 'input.paste',
});
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export function PluginCard({
id={`plugin-${name.toLowerCase().replace(/\s+/g, '-')}`}
checked={enabled}
onChange={checked => onToggle?.(checked)}
disabled={isBuiltIn}
/>
{!isBuiltIn && onUninstall && (
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function PluginsSection() {
const [pluginsPath, setPluginsPath] = useState('');
const [isReloading, setIsReloading] = useState(false);
const [configValues, setConfigValues] = useState<Record<string, Record<string, unknown>>>({});
const [builtInEnabled, setBuiltInEnabled] = useState<Record<string, boolean>>({});
const [refreshKey, setRefreshKey] = useState(0);

// Listen for plugin install events from BrowseTab
Expand Down Expand Up @@ -77,6 +78,13 @@ export function PluginsSection() {
}));
setPlugins(pluginList);

// Load built-in plugin enabled states from DB
const builtInStates: Record<string, boolean> = {};
for (const bp of builtInPlugins) {
builtInStates[bp.id] = stateMap.get(bp.id) ?? true;
}
setBuiltInEnabled(builtInStates);

// Load config values for all plugins with schemas (built-in + community)
const configs: Record<string, Record<string, unknown>> = {};

Expand Down Expand Up @@ -111,11 +119,14 @@ export function PluginsSection() {
void loadPlugins();
}, [refreshKey]);

// Toggle plugin enabled/disabled
// Toggle plugin enabled/disabled (works for both built-in and community)
const handleToggle = useCallback(async (pluginId: string, enabled: boolean) => {
try {
await window.readied.plugins.setEnabled(pluginId, enabled);
// Update community plugins state
setPlugins(prev => prev.map(p => (p.id === pluginId ? { ...p, enabled } : p)));
// Update built-in plugins state
setBuiltInEnabled(prev => ({ ...prev, [pluginId]: enabled }));
// Trigger reload in main window so preview updates immediately
window.readied.plugins.requestReload();
toast.success(`Plugin ${enabled ? 'enabled' : 'disabled'}`);
Expand Down Expand Up @@ -293,7 +304,8 @@ export function PluginsSection() {
version={plugin.version}
description={plugin.description}
isBuiltIn={true}
enabled={true}
enabled={builtInEnabled[plugin.id] ?? true}
onToggle={enabled => handleToggle(plugin.id, enabled)}
configSchema={BUILT_IN_CONFIG_SCHEMAS[plugin.id]}
configValues={configValues[plugin.id]}
onConfigChange={(key, value) => handleConfigChange(plugin.id, key, value)}
Expand Down
9 changes: 6 additions & 3 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@
},
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts"
"dev": "tsx src/index.ts",
"test": "vitest run"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"sql.js": "^1.14.1",
"better-sqlite3": "^11.7.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.0",
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.7.0"
"typescript": "^5.7.0",
"vitest": "^3.2.1"
}
}
160 changes: 160 additions & 0 deletions packages/mcp-server/src/__tests__/fts5-triggers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Database from 'better-sqlite3';
import { openDb } from '../db.js';

/**
* Minimal schema that mirrors the real Readied database enough to
* exercise the FTS5 triggers that caused "no such module: fts5"
* when the MCP server used sql.js (which lacks FTS5).
*/
const SCHEMA = `
CREATE TABLE notes (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
title TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
word_count INTEGER NOT NULL DEFAULT 0,
notebook_id TEXT DEFAULT 'inbox',
is_pinned INTEGER DEFAULT 0,
is_deleted INTEGER DEFAULT 0,
status TEXT DEFAULT 'active',
needs_sync INTEGER DEFAULT 0,
local_version INTEGER DEFAULT 1,
sync_version INTEGER DEFAULT 0
);

CREATE TABLE notebooks (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
parent_id TEXT,
depth INTEGER NOT NULL DEFAULT 0,
"order" INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

INSERT INTO notebooks (id, name, created_at, updated_at)
VALUES ('inbox', 'Inbox', datetime('now'), datetime('now'));

-- FTS5 virtual table (migration 008)
CREATE VIRTUAL TABLE notes_fts USING fts5(
id UNINDEXED,
title,
content,
tokenize='porter unicode61'
);

-- Trigger: sync FTS on INSERT
CREATE TRIGGER notes_fts_insert AFTER INSERT ON notes
WHEN NEW.is_deleted = 0 OR NEW.is_deleted IS NULL
BEGIN
INSERT INTO notes_fts(id, title, content)
VALUES (NEW.id, NEW.title, NEW.content);
END;

-- Trigger: sync FTS on UPDATE (delete + re-insert)
CREATE TRIGGER notes_fts_update AFTER UPDATE ON notes
BEGIN
DELETE FROM notes_fts WHERE id = OLD.id;
INSERT INTO notes_fts(id, title, content)
SELECT NEW.id, NEW.title, NEW.content
WHERE NEW.is_deleted = 0 OR NEW.is_deleted IS NULL;
END;

-- Trigger: sync FTS on DELETE
CREATE TRIGGER notes_fts_delete AFTER DELETE ON notes
BEGIN
DELETE FROM notes_fts WHERE id = OLD.id;
END;
`;

describe('FTS5 trigger execution', () => {
let db: Database.Database;

beforeEach(() => {
db = new Database(':memory:');
db.exec(SCHEMA);
});

afterEach(() => {
db.close();
});

it('INSERT fires notes_fts_insert trigger without error', () => {
const now = new Date().toISOString();

expect(() => {
db.prepare(
`INSERT INTO notes (id, content, title, created_at, updated_at, word_count, notebook_id, status, needs_sync, local_version, sync_version)
VALUES (?, ?, ?, ?, ?, ?, ?, 'active', 1, 1, 0)`
).run('note-1', '# Test Note\n\nHello world', 'Test Note', now, now, 2, 'inbox');
}).not.toThrow();

const ftsRow = db.prepare('SELECT * FROM notes_fts WHERE notes_fts MATCH ?').get('hello');
expect(ftsRow).toBeTruthy();
});

it('UPDATE fires notes_fts_update trigger without error', () => {
const now = new Date().toISOString();
db.prepare(
`INSERT INTO notes (id, content, title, created_at, updated_at, word_count)
VALUES (?, ?, ?, ?, ?, ?)`
).run('note-1', '# Original\n\nOriginal content', 'Original', now, now, 2);

expect(() => {
db.prepare(
'UPDATE notes SET content = ?, title = ?, updated_at = ?, word_count = ?, needs_sync = 1, local_version = local_version + 1 WHERE id = ?'
).run('# Updated\n\nBrand new content', 'Updated', now, 3, 'note-1');
}).not.toThrow();

const oldMatch = db.prepare('SELECT * FROM notes_fts WHERE notes_fts MATCH ?').get('original');
expect(oldMatch).toBeUndefined();

const newMatch = db.prepare('SELECT * FROM notes_fts WHERE notes_fts MATCH ?').get('brand');
expect(newMatch).toBeTruthy();
});

it('soft-delete UPDATE removes entry from FTS index', () => {
const now = new Date().toISOString();
db.prepare(
`INSERT INTO notes (id, content, title, created_at, updated_at, word_count)
VALUES (?, ?, ?, ?, ?, ?)`
).run('note-1', '# Trashable\n\nGoing away', 'Trashable', now, now, 2);

expect(() => {
db.prepare(
'UPDATE notes SET is_deleted = 1, updated_at = ?, needs_sync = 1 WHERE id = ?'
).run(now, 'note-1');
}).not.toThrow();

const match = db.prepare('SELECT * FROM notes_fts WHERE notes_fts MATCH ?').get('trashable');
expect(match).toBeUndefined();
});

it('DELETE fires notes_fts_delete trigger without error', () => {
const now = new Date().toISOString();
db.prepare(
`INSERT INTO notes (id, content, title, created_at, updated_at, word_count)
VALUES (?, ?, ?, ?, ?, ?)`
).run('note-1', '# Deletable\n\nWill be removed', 'Deletable', now, now, 3);

expect(() => {
db.prepare('DELETE FROM notes WHERE id = ?').run('note-1');
}).not.toThrow();

const match = db.prepare('SELECT * FROM notes_fts WHERE notes_fts MATCH ?').get('deletable');
expect(match).toBeUndefined();
});
});

describe('FTS5 runtime check', () => {
it('openDb succeeds with an in-memory database (FTS5 available)', () => {
const db = openDb(':memory:');
expect(db).toBeTruthy();
// Verify FTS5 actually works on the returned connection
db.prepare('CREATE VIRTUAL TABLE _test_fts USING fts5(x)').run();
db.prepare('DROP TABLE _test_fts').run();
db.close();
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading