Revision 9 of 16
Makes structure part of the UX: the publish bar is a full-width band, link rows are cards of plain text edited in place, and My Lists tiles get exact anatomy.
+33 −17 lines of text vs revision 8 (this site's diff)
| Old | New | Change | Line |
|---|---|---|---|
| @@ -4,6 +4,8 @@ | |||
| 4 | 4 | ||
| 5 | 5 | If a minor detail is not specified, choose a reasonable solution and document it. Replicate the observable user experience of the reference app exactly: page structure, visible copy (button labels, errors, headings, tooltips, states), and interactions (what happens, in what order, on every action). Do **not** replicate the reference app's visual design, CSS framework, icon set, or color palette — restyle every screen using this repository's own Postrboard design system instead. Do not copy the reference app's implementation or framework. | |
| 6 | 6 | ||
| 7 | + | **Structure and interaction patterns are part of the UX, not the visual design.** Replicate them: which values are plain text edited in place versus real input boxes, what is a full-width band versus a card, which controls sit outside versus inside a card, and where each element sits. Restyle those structures with Postrboard; do not change them. | |
| 8 | + | ||
| 7 | 9 | Reference app (source of truth for UX behavior and copy only, not visuals): `the-urlist/blazor-static-web-apps` at https://github.com/the-urlist/blazor-static-web-apps — see `Client/Pages/*.razor` and `Client/Shared/*.razor` for exact copy, layout structure, and interaction behavior. Ignore `Client/wwwroot/css/app.css` and its Bulma/Font Awesome styling entirely. A live demo (if available) is linked from that repository's README. | |
| 8 | 10 | ||
| 9 | 11 | ## Technical specification and checklist | |
| @@ -67,7 +69,7 @@ | |||
| 67 | 69 | 3. `About` — external link `https://aka.ms/theurlist`, a "help/question" icon | |
| 68 | 70 | 4. `Terms` — link to `/s/terms`, an "info" icon | |
| 69 | 71 | ||
| 70 | − | Right: theme dropdown, then either the Login item (a "sign in" icon, label "Login", opens the login modal) or the signed-in user's avatar and name with a dropdown showing "Signed in with {provider}" and a "Log Out" item (a "sign out" icon). | |
| 72 | + | Right: the theme dropdown (an icon-only button showing the active theme's icon), then either the Login item (a "sign in" icon, label "Login", opens the login modal) or the signed-in user's avatar and name followed by a dropdown chevron; that dropdown shows "Signed in with {provider}" and a "Log Out" item (a "sign out" icon). | |
| 71 | 73 | ||
| 72 | 74 | Clicking `New` while on `/s/new` with a non-empty draft must pop a confirm modal: title **"Clear this list?"**, prompt **"This will reset the current list and you will lose all changes. Are you sure you want to do that?"** with OK/Cancel. On OK, reset the draft and go to `/s/new`. Otherwise, clicking `New` resets the draft and goes to `/s/new`. | |
| 73 | 75 | ||
| @@ -96,26 +98,40 @@ | |||
| 96 | 98 | ||
| 97 | 99 | Keep one draft per browser profile in browser-local storage. It holds the list's alias, description, and links with their order. It must survive reload, navigation, login, and logout. Clear it only after successful publication or a confirmed reset via the `New` guard above. | |
| 98 | 100 | ||
| 99 | − | Both editor pages (`/s/new` and `/s/edit`) show, top to bottom: the **publish bar**, the link list (when non-empty), and (on `/s/new`) the first-link input widget for adding more links. | |
| 101 | + | Both editor pages (`/s/new` and `/s/edit`) show, top to bottom: | |
| 100 | 102 | ||
| 103 | + | 1. the **publish bar** — a full-width band directly under the navbar; | |
| 104 | + | 2. the first-link input widget (the same "Enter a link and press enter" control as the home page) for adding more links — on **both** pages, new and edit mode; | |
| 105 | + | 3. when the list has links, the **"Links"** heading with its right-aligned hint, then the link rows; | |
| 106 | + | 4. on `/s/edit` only, the **"Delete This List"** button below the rows (see Delete). | |
| 107 | + | ||
| 101 | 108 | ### Publish bar | |
| 102 | 109 | ||
| 103 | − | A card with three columns: **Vanity Url** input, **Description** textarea, and the **Publish** button. | |
| 110 | + | Not a card. A full-width band directly under the navbar, on a background distinct from the page body below it (the reference separates it with a soft shadow). Three columns on desktop, stacked on mobile: | |
| 104 | 111 | ||
| 105 | − | - **Vanity Url** (label "Vanity Url"): tooltip *"Optional: Enter a vanity url for this list (i.e. my-list becomes theurlist.com/my-list). If you leave this box blank, we'll generate a random vanity for you."* While the user types: if it contains anything other than letters, numbers, or dashes, show **"Vanity URLs can only contain letters, numbers, and dashes."** Otherwise, after ≥300ms of the value being stable, check availability against the server; if it is in use, show **"This vanity URL is already in use. Please choose another."** On `/s/edit` the input is **disabled** (the alias never changes after publication). | |
| 106 | − | - **Description** (label "Description"): textarea, 2 rows. Tooltip *"Optional: The description will show up as the title on your public list page."* | |
| 107 | − | - **Publish** button (large, primary, bold): while signed out it is disabled with the label **"Login to Publish"** (clicking it opens the login modal). While signed in it is enabled only when the alias is valid (or blank) and the list has at least one link. On success the server response becomes the editor state and the browser navigates to `/{vanity}`. On failure keep all user input and show an error. | |
| 112 | + | - **Vanity Url** (label above): the largest field in the app — a tall input whose value renders in large, medium-weight type. Tooltip *"Optional: Enter a vanity url for this list (i.e. my-list becomes theurlist.com/my-list). If you leave this box blank, we'll generate a random vanity for you."* While the user types: if it contains anything other than letters, numbers, or dashes, show **"Vanity URLs can only contain letters, numbers, and dashes."** Otherwise, after ≥300ms of the value being stable, check availability against the server; if it is in use, show **"This vanity URL is already in use. Please choose another."** On `/s/edit` the input is **disabled** (the alias never changes after publication) and visibly rendered in a disabled, muted/filled state. | |
| 113 | + | - **Description** (label above): a normal bordered textarea, 2 rows. Tooltip *"Optional: The description will show up as the title on your public list page."* | |
| 114 | + | - **Publish** button (large, primary, bold, rounded; top-aligned with the two fields): while signed out it is disabled with the label **"Login to Publish"** (clicking it opens the login modal). While signed in it is enabled only when the alias is valid (or blank) and the list has at least one link. On success the server response becomes the editor state and the browser navigates to `/{vanity}`. On failure keep all user input and show an error. | |
| 108 | 115 | ||
| 109 | 116 | ### Link list editor | |
| 110 | 117 | ||
| 111 | − | When the list has links, show a "Links" heading with a right-aligned hint **"Drag links to re-order"**, then the linked rows. | |
| 118 | + | When the list has links, show a primary-colored "Links" heading with a right-aligned hint **"Drag links to re-order"**, then one row per link in stored order. | |
| 112 | 119 | ||
| 113 | − | Each row: a drag grip icon at the far left; a 64px image (24px on mobile) or a Postrboard-native placeholder in its place; a **Title** text input (placeholder "Enter a title", bold text); a **Description** textarea (placeholder "Enter a description"); a **URL** text input showing the destination (editable); and a delete icon button that removes the row immediately with **no confirmation**. | |
| 120 | + | **Row anatomy — rows are cards of text, not forms:** | |
| 114 | 121 | ||
| 115 | − | While a link's metadata fetch is in flight, show a small progress bar overlay on that row; when it settles the progress bar disappears. There are no per-link status badges or error messages: failed metadata simply leaves the fields as they are (or empty) and clears the progress bar. | |
| 122 | + | - A drag grip icon at the far left, **outside** the card, vertically centered on it. It is the only drag handle. | |
| 123 | + | - A delete icon (an "×") at the far right, **outside** the card, vertically centered on it. Clicking it removes the row immediately with **no confirmation**. | |
| 124 | + | - Between them, a card (surface with a soft shadow) of uniform height (about 120px in the reference) containing, left to right: | |
| 125 | + | - the 64px link image, vertically centered, or a Postrboard-native placeholder when there is no image. On mobile the 64px image is replaced by a 24px thumbnail sitting inline to the left of the title; | |
| 126 | + | - a text column: the **Title** (bold, one line, truncated with an ellipsis), the **Description** (regular, smaller, muted, clamped to about two lines with an ellipsis), and at the bottom the destination **URL** (small text in the link color, one line, truncated with an ellipsis). | |
| 127 | + | - While the row's metadata fetch is in flight, a thin progress bar spans the top edge of the card; when the fetch settles it disappears. | |
| 116 | 128 | ||
| 117 | − | Reordering is **drag-and-drop only**, via the grip handle, with smooth animation. No other reorder affordances. | |
| 129 | + | **In-place editing (this is the key interaction):** the title, description, and URL are rendered as plain text — there are no visible input boxes, borders, field backgrounds, labels, or save buttons inside the row. All three are nevertheless editable in place: clicking, tapping, or tabbing into the text places a caret and lets the author type directly; the only visual affordance is a subtle hover/focus cue (for example a thin accent edge or focus ring around the piece of text being edited). The placeholders **"Enter a title"** and **"Enter a description"** appear only while that value is empty. Edits update the draft/editor state immediately as they are typed; there is no per-row save — the list is saved when Publish is pressed. Implement the three fields as real form controls (borderless single-line inputs for title and URL, a borderless textarea for the description, or an equivalent) with visually hidden labels so they remain keyboard- and screen-reader-operable. Fetched metadata fills only fields the author has not typed into; it never replaces typed text. | |
| 118 | 130 | ||
| 131 | + | There are no per-link status badges or error messages: failed metadata simply leaves the text as it is (or empty) and clears the progress bar. | |
| 132 | + | ||
| 133 | + | Reordering is **drag-and-drop only**, via the grip handle, with smooth animation (the whole card moves). No other reorder affordances. | |
| 134 | + | ||
| 119 | 135 | Allow duplicate destination URLs as separate entries. No product-level link limit. Each link keeps a stable server ID, destination URL, optional title, optional description, optional image, and position. Persist order. | |
| 120 | 136 | ||
| 121 | 137 | ## Live metadata | |
| @@ -154,10 +170,10 @@ | |||
| 154 | 170 | ||
| 155 | 171 | ## My Lists | |
| 156 | 172 | ||
| 157 | − | Login required. H2 (large, medium weight, primary color): **"My Lists"**. Then a responsive grid of tiles: 1 column on mobile, 2 on tablet, 3 on desktop, 4 on widescreen. | |
| 173 | + | Login required. H2 (large, medium weight, primary color): **"My Lists"**. Then a responsive grid of uniform-height tiles (about 195px in the reference): 1 column on mobile, 2 on tablet, 3 on desktop, 4 on widescreen. | |
| 158 | 174 | ||
| 159 | − | - First tile: a dashed placeholder tile containing a large **+** and the text **"Create new list"**; clicking it starts a fresh draft and goes to `/s/new`. | |
| 160 | − | - Per-list tile: a Postrboard-native subtle texture/background in the card (in place of the reference app's `bg.png` dots); a primary-colored tag reading **"{N} Links"** (e.g. "4 Links"); the vanity URL as the title; the description below it (omitted when empty). The whole tile is clickable and loads that list into the editor state, then goes to `/s/edit`. | |
| 175 | + | - First tile: a dashed-border placeholder tile of the same size as the others, containing a large **+** centered above the text **"Create new list"**; clicking it starts a fresh draft and goes to `/s/new`. | |
| 176 | + | - Per-list tile: a card (surface with a soft shadow) with a primary-colored pill tag reading **"{N} Links"** (e.g. "4 Links") positioned over the tile's top-left edge, straddling the card's top border; a Postrboard-native subtle texture at the tile's top-right corner (in place of the reference app's `bg.png` dots); inside the card, left-aligned, the vanity URL as the title (medium weight, larger) and the description below it in regular text (omitted when empty). The whole tile is clickable (pointer cursor) and loads that list into the editor state, then goes to `/s/edit`. | |
| 161 | 177 | - While loading: 3 skeleton tiles. If the request fails, show an empty grid without error text. | |
| 162 | 178 | ||
| 163 | 179 | There is **no Deleted section and no Restore action**. | |
| @@ -179,7 +195,7 @@ | |||
| 179 | 195 | - LinkedIn: `https://www.linkedin.com/shareArticle?mini=true&summary={description}&url=https://theurlist.com/{vanity}` | |
| 180 | 196 | - View toggle (right, a connected group of two icon buttons with an accessible selected state): **"View as List"** and **"View as QR Code"**. | |
| 181 | 197 | - QR view: a centered, scannable QR code SVG of the URL `https://theurlist.com/{vanity}` (4x module scale, colors `#121212` on `#F9FAFC`, error correction medium). | |
| 182 | − | - List view: link cards in stored order. Each card is itself a link opening the destination in a new tab (`rel="noopener noreferrer"`), showing the 64px image or placeholder, the bold title (fall back to the destination URL), the description (omitted when empty), and the plain destination URL. | |
| 198 | + | - List view: link cards in stored order, with the same card anatomy as the editor rows (64px image or placeholder on the left, bold title, description, small destination URL) but read-only — no grip, no delete, no editing. Each card is itself a link opening the destination in a new tab (`rel="noopener noreferrer"`); the title falls back to the destination URL; the description is omitted when empty. | |
| 183 | 199 | - Below the cards, a link **"Report this list"** that opens a mailto to `support@theurlist.com` with subject "LinkBundle Flagged" and the list URL in the body. | |
| 184 | 200 | ||
| 185 | 201 | When the alias does not resolve to an active list, show the not-found state instead: a Postrboard-native "not found" illustration or icon (in place of the reference app's mascot image), H2 **"We couldn't find that Urlist"**, and H3 **"But don't be sad! That means {vanity} is still available."** where the alias is a link. Clicking that alias starts a fresh draft pre-filled with it and goes to `/s/new`. There is no tombstone or "deleted" state. | |
| @@ -190,7 +206,7 @@ | |||
| 190 | 206 | ||
| 191 | 207 | Support current Chromium from desktop down to 320 CSS pixels with no page-level horizontal scrolling; the editor, modals, and public page must remain usable. | |
| 192 | 208 | ||
| 193 | − | Meet WCAG 2.2 AA, including full keyboard operation, reduced-motion support, sensible focus management, and announced status/error changes. | |
| 209 | + | Meet WCAG 2.2 AA, including full keyboard operation, reduced-motion support, sensible focus management, and announced status/error changes. In-place editable text in link rows must be reachable with Tab, have an accessible name, and show a visible focus indicator. | |
| 194 | 210 | ||
| 195 | 211 | Show truthful loading, empty, success, blocked, and error states. Errors must explain recovery. Never present failure as success. | |
| 196 | 212 | ||
| @@ -222,7 +238,7 @@ | |||
| 222 | 238 | ||
| 223 | 239 | Use Vitest for URL and alias validation, random alias generation, metadata parse precedence (title/description/image including the favicon fallback), ownership, SQLite transactions, and share URL construction. | |
| 224 | 240 | ||
| 225 | − | Use Playwright for: the home page first-link flow (valid and invalid → "That doesn't look like a valid URL"); draft persistence across reload and logout; publishing while signed out (disabled "Login to Publish" → login modal with the three provider buttons → publish → landing on `/{vanity}`); live "already in use" and "letters, numbers, and dashes" validation; edit mode (disabled alias, field editing, drag-and-drop reorder); "Delete This List" confirmation ("The url … will be released for others to use.") and the alias being immediately claimable by another user; My Lists (create-new tile, "N Links" tags, skeleton loading, click-through to `/s/edit`); the public page (skeleton "Loading …", description heading, X/Facebook/LinkedIn share URLs, List/QR toggle with a scannable QR of `https://theurlist.com/{vanity}`, "Report this list" mailto); the not-found state ("We couldn't find that Urlist" + click-the-alias pre-filled draft flow); the terms page and the 404 page; the New-draft "Clear this list?" guard; theme switching, persistence, and no flash; and desktop plus 320px mobile layouts of the home, editor, and public pages. | |
| 241 | + | Use Playwright for: the home page first-link flow (valid and invalid → "That doesn't look like a valid URL"); draft persistence across reload and logout; publishing while signed out (disabled "Login to Publish" → login modal with the three provider buttons → publish → landing on `/{vanity}`); live "already in use" and "letters, numbers, and dashes" validation; edit mode (disabled alias; adding a link from the editor's input widget; in-place editing of a row's title, description, and URL by clicking the text and typing, with no visible field chrome; drag-and-drop reorder); "Delete This List" confirmation ("The url … will be released for others to use.") and the alias being immediately claimable by another user; My Lists (create-new tile, "N Links" tags, skeleton loading, click-through to `/s/edit`); the public page (skeleton "Loading …", description heading, X/Facebook/LinkedIn share URLs, List/QR toggle with a scannable QR of `https://theurlist.com/{vanity}`, "Report this list" mailto); the not-found state ("We couldn't find that Urlist" + click-the-alias pre-filled draft flow); the terms page and the 404 page; the New-draft "Clear this list?" guard; theme switching, persistence, and no flash; and desktop plus 320px mobile layouts of the home, editor, and public pages. | |
| 226 | 242 | ||
| 227 | 243 | Tests use a separate temporary database and must not depend on order. | |
| 228 | 244 | ||
| @@ -234,7 +250,7 @@ | |||
| 234 | 250 | ||
| 235 | 251 | 1. Run `npm run lint`, `npm run typecheck`, `npm test`, `npm run test:e2e`, and `npm run build`. | |
| 236 | 252 | 2. Start the production build and confirm the app responds. | |
| 237 | − | 3. In a real Chromium browser, complete every user journey: login/logout for at least two mock identities via the three provider buttons; signed-out → disabled "Login to Publish" → login → publish; owned-list create, drag-reorder, and field editing; non-owner denial (401); hard delete and alias re-claim by a second user; My Lists create/edit flows; public links/QR/share; the not-found pre-filled-alias flow; the terms and 404 pages; the New-draft guard; themes; and desktop/mobile layouts. | |
| 253 | + | 3. In a real Chromium browser, complete every user journey: login/logout for at least two mock identities via the three provider buttons; signed-out → disabled "Login to Publish" → login → publish; owned-list create, drag-reorder, and in-place field editing; non-owner denial (401); hard delete and alias re-claim by a second user; My Lists create/edit flows; public links/QR/share; the not-found pre-filled-alias flow; the terms and 404 pages; the New-draft guard; themes; and desktop/mobile layouts. | |
| 238 | 254 | 4. Confirm active data survives a complete restart. | |
| 239 | 255 | 5. Reconcile `TECHNICAL_SPEC.md` against this instruction, fix every missing or incorrect requirement, and confirm each checked item has evidence. | |
| 240 | 256 | 6. Repeat affected validation and leave no unchecked item unless it has a reported external blocker. | |