-
-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathprd.json
More file actions
603 lines (603 loc) · 18 KB
/
Copy pathprd.json
File metadata and controls
603 lines (603 loc) · 18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
[
{
"category": "database",
"description": "Create SQLite database with better-auth schema",
"steps": [
"Create data/ directory and app.db SQLite file",
"Create user table with id, name, email, emailVerified, image, timestamps",
"Create session table with userId foreign key and token",
"Create account table for credential provider",
"Create verification table",
"Verify all tables exist with correct structure"
],
"passes": false
},
{
"category": "database",
"description": "Create notes table with indexes",
"steps": [
"Create notes table with id, user_id, title, content_json, is_public, public_slug, timestamps",
"Add FOREIGN KEY on user_id references user(id)",
"Add UNIQUE constraint on public_slug",
"Create indexes on user_id, public_slug, is_public",
"Verify table structure and indexes"
],
"passes": false
},
{
"category": "database",
"description": "Build database access utilities (lib/db.ts)",
"steps": [
"Create lib/db.ts with Bun SQLite connection",
"Implement singleton getDb() pattern",
"Create query<T>() utility with generic type safety",
"Create get<T>() for single row queries",
"Create run() for INSERT/UPDATE/DELETE",
"Test utilities with sample queries"
],
"passes": false
},
{
"category": "database",
"description": "Define Note type and create helper functions (lib/notes.ts)",
"steps": [
"Define Note type with camelCase fields",
"Create helper to map DB row to Note type",
"Create generatePublicSlug() using nanoid (16+ chars)",
"Create default empty TipTap JSON constant",
"Export all types and helpers"
],
"passes": false
},
{
"category": "database",
"description": "Implement createNote repository function",
"steps": [
"Create async createNote(userId, data) function",
"Generate unique ID for new note",
"Set default title 'Untitled note' if not provided",
"Set default empty TipTap JSON if contentJson not provided",
"INSERT with user_id scoping",
"Return created Note"
],
"passes": false
},
{
"category": "database",
"description": "Implement getNoteById and getNotesByUser functions",
"steps": [
"Create getNoteById(userId, noteId) with authorization check",
"Return null if note doesn't exist or user doesn't own it",
"Create getNotesByUser(userId) returning all user's notes",
"Order by updated_at DESC",
"Test authorization is enforced"
],
"passes": false
},
{
"category": "database",
"description": "Implement updateNote function",
"steps": [
"Create updateNote(userId, noteId, data) function",
"Accept partial updates for title and contentJson",
"Update updated_at timestamp automatically",
"Enforce user_id authorization",
"Return updated Note or null"
],
"passes": false
},
{
"category": "database",
"description": "Implement deleteNote function",
"steps": [
"Create deleteNote(userId, noteId) function",
"DELETE only if user owns the note",
"Return boolean indicating success",
"Test authorization check works"
],
"passes": false
},
{
"category": "database",
"description": "Implement note sharing functions",
"steps": [
"Create setNotePublic(userId, noteId, isPublic) function",
"Generate public_slug when enabling sharing",
"Clear public_slug when disabling",
"Create getNoteByPublicSlug(slug) for public access",
"Only return note if is_public = 1"
],
"passes": false
},
{
"category": "auth",
"description": "Configure better-auth server (lib/auth.ts)",
"steps": [
"Create lib/auth.ts with betterAuth configuration",
"Configure SQLite database adapter using Bun SQLite",
"Set up credential provider for email/password",
"Configure session management",
"Export auth instance"
],
"passes": false
},
{
"category": "auth",
"description": "Create better-auth API route",
"steps": [
"Create app/api/auth/[...all]/route.ts",
"Export GET and POST handlers from better-auth",
"Use auth.handler for both methods",
"Test auth endpoints respond at /api/auth/*"
],
"passes": false
},
{
"category": "auth",
"description": "Implement server-side auth helpers",
"steps": [
"Create getSession() helper for server components",
"Create requireAuth() helper that throws/redirects if not authenticated",
"Export helpers from lib/auth.ts",
"Test helpers return correct session data"
],
"passes": false
},
{
"category": "auth",
"description": "Create auth client for frontend",
"steps": [
"Create lib/auth-client.ts with createAuthClient()",
"Export signIn, signUp, signOut methods",
"Export useSession hook for client components",
"Test client can be imported without errors"
],
"passes": false
},
{
"category": "ui",
"description": "Build sign up form component",
"steps": [
"Create components/SignUpForm.tsx client component",
"Add name, email, password inputs with controlled state",
"Add client-side validation (email format, password length)",
"Integrate with auth client signUp method",
"Display error messages on failure",
"Handle success (will redirect in parent)"
],
"passes": false
},
{
"category": "ui",
"description": "Build login form component",
"steps": [
"Create components/LoginForm.tsx client component",
"Add email, password inputs with controlled state",
"Add client-side validation",
"Integrate with auth client signIn method",
"Display error messages on failure",
"Handle success (will redirect in parent)"
],
"passes": false
},
{
"category": "ui",
"description": "Create authentication page",
"steps": [
"Create app/authenticate/page.tsx",
"Include both SignUpForm and LoginForm components",
"Add toggle between sign up and login modes",
"Redirect to /dashboard after successful auth",
"Style page layout"
],
"passes": false
},
{
"category": "ui",
"description": "Add app header with auth state",
"steps": [
"Create components/Header.tsx server component",
"Check auth state with getSession()",
"Show app name/logo linking to home",
"Show Login/Sign up links when not authenticated",
"Show Dashboard link and Logout button when authenticated",
"Add to app/layout.tsx"
],
"passes": false
},
{
"category": "ui",
"description": "Implement logout functionality",
"steps": [
"Create components/LogoutButton.tsx client component",
"Call signOut from auth client on click",
"Redirect to / after logout",
"Add to Header component",
"Test logout clears session"
],
"passes": false
},
{
"category": "actions",
"description": "Create server action for creating notes",
"steps": [
"Create lib/actions/notes.ts with 'use server'",
"Implement createNoteAction() that calls repository",
"Get session and validate user is authenticated",
"Return created note or error",
"Use Zod for input validation if data provided"
],
"passes": false
},
{
"category": "actions",
"description": "Create server action for updating notes",
"steps": [
"Implement updateNoteAction(noteId, data) in lib/actions/notes.ts",
"Validate user owns the note",
"Use Zod schema for title/contentJson validation",
"Return updated note or error",
"Revalidate path after update"
],
"passes": false
},
{
"category": "actions",
"description": "Create server action for deleting notes",
"steps": [
"Implement deleteNoteAction(noteId) in lib/actions/notes.ts",
"Validate user owns the note",
"Delete and revalidate dashboard path",
"Return success/error result"
],
"passes": false
},
{
"category": "actions",
"description": "Create server action for toggling note sharing",
"steps": [
"Implement toggleShareAction(noteId, isPublic) in lib/actions/notes.ts",
"Validate user owns the note",
"Call setNotePublic from repository",
"Return updated note with publicSlug",
"Revalidate note page"
],
"passes": false
},
{
"category": "ui",
"description": "Build dashboard page with RSC data fetching",
"steps": [
"Create app/dashboard/page.tsx as server component",
"Use requireAuth() to protect and get session",
"Fetch notes with getNotesByUser() directly",
"Pass notes to client component for display",
"Handle empty state"
],
"passes": false
},
{
"category": "ui",
"description": "Create notes list component",
"steps": [
"Create components/NoteList.tsx",
"Display note title, updated date, public status for each note",
"Link each note to /notes/[id]",
"Style as card layout",
"Show helpful message when list is empty"
],
"passes": false
},
{
"category": "ui",
"description": "Add create note button to dashboard",
"steps": [
"Create components/CreateNoteButton.tsx client component",
"Call createNoteAction on click",
"Navigate to /notes/[id] after creation",
"Show loading state while creating",
"Add to dashboard page"
],
"passes": false
},
{
"category": "ui",
"description": "Create loading and error states for dashboard",
"steps": [
"Create app/dashboard/loading.tsx with skeleton UI",
"Create app/dashboard/error.tsx with error boundary",
"Add 'Try again' functionality",
"Style appropriately"
],
"passes": false
},
{
"category": "ui",
"description": "Build note editor page with RSC",
"steps": [
"Create app/notes/[id]/page.tsx as server component",
"Use requireAuth() and get session",
"Fetch note with getNoteById()",
"Return 404 if not found or not owned",
"Pass note data to editor component"
],
"passes": false
},
{
"category": "ui",
"description": "Create TipTap editor component",
"steps": [
"Create components/NoteEditor.tsx client component",
"Import useEditor and EditorContent from @tiptap/react",
"Import StarterKit, Code, CodeBlock extensions",
"Configure heading levels 1-3",
"Initialize with note's contentJson",
"Add onChange callback to track content changes"
],
"passes": false
},
{
"category": "ui",
"description": "Add title input to editor",
"steps": [
"Add controlled title input in NoteEditor",
"Initialize with note.title",
"Track title changes in state",
"Style input appropriately"
],
"passes": false
},
{
"category": "ui",
"description": "Build editor toolbar with formatting buttons",
"steps": [
"Create components/EditorToolbar.tsx component",
"Add Bold and Italic toggle buttons",
"Add H1, H2, H3 heading buttons",
"Add Paragraph button to reset formatting",
"Show active state for current formatting"
],
"passes": false
},
{
"category": "ui",
"description": "Add code and list buttons to toolbar",
"steps": [
"Add Inline code button",
"Add Code block button",
"Add Bullet list button",
"Add Horizontal rule button",
"Test all formatting options work"
],
"passes": false
},
{
"category": "ui",
"description": "Implement save functionality",
"steps": [
"Add Save button to editor that calls updateNoteAction",
"Implement debounced auto-save on changes (1-2 second delay)",
"Show save status indicator (saving/saved/error)",
"Don't trigger auto-save on initial load"
],
"passes": false
},
{
"category": "ui",
"description": "Add delete functionality to editor",
"steps": [
"Add Delete button to editor",
"Show confirmation dialog before delete",
"Call deleteNoteAction on confirm",
"Redirect to /dashboard after delete",
"Show loading state during deletion"
],
"passes": false
},
{
"category": "ui",
"description": "Add navigation and error handling to editor",
"steps": [
"Add 'Back to Dashboard' link",
"Handle 404 errors gracefully",
"Show toast notifications for save/delete results",
"Create loading.tsx for editor page"
],
"passes": false
},
{
"category": "ui",
"description": "Build share toggle component",
"steps": [
"Create components/ShareToggle.tsx client component",
"Add toggle switch for public/private",
"Call toggleShareAction on change",
"Display public URL when enabled",
"Add Copy URL button"
],
"passes": false
},
{
"category": "ui",
"description": "Add copy URL functionality",
"steps": [
"Implement clipboard copy with navigator.clipboard",
"Show 'Copied!' feedback for 2 seconds",
"Handle copy errors gracefully",
"Integrate into ShareToggle"
],
"passes": false
},
{
"category": "ui",
"description": "Build public note page with RSC",
"steps": [
"Create app/p/[slug]/page.tsx as server component",
"Fetch note with getNoteByPublicSlug()",
"Return 404 if not found or not public",
"No auth check needed (public route)",
"Pass note to viewer component"
],
"passes": false
},
{
"category": "ui",
"description": "Create public note viewer component",
"steps": [
"Create components/PublicNoteViewer.tsx",
"Display note title as heading",
"Render TipTap content with editable: false",
"Style for reading (typography, spacing)",
"No editing controls visible"
],
"passes": false
},
{
"category": "ui",
"description": "Style the landing page",
"steps": [
"Update app/page.tsx with better marketing copy",
"Add feature highlights",
"Improve CTA button styling",
"Ensure responsive layout"
],
"passes": false
},
{
"category": "ui",
"description": "Apply consistent styling and dark mode",
"steps": [
"Review all pages for consistent spacing/padding",
"Style editor with clean, minimal design",
"Add typography styles for note content",
"Test dark mode works across all pages",
"Fix any contrast issues"
],
"passes": false
},
{
"category": "ui",
"description": "Implement responsive design",
"steps": [
"Test all pages on mobile viewport (375px)",
"Test on tablet viewport (768px)",
"Adjust layouts with Tailwind responsive classes",
"Ensure touch targets are appropriate",
"Test navigation works on mobile"
],
"passes": false
},
{
"category": "testing",
"description": "Set up Vitest configuration",
"steps": [
"Create vitest.config.ts with jsdom environment",
"Configure path aliases matching tsconfig",
"Set test file patterns",
"Verify 'bun test' runs correctly"
],
"passes": false
},
{
"category": "testing",
"description": "Write unit tests for database utilities",
"steps": [
"Create __tests__/lib/db.test.ts",
"Test getDb() singleton pattern",
"Test query<T>() with sample data",
"Test get<T>() returns single row",
"Test run() executes mutations"
],
"passes": false
},
{
"category": "testing",
"description": "Write unit tests for notes repository",
"steps": [
"Create __tests__/lib/notes.test.ts",
"Test createNote() with defaults",
"Test getNoteById() authorization",
"Test updateNote() updates timestamp",
"Test deleteNote() authorization",
"Test setNotePublic() generates/clears slug"
],
"passes": false
},
{
"category": "testing",
"description": "Write unit tests for server actions",
"steps": [
"Create __tests__/actions/notes.test.ts",
"Mock auth session",
"Test createNoteAction creates note",
"Test updateNoteAction validates ownership",
"Test deleteNoteAction validates ownership",
"Test toggleShareAction updates correctly"
],
"passes": false
},
{
"category": "testing",
"description": "Set up Playwright for E2E testing",
"steps": [
"Install @playwright/test and browsers",
"Create playwright.config.ts with webServer config",
"Create e2e/ directory",
"Create e2e/helpers/auth.ts with signUp/login helpers",
"Create e2e/setup.ts for test database setup",
"Verify 'bun test:e2e' runs"
],
"passes": false
},
{
"category": "testing",
"description": "Write E2E tests for authentication",
"steps": [
"Create e2e/auth.spec.ts",
"Test user can sign up",
"Test user can log in",
"Test invalid credentials show error",
"Test redirect to dashboard after auth",
"Test logout works"
],
"passes": false
},
{
"category": "testing",
"description": "Write E2E tests for notes CRUD",
"steps": [
"Create e2e/notes.spec.ts",
"Test creating new note from dashboard",
"Test editing note title and content",
"Test formatting (bold, italic, headings)",
"Test auto-save works",
"Test delete with confirmation"
],
"passes": false
},
{
"category": "testing",
"description": "Write E2E tests for note sharing",
"steps": [
"Create e2e/sharing.spec.ts",
"Test toggling public sharing",
"Test public URL is displayed",
"Test anonymous access to public note",
"Test 404 for private/invalid slugs",
"Test copy URL works"
],
"passes": false
},
{
"category": "testing",
"description": "Write E2E tests for responsive and dark mode",
"steps": [
"Create e2e/responsive.spec.ts",
"Test mobile viewport layout",
"Test tablet viewport layout",
"Test dark mode styling",
"Verify all interactions work on mobile"
],
"passes": false
}
]