@knotpad/app 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/bin/brief.js +165 -78
  2. package/package.json +6 -86
  3. package/app/(app)/calendar/page.tsx +0 -57
  4. package/app/(app)/error.tsx +0 -35
  5. package/app/(app)/graph/page.tsx +0 -32
  6. package/app/(app)/guide/page.tsx +0 -21
  7. package/app/(app)/kanban/loading.tsx +0 -24
  8. package/app/(app)/kanban/page.tsx +0 -59
  9. package/app/(app)/layout.tsx +0 -122
  10. package/app/(app)/list/loading.tsx +0 -21
  11. package/app/(app)/list/page.tsx +0 -137
  12. package/app/(app)/loading.tsx +0 -18
  13. package/app/(app)/notes/[noteId]/page.tsx +0 -84
  14. package/app/(app)/notes/layout.tsx +0 -30
  15. package/app/(app)/notes/page.tsx +0 -39
  16. package/app/(app)/page.tsx +0 -5
  17. package/app/(app)/settings/agent-token/page.tsx +0 -59
  18. package/app/(app)/settings/backup/page.tsx +0 -49
  19. package/app/(app)/settings/billing/page.tsx +0 -53
  20. package/app/(app)/settings/calendar/page.tsx +0 -41
  21. package/app/(app)/settings/layout.test.tsx +0 -39
  22. package/app/(app)/settings/layout.tsx +0 -71
  23. package/app/(app)/settings/page.tsx +0 -4
  24. package/app/(app)/settings/security/page.tsx +0 -43
  25. package/app/(app)/settings/team/page.tsx +0 -74
  26. package/app/(app)/settings/workspace/page.tsx +0 -27
  27. package/app/(app)/tasks/[taskId]/page.tsx +0 -79
  28. package/app/(auth)/forgot-password/page.tsx +0 -106
  29. package/app/(auth)/guest/page.tsx +0 -56
  30. package/app/(auth)/layout.tsx +0 -13
  31. package/app/(auth)/login/page.tsx +0 -14
  32. package/app/(auth)/register/page.tsx +0 -193
  33. package/app/(auth)/reset-password/page.tsx +0 -138
  34. package/app/api/account/claim/route.tsx +0 -135
  35. package/app/api/admin/backfill-encryption/route.tsx +0 -43
  36. package/app/api/admin/license/route.tsx +0 -42
  37. package/app/api/auth/2fa/route.tsx +0 -148
  38. package/app/api/auth/[...nextauth]/route.tsx +0 -3
  39. package/app/api/auth/change-password/route.tsx +0 -61
  40. package/app/api/auth/check-2fa/route.tsx +0 -19
  41. package/app/api/auth/forgot-password/route.tsx +0 -65
  42. package/app/api/auth/reset-password/route.tsx +0 -52
  43. package/app/api/auth/verify-2fa/route.tsx +0 -88
  44. package/app/api/backup/download/db/route.ts +0 -29
  45. package/app/api/backup/download/notes/route.ts +0 -25
  46. package/app/api/backup/settings/route.ts +0 -92
  47. package/app/api/billing/checkout/route.tsx +0 -81
  48. package/app/api/billing/migrate/route.tsx +0 -163
  49. package/app/api/billing/portal/route.tsx +0 -24
  50. package/app/api/billing/setup-intent/route.tsx +0 -55
  51. package/app/api/billing/status/route.tsx +0 -36
  52. package/app/api/billing/subscribe/route.tsx +0 -85
  53. package/app/api/billing/webhook/route.tsx +0 -199
  54. package/app/api/calendar-feeds/[feedId]/route.tsx +0 -67
  55. package/app/api/calendar-feeds/[feedId]/sync/route.tsx +0 -37
  56. package/app/api/calendar-feeds/events/route.tsx +0 -82
  57. package/app/api/calendar-feeds/route.tsx +0 -52
  58. package/app/api/calendar-feeds/sync-all/route.tsx +0 -34
  59. package/app/api/cron/calendar-feeds/route.tsx +0 -31
  60. package/app/api/cron/stale-tasks/route.tsx +0 -51
  61. package/app/api/cron/sync/route.tsx +0 -34
  62. package/app/api/devices/[deviceId]/route.tsx +0 -25
  63. package/app/api/devices/route.tsx +0 -41
  64. package/app/api/export/route.tsx +0 -40
  65. package/app/api/feedback/route.tsx +0 -54
  66. package/app/api/folders/[folderId]/route.tsx +0 -51
  67. package/app/api/folders/route.tsx +0 -37
  68. package/app/api/graph/route.tsx +0 -242
  69. package/app/api/guest/route.tsx +0 -58
  70. package/app/api/health/route.tsx +0 -10
  71. package/app/api/holidays/countries/route.tsx +0 -14
  72. package/app/api/holidays/route.tsx +0 -49
  73. package/app/api/holidays/states/route.tsx +0 -21
  74. package/app/api/invites/[token]/route.tsx +0 -131
  75. package/app/api/invites/route.tsx +0 -74
  76. package/app/api/mcp/generate-token/route.tsx +0 -55
  77. package/app/api/mcp/revoke-token/[tokenId]/route.tsx +0 -30
  78. package/app/api/mcp/update-alias/[tokenId]/route.tsx +0 -22
  79. package/app/api/notes/[noteId]/export/route.tsx +0 -45
  80. package/app/api/notes/[noteId]/route.tsx +0 -360
  81. package/app/api/notes/route.tsx +0 -112
  82. package/app/api/notifications/route.tsx +0 -44
  83. package/app/api/register/route.tsx +0 -67
  84. package/app/api/restore/route.tsx +0 -148
  85. package/app/api/sync/conflicts/[conflictId]/route.tsx +0 -134
  86. package/app/api/sync/conflicts/route.tsx +0 -48
  87. package/app/api/sync/status/route.tsx +0 -49
  88. package/app/api/sync/trigger/route.tsx +0 -15
  89. package/app/api/tasks/[taskId]/detail/route.tsx +0 -68
  90. package/app/api/tasks/[taskId]/route.tsx +0 -259
  91. package/app/api/tasks/bulk/route.tsx +0 -133
  92. package/app/api/tasks/route.tsx +0 -36
  93. package/app/api/workspace/active/route.tsx +0 -39
  94. package/app/api/workspace/create-team/route.tsx +0 -42
  95. package/app/api/workspace/kanban-statuses/route.tsx +0 -71
  96. package/app/api/workspace/members/[memberId]/route.tsx +0 -69
  97. package/app/api/workspace/route.tsx +0 -24
  98. package/app/download/page.tsx +0 -170
  99. package/app/favicon.ico +0 -0
  100. package/app/generated/prisma/client.d.ts +0 -1
  101. package/app/generated/prisma/client.js +0 -5
  102. package/app/generated/prisma/default.d.ts +0 -1
  103. package/app/generated/prisma/default.js +0 -5
  104. package/app/generated/prisma/edge.d.ts +0 -1
  105. package/app/generated/prisma/edge.js +0 -497
  106. package/app/generated/prisma/index-browser.js +0 -523
  107. package/app/generated/prisma/index.d.ts +0 -46376
  108. package/app/generated/prisma/index.js +0 -497
  109. package/app/generated/prisma/package.json +0 -144
  110. package/app/generated/prisma/query_compiler_fast_bg.js +0 -2
  111. package/app/generated/prisma/query_compiler_fast_bg.wasm +0 -0
  112. package/app/generated/prisma/query_compiler_fast_bg.wasm-base64.js +0 -2
  113. package/app/generated/prisma/runtime/client.d.ts +0 -3386
  114. package/app/generated/prisma/runtime/client.js +0 -86
  115. package/app/generated/prisma/runtime/index-browser.d.ts +0 -90
  116. package/app/generated/prisma/runtime/index-browser.js +0 -6
  117. package/app/generated/prisma/runtime/wasm-compiler-edge.js +0 -76
  118. package/app/generated/prisma/schema.prisma +0 -456
  119. package/app/generated/prisma/wasm-edge-light-loader.mjs +0 -5
  120. package/app/generated/prisma/wasm-worker-loader.mjs +0 -5
  121. package/app/globals.css +0 -54
  122. package/app/invite/[token]/page.tsx +0 -52
  123. package/app/layout.tsx +0 -90
  124. package/app/mcp/route.tsx +0 -430
  125. package/app/opengraph-image.tsx +0 -120
  126. package/app/page.tsx +0 -398
  127. package/app/privacy/page.tsx +0 -69
  128. package/app/robots.tsx +0 -25
  129. package/app/sitemap.tsx +0 -36
  130. package/app/terms/page.tsx +0 -69
  131. package/app/upgrade/page.tsx +0 -75
  132. package/auth.config.ts +0 -33
  133. package/auth.ts +0 -79
  134. package/components/auth/login-form.tsx +0 -302
  135. package/components/auth/password-checklist.tsx +0 -31
  136. package/components/auth/password-input.tsx +0 -36
  137. package/components/auth/switch-account-button.test.tsx +0 -22
  138. package/components/auth/switch-account-button.tsx +0 -19
  139. package/components/auth/two-factor-input.tsx +0 -116
  140. package/components/billing/billing-dashboard.tsx +0 -265
  141. package/components/billing/card-form.tsx +0 -210
  142. package/components/billing/claim-account-form.tsx +0 -99
  143. package/components/branding/app-logo.test.tsx +0 -20
  144. package/components/branding/app-logo.tsx +0 -25
  145. package/components/calendar/calendar-agenda.tsx +0 -150
  146. package/components/calendar/calendar-drag.test.tsx +0 -177
  147. package/components/calendar/calendar-grid.tsx +0 -357
  148. package/components/calendar/calendar-hooks.test.tsx +0 -27
  149. package/components/calendar/calendar-hooks.ts +0 -351
  150. package/components/calendar/calendar-toolbar.test.tsx +0 -68
  151. package/components/calendar/calendar-toolbar.tsx +0 -291
  152. package/components/calendar/calendar-types.ts +0 -148
  153. package/components/calendar/calendar-view.test.tsx +0 -295
  154. package/components/calendar/calendar-view.tsx +0 -307
  155. package/components/calendar/day-detail-popover.tsx +0 -174
  156. package/components/calendar/task-chip.tsx +0 -86
  157. package/components/command/command-palette.test.tsx +0 -33
  158. package/components/command/command-palette.tsx +0 -310
  159. package/components/download-cta.tsx +0 -87
  160. package/components/feedback/feedback-popup.tsx +0 -207
  161. package/components/graph/graph-draw.ts +0 -337
  162. package/components/graph/graph-overlays.tsx +0 -160
  163. package/components/graph/graph-page.test.tsx +0 -131
  164. package/components/graph/graph-page.tsx +0 -263
  165. package/components/graph/graph-types.ts +0 -47
  166. package/components/graph/graph-view.tsx +0 -322
  167. package/components/guide/guide-view.tsx +0 -522
  168. package/components/kanban/kanban-board.test.tsx +0 -128
  169. package/components/kanban/kanban-board.tsx +0 -361
  170. package/components/kanban/kanban-card-menu.tsx +0 -102
  171. package/components/kanban/kanban-card.tsx +0 -227
  172. package/components/kanban/kanban-column.tsx +0 -49
  173. package/components/kanban/kanban-status-context.tsx +0 -28
  174. package/components/landing/calendar-sandbox.test.tsx +0 -15
  175. package/components/landing/calendar-sandbox.tsx +0 -107
  176. package/components/landing/graph-sandbox.test.tsx +0 -27
  177. package/components/landing/graph-sandbox.tsx +0 -80
  178. package/components/landing/kanban-sandbox.test.tsx +0 -24
  179. package/components/landing/kanban-sandbox.tsx +0 -101
  180. package/components/landing/landing-showcase.test.tsx +0 -21
  181. package/components/landing/landing-showcase.tsx +0 -54
  182. package/components/landing/list-sandbox.tsx +0 -86
  183. package/components/landing/mock-workspace.ts +0 -168
  184. package/components/landing/notes-sandbox.test.tsx +0 -14
  185. package/components/landing/notes-sandbox.tsx +0 -88
  186. package/components/layout/app-shell.tsx +0 -83
  187. package/components/layout/backup-scheduler.tsx +0 -122
  188. package/components/layout/bottom-nav.tsx +0 -43
  189. package/components/layout/icon-bar.test.tsx +0 -29
  190. package/components/layout/icon-bar.tsx +0 -118
  191. package/components/layout/mobile-top-bar.tsx +0 -68
  192. package/components/layout/notes-panel-folder.tsx +0 -127
  193. package/components/layout/notes-panel-note-item.tsx +0 -140
  194. package/components/layout/notes-panel-task-tab.tsx +0 -63
  195. package/components/layout/notes-panel-types.ts +0 -44
  196. package/components/layout/notes-panel.tsx +0 -476
  197. package/components/layout/notification-bell.tsx +0 -251
  198. package/components/layout/paywall-screen.tsx +0 -41
  199. package/components/layout/pro-banner.tsx +0 -76
  200. package/components/layout/sw-register.tsx +0 -27
  201. package/components/layout/workspace-switcher.tsx +0 -90
  202. package/components/notes/mobile-bottom-sheet.tsx +0 -99
  203. package/components/notes/note-editor-context-menu.tsx +0 -47
  204. package/components/notes/note-editor-dom.ts +0 -33
  205. package/components/notes/note-editor-dropdowns.tsx +0 -484
  206. package/components/notes/note-editor-hooks.ts +0 -692
  207. package/components/notes/note-editor-keyboard.ts +0 -305
  208. package/components/notes/note-editor-overlay.tsx +0 -90
  209. package/components/notes/note-editor.test.tsx +0 -372
  210. package/components/notes/note-editor.tsx +0 -662
  211. package/components/notes/note-preview-pane.tsx +0 -156
  212. package/components/notes/note-tabs.tsx +0 -120
  213. package/components/notes/note-types.tsx +0 -157
  214. package/components/settings/accept-invite.tsx +0 -108
  215. package/components/settings/agent-token-settings.tsx +0 -369
  216. package/components/settings/backup-restore-settings.test.tsx +0 -25
  217. package/components/settings/backup-restore-settings.tsx +0 -327
  218. package/components/settings/calendar-feeds-settings.tsx +0 -489
  219. package/components/settings/calendar-general-settings.tsx +0 -174
  220. package/components/settings/confirm-danger-action.test.tsx +0 -215
  221. package/components/settings/confirm-danger-action.tsx +0 -65
  222. package/components/settings/security-settings.tsx +0 -252
  223. package/components/settings/settings-guidance.test.tsx +0 -98
  224. package/components/settings/team-settings.tsx +0 -319
  225. package/components/settings/two-factor-auth.tsx +0 -296
  226. package/components/settings/workspace-settings-client.tsx +0 -363
  227. package/components/settings/workspace-settings-form.tsx +0 -73
  228. package/components/sync/conflict-viewer.tsx +0 -247
  229. package/components/sync/sync-indicator.tsx +0 -171
  230. package/components/tasks/snippet-thread.tsx +0 -119
  231. package/components/tasks/status-dot.tsx +0 -47
  232. package/components/tasks/task-badge.tsx +0 -43
  233. package/components/tasks/task-detail.test.tsx +0 -187
  234. package/components/tasks/task-detail.tsx +0 -458
  235. package/components/tasks/task-list-filters.test.tsx +0 -75
  236. package/components/tasks/task-list-filters.tsx +0 -163
  237. package/components/tasks/task-list-types.ts +0 -20
  238. package/components/tasks/task-list.test.tsx +0 -175
  239. package/components/tasks/task-list.tsx +0 -481
  240. package/components/tasks/task-row.tsx +0 -85
  241. package/components/tasks/task-table-row.tsx +0 -259
  242. package/components/ui/skeleton.tsx +0 -3
  243. package/components/ui/toast.test.tsx +0 -42
  244. package/components/ui/toast.tsx +0 -70
  245. package/electron/main.ts +0 -251
  246. package/electron/preload.ts +0 -56
  247. package/instrumentation.tsx +0 -23
  248. package/lib/api-error.ts +0 -50
  249. package/lib/backup/backup-runner.test.ts +0 -32
  250. package/lib/backup/backup-runner.ts +0 -19
  251. package/lib/backup/backup-schedule.test.ts +0 -23
  252. package/lib/backup/backup-schedule.ts +0 -55
  253. package/lib/backup/backup-settings.test.ts +0 -30
  254. package/lib/backup/backup-settings.ts +0 -27
  255. package/lib/backup/export-notes-zip.test.ts +0 -26
  256. package/lib/backup/export-notes-zip.ts +0 -82
  257. package/lib/backup/export-workspace-backup.test.ts +0 -17
  258. package/lib/backup/export-workspace-backup.ts +0 -77
  259. package/lib/backup/restore-workspace-from-export.test.ts +0 -18
  260. package/lib/backup/restore-workspace-from-export.ts +0 -183
  261. package/lib/backup/types.ts +0 -14
  262. package/lib/brand-icons.ts +0 -1
  263. package/lib/calendar-feed-crypto.ts +0 -38
  264. package/lib/calendar-feed.ts +0 -239
  265. package/lib/client/online-status.ts +0 -47
  266. package/lib/conflict-resolver.test.ts +0 -57
  267. package/lib/conflict-resolver.ts +0 -240
  268. package/lib/db-init.ts +0 -79
  269. package/lib/email.ts +0 -159
  270. package/lib/encryption.test.ts +0 -41
  271. package/lib/encryption.ts +0 -98
  272. package/lib/extract-snippet.test.ts +0 -123
  273. package/lib/extract-snippet.ts +0 -69
  274. package/lib/kanban-status.ts +0 -55
  275. package/lib/license.ts +0 -21
  276. package/lib/limits.ts +0 -31
  277. package/lib/mcp-auth.test.ts +0 -58
  278. package/lib/mcp-auth.ts +0 -65
  279. package/lib/mcp-contract.test.ts +0 -25
  280. package/lib/mcp-contract.ts +0 -210
  281. package/lib/mcp-handler.ts +0 -31
  282. package/lib/mcp-url.test.ts +0 -12
  283. package/lib/mcp-url.ts +0 -7
  284. package/lib/mentions.test.ts +0 -45
  285. package/lib/mentions.ts +0 -73
  286. package/lib/note-crypto.ts +0 -108
  287. package/lib/note-sync.ts +0 -201
  288. package/lib/note-title.ts +0 -93
  289. package/lib/prisma.ts +0 -193
  290. package/lib/pro-flush.ts +0 -292
  291. package/lib/rate-limit.ts +0 -57
  292. package/lib/stripe.ts +0 -38
  293. package/lib/sync-worker.ts +0 -388
  294. package/lib/task-parser.test.ts +0 -91
  295. package/lib/task-parser.ts +0 -81
  296. package/lib/task-utils.ts +0 -52
  297. package/lib/use-is-electron.ts +0 -19
  298. package/lib/use-is-mobile.ts +0 -22
  299. package/lib/validation/calendar-feed.ts +0 -31
  300. package/lib/validation/note.ts +0 -27
  301. package/lib/validation/task.ts +0 -26
  302. package/lib/view-preferences.test.ts +0 -54
  303. package/lib/view-preferences.ts +0 -28
  304. package/lib/workspace.ts +0 -66
  305. package/next.config.ts +0 -21
  306. package/postcss.config.mjs +0 -7
  307. package/prisma/migrations/20260519021916_init/migration.sql +0 -388
  308. package/prisma/migrations/20260519061113_drop_sync_password/migration.sql +0 -8
  309. package/prisma/migrations/20260520065016_add_task_start_date/migration.sql +0 -2
  310. package/prisma/migrations/20260529010600_remove_encryption_fields/migration.sql +0 -12
  311. package/prisma/migrations/20260529020000_restore_encryption_salt/migration.sql +0 -3
  312. package/prisma/migrations/20260529030000_add_folders/migration.sql +0 -17
  313. package/prisma/migrations/20260605000000_deferred_fixes/migration.sql +0 -31
  314. package/prisma/migrations/20260605020806_add_pending_sync_to_note_and_task/migration.sql +0 -5
  315. package/prisma/migrations/20260605063634_add_stripe_webhook_event_sync_lock/migration.sql +0 -14
  316. package/prisma/migrations/20260605100000_add_prod_indexes/migration.sql +0 -26
  317. package/prisma/migrations/20260608081404_add_kanban_statuses/migration.sql +0 -23
  318. package/prisma/migrations/20260611032723_add_calendar_feeds/migration.sql +0 -43
  319. package/prisma/migrations/20260611040000_add_calendar_feed_color/migration.sql +0 -2
  320. package/prisma/migrations/20260611050000_add_task_priority/migration.sql +0 -14
  321. package/prisma/migrations/20260612060000_add_critical_priority/migration.sql +0 -2
  322. package/prisma/migrations/20260613090000_add_backup_settings/migration.sql +0 -25
  323. package/prisma/migrations/20260614160000_add_feedback/migration.sql +0 -20
  324. package/prisma/migrations/20260614210000_add_2fa/migration.sql +0 -4
  325. package/prisma/migrations/migration_lock.toml +0 -3
  326. package/prisma/schema.prisma +0 -457
  327. package/public/Logo_icon.svg +0 -1
  328. package/public/file.svg +0 -1
  329. package/public/globe.svg +0 -1
  330. package/public/icon-192.png +0 -0
  331. package/public/icon-512.png +0 -0
  332. package/public/icon.svg +0 -4
  333. package/public/icon_dark.svg +0 -1
  334. package/public/knotpad_icon.svg +0 -1
  335. package/public/knotpad_logo_full.svg +0 -1
  336. package/public/manifest.json +0 -14
  337. package/public/next.svg +0 -1
  338. package/public/sw.js +0 -137
  339. package/public/vercel.svg +0 -1
  340. package/public/window.svg +0 -1
  341. package/tsconfig.json +0 -35
