Revision 3 of 16

Condenses the whole document: one exact stack list, a five-row routes table, and the metadata, alias, login and accessibility rules cut to a paragraph each.

11.3 KB

+121 −725 lines of text vs revision 2 (this site's diff)

The text of revision 3 against revision 2: + marks an added line, − a removed one, with three unchanged lines of context around each change; old and new line numbers on the left where there is room.
Old New Change Line
@@ -1,821 +1,217 @@
11# Build The Urlist
22
3Build the complete application described below in this repository.
4
5Work autonomously from start to finish. Make an internal plan, implement all features, add tests, run validation, fix defects, and continue until the application is complete. If a small implementation detail is not specified, choose a reasonable solution and document it. Do not stop after scaffolding or partial implementation.
3+Build the complete application in this repository. Work autonomously from start to finish: plan internally, implement all requirements, test them, fix defects, and stop only when the app is complete.
64
7Do not access or copy an existing implementation of The Urlist.
5+If a minor detail is not specified, choose a reasonable solution and document it. Do not copy an existing implementation of The Urlist.
86
9## Required design skill
7+## Stack and design
108
11Before you design or implement the user interface, invoke the `/postrboard` skill and follow its instructions.
9+Use:
1210
13Use the Postrboard design language across the complete application. Its behavioral rules, tokens, components, layout guidance, accessibility rules, and self-review requirements are mandatory.
11+- Next.js App Router, React, strict TypeScript, Node.js, and npm
12+- SQLite with direct parameterized SQL through `better-sqlite3`; no ORM
13+- Vitest and Playwright
14+- Current stable package versions and a committed `package-lock.json`
1415
15The result must feel specific to The Urlist:
16+Use the Node.js runtime, not Edge, for SQLite, sessions, file access, and metadata fetching.
1617
17- Treat list creation and editing as the primary work surface.
18- Keep the design calm, code-native, restrained, and content-led.
19- Do not use generic SaaS sections, decorative cards, fake metrics, invented social proof, gradient blobs, emoji, or icon grids.
20- Use Postrboard classes and tokens before custom CSS.
21- Use one deliberate accent per surface and keep semantic status colors meaningful.
22- Use concise labels that describe actual objects, actions, states, and consequences.
23- Run the Postrboard self-review scan before declaring the interface complete.
18+Before UI work, invoke `/postrboard` and follow it across the app. Make list creation and editing the primary work surface. Use Postrboard classes and tokens before custom CSS. Avoid generic SaaS sections, decorative cards, fake data, emoji, icon grids, and gradient blobs. Run the Postrboard self-review before completion.
2419
2520## Product
2621
27The Urlist lets people create ordered lists of web links and publish each list at one short public URL.
22+The Urlist lets people create ordered lists of web links and publish each list at a public alias.
2823
29Use this main copy:
24+Use this home-page copy:
3025
3126> Group links, save and share them with the world.
3227
3328> Add links to a list and share it with one simple URL.
3429
35A person can start a list without an account.
36
37- A signed-out person can publish an anonymous list. After publication, the list is permanent and cannot be edited, deleted, or claimed.
38- A signed-in person publishes an account-owned list. Its owner can edit it, soft-delete it, and restore it.
39- Anyone can view an active list without signing in.
40
41## Required technology
42
43Use:
44
45- Next.js with the App Router
46- React
47- TypeScript with strict mode
48- Node.js
49- npm
50- SQLite
51- Direct parameterized SQL through `better-sqlite3`
52- Vitest for unit and integration tests
53- Playwright for browser tests
54
55Do not use:
56
57- An ORM
58- A real OAuth provider
59- A cloud database
60- A required paid service
61- Static metadata fixtures in place of live metadata fetching
62
63Use current stable, non-prerelease package versions. Commit `package-lock.json`.
64
65Code that uses SQLite, server sessions, the file system, or metadata fetching must run in the Node.js runtime, not the Edge runtime.
66
67## Required routes
68
69| Route | Access | Behavior |
70|---|---|---|
71| `/` | Public | Home page and first-link entry |
72| `/s/new` | Public | Create or resume the one local draft |
73| `/s/edit/{alias}` | Owner only | Load and edit an account-owned list; direct navigation and reload must work |
74| `/s/mylists` | Signed in | Show the current user's active and deleted lists |
75| `/{alias}` | Public | Show an active list, deleted tombstone, or never-used alias state |
76
77Aliases can contain multiple path segments. For example:
78
79```text
80/burke
81/burke/links
82/teams/web/resources
83```
84
85Use App Router catch-all segments where needed so both public and edit routes accept nested aliases.
86
87Static application routes must take priority over public alias routing.
88
89Reserve these first path segments:
90
91- `s`
92- `api`
93- `auth`
94- `__test`
95- `.well-known`
96
97Document any additional reserved paths.
98
99## Global navigation
100
101Provide:
102
103- The Urlist home link
104- New
105- My Lists when signed in
106- Light, Dark, and System theme choices
107- Login when signed out
108- Current user identity and Log out when signed in
109
110At mobile widths, these controls can use a labeled menu.
111
112Selecting New must not silently erase a non-empty draft. Ask for confirmation first.
113
114## Home page
115
116The home page must contain:
117
118- The product name and core copy
119- A field for the first link URL
120- An Add link action
121- Clear validation feedback
122
123When the first valid link is added:
124
1251. Add it to the current draft.
1262. Navigate to `/s/new`.
1273. Start metadata enrichment.
128
129If a draft already exists, add the link to that draft. Do not clear existing content.
130
131## Drafts
132
133Keep one unpublished draft per browser profile.
134
135Store the draft in browser-local storage. It must not depend on the current server login session. Server metadata responses update the browser-local draft.
136
137The draft must:
138
139- Work for signed-out and signed-in users
140- Survive reload and navigation
141- Store list description, requested alias, links, order, edited metadata, and completed metadata states
142- Remain when the user logs in or out
143- Clear after successful publication
144
145Do not clear a non-empty draft without confirmation.
30+Ownership is set at publication:
14631
147Ownership is decided when the list is published:
32+- Signed out: the list is anonymous, permanent, immutable, non-deletable, and never claimable.
33+- Signed in: the list belongs to the current user, who can edit, soft-delete, and restore it.
14834
149- Signed out: anonymous
150- Signed in: owned by the current mock user
35+Anyone can view an active list.
15136
152## Link URL rules
37+## Routes
15338
154A link destination must be an absolute HTTP or HTTPS URL.
39+| Route | Behavior |
40+|---|---|
41+| `/` | Home page and first-link entry |
42+| `/s/new` | Create or resume the one local draft |
43+| `/s/edit/{alias}` | Owner-only, reloadable edit page |
44+| `/s/mylists` | Current user's active and deleted lists |
45+| `/{alias}` | Active public list, deleted tombstone, or unused-alias state |
15546
156Reject:
47+Aliases can have multiple segments. Use catch-all App Router routes where needed. Static app routes take priority.
15748
158- Empty values
159- Relative URLs
160- Schemes other than HTTP and HTTPS
161- Invalid or missing hosts
162- URLs with embedded credentials
163- Values that are not one valid absolute URL
49+Reserve first segments `s`, `api`, `auth`, `__test`, and `.well-known`.
16450
165You may add `https://` to a domain-like value without a scheme. If you do, show the normalized URL before publication.
51+Global navigation must include Home, New, My Lists when signed in, Login or the current user with Log out, and Light/Dark/System theme controls. Confirm before New clears a non-empty draft.
16652
167Show validation errors next to the URL field and associate them with the field.
53+## Draft and editor
16854
169After a link is added, its destination URL is immutable. Show it as readable text. To change it, the author must delete the link and add a new one.
55+Keep one draft per browser profile in browser-local storage. It must survive reload, navigation, login, and logout. Clear it only after publication or confirmed discard.
17056
171## Links
57+The home page accepts the first URL, adds it to the current draft, starts metadata enrichment, and opens `/s/new`.
17258
17359A list:
17460
175- Must contain at least one link before publication or save
176- Has no product-level maximum number of links
177- Can contain duplicate destination URLs
178- Keeps each duplicate as an independent entry
179- Preserves link order
180
181For each link, store:
182
183- Stable ID
184- Destination URL
185- Title
186- Description
187- Image URL
188- Position
189- Metadata status
190
191Authors can:
192
193- Add links
194- Delete links
195- Edit title
196- Edit description
197- Reorder links with drag-and-drop
198
199Authors cannot directly edit:
200
201- Destination URL
202- Image URL
203
204Drag-and-drop is the primary reordering interaction.
205
206Requirements:
207
208- Each editable link has a visible drag handle.
209- A pointer user can drag a link to any position in the list.
210- The interface shows clear picked-up, dragging, valid-drop-position, and dropped states.
211- The list updates immediately after a drop.
212- The new order persists in the browser-local draft.
213- Saving an account-owned list persists the new order in SQLite.
214- The public list displays the saved order.
215- Reordering works at desktop and mobile widths.
216- Touch input is supported where the browser provides it.
217
218Also provide named Move up and Move down controls so reordering works with a keyboard and assistive technology. Drag-and-drop alone is not sufficient.
219
220## Live metadata enrichment
221
222After a link is added, fetch and parse the live destination page on the server.
223
224Each link must show one metadata state:
225
226- Pending
227- Succeeded
228- Failed
229
230The network operation must stop no later than 10 seconds after it starts. Show Succeeded or Failed no later than one additional second after the deadline.
231
232Publication and save must wait while any link is Pending.
233
234If enrichment fails:
235
236- Keep the link
237- Show a clear failure state
238- Let the author edit title and description
239- Allow publication or save after the request reaches Failed
240
241Use this metadata precedence:
242
243| Field | First | Second | Fallback |
244|---|---|---|---|
245| Title | `og:title` | Twitter Card title | HTML `<title>`, then suitable `<h1>` |
246| Description | `og:description` | Twitter Card description | HTML meta description |
247| Image | `og:image` | Twitter Card image | Declared page icon, then `/favicon.ico` |
248
249Support Twitter Card tags that use `name` or `property`.
250
251Resolve relative images and icons against the final document URL after redirects.
252
253An HTML page with no useful metadata is still a successful enrichment. Title, description, and image are optional.
254
255Show a neutral placeholder if there is no usable image.
256
257If an author edits an enriched title or description, preserve the manual value.
258
259## Metadata-fetch security
260
261The server-side metadata fetcher must protect internal networks.
262
263It must:
264
265- Allow only HTTP and HTTPS
266- Reject embedded credentials
267- Reject loopback addresses
268- Reject private network ranges
269- Reject link-local addresses
270- Reject multicast, unspecified, and reserved ranges
271- Reject cloud instance metadata addresses
272- Validate all resolved addresses before connecting
273- Validate the scheme, host, and resolved address after every redirect
274- Follow no more than five redirects
275- Protect against DNS rebinding
276- Stop the complete network operation after 10 seconds
277- Limit response bodies to 2 MiB or less
278- Parse only HTML or XHTML
279- Never forward application cookies, authorization headers, or user credentials
280
281Treat remote metadata as untrusted input.
282
283## Alias rules
284
285An alias contains one or more path segments separated by `/`.
286
287Normalize ASCII letters to lowercase before checking or storing the alias.
288
289Each segment:
290
291- Contains only lowercase letters `a-z`, digits `0-9`, and hyphens
292- Contains 1 to 50 characters
293- Cannot start or end with a hyphen
294
295The complete alias, including separators, cannot exceed 200 characters.
296
297Reject aliases with:
298
299- Empty segments
300- Leading or trailing `/`
301- Two adjacent `/` characters
302- `.` or `..` segments
303- Query strings or fragments
304- Backslashes
305- Encoded slashes or backslashes
306- Reserved first segments
307
308Use the exact complete normalized path for lookup. Prefix aliases are independent, so `burke` and `burke/links` can both exist.
309
310An alias is unavailable if it belongs to:
311
312- An active list
313- A deleted list
314- An anonymous list
315
316If the author leaves the alias blank, generate an available seven-character alias that uses lowercase letters and digits.
317
318Use a database uniqueness constraint as the final collision control. If two requests use the same normalized alias, exactly one can succeed. Never overwrite an existing list.
319
320An alias cannot change after publication.
321
322## Publication
323
324A list description is optional.
325
326Block publication or save when:
327
328- The list has no links
329- A metadata request is Pending
330- The alias is invalid
331- The alias is unavailable
332
333Repeat all validation on the server.
334
335If publication fails:
336
337- Show a clear recoverable error
338- Keep the draft
339- Do not show false success
340
341After successful publication:
342
343- Clear the draft
344- Navigate to the canonical public list path
345
346## Anonymous publication
347
348A signed-out person can publish.
349
350Before writing the list, show a blocking confirmation with this meaning:
351
352> You are not signed in. After publication, you cannot edit or delete this list. Its alias stays reserved permanently.
353
354Use clear actions:
355
356- Publish permanently
357- Cancel
358
359Cancel must return to the unchanged draft.
360
361After confirmation, the anonymous list:
362
363- Has no account owner
364- Is immutable
365- Cannot be deleted through product interfaces or normal product APIs
366- Cannot be claimed after login
367- Never appears in My Lists
368- Keeps its alias reserved permanently
369
370Enforce these rules on the server.
371
372## Mock GitHub login
373
374Provide a local mock GitHub provider. Do not call GitHub and do not implement OAuth.
375
376Provide two fictional users with stable, distinct:
377
378- User IDs
379- Display names
380- Avatars or placeholders
61+- Requires at least one link to publish or save
62+- Has no product-level link limit
63+- Allows duplicate destination URLs as separate entries
64+- Has an optional description and alias
38165
382Show user A first and user B second. Each control must have the accessible name:
66+For each link, store a stable ID, immutable destination URL, optional title, optional description, optional enrichment-controlled image, and position.
38367
384```text
385Continue as {display name}
386```
68+Authors can add and delete links and edit titles and descriptions. They cannot edit an existing destination or image URL.
38769
388Login must:
70+Accept only absolute HTTP or HTTPS destinations with valid hosts and no embedded credentials. You may add `https://` to domain-like input, but show the normalized URL. Show field-associated validation errors.
38971
390- Create a server-verifiable session
391- Survive reload
392- Use an HTTP-only cookie with an appropriate SameSite policy or an equivalent safe mechanism
393- Use the Secure cookie flag in production over HTTPS; local HTTP development and tests must still work
72+### Reordering
39473
395Logout must end the session.
74+Drag-and-drop is the primary reorder interaction.
39675
397Use the stable user ID for ownership checks. Never trust a client-supplied owner ID.
76+- Show a visible drag handle on each editable link.
77+- Support pointer and touch dragging to any position.
78+- Show clear picked-up, dragging, drop-position, and dropped states.
79+- Update draft order immediately.
80+- Save account-owned order to SQLite.
81+- Show saved order on the public page.
82+- Support desktop and mobile widths.
39883
399Keep authentication behind a clear boundary so a real provider could replace it later without changing ownership rules.
84+Also provide accessible Move up and Move down controls. Drag-and-drop alone is not sufficient.
40085
401## Account-owned lists
86+## Live metadata
40287
403A list published while signed in belongs to the current user.
88+Fetch and parse the live destination page on the server after a link is added. Show Pending, Succeeded, or Failed per link. Block publish and save while any link is Pending.
40489
405Only its owner can:
90+Stop the network operation after 10 seconds and show a terminal state within one more second. Failure keeps the link and permits manual title/description editing and publication.
40691
407- Open its edit page
408- Save changes
409- Delete it
410- Restore it
92+Metadata precedence:
41193
412Enforce ownership on the server for every mutation. Hiding controls is not sufficient.
94+1. Open Graph
95+2. Twitter Card (`name` or `property`)
96+3. HTML title/meta description/page icon, with `<h1>` and `/favicon.ico` as final fallbacks
41397
414The edit page must:
98+Resolve relative images against the final URL after redirects. Empty metadata is a successful result. Preserve manual title and description edits. Show a neutral image placeholder when needed.
41599
416- Load the list from persistent storage
417- Work after direct navigation and reload
418- Show alias as read-only
419- Allow changes to list description
420- Allow adding and deleting links
421- Allow changing link order
422- Allow editing link titles and descriptions
423- Provide Save
424- Provide Delete list
100+The fetcher must allow only HTTP/HTTPS; reject credentials and non-public addresses, including loopback, private, link-local, reserved, and cloud metadata ranges; revalidate DNS and every redirect; prevent DNS rebinding; follow at most five redirects; limit content to 2 MiB; accept only HTML/XHTML; and never forward app credentials.
425101
426If save is asynchronous, show Saving, Saved, and Failed states. Preserve unsaved input after failure.
102+## Aliases and publication
427103
428The alias and existing destination URLs cannot change.
104+Normalize aliases to lowercase. An alias has one or more `/`-separated segments. Each segment:
429105
430## My Lists
106+- Uses only `a-z`, `0-9`, and hyphens
107+- Is 1–50 characters
108+- Does not start or end with a hyphen
431109
432`/s/mylists` requires login.
110+The full alias is at most 200 characters. Reject empty, `.`/`..`, reserved, query/fragment, backslash, and encoded-separator forms.
433111
434Show only lists owned by the current user.
112+Match the exact full path, so `burke` and `burke/links` can both exist. Active, deleted, and anonymous aliases are all reserved. Enforce uniqueness in SQLite.
435113
436Provide:
114+If blank, generate an available seven-character lowercase alphanumeric alias. Aliases never change after publication.
437115
438- Create new list
439- Active lists section
440- Deleted lists section
441- Explicit empty states
116+Block publish/save when the list is empty, metadata is Pending, or the alias is invalid/unavailable. Repeat validation on the server. On failure, keep all user input.
442117
443Each active list summary must show:
118+For anonymous publication, require a blocking confirmation that says the list cannot be edited or deleted and its alias stays reserved permanently. Use **Publish permanently** and **Cancel**. Enforce immutability on the server. Anonymous lists never appear in My Lists.
444119
445- Alias
446- Description when present
447- Link count
120+## Mock login and ownership
448121
449Selecting an active list opens its edit route.
122+Provide a local mock GitHub provider with two stable fictional users. Show user A first and user B second with controls named `Continue as {display name}`. Do not call GitHub.
450123
451Each deleted list summary must provide Restore.
124+Use a server-verifiable HTTP-only session cookie with an appropriate SameSite policy. Use Secure in production HTTPS while allowing local HTTP development. Login survives reload; logout ends the session. Use stable user IDs, not display names, for ownership.
452125
453## Delete, tombstone, and restore
126+Only an owner can load, save, delete, or restore an account list. Enforce every check on the server and ignore client-supplied owner IDs.
454127
455Delete is a soft delete.
128+The edit page loads persistent data directly, keeps alias and destinations read-only, and permits description, membership, order, title, and description changes. Show save progress and errors without losing input.
456129
457Before deletion, show a confirmation with clear Delete list and Cancel actions.
130+My Lists requires login and shows only the current user's lists in separate Active and Deleted sections. Active cards show alias, optional description, and link count. Provide Create new list, Restore, and clear empty/loading/error states.
458131
459After deletion:
132+## Delete and restore
460133
461- Keep all list content and ownership
462- Keep the alias reserved
463- Move the list to Deleted lists
464- Show a public tombstone at the old path
134+Delete requires confirmation and performs a soft delete. Keep content, ownership, and alias forever. There is no permanent-delete action.
465135
466The tombstone must show only:
136+The public tombstone shows only:
467137
468138> This list was deleted.
469139
470Do not show the former description, links, owner, or deletion reason.
471
472If the signed-in visitor is the owner, also show Restore.
473
474Other visitors and other signed-in users must not see Restore.
475
476Restoring must reactivate the same:
477
478- List
479- Alias
480- Content
481- Link order
482- Ownership
483
484There is no permanent-delete product action.
485
486## Public list page
487
488Anyone can view an active list without logging in.
489
490Use the list description as the heading. If it is blank, use the alias.
491
492Show links in saved order.
493
494Each public link card must show:
495
496- Image or neutral placeholder
497- Title, or destination hostname or URL when title is blank
498- Description when present
499- Destination URL
500
501Opening a destination must use safe new-context behavior such as `noopener` and `noreferrer`.
502
503Provide two views:
504
505- Links
506- QR code
507
508Expose the selected view to assistive technology.
509
510## Sharing
511
512Provide:
513
514- Copy link
515- X or Twitter share link
516- Facebook share link
517- LinkedIn share link
518
519Copy link must copy the canonical absolute public URL and announce success or failure.
520
521Platform share links must contain a correctly percent-encoded canonical URL. Include the encoded list heading where the platform supports it.
522
523User content must not inject additional share parameters.
524
525## QR code
526
527Generate a real scannable QR code for the canonical absolute public-list URL.
528
529Requirements:
530
531- A standard decoder recovers the exact URL
532- It works in light and dark themes
533- It has sufficient contrast
534- Adjacent text or an accessible alternative identifies the encoded URL
535
536QR download is not required.
537
538## Never-used aliases
539
540When a valid alias has never been used:
541
542- Show that the list was not found
543- Say that the alias is available
544- Provide Create a list with this alias
545
546The action must prefill the alias in the draft.
140+Only the signed-in owner also sees Restore. Restore reactivates the same alias, content, order, and ownership.
547141
548Do not silently overwrite a non-empty draft.
142+## Public list
549143
550For an invalid or reserved path, show a generic not-found or invalid-path state. Do not offer it as an available alias.
144+Use the description as the heading or the alias when blank. Show links in saved order.
551145
552## Themes
146+Each link card shows its image/placeholder, title or hostname/URL fallback, optional description, and destination. Open destinations safely in a new context with `noopener` and `noreferrer`.
553147
554148Provide:
555149
556- Light
557- Dark
558- System
559
560Persist the selection in the browser.
561
562System follows the operating system preference.
563
564Avoid a visible flash of the wrong theme when practical.
565
566Keep accessible contrast in every theme.
567
568## Responsive behavior
569
570Support current Chromium at desktop and mobile widths down to 320 CSS pixels.
571
572At mobile widths:
573
574- Navigation can collapse into a labeled menu
575- Alias, description, and primary action stack vertically
576- Link cards keep title, URL, status, and primary actions visible
577- My Lists uses one column or an appropriate reduced grid
578- Share and view controls remain available
579- Modals fit the viewport and can scroll internally
580
581Do not require page-level horizontal and vertical scrolling together.
582
583## Accessibility
584
585The implemented journeys must meet WCAG 2.2 AA.
586
587Requirements:
588
589- One descriptive level-one heading per page
590- Logical heading order
591- Accessible names for all controls
592- Labels and associated errors for form fields
593- Programmatic required and invalid states
594- Useful image alternatives
595- Full keyboard operation
596- Visible focus in every theme
597- Logical focus order
598- Keyboard link reordering
599- Status announcements for loading, metadata, save, publish, copy, and errors
600- Color is not the only way to show state
601- Usable at 200% zoom
602- Reflow at 320 CSS pixels
603- Respect `prefers-reduced-motion`
604
605A modal must:
606
607- Have a title and modal semantics
608- Receive focus when opened
609- Trap focus while open
610- Close on Escape when cancellation is allowed
611- Return focus to its trigger
612
613After delete or reorder, move focus to a logical nearby control.
614
615## State and error handling
616
617Represent:
618
619- Initial loading and fatal failure
620- Metadata Pending, Succeeded, and Failed
621- Alias checking, available, invalid, and unavailable
622- Publication blocked, publishing, success, and failure
623- Save progress and failure
624- My Lists loading, results, empty, and failure
625- Public loading, active, never used, deleted, and failure
626- Copy success and failure
627- Signed-out, identity selection, signed-in, and login failure
628
629Errors must explain what the user can do next.
630
631Do not turn failures into success-shaped fallbacks.
632
633Preserve user input after failed publish or save.
634
635Use live regions for status changes that need announcement.
636
637If Publish is disabled, provide a discoverable reason.
638
639## Data and persistence
640
641Use SQLite with direct parameterized SQL through `better-sqlite3`.
642
643Provide ordered SQL migrations or a repeatable versioned schema initializer.
644
645Enable SQLite foreign keys for every connection.
646
647Use transactions for:
648
649- Publication
650- Save
651- Delete
652- Restore
653- Reset
654
655At minimum, persist:
656
657### User
658
659- Stable user ID
660- Provider
661- Display name
662- Avatar
663
664### List
665
666- Stable list ID
667- Normalized immutable alias
668- Optional description
669- Anonymous or account ownership type
670- Owner user ID for account-owned lists
671- Active or deleted status
672- Created time
673- Updated time
674- Deleted time when deleted
675
676### Link
150+- Links and QR code views with accessible selected state
151+- A scannable QR code for the canonical absolute list URL
152+- Copy link with announced success/failure
153+- Correctly encoded X/Twitter, Facebook, and LinkedIn share links
677154
678- Stable link ID
679- Parent list ID
680- Immutable destination URL
681- Optional title
682- Optional description
683- Optional image URL
684- Position
155+For a valid unused alias, show Not found and a Create with this alias action. Prefill the draft without silently replacing existing content. Invalid/reserved paths get a generic not-found state.
685156
686Published and deleted data must survive a full application restart.
157+## Theme, responsive UI, and accessibility
687158
688Use an environment variable for the database path and provide a safe development default.
159+Provide persisted Light, Dark, and System themes. System follows OS preference. Keep accessible contrast and avoid an initial wrong-theme flash when practical.
689160
690Exclude database files from Git.
161+Support current Chromium from desktop down to 320 CSS pixels. Keep navigation, editor fields, drag controls, link actions, sharing, QR, lists, and modals usable without page-level two-dimensional scrolling.
691162
692## General security
163+Meet WCAG 2.2 AA:
693164
694The server must:
165+- Semantic headings, labels, names, errors, and status announcements
166+- Full keyboard use, visible focus, logical focus order, and keyboard reordering
167+- No color-only meaning; usable at 200% zoom; reduced-motion support
168+- Modal focus entry, trap, Escape cancellation, and focus return
169+- Logical focus after deletion or reordering
695170
696- Validate all input independently of the client
697- Enforce every ownership rule
698- Use parameterized SQL
699- Derive ownership from the authenticated session
700- Render user and remote text safely
701- Prevent stored and reflected script execution
702- Encode share parameters safely
703- Use CSRF protection appropriate for the mutation design
704- Avoid exposing stack traces, secrets, internal paths, or database details
171+Show truthful loading, empty, success, blocked, and error states. Errors must explain recovery. Never present failure as success.
705172
706Keep runtime database files, secrets, build output, and dependencies out of Git.
173+## Storage and security
707174
708## Test reset
175+Use versioned SQL migrations or an idempotent versioned initializer. Enable SQLite foreign keys. Use transactions for publish, save, delete, restore, and reset.
709176
710Provide a development/test-only way to:
177+Persist users, lists, ownership/state/timestamps, links, and positions. Published and deleted data must survive a complete restart. Configure the database path by environment variable with a safe local default; do not commit database files.
711178
712- Delete all published and deleted lists
713- Clear server sessions when practical
714- Restore the two stable mock users
715- Return to a deterministic clean state
716- Run repeatedly with the same result
179+Validate all untrusted input on the server, use parameterized SQL, escape user and remote content, prevent stored/reflected script execution, encode share parameters, apply suitable CSRF protection, and do not expose secrets or internals in errors.
717180
718Prefer:
181+Provide a development/test-only deterministic reset, preferably `POST /__test/reset` returning `204`. It clears lists and sessions and restores the two users. Disable or protect it in production.
719182
720```http
721POST /__test/reset
722```
183+## Scripts, tests, and documentation
723184
724with:
185+Provide npm scripts:
725186
726```http
727204 No Content
187+```text
188+dev
189+build
190+start
191+lint
192+typecheck
193+test
194+test:e2e
195+db:init
196+db:reset
728197```
729198
730Disable or strongly protect reset in production mode.
731
732## Required npm scripts
733
734Provide:
735
736| Script | Purpose |
737|---|---|
738| `dev` | Start development mode |
739| `build` | Create a production build |
740| `start` | Start the production build |
741| `lint` | Run linting |
742| `typecheck` | Run strict TypeScript checks |
743| `test` | Run Vitest once |
744| `test:e2e` | Run Playwright once |
745| `db:init` | Initialize or migrate SQLite |
746| `db:reset` | Safely reset development or test data |
747
748Scripts must return a nonzero status on failure. Test scripts must not start in watch mode.
749
750## Tests
751
752Use Vitest for:
753
754- URL validation
755- Alias normalization and validation
756- Metadata precedence and relative URLs
757- SSRF address policy
758- Ownership and mutation rules
759- SQLite behavior
760- Publication, delete, and restore transactions
761- Share URL construction
762
763Use Playwright for:
764
765- Anonymous draft and permanent publication
766- Login and logout with both users
767- Account publication
768- My Lists
769- Owner editing
770- Non-owner isolation
771- Delete, tombstone, and restore
772- Public Links and QR views
773- Theme persistence
774- Pointer drag-and-drop reordering and persisted public order
775- Keyboard operation
776- Mobile layout
777
778Tests must use a separate temporary SQLite database, reset their state, avoid order dependence, and clean up started processes.
199+Use Vitest for validation, aliases, metadata parsing/network policy, ownership, SQLite transactions, and share URLs.
779200
780## README
201+Use Playwright for anonymous and account publication, both users, My Lists, owner/non-owner access, drag-and-drop plus persisted order, keyboard reordering, delete/restore/tombstone, public/QR/share views, themes, accessibility basics, and mobile layout.
781202
782Document:
203+Tests use a separate temporary database, reset state, avoid order dependence, and clean up processes.
783204
784- Required Node.js version
785- Installation
786- Environment variables
787- Database initialization
788- Development startup
789- Production build and startup
790- Test commands
791- Mock login
792- Reset behavior
793- Database location
794- Live metadata behavior and security limits
795- Troubleshooting
796- Any assumptions you made
205+The README must cover prerequisites, setup, environment, database, development and production commands, mock login, reset, tests, metadata behavior, troubleshooting, and assumptions.
797206
798207## Completion
799208
800209Before you finish:
801210
8021. Install from the committed lock file.
8032. Initialize a clean database.
8043. Run `npm run lint`.
8054. Run `npm run typecheck`.
8065. Run `npm test`.
8076. Run `npm run test:e2e`.
8087. Run `npm run build`.
8098. Start the production build and confirm the application responds.
8109. Confirm that active, anonymous, and deleted data survives a complete restart.
81110. Review every section of this instruction against the implementation.
81211. Fix every defect or omission that you find.
81312. Repeat the affected validation.
814
815When complete, report:
211+1. Run `npm run lint`, `npm run typecheck`, `npm test`, `npm run test:e2e`, and `npm run build`.
212+2. Start the production build and confirm the app responds.
213+3. Confirm active, anonymous, and deleted data survives restart.
214+4. Review this entire instruction and fix every missing or incorrect requirement.
215+5. Repeat affected validation.
816216
817- What you implemented
818- Important assumptions
819- Database and architecture choices
820- Exact validation commands and results
821- Anything incomplete and the exact reason
217+Report what you built, assumptions, architecture, exact command results, and anything incomplete with its reason. Do not claim a check passed unless you ran it successfully.