@@ -1,122 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { IconBar } from "@/components/layout/icon-bar";
5
- import { NotesPanel } from "@/components/layout/notes-panel";
6
- import { ProBanner } from "@/components/layout/pro-banner";
7
- import { AppShell } from "@/components/layout/app-shell";
8
- import { PaywallScreen } from "@/components/layout/paywall-screen";
9
- import { SessionProvider } from "next-auth/react";
10
- import { getActiveWorkspaceId, listUserWorkspaces } from "@/lib/workspace";
11
- import { getKanbanStatuses } from "@/lib/kanban-status";
12
- import { isWorkspacePro } from "@/lib/license";
13
- import { KanbanStatusProvider } from "@/components/kanban/kanban-status-context";
14
- import { CommandPaletteProvider } from "@/components/command/command-palette";
15
- import { BackupScheduler } from "@/components/layout/backup-scheduler";
16
-
17
- // Cap the shell's eager note/task fetch so navigation stays fast at scale.
18
- const LAYOUT_LIST_CAP = 500;
19
-
20
- const IS_CLOUD = process.env.IS_CLOUD === "true";
21
-
22
- export default async function AppLayout({ children }: { children: React.ReactNode }) {
23
- const session = await auth();
24
- if (!session) redirect("/login");
25
-
26
- const [workspaceId, allWorkspaces] = await Promise.all([
27
- getActiveWorkspaceId(session.user.id),
28
- listUserWorkspaces(session.user.id),
29
- ]);
30
-
31
- if (!workspaceId) {
32
- // On cloud, a user with no Pro-accessible workspace isn't missing
33
- // membership — they're FREE-only and must upgrade or be invited to a team.
34
- if (IS_CLOUD) return <PaywallScreen />;
35
- redirect("/login");
36
- }
37
-
38
- const workspaces = IS_CLOUD ? allWorkspaces.filter((w) => w.isPro) : allWorkspaces;
39
-
40
- const member = await prisma.workspaceMember.findFirst({
41
- where: { userId: session.user.id, workspaceId },
42
- include: {
43
- workspace: {
44
- include: {
45
- notes: {
46
- orderBy: { updatedAt: "desc" },
47
- // Bounded so the shell (and command-palette index) stay fast on
48
- // large workspaces. Matches the cap on GET /api/notes.
49
- take: LAYOUT_LIST_CAP,
50
- include: {
51
- folder: { select: { id: true, name: true } },
52
- _count: { select: { tasks: true } },
53
- tasks: {
54
- where: { status: { in: ["CLAIMED", "IN_PROGRESS", "REVIEW"] } },
55
- select: { id: true },
56
- },
57
- },
58
- },
59
- folders: { orderBy: { name: "asc" }, select: { id: true, name: true } },
60
- tasks: {
61
- orderBy: { updatedAt: "desc" },
62
- take: LAYOUT_LIST_CAP,
63
- select: {
64
- id: true,
65
- title: true,
66
- status: true,
67
- note: { select: { id: true, title: true } },
68
- },
69
- },
70
- },
71
- },
72
- },
73
- });
74
-
75
- if (!member) redirect("/login");
76
-
77
- const notes = member.workspace.notes.map((n) => ({
78
- id: n.id,
79
- title: n.title,
80
- taskCount: n._count.tasks,
81
- activeTaskCount: n.tasks.length,
82
- isLocked: n.isLocked,
83
- folderId: n.folderId,
84
- }));
85
- const folders = member.workspace.folders;
86
- const tasks = member.workspace.tasks.map((t) => ({
87
- id: t.id,
88
- title: t.title,
89
- status: t.status,
90
- note: t.note,
91
- }));
92
- const latestNoteId = notes[0]?.id;
93
- const notesHref = latestNoteId ? `/notes/${latestNoteId}` : "/notes";
94
-
95
- const commandData = {
96
- notes: notes.map((n) => ({ id: n.id, title: n.title })),
97
- tasks: tasks.map((t) => ({ id: t.id, title: t.title, note: t.note })),
98
- folders,
99
- notesHref,
100
- };
101
-
102
- const kanbanStatuses = await getKanbanStatuses(workspaceId);
103
-
104
- return (
105
- <SessionProvider>
106
- <BackupScheduler />
107
- <KanbanStatusProvider statuses={kanbanStatuses}>
108
- <CommandPaletteProvider data={commandData}>
109
- <div className="flex h-full flex-col overflow-hidden">
110
- <ProBanner initialIsPro={isWorkspacePro(member.workspace)} />
111
- <div className="flex flex-1 min-h-0 overflow-hidden">
112
- <IconBar latestNoteId={latestNoteId} />
113
- <AppShell panel={<NotesPanel notes={notes} folders={folders} tasks={tasks} workspaces={workspaces} activeWorkspaceId={workspaceId} />}>
114
- {children}
115
- </AppShell>
116
- </div>
117
- </div>
118
- </CommandPaletteProvider>
119
- </KanbanStatusProvider>
120
- </SessionProvider>
121
- );
122
- }
@@ -1,21 +0,0 @@
1
- import { Skeleton } from "@/components/ui/skeleton";
2
-
3
- export default function ListLoading() {
4
- return (
5
- <div className="flex flex-1 flex-col overflow-hidden">
6
- <div className="border-b border-zinc-800 px-3 py-3 md:px-6">
7
- <Skeleton className="h-4 w-24" />
8
- </div>
9
- <div className="flex flex-col gap-4 px-3 py-4 md:px-6">
10
- {[0, 1, 2].map((g) => (
11
- <div key={g} className="space-y-2">
12
- <Skeleton className="h-3 w-20" />
13
- {[0, 1, 2].map((r) => (
14
- <Skeleton key={r} className="h-10 w-full" />
15
- ))}
16
- </div>
17
- ))}
18
- </div>
19
- </div>
20
- );
21
- }
@@ -1,137 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { getActiveWorkspaceId } from "@/lib/workspace";
5
- import { TaskList } from "@/components/tasks/task-list";
6
- import { TaskListFilters } from "@/components/tasks/task-list-filters";
7
- import { TaskStatus } from "@/app/generated/prisma/client";
8
-
9
- export default async function ListPage({
10
- searchParams,
11
- }: {
12
- searchParams: Promise<{
13
- note?: string;
14
- sort?: string;
15
- folder?: string;
16
- status?: string;
17
- assigneeType?: string;
18
- overdue?: string;
19
- hasDueDate?: string;
20
- }>;
21
- }) {
22
- const session = await auth();
23
- if (!session) redirect("/login");
24
-
25
- const {
26
- note: filterNoteId,
27
- sort = "status",
28
- folder: filterFolderId,
29
- status: statusFilter,
30
- assigneeType: assigneeFilter,
31
- overdue: overdueOnly,
32
- hasDueDate: hasDueDateFilter,
33
- } = await searchParams;
34
-
35
- const workspaceId = await getActiveWorkspaceId(session.user.id);
36
- if (!workspaceId) redirect("/login");
37
-
38
- // Build status filter condition
39
- const statusFilterCondition = statusFilter
40
- ? { status: { in: statusFilter.split(",") as TaskStatus[] } }
41
- : {};
42
-
43
- // Build assignee filter condition
44
- const assigneeFilterCondition = assigneeFilter
45
- ? { assigneeType: assigneeFilter as "HUMAN" | "AGENT" }
46
- : {};
47
-
48
- // Build overdue filter condition
49
- const now = new Date();
50
- const overdueFilterCondition = overdueOnly === "true"
51
- ? { dueDate: { lt: now }, NOT: { status: "DONE" as TaskStatus } }
52
- : {};
53
-
54
- // Build hasDueDate filter condition
55
- const hasDueDateCondition = hasDueDateFilter === "true"
56
- ? { dueDate: { not: null } }
57
- : {};
58
-
59
- // Get folders for filter dropdown
60
- const foldersPromise = prisma.folder.findMany({
61
- where: { workspaceId },
62
- select: { id: true, name: true },
63
- orderBy: { name: "asc" },
64
- });
65
-
66
- const [tasks, filterNote, folders] = await Promise.all([
67
- prisma.task.findMany({
68
- where: {
69
- workspaceId,
70
- ...(filterNoteId && { noteId: filterNoteId }),
71
- ...(filterFolderId && { note: { folderId: filterFolderId } }),
72
- ...statusFilterCondition,
73
- ...assigneeFilterCondition,
74
- ...overdueFilterCondition,
75
- ...hasDueDateCondition,
76
- },
77
- orderBy: { createdAt: "desc" },
78
- include: {
79
- note: { select: { id: true, title: true, folder: { select: { id: true, name: true } } } },
80
- assignee: { select: { id: true, name: true, email: true, image: true } },
81
- },
82
- }),
83
- filterNoteId
84
- ? prisma.note.findFirst({ where: { id: filterNoteId, workspaceId }, select: { title: true } })
85
- : Promise.resolve(null),
86
- foldersPromise,
87
- ]);
88
-
89
- return (
90
- <div className="flex flex-1 flex-col overflow-hidden">
91
- <div className="flex items-center justify-between border-b border-zinc-800 px-3 md:px-6 py-3">
92
- <div className="flex items-center gap-2">
93
- <h1 className="text-sm font-semibold text-zinc-300">
94
- {filterNoteId || filterFolderId ? "Filtered tasks" : "All Tasks"}
95
- </h1>
96
- {filterNote && (
97
- <span className="rounded-full bg-zinc-800 px-2 py-0.5 text-xs text-zinc-400">
98
- {filterNote.title}
99
- </span>
100
- )}
101
- </div>
102
- <div className="flex items-center gap-2 text-xs text-zinc-500">
103
- <span>Sort:</span>
104
- {(() => {
105
- const params = new URLSearchParams();
106
- if (filterNoteId) params.set("note", filterNoteId);
107
- if (filterFolderId) params.set("folder", filterFolderId);
108
- const baseQuery = params.toString();
109
- return (
110
- <>
111
- <a href={`?${baseQuery ? baseQuery + "&" : ""}sort=status`}
112
- className={sort === "status" ? "text-zinc-300" : "hover:text-zinc-300"}>Status</a>
113
- <span>/</span>
114
- <a href={`?${baseQuery ? baseQuery + "&" : ""}sort=note`}
115
- className={sort === "note" ? "text-zinc-300" : "hover:text-zinc-300"}>Note</a>
116
- </>
117
- );
118
- })()}
119
- </div>
120
- </div>
121
-
122
- {/* Filter Bar - Tasks 2.4.1-2.4.5 */}
123
- <TaskListFilters
124
- folders={folders}
125
- filterFolderId={filterFolderId}
126
- statusFilter={statusFilter}
127
- assigneeFilter={assigneeFilter}
128
- overdueOnly={overdueOnly}
129
- hasDueDateFilter={hasDueDateFilter}
130
- />
131
-
132
- <div className="flex-1 overflow-y-auto">
133
- <TaskList tasks={tasks} groupBy={sort === "note" ? "note" : "status"} />
134
- </div>
135
- </div>
136
- );
137
- }
@@ -1,18 +0,0 @@
1
- import Image from "next/image";
2
-
3
- export default function Loading() {
4
- return (
5
- <div className="flex flex-1 flex-col items-center justify-center gap-4 px-6">
6
- <Image
7
- src="/knotpad_logo_full.svg"
8
- alt="Knotpad"
9
- width={200}
10
- height={60}
11
- className="h-16 w-auto"
12
- priority
13
- />
14
- <p className="text-sm text-zinc-500">Welcome to Knotpad</p>
15
- <div className="h-6 w-6 animate-spin rounded-full border-2 border-zinc-700 border-t-zinc-400" />
16
- </div>
17
- );
18
- }
@@ -1,84 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect, notFound } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { decryptContent } from "@/lib/note-crypto";
5
- import { getActiveWorkspaceId } from "@/lib/workspace";
6
- import { NoteEditor } from "@/components/notes/note-editor";
7
-
8
- export default async function NotePage({
9
- params,
10
- searchParams,
11
- }: {
12
- params: Promise<{ noteId: string }>;
13
- searchParams: Promise<{ q?: string }>;
14
- }) {
15
- const session = await auth();
16
- if (!session) redirect("/login");
17
-
18
- const { noteId } = await params;
19
- const { q: highlight } = await searchParams;
20
-
21
- const activeWs = await getActiveWorkspaceId(session.user.id);
22
- const member = await prisma.workspaceMember.findFirst({
23
- where: { userId: session.user.id, workspaceId: activeWs ?? undefined },
24
- include: {
25
- workspace: {
26
- include: {
27
- members: {
28
- include: { user: { select: { id: true, name: true, email: true } } },
29
- },
30
- },
31
- },
32
- },
33
- });
34
- if (!member) redirect("/login");
35
-
36
- const [note, allNotes, allTasks] = await Promise.all([
37
- prisma.note.findFirst({
38
- where: { id: noteId, workspaceId: member.workspaceId },
39
- include: {
40
- folder: { select: { id: true, name: true } },
41
- tasks: {
42
- orderBy: { createdAt: "asc" },
43
- include: { assignee: { select: { id: true, name: true, email: true } } },
44
- },
45
- },
46
- }),
47
- prisma.note.findMany({
48
- where: { workspaceId: member.workspaceId },
49
- orderBy: { updatedAt: "desc" },
50
- select: { id: true, title: true },
51
- }),
52
- prisma.task.findMany({
53
- where: { workspaceId: member.workspaceId, status: { not: "DONE" } },
54
- orderBy: { createdAt: "desc" },
55
- take: 200,
56
- select: { id: true, title: true, note: { select: { title: true } } },
57
- }),
58
- ]);
59
-
60
- if (!note) notFound();
61
-
62
- const decryptedContent = await decryptContent(note.content, member.workspaceId);
63
-
64
- const members = member.workspace.members
65
- .map((m) => ({
66
- handle: `@${(m.user.name ?? m.user.email ?? "user").toLowerCase().replace(/\s+/g, "")}`,
67
- label: (m.user.name ?? m.user.email ?? "Unknown") + (m.userId === session.user.id ? " (you)" : ""),
68
- isAgent: false,
69
- }));
70
-
71
- const notesList = allNotes
72
- .filter((n) => n.id !== noteId)
73
- .map((n) => ({ id: n.id, title: n.title }));
74
-
75
- const tasksList = allTasks.map((t) => ({
76
- id: t.id,
77
- title: t.title,
78
- noteTitle: t.note.title,
79
- }));
80
-
81
- // key by note id so the editor fully remounts (fresh state + flush of the
82
- // previous note's pending save) when navigating between notes via tabs/links.
83
- return <NoteEditor key={note.id} note={{ ...note, content: decryptedContent }} members={members} notesList={notesList} tasksList={tasksList} highlight={highlight} />;
84
- }
@@ -1,30 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { getActiveWorkspaceId } from "@/lib/workspace";
5
- import { NoteTabs } from "@/components/notes/note-tabs";
6
-
7
- /**
8
- * Wraps the notes routes with a browser-style tab strip. Only mounts on
9
- * /notes/* so the board/list/calendar views stay tab-free.
10
- */
11
- export default async function NotesLayout({ children }: { children: React.ReactNode }) {
12
- const session = await auth();
13
- if (!session) redirect("/login");
14
-
15
- const workspaceId = await getActiveWorkspaceId(session.user.id);
16
- const notes = workspaceId
17
- ? await prisma.note.findMany({
18
- where: { workspaceId },
19
- orderBy: { updatedAt: "desc" },
20
- select: { id: true, title: true },
21
- })
22
- : [];
23
-
24
- return (
25
- <div className="flex flex-1 flex-col overflow-hidden">
26
- <NoteTabs notes={notes} />
27
- <div className="flex flex-1 flex-col overflow-hidden">{children}</div>
28
- </div>
29
- );
30
- }
@@ -1,39 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { getActiveWorkspaceId } from "@/lib/workspace";
5
-
6
- export default async function NotesHome() {
7
- const session = await auth();
8
- if (!session) redirect("/login");
9
-
10
- const workspaceId = await getActiveWorkspaceId(session.user.id);
11
- const latestNote = workspaceId
12
- ? await prisma.note.findFirst({
13
- where: { workspaceId },
14
- orderBy: { updatedAt: "desc" },
15
- select: { id: true },
16
- })
17
- : null;
18
- if (latestNote) redirect(`/notes/${latestNote.id}`);
19
-
20
- return (
21
- <div className="flex flex-1 flex-col items-center justify-center gap-4 text-center px-6">
22
- <div className="max-w-xs space-y-2">
23
- <h2 className="text-lg font-semibold text-zinc-300">No notes yet</h2>
24
- <p className="text-sm text-zinc-500">
25
- Click the{" "}
26
- <span className="font-mono text-xs bg-zinc-800 px-1.5 py-0.5 rounded text-zinc-400">
27
- +
28
- </span>{" "}
29
- button in the panel to create your first note.
30
- </p>
31
- <p className="text-xs text-zinc-600 mt-3">
32
- Write{" "}
33
- <code className="bg-zinc-800 px-1 rounded text-zinc-400">- [ ] task @you</code>{" "}
34
- inside a note to create tasks automatically.
35
- </p>
36
- </div>
37
- </div>
38
- );
39
- }
@@ -1,5 +0,0 @@
1
- import { redirect } from "next/navigation";
2
-
3
- export default function AppHome() {
4
- redirect("/notes");
5
- }
@@ -1,59 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { AgentTokenSettings } from "@/components/settings/agent-token-settings";
5
- import { getMcpUrl } from "@/lib/mcp-url";
6
-
7
- export default async function AgentTokenPage() {
8
- const session = await auth();
9
- if (!session) redirect("/login");
10
-
11
- const memberships = await prisma.workspaceMember.findMany({
12
- where: { userId: session.user.id, revokedAt: null },
13
- include: { workspace: true },
14
- orderBy: { joinedAt: "asc" },
15
- });
16
- if (!memberships.length) redirect("/login");
17
-
18
- const tokens = await prisma.mcpToken.findMany({
19
- where: {
20
- userId: session.user.id,
21
- workspaceId: { in: memberships.map((m) => m.workspaceId) },
22
- revokedAt: null,
23
- },
24
- });
25
-
26
- const tokenByWorkspace = Object.fromEntries(tokens.map((t) => [t.workspaceId, t]));
27
-
28
- const workspaces = memberships.map((m) => {
29
- const t = tokenByWorkspace[m.workspaceId] ?? null;
30
- return {
31
- id: m.workspaceId,
32
- name: m.workspace.name,
33
- slug: m.workspace.slug,
34
- existingToken: t
35
- ? { id: t.id, alias: t.alias, lastUsed: t.lastUsed?.toISOString() ?? null }
36
- : null,
37
- };
38
- });
39
-
40
- const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
41
- const mcpUrl = getMcpUrl(appUrl, process.env.NEXT_PUBLIC_MCP_URL);
42
-
43
- return (
44
- <div className="max-w-2xl space-y-6">
45
- <div>
46
- <h2 className="text-base font-semibold text-zinc-200">Agent Token</h2>
47
- <p className="text-sm text-zinc-500 mt-1">
48
- Generate a personal MCP token, then reuse the setup templates below whenever you reconnect an AI agent.
49
- </p>
50
- </div>
51
- <AgentTokenSettings
52
- workspaces={workspaces}
53
- mcpUrl={mcpUrl}
54
- appUrl={appUrl}
55
- userId={session.user.id}
56
- />
57
- </div>
58
- );
59
- }
@@ -1,49 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { BackupRestoreSettings } from "@/components/settings/backup-restore-settings";
5
-
6
- export default async function BackupPage() {
7
- const session = await auth();
8
- if (!session) redirect("/login");
9
-
10
- const member = await prisma.workspaceMember.findFirst({
11
- where: { userId: session.user.id, revokedAt: null },
12
- include: {
13
- workspace: {
14
- include: {
15
- backupSettings: true,
16
- },
17
- },
18
- },
19
- });
20
- if (!member) redirect("/login");
21
-
22
- const initialSettings = member.workspace.backupSettings
23
- ? {
24
- scheduleEnabled: member.workspace.backupSettings.scheduleEnabled,
25
- scheduleCadence: member.workspace.backupSettings.scheduleCadence,
26
- destinationPath: member.workspace.backupSettings.destinationPath,
27
- includeMarkdownZip: member.workspace.backupSettings.includeMarkdownZip,
28
- lastBackupAt: member.workspace.backupSettings.lastBackupAt?.toISOString() ?? null,
29
- lastBackupStatus: member.workspace.backupSettings.lastBackupStatus,
30
- lastBackupError: member.workspace.backupSettings.lastBackupError,
31
- }
32
- : null;
33
-
34
- return (
35
- <div className="max-w-2xl space-y-6">
36
- <div>
37
- <h2 className="text-base font-semibold text-zinc-200">Backup & Restore</h2>
38
- <p className="mt-1 text-sm text-zinc-500">
39
- Configure local backups, download exports, and restore from a backup file.
40
- </p>
41
- </div>
42
- <BackupRestoreSettings
43
- isCloudWorkspace={member.workspace.isCloud}
44
- isOwner={member.role !== "MEMBER"}
45
- initialSettings={initialSettings}
46
- />
47
- </div>
48
- );
49
- }
@@ -1,53 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { BillingDashboard } from "@/components/billing/billing-dashboard";
5
-
6
- export default async function BillingPage() {
7
- const session = await auth();
8
- if (!session) redirect("/login");
9
-
10
- const member = await prisma.workspaceMember.findFirst({
11
- where: { userId: session.user.id, revokedAt: null },
12
- include: {
13
- workspace: {
14
- include: { _count: { select: { members: { where: { revokedAt: null } } } } },
15
- },
16
- },
17
- orderBy: { joinedAt: "asc" },
18
- });
19
- if (!member) redirect("/login");
20
-
21
- const { workspace } = member;
22
-
23
- // A guest (local, no-password account) must create a real account before they
24
- // can pay — surfaced as a claim step in the dashboard.
25
- const me = await prisma.user.findUnique({
26
- where: { id: session.user.id },
27
- select: { email: true, passwordHash: true },
28
- });
29
- const isGuest = !!me?.email?.endsWith("@local.brief") && !me?.passwordHash;
30
-
31
- return (
32
- <div className="max-w-lg space-y-6">
33
- <div>
34
- <h2 className="text-base font-semibold text-zinc-200">Billing</h2>
35
- <p className="text-sm text-zinc-500 mt-1">
36
- Manage your subscription and seats.
37
- </p>
38
- </div>
39
- <BillingDashboard
40
- isPro={workspace.isPro}
41
- isOwner={member.role === "OWNER"}
42
- memberCount={workspace._count.members}
43
- seatCount={workspace.seatCount}
44
- stripeId={workspace.stripeId}
45
- planType={workspace.planType}
46
- licenseType={workspace.licenseType}
47
- workspaceType={workspace.type}
48
- workspaceId={workspace.id}
49
- isGuest={isGuest}
50
- />
51
- </div>
52
- );
53
- }
@@ -1,41 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { CalendarFeedsSettings } from "@/components/settings/calendar-feeds-settings";
5
- import { CalendarGeneralSettings } from "@/components/settings/calendar-general-settings";
6
-
7
- export default async function CalendarSettingsPage() {
8
- const session = await auth();
9
- if (!session) redirect("/login");
10
-
11
- const feeds = await prisma.calendarFeed.findMany({
12
- where: { userId: session.user.id },
13
- orderBy: { createdAt: "asc" },
14
- select: { id: true, label: true, color: true, enabled: true, lastFetchedAt: true, lastError: true },
15
- });
16
-
17
- return (
18
- <div className="max-w-lg space-y-8">
19
- <div>
20
- <h2 className="text-base font-semibold text-zinc-200">Calendars</h2>
21
- <p className="text-sm text-zinc-500 mt-1">
22
- Connect read-only calendar subscriptions (Google, Apple, etc.) to see your busy times
23
- on the Knotpad calendar. This is import-only — Knotpad never edits these calendars.
24
- </p>
25
- </div>
26
-
27
- <CalendarGeneralSettings />
28
-
29
- <CalendarFeedsSettings
30
- feeds={feeds.map((f) => ({
31
- id: f.id,
32
- label: f.label,
33
- color: f.color,
34
- enabled: f.enabled,
35
- lastFetchedAt: f.lastFetchedAt?.toISOString() ?? null,
36
- lastError: f.lastError,
37
- }))}
38
- />
39
- </div>
40
- );
41
- }