The PWA’s design system#

The phone app is a tool you reach for one-handed, often in a hurry: open, capture, gone. The design follows from that, and from keeping the app tiny: the shell (capture, lists, pages, search) uses no UI framework, icon library or web font, and its JavaScript is held under 16.5 kB gzipped (npm run size, web/scripts/size.mjs). The one exception is the drawing viewer for a photo’s drawing: a separate chunk, fetched only when a drawing is shown, built on React and Excalidraw with Excalidraw’s own fonts (served by scrap from /excalidraw/fonts/), and outside that budget. Everything visual is defined once, in web/src/tokens.css; the components in web/src/style.css (which imports web/src/css/*.css) and web/src/excalidraw.css (the photo panel and the drawing viewer) use nothing else. This page is the portable description, for carrying the design into another tool and back.

Principles#

  • Paper and ink, one colour. Warm off-white paper and warm black ink in daylight, a warm near-black and cream at night, and a single amber (a sticky note: scrap’s mark). Filled amber is the one hero (the screen’s primary action) and the badge dot; amber-soft means only “selected” or “active” (a chosen chip or segment, the current nav tab, a match highlight), never a label, count or state; and there is no amber decorative text and no amber link in the app’s chrome (counts, headers, Claude’s cards): links there are ink, underlined (Links, below). Amber link text is the owner’s own text’s alone. Red means only “destructive”, “failed” or “overdue”. No blue-greys, no second accent.

  • Links: every text link is underlined; its colour says whose it is. A link in the owner’s own text (a page’s Markdown and [[wiki]] links, a topic’s notes, the editor’s Preview: .body) is --accent-ink, underlined. Every other text link, Claude’s (Related, Topics, Sources, a write-up’s links) and the app’s (Regenerate, Clear filters, Clear all, Edit pins, Put back, Discard it, “by Claude”), is its text colour (--ink, or --ink-2 for a quiet control) over a thin underline: --line-strong, 1 px, offset 0.2 em. On a card where --line-strong is under 3:1 (the Claude card’s --sunk, the restored-draft line’s --accent-soft) the underline is --ink-2 (--link-line). Each keeps its weight; Claude’s links are regular. One rule draws them all (a, .link in css/base.css, and :where(.body) a for the owner’s, a write-up’s .body excepted), so a new link follows it without a rule of its own. Not text links, so not underlined: whole-row list items (search, Todos, topic, Pinned and Recent rows), chips and pills (tags, filters, the due chip), icon buttons, the nav and the kind row, buttons that look like buttons (.button, Download, a link card, a file card), the top bar’s Back and jump, footnote marks, and a section’s heading that links (a Type heading, which reads as a heading).

  • One hero per screen. Composer and editor: Save, the one amber fill. Todos: no hero (New todo is a quiet + in the top bar). Capture: no hero; its four actions (Camera, Gallery, File, Note) are equal tiles, since none is more likely than the others (on a computer two: Add files and Note). Page: a todo’s or a Later’s Mark done, the toolbar’s one fill; a page with no status has none. Everything else steps down: secondary (outlined surface), then ghost (text only).

  • Under the thumb. Actions on the thing in hand live at the bottom: Capture’s actions and the nav on a list screen, a page’s toolbar on a page. Getting around lives in the top bar (Back and the jump to the list), so a page has no nav and its bottom bar only ever acts on that page. On a computer (a mouse and a window 900 px wide or more: css/desktop.css, one media query, so a phone or a touch tablet never changes) things go where the eye and the mouse already are instead: the nav is tabs in the middle of the top bar (icon beside its word, the current one on --accent-soft, the Todos count after the word; on a list screen the top bar’s own column is 960 px there, and a long development version in the wordmark is cut with …, so four tabs clear the wordmark and Settings; a page, which has no nav, keeps its Back, jump to the list and pager on the same column, 800 px by default and as wide as Settings’ Maximum column width allows), Capture’s tiles are a row over the list, a page’s actions are a row of buttons under its title, meta line, dates line and chip row, kept in view under the top bar as it scrolls (Mark done filled, Delete apart at the end), toasts sit at the window’s foot as wide as they need, and rows, chips, tiles and ghost buttons take --sunk on hover. Cancel, Preview and Save sit in the editor’s top bar because the keyboard owns the bottom.

  • Drill down, then Back, named. A page is a drill-down. On every phone and browser the top bar swaps the wordmark for Back named for where it goes (‹ Search, ‹ Todos, ‹ Topics, ‹ Capture, or a page’s title), as a mail app’s ‹ Inbox, and it goes up one level: to the page you followed a link from, else to the list you opened it from (past the pages the pager stepped through). When Back doesn’t go to the list, the list’s name follows it as a second target, ‹ A · Search, which jumps straight back to the list. A page opened cold goes up to its parent list (Todos for a todo, Topics for a topic, Capture for any other page): Back goes where its label says, with no request, so while the page is still loading that is Capture and on a stalled link it is never a dead button. Every link to a page, [[slug]] or its full address on any address the server is reached at (the one the app was opened at, and each in SCRAP_PUBLIC_URL, which the server writes into index.html’s scrap-origins meta tag), opens in place, so the phone’s own Back walks back through them one step at a time, as the top bar’s Back does after a link. Delete is one step back (the list, or the page whose link opened it) with Undo; opened cold, it goes where Back names. Whichever Back (the top bar’s, the jump, the phone’s) or Forward, a screen comes back scrolled where it was left (scroll.ts: the position rides in its history entry, put back once the screen is tall enough, never smoothly; the search screen keeps its own).

  • Icons with words. Icons help find a control; the word says what it does. Icon-only buttons (close, previous/next photo, remove a photo) carry an aria-label.

  • Calm states. Empty, offline and error states are an icon, one plain sentence and at most one action (offline or unreachable, everywhere search included: why, and Retry). On the capture screen a problem is a quiet inline line, never a whole screen; offline there is the badge, the waiting rows and one line, with no card, Retry or toast.

  • Say it once. A row shows its kind by its icon alone, and a status that ends it (done, cancelled) as a word on its meta line (”· done”). A page says its kind and status once, in its meta line. A count shown in a header is not said again by an empty state (a search with no results shows no count).

  • Few sizes, one style per job. A screen’s text uses few sizes, each for one role (a page’s header: two; a Claude card: two); a pill or chip has one style per job: selected or active on --accent-soft, a tag on --sunk, an editable chip as the others it sits with. One chip size app-wide (Chip, below): --t-sm text in a 32 px pill drawn inside a 48 px target. A label never breaks inside a phrase: a line of “ · “-separated parts breaks only before a “·”, which leads the part after it, so no line ends in one.

  • Dark is designed, not inverted. Surfaces get lighter as they come forward; amber is a step lighter; toasts stay dark slabs in both themes.

  • Every gesture has a button. Swipes are shortcuts, never the only way: a row’s Edit and Delete are also on its page (and a row’s tick is a button of its own, with Mark done and Reopen on the page too), the page’s swipe is also its ∧ ∨ pager and the arrow keys. Gestures start away from the screen’s edges, which belong to the phone’s Back gesture.

  • Motion confirms, never decorates. 90-260 ms, eased out; none under reduced motion. The two looping indicators (busy, loading image) are the only motion that runs longer.

Tokens#

Light / dark values, held to WCAG AA (4.5:1 for text, 3:1 for control edges) twice: web/tests/contrast.test.ts reads tokens.css and checks the listed text/background pairs, every rule in web/src/css/ that sets both a text colour and a background from tokens, and white text on --overlay over a white photo; and an end-to-end sweep (the lowContrast helper in web/e2e/contrast.ts, run by web/e2e/view.spec.ts on each screen in both themes and by several other e2e specs) computes the real colour of every visible piece of text and of what lies behind it.

The theme follows the system unless Settings (the top bar’s cog) says otherwise (24. A settings pane in the top bar, with theme overrides): Light or Dark sets data-theme on <html>, which tokens.css reads in place of the media query (web/src/theme.ts). Drawings have a theme of their own, Light by default.

The browser’s theme-color follows the colour scheme: two <meta name="theme-color"> tags in web/index.html, one per scheme, with the --bg values below (theme.ts sets both to the chosen one when the theme is forced). The PWA manifest (web/public/manifest.webmanifest) does not: its theme_color and background_color are a fixed dark #161411.

Token

Light

Dark

Role

--bg

#f6f3ec

#161411

the page; theme-color

--surface

#fffdf9

#211e1a

cards, list groups, fields

--sunk

#ece7dd

#2c2723

wells: tiles, segmented track, placeholders, pressed

--ink

#1e1b16

#f2ece1

text

--ink-2

#5c564c

#b8b0a2

secondary text: dates, hints

--ink-3

#6e675c

#958d80

placeholders, captions

--line

#e2dccf

#423b33

hairlines (decorative)

--line-strong

#8f8778

#706858

field and control edges; the underline of a link that isn’t the owner’s (--ink-2 on --sunk, where it is under 3:1)

--accent

#eba13a

#f0ac4b

the hero’s fill, the badge dot, a selected segment’s icon, a new topic’s dot

--on-accent

#1e1b16

#1e1b16

text on accent

--accent-ink

#8a4f00

#f3b65c

accent as text: links in the owner’s text, focus ring, active icon

--accent-soft

#f8e6c6

#3a2c17

selected or active only: a chosen chip, the active nav pill, a pressed toggle, match highlights, a swipe to Edit

--danger

#b42318

#f29086

destructive, failed (text)

--danger-soft

#fbe4df

#3b201c

failed badge, conflict banner

--toast / --on-toast

#2a2620 / #f6f1e7

#3a352d / #f2ece1

toasts

--toast-action

#f3b65c

#f3b65c

a toast’s action

--viewer

#000000

#000000

the photo viewer’s backdrop

--on-overlay

#ffffff

#ffffff

text and icons over photos

--overlay

rgb(0 0 0 / 65%)

same

pills, scrims and buttons over photos (white on it is over 4.5:1 even on a white photo)

Presses, edges and washes that need a translucent tint mix a token with transparent (color-mix(in srgb, var(--ink) 14%, transparent)) rather than writing a new colour.

Type: the system UI face (Roboto on Android; --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, and --mono for code: ui-monospace, "SF Mono", Menlo, Consolas, monospace); a 17 px root, so a rem is 17 px and --t-xs (0.75rem) is 12.75 px. Settings’ Text size sets the root: 100% (the default, 17 px, sized as a web page is, so the browser’s zoom applies) or a percentage of it, inline on <html> (main.ts at the start and when the app comes back, theme-init.js before the first paint), or Phone (data-text="phone"). With Phone, on Android the root follows the phone’s font size: index.html opts in with <meta name="text-scale" content="scale"> (Chrome 146+) and the root is calc(17px * env(preferred-text-scale, 1)); on iPhone the root follows the phone’s text size, Larger Text too (font: -apple-system-body, applied only where -webkit-touch-callout exists, so iOS and not a Mac), with scrap’s own family and line height set again after it; a small check in main.ts keeps it from going under 17 px (the smaller settings, with Phone), measured again whenever the app comes back. --t-xs 0.75rem (labels under icons, chips), --t-sm 0.875rem (meta, small buttons), --t-md 1rem (body, list titles), --t-lg 1.1875rem (wordmark, field titles, a Markdown h2), --t-xl 1.5rem (page title). Markdown headings step down a size a level, all under the page title: h1 1.3125rem, h2 --t-lg, h3 1.0625rem semibold, h4-h6 --t-md. Line height --lh 1.5, --lh-tight 1.25 for titles, 1.6 for page text. Weights 500 (controls), 600 (titles, labels), 700 (page title, active nav).

Space: a 4 px grid, --s-1..--s-7 = 4, 8, 12, 16, 24, 32, 48. Screen gutter 16. Radius: --r-sm 8 (thumbnails, code), --r-md 12 (buttons, fields, toasts), --r-lg 16 (cards, lists, photos), --r-pill (badge, search field, nav indicator). Size: --tap 48 (the touch target, extended with an invisible ::after where the drawn control is smaller; the exceptions are listed under “What we don’t do”), --bar-h 56, --nav-h 64 (max(64px, 1.125rem + 44px): taller when its labels are, at a large text size). Elevation: --shadow (selected segment), --shadow-2 (capture cluster, toasts). Motion: --d-1 90 ms (press), --d-2 180 ms (view enter, toast), --d-3 260 ms (editor sheet, viewer); --ease cubic-bezier(0.2, 0.8, 0.2, 1). Under prefers-reduced-motion all three are 0, and the two looping indicators stop: the placeholder shimmer (1.2 s, alternating) and the busy icon’s pulse (0.8 s, see Busy under States). Those two are not on the --d-* scale: they repeat only while the work or the image is in progress.

Components#

Component

Class / element

Notes

Button, secondary

button, .button

surface, hairline edge, 48 high, icon 20 + label; presses scale to 0.97 on --sunk

Button, primary

.primary

amber fill, ink label; one per screen (Save)

Button, ghost

.ghost, button.link

no fill (Cancel, Discard it, the pager’s ∧ ∨)

Button, danger

.danger

red label, never filled

Tile

.tile

icon over a short label on --sunk, 72 high; the capture actions Camera, Gallery, File and Note, four equal columns; on a computer Add files (.desk, shown only on a computer, DESK: one picker for photos and files) and Note, two

Toolbar

.tools

icon-over-label tiles sharing the width by their labels (flex: 1 1 auto), so no word is cut, every label --t-xs; page actions only, no navigation tiles (sticky at the bottom, where the nav would be: a page has none; over the page text: z-index 2), viewer tools. A page’s order: Mark done (a todo or a Later open or in progress: .primary, --accent fill, --on-accent label, --r-md corners, wider at flex: 1.6 1 auto, the check icon), or Reopen in its place once done or cancelled (plain, the retry icon: a cancelled one looks closed, so it offers only Reopen), Edit (or Edit draft), Share, Delete (danger); Download is in Share’s menu, or in Share’s place where the browser can’t share. Four fit whole at 360 px. Always the page’s: a page has no form in place (its tags and due date are changed in the editor, Edit), so the row never changes shape. On a computer the same row under the header

Segmented control

.chips (radio group)

the editor’s kind (.kinds: Note, Todo, Later, Board, Doc: short labels so all five fit one row from 320 px, with the full names Whiteboard and Document as the radios’ accessible names and titles), the search screen’s kind row (.kindrow: All, Notes, Todos, Later, Boards, Docs, each its kind’s 18 px icon over its word, All a grid of four tiles, so all six fit one row from 320 px; always shown, under the filters on; one tap sets or clears kind:, Todos included: it filters in place like the others), the Todos screen’s Now (a clock), Upcoming (the due calendar) and All (the grid), drawn as the kind row; selected = raised surface, amber icon. The editor’s kind picker draws each kind’s icon over its word too (--t-xs), as the kind rows do, so every kind bar shows its icons at every width; at a large text size the kind rows and the picker wrap rather than clip

Chip

.facet, .tags :is(a, button), .chipfield

one chip size app-wide: every chip (the tag picker’s chosen and offered, the due chips, the Filters panel’s When, Order and Group, the Filters on, a page’s own chip row) is --t-sm text (--lh-tight) in a 32 px pill drawn inside a 48 px target: 8 px transparent borders above and below, the fill clipped to the padding box (background-clip: padding-box, so a fill is set with background-color, never the background shorthand) and an outlined chip’s edge an inset 1 px ring (box-shadow: --line, --line-strong for the picker’s offers; none on a chosen one); 0.6em inline padding. Rows of them touch (no row gap), leaving 16 px between the drawn pills. A long tag path wraps inside its chip, which grows a line. The fields beside them, the tag box and the Priority select (.chipfield), are drawn the same: 32 px in a 48 px target, a --line-strong ring, the focus ring an inset 2 px --accent-ink ring. sim/sizes.mjs (the design sim) checks none is larger than --t-sm

Top bar

header

wordmark with the scrap mark; at the right, the queue badge, then Settings (#settings, a ghost cog with eight teeth, “Settings”, on every screen: it opens a sheet like the editor’s with two radio groups, App theme System / Light / Dark and Drawings theme Light / Dark / Same as app, then under Store a Store health link and a Trash link, each with a button’s look and a hint line; Done, Escape or Back close it, and Store health or Trash closes it, then opens its screen). The wordmark’s version (#ver, --t-sm, --ink-2) sits on its baseline. On a page, on every phone and browser, Back in the wordmark’s place (#back, a ghost button 48 high: the arrow, then where it goes in --t-md semibold, #backto: “Search”, “Todos”, “Capture”, or the title of the page a link was followed from; accessible name “Back to Search”): it goes up one level, to the page a link was followed from, else to the list the page was opened from, else up to a parent: Todos for a todo, Topics for a topic, Capture for any other page (go() gives each entry it pushes its depth, the list it came from, kept by a page opened from a page, and, opened from a page, that page’s depth and title, kept by a replace, so Back is one history.go(-n); each screen records its name for its depth (crumbs.ts: a list’s when drawn, a page’s title each time it is shown, so renamed in its editor or by Claude too) in sessionStorage and in memory, so Back names a page as it is now, renamed, stepped on by the pager or replaced by its parent, a reload keeps it, and a browser that refuses storage still has it; a list’s mark says where it was, so only a name recorded there names it). After Back, either the jump to the list or the pager. The jump (#jump, on every phone and computer: a page has no nav), when Back goes to a page that isn’t the list: a dot (aria-hidden), then the list’s name, a ghost text button with no icon (#tolist, --t-sm, --ink-2, 48 high; accessible name “Back to Search”), which goes straight to the list (one history.go(-n)); the pager (below, in #pos) when the page was opened from a list. Then the queue badge and Settings. Back ends where the jump begins (header.crumb), else at the free space before the right-hand controls; its label is cut with …. Beside the jump (header.crumb) Back never shrinks under 6.5 rem (the arrow and a few letters) and the badge and Settings never shrink (flex: none), so Back’s label gives up the width; elsewhere (a list screen, a page with its pager) they may, so the wordmark, a two-part badge, New todo and Settings fit at 320 px, and a pager beside a badge does not widen the page. Sharing the bar with the jump or the pager under 560 px, the badge is compact, its dot and count (”● 2”, a 48 px target with the 36 px pill drawn inside), named with the count and its words (“2 queued: 1 waiting · 1 failed”): at 360 px with it up, Back keeps about 150 px of a title, at 320 about 110. On the Todos screen, New todo (#new, a ghost plus, “New todo”) at the right end, the badge beside it when up: it opens the composer with Todo chosen (kept even if the text is a link), which closes back onto the list; the list fetches again as the upload queue changes, so the new todo joins it

Badge

.badge, .badge.failed

a pill on --sunk with a status dot: the amber dot alone says waiting (the pill is no amber-soft: that means selected); failed is --danger-soft with a red dot and --danger words; “1 waiting · 1 failed”, named with its count first, “2 queued: 1 waiting · 1 failed”, so the compact badge’s “2” leads its name. On a page under 560 px wide whose top bar also holds the jump or the pager: compact, the dot and the count (”● 2”)

Bottom nav

nav

four tabs, Capture, Todos, Search, Topics (the newest last, so the others keep their places): icon over label; active item gets an amber-soft pill and ink label (aria-current). List screens only: a page hides it (body.paged), its toolbar is its only bottom bar, and the top bar’s Back and jump are the way out. It grows with its labels at a large text size (--nav-h)

Nav count

nav .due-n

the Todos tab’s open todos due today or overdue: a 20 px --ink pill with --bg digits on the icon’s corner (after the word on a computer), ringed in --bg: a count, never amber; hidden at 0; the tab’s accessible name says it (“Todos, 3 due”). One count request when the app opens or comes back and after a change, never polled

List group

.list

rows in one rounded card; .pending is dashed (still on the phone)

List row

.list li

48 thumbnail or kind icon (the icon says the kind), title, then one meta line: its date, or for a todo with a due line its due line in the date’s place (“high · due 26 Oct · 1 of 4”, small.due; a priority alone says “low priority”), a Later’s medium (”· Book”), a topic’s “· Topic”, and a status that ends it as a word (”· done”, em, classed by its word); a search hit’s snippet, tidied (snipclean.ts: task boxes out, a web address said as words, “en.wikipedia.org · wiki / Integrated information theory”, a title leading it cut away; a long word breaks rather than being clipped), and none if it only repeats the title (the same whole words in order, punctuation, hyphens, underscores and case aside: echoes() in snippet.ts); a cancelled row (li.cancelled) has its title struck through in --ink-2; actions under it (.rowbar: whole words, the bar wraps to a second line rather than breaking a label such as Split and retry); a focused row draws its ring inside the card

Swipe row

.list li.swipe, .row-under

follows the finger; behind it Edit on --accent-soft (right) or Delete on --danger-soft (left); past the threshold the icon grows (Edit’s turns --accent-ink, its fill staying --accent-soft) and letting go does it

Fold

.claude-text (details)

hairline above, a chevron before the heading: right folded, down open

Pager

.pager

a page opened from a list, in the top bar as a mail app has it: “3 of 23” (.pager .at, --ink-2), then ∧ (previous) and ∨ (next), 40 px ghost links, the one with nothing that way at 35 % opacity. Its group’s label says which list (“In Latest”): “Latest” (Capture’s list, and the search screen’s newest-first list), the kind row’s name when one kind is chosen alone (“Notes”, “Todos”, “Later”, “Boards”, “Docs”), “Search results”, “Todos · Now”, “Todos · Upcoming”, “Todos · All” and “Todos · Done” (the Todos screen’s folded done and cancelled), “Topics”; Back beside it names the list’s screen and goes back to it; the page follows a sideways swipe and slides off to the next. From the Todos screen, Mark done steps on to the next todo with a toast “Done: . Next todo, 3 of 9” and <strong>Undo</strong> (6 s); on the last, “Done: that was the last in the list” and <strong>Back to list</strong></p></td> </tr> <tr class="row-odd"><td><p>Status word</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.list</span> <span class="pre">em</span></code></p></td> <td><p>on a row only for a status that ends it: “· done”, “· cancelled”, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> at <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code> on its meta line, as a page’s meta line says it (no pill; an open todo or Later has none: its icon says the kind)</p></td> </tr> <tr class="row-even"><td><p>Meta line</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.meta</span></code> (<code class="docutils literal notranslate"><span class="pre">pagehead.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">pagemeta.ts</span></code>)</p></td> <td><p>a page’s header under its title: one quiet <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code> <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> line, its kind word first in <code class="docutils literal notranslate"><span class="pre">--ink</span></code> semibold after its kind’s 16 px icon (a topic’s: the open book), then what else it is (a Later’s medium and what it is for, “Later · Book · to read”, or once done what was done, “Later · Book · read”; a status other than open; a todo’s tasks), then “by Claude” on a page Claude made (below), then, on one line when the kind is all there is to say, the dates (below), else the dates on a line of their own. The parts are children of a flex-wrapping <code class="docutils literal notranslate"><span class="pre">.parts</span></code>, each <code class="docutils literal notranslate"><span class="pre">nowrap</span></code>; each part after the first draws its own leading “·” in a gutter the row is pulled left over and the line clips, so a part that starts a line loses its dot and no line ever ends or starts with one. The line has 4px of padding taken back by margin, so a link’s focus ring is not clipped. Two sizes in the whole header (the title, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>) and one red word (overdue, in the due chip)</p></td> </tr> <tr class="row-odd"><td><p>Dates line</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.dates</span></code></p></td> <td><p>“Created 24 Oct, 09:12 · Edited 25 Oct, 14:03” (<code class="docutils literal notranslate"><span class="pre">whenShort</span></code>), <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>; Edited only when the last change was more than a minute after creation. When the kind is all the meta line has to say (a note, board, doc or plain todo) the dates join it on one line (“Todo · Created 25 Oct, 11:52”); otherwise they are a second line. Its parts wrap as the meta line’s do</p></td> </tr> <tr class="row-even"><td><p>Due line</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.list</span> <span class="pre">.due</span></code>, <code class="docutils literal notranslate"><span class="pre">.duechip</span></code>, <code class="docutils literal notranslate"><span class="pre">.todoedit</span></code></p></td> <td><p>a todo’s priority and due date in words (“high · due 3 Nov”; a priority alone “low priority”), <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, its parts never breaking inside (no-break spaces, the line breaking only before a “·”); overdue (<code class="docutils literal notranslate"><span class="pre">.late</span></code>: due before today, not done or cancelled) says “overdue” in <code class="docutils literal notranslate"><span class="pre">--danger</span></code>, semibold: the word carries it, never colour alone; done or cancelled (<code class="docutils literal notranslate"><span class="pre">.closed</span></code>) keeps its due date in <code class="docutils literal notranslate"><span class="pre">--ink-3</span></code>, said as a date (“due 3 Nov”), never “today” or “overdue”. On its page, the due chip leads the tag row (the calendar icon, “overdue 24 Oct · high” with only “overdue” in red, else “Add due date” on an open todo; a closed todo with none shows no chip: Reopen it first), a shortcut into editing: a tap opens the page editor with its <strong>Due</strong> fold open and focus on the due chip pressed (<code class="docutils literal notranslate"><span class="pre">pageedit.ts</span></code>’s <code class="docutils literal notranslate"><span class="pre">due</span></code>). The fold holds <code class="docutils literal notranslate"><span class="pre">.todoedit</span></code> (<code class="docutils literal notranslate"><span class="pre">todoedit.ts</span></code>): one wrapping row of <code class="docutils literal notranslate"><span class="pre">.facet</span></code> chips (<code class="docutils literal notranslate"><span class="pre">.soon</span></code>, a group “Due soon”: <strong>Today</strong>, <strong>Tomorrow</strong>, <strong>Next week</strong> (seven days on by the phone’s calendar), <strong>Pick a date…</strong> and <strong>No date</strong>; the one matching the date set <code class="docutils literal notranslate"><span class="pre">aria-pressed</span></code> on <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code>, semibold, No date while there is none). Pick a date… opens the native date picker (<code class="docutils literal notranslate"><span class="pre">showPicker()</span></code>) of a date input kept under it out of sight (<code class="docutils literal notranslate"><span class="pre">.vh</span></code>, named “Due”, not in the tab order); where the picker can’t be opened the input is shown beside it and focused. Any other day picked, that chip says it (“31 Oct”, with its year when not this one’s; named “Pick a date… 31 Oct”) and is the one pressed. No always-visible date field. Then <strong>Priority</strong> (a small uppercase <code class="docutils literal notranslate"><span class="pre">.flabel</span></code> heading, as the Filters panel’s rows), a native <code class="docutils literal notranslate"><span class="pre">select</span></code> drawn as the chips are (<code class="docutils literal notranslate"><span class="pre">.chipfield</span></code>). No buttons of its own: the editor’s top bar has Cancel and Save. Every row starts at the same left edge. The composer’s Due fold is the same. The fold shows while the kind is Todo, and says what it holds (“24 Oct · high”). A todo is put off by moving its due date. The due line wraps rather than being cut (when it is due outranks one tidy line)</p></td> </tr> <tr class="row-odd"><td><p>Public chip</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.tags</span> <span class="pre">></span> <span class="pre">.pubchip</span></code> (<code class="docutils literal notranslate"><span class="pre">pagehead.ts</span></code>)</p></td> <td><p>on a public page (#497), first in the chip row: a tag chip in every way (<code class="docutils literal notranslate"><span class="pre">--sunk</span></code> fill, the one chip size, no amber), a 16 px globe and “Public”, a link to the page on the public site in a new window (a plain chip when scrap has no <code class="docutils literal notranslate"><span class="pre">SCRAP_PUBLISH_SITE</span></code>). Its tooltip and name say when it was published. The dates line gains a last part, <code class="docutils literal notranslate"><span class="pre">.pubdate</span></code>: “Published 1 Nov, 14:02”, “Publishing…” while it is sent, or “Public · not published yet”. Share’s last row is <strong>Make public…</strong> or <strong>Make private</strong>, with the same globe</p></td> </tr> <tr class="row-even"><td><p>Question</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">dialog.pubask</span></code> (<code class="docutils literal notranslate"><span class="pre">publicask.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">css/publicpages.css</span></code>)</p></td> <td><p>public pages’ in-app questions (Make public’s warning, every time, and Refresh all’s first-time list): a <code class="docutils literal notranslate"><span class="pre">--surface</span></code> card in the middle, <code class="docutils literal notranslate"><span class="pre">--r-lg</span></code>, <code class="docutils literal notranslate"><span class="pre">--shadow-2</span></code>, over a 40 % black backdrop; a semibold <code class="docutils literal notranslate"><span class="pre">--t-md</span></code> title, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code> lines and a bulleted list, a quiet <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> hint; <strong>Cancel</strong> (secondary, focused, so Enter never publishes) and the one amber button. The buttons share a row and stack when the text size leaves no room. Escape, Back or a tap outside is Cancel. Never <code class="docutils literal notranslate"><span class="pre">confirm()</span></code></p></td> </tr> <tr class="row-odd"><td><p>Filters button</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.field-search</span> <span class="pre">.ffield</span></code></p></td> <td><p>a slider icon (filter) at the search field’s end, its whole 48 px width the target, nothing drawn around it, so the box keeps one clean edge at any text size: <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>; while the panel is open (<code class="docutils literal notranslate"><span class="pre">aria-expanded</span></code>) the icon alone turns <code class="docutils literal notranslate"><span class="pre">--accent-ink</span></code> (the active icon); while filters are on (<code class="docutils literal notranslate"><span class="pre">.on</span></code>), a 9 px <code class="docutils literal notranslate"><span class="pre">--accent</span></code> dot on the icon’s corner, ringed in <code class="docutils literal notranslate"><span class="pre">--surface</span></code> (the badge dot’s look). No count: the chips below say what is on</p></td> </tr> <tr class="row-even"><td><p>Filters on</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.refine</span> <span class="pre">.fon</span></code></p></td> <td><p>under the kind bar, each filter on (not the kind row’s kind) as a <code class="docutils literal notranslate"><span class="pre">.facet.on</span></code> chip with a ✕ (<code class="docutils literal notranslate"><span class="pre">aria-label</span></code> “Remove #home”), saying it in words (“#home”, “past month”, “A–Z”, “Written by Claude”), in a row of its own that wraps (never scrolls sideways); no filters, no row. The search screen’s order is: search box, kind bar, Filters on, results header, results</p></td> </tr> <tr class="row-odd"><td><p>Results header</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.rhead</span></code>: <code class="docutils literal notranslate"><span class="pre">.count</span></code>, <code class="docutils literal notranslate"><span class="pre">.acts</span></code></p></td> <td><p>one 48 px line that never changes shape: the count on the left (<code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>, “10 pages”; none when nothing was found, the empty state saying so), then its actions as 48 px ghost buttons on the right. On the search screen <strong>Pin search</strong> and <strong>Copy link</strong> (<code class="docutils literal notranslate"><span class="pre">searchacts.ts</span></code>), icon buttons; a pinned search’s pin is filled (<code class="docutils literal notranslate"><span class="pre">.on</span></code>) on an <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code> disc, named “Unpin search”. The Todos and Topics screens draw their count line with it too: <strong>Refine</strong> (the filter icon, named “Refine in Search: <query>”, tooltip “Refine in Search”), the view as a search. No ⋯ fallback and no amber text links, on any screen. Icon-only is a deliberate exception to “icons with words”: each has an accessible name and a tooltip, and Pin’s and Copy’s toasts (“Pinned”, “Link copied”) say the result in words</p></td> </tr> <tr class="row-even"><td><p>Filters panel</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.fpanel</span></code></p></td> <td><p>opens in place under the search field, above the kind row (the filters it puts on show under the kind row, as chips), a <code class="docutils literal notranslate"><span class="pre">--surface</span></code> card with a <code class="docutils literal notranslate"><span class="pre">--line</span></code> hairline and <code class="docutils literal notranslate"><span class="pre">--r-lg</span></code> corners. Rows (<code class="docutils literal notranslate"><span class="pre">.frow</span></code>, a group each, their rows of 48 px targets touching: the air is inside them), every row starting at the same left edge, its chips wrapping, never scrolling sideways or clipping. <strong>Tags</strong> (<code class="docutils literal notranslate"><span class="pre">.ftags</span></code>) is the <strong>Tag picker</strong> (below): its small uppercase <code class="docutils literal notranslate"><span class="pre">.flabel</span></code> heading in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> and a pill field “Add a tag” (<code class="docutils literal notranslate"><span class="pre">.chipfield</span></code>: drawn as the chips, 32 px in a 48 px target) on one line, the tags it offers flowing on after it, two rows in all. It offers, with nothing typed, the current results’ top tags with their counts, or with no search yet the tags most pages were given, in full (<code class="docutils literal notranslate"><span class="pre">kind/reference</span></code>, not its parent <code class="docutils literal notranslate"><span class="pre">kind</span></code>, which narrows little), counted by the pages with that very tag; as typed, the results’ tags that match (from <code class="docutils literal notranslate"><span class="pre">search/facets?top=1000</span></code>, asked for only then; before any search, from every tag in use); at most 8, those that fit; none, a line saying why. <strong>When</strong> (<code class="docutils literal notranslate"><span class="pre">.frow.inline</span></code>: its heading, then its chips on the same line when they fit: 1w, 1m, 1y, Dates…, and By created date once a range is on; Dates… opens From and To date inputs, applied as picked); then <strong>Arrange</strong> (<code class="docutils literal notranslate"><span class="pre">details.farrange</span></code>), folded to one quiet 48 px line: its heading, what is chosen in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> (“Last edited · None”), a chevron (down folded, up open), kept folded or open on the device (<code class="docutils literal notranslate"><span class="pre">scrap.fold.arrange</span></code>; folded at first); open, <strong>Order</strong> and <strong>Group</strong>, each leading with a small sub-label (<code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>) on the line of its chips: <strong>Order</strong> (the default first: Best match with words typed, else Last edited; then Newest, Oldest, Last edited with words typed, A–Z, and for <code class="docutils literal notranslate"><span class="pre">kind:todo</span></code> Due soonest and Priority); a chosen chip <code class="docutils literal notranslate"><span class="pre">aria-pressed</span></code> on <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code>, semibold (a chip’s count, <code class="docutils literal notranslate"><span class="pre">small</span></code>, shares the label’s line height so it sits on its baseline rather than raised); then <strong>Clear filters</strong> (<code class="docutils literal notranslate"><span class="pre">button.link</span></code>) while any are on. At 390 px with 130% text, panel open and Arrange folded, the first result’s top is at 511 px with no filter on and 591 px with one long tag on (the nav at 775). The tag filters on, under the kind row, say their full path (“#tech/epics/thoth”), as every tag chip does; a long one wraps inside its chip after a “/” (<code class="docutils literal notranslate"><span class="pre"><wbr></span></code>, else <code class="docutils literal notranslate"><span class="pre">overflow-wrap:</span> <span class="pre">anywhere</span></code>), never wider than the screen, the ✕ kept at its end. <code class="docutils literal notranslate"><span class="pre">refine.ts</span></code>, a chunk of its own, kept offline by the service worker</p></td> </tr> <tr class="row-odd"><td><p>Todos screen</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.todos</span></code></p></td> <td><p>the segmented control again, as the kind row (<strong>Now</strong>, a clock; <strong>Upcoming</strong>, the due calendar: due from tomorrow to 7 days away; <strong>All</strong>, the grid: each sets its own order, so no Sort), then the results header (<code class="docutils literal notranslate"><span class="pre">.rhead</span></code>, above: “12 open todos”, and at its end <strong>Refine</strong>), then Now’s groups under <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> headings (“Overdue · 3”, “Today”, “High priority”, “Open”: not “Later”, which is a kind), each its own <code class="docutils literal notranslate"><span class="pre">.list</span></code>; rows are two lines on every tab: the title (wrapping to a second line rather than cut: no thumbnail beside it), then the due line (CSS <code class="docutils literal notranslate"><span class="pre">order</span></code>) and a done or cancelled word; no edit date, no kind icon and no thumbnail (every row is a todo, so the room goes to the title and every title lines up; a todo with a photo has a small picture mark after its details line instead). New todo is the top bar’s + (above)</p></td> </tr> <tr class="row-even"><td><p>Topics screen</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.topics</span></code> (<code class="docutils literal notranslate"><span class="pre">topics.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">topics.css</span></code>: a lazy chunk)</p></td> <td><p>the topic pages Claude keeps (#261), from <code class="docutils literal notranslate"><span class="pre">GET</span> <span class="pre">/api/topics</span></code>: a filter box (<code class="docutils literal notranslate"><span class="pre">.field-search</span></code>, “Filter topics”, matching every word typed in the title, summary or area; kept in the history entry, so Back from a topic finds it as typed), the results header (<code class="docutils literal notranslate"><span class="pre">.rhead</span></code>: “12 topics”, “2 of 12 topics” while filtered, and <strong>Refine</strong>, to the search <code class="docutils literal notranslate"><span class="pre">is:topic</span></code>), then a group per area under an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> (“epics · 4”; <em>Other</em> for no area, last), each a <code class="docutils literal notranslate"><span class="pre">.list</span></code> of text rows with no thumbnail or kind icon, so every title lines up: the title, the synthesis’s lead on one line cut with …, and “3 sources · 25 Oct, 10:04”. A topic new or changed since this device last looked has a 9 px <code class="docutils literal notranslate"><span class="pre">--accent</span></code> dot at its row’s end by its title (<code class="docutils literal notranslate"><span class="pre">.new</span></code>, named “new or changed”, with a tooltip: not colour alone), kept until the app closes or the topic’s page is shown, however it was reached (the list, a</p></td> </tr> <tr class="row-odd"><td><p>search, a link, the pager); what was seen is <code class="docutils literal notranslate"><span class="pre">scrap.topics.seen</span></code> on the device, and the first look shows no dots. The rows are one pager trail, “Topics”. Empty: the open book, “No topics yet.” and a line on the weekly pass</p></td> <td><p></p></td> <td><p></p></td> </tr> <tr class="row-even"><td><p>Store health</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.health</span></code> (<code class="docutils literal notranslate"><span class="pre">health.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">health.css</span></code>, a chunk of its own; <code class="docutils literal notranslate"><span class="pre">/health</span></code>)</p></td> <td><p>opened from Settings: an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> “Store health”, a <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> hint line, then the health checks as one <code class="docutils literal notranslate"><span class="pre">.list</span></code> group, a row each (Orphans, Untagged, Stale todos, Duplicates, Dangling links, Outdated write-ups: <code class="docutils literal notranslate"><span class="pre">checks.ts</span></code>): its name (<code class="docutils literal notranslate"><span class="pre">strong</span></code>, wrapping) over what it finds (<code class="docutils literal notranslate"><span class="pre">small</span></code>), and at the row’s end its count (<code class="docutils literal notranslate"><span class="pre">.n</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-md</span></code> semibold <code class="docutils literal notranslate"><span class="pre">--ink</span></code>, tabular figures; <code class="docutils literal notranslate"><span class="pre">0</span></code> in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> at 500), a link to the check’s search (accessible name “Orphans: 3”), rows 48 high at least (no thumbnail); a hint line under it says an outdated write-up waits for the owner, and that the nightly and weekly passes refresh related pages and topic syntheses (not listed). No nav tab is current; Back (the phone’s) goes back to where it was opened from. The Filters on row names a check’s chip by its name (“Dangling links”)</p></td> </tr> <tr class="row-odd"><td><p>Trash</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.trash</span></code> (<code class="docutils literal notranslate"><span class="pre">trash.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">trash.css</span></code>, a chunk of its own; <code class="docutils literal notranslate"><span class="pre">/trash</span></code>)</p></td> <td><p>opened from Settings: an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> “Trash”, a <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> hint line saying how long a deleted page stays (“Deleted pages stay here for 30 days, then scrap deletes them for good…”, from <code class="docutils literal notranslate"><span class="pre">GET</span> <span class="pre">/api/trash</span></code>’s <code class="docutils literal notranslate"><span class="pre">days</span></code>; with purging off, “…until you restore them”), then the pages in the trash as one <code class="docutils literal notranslate"><span class="pre">.list</span></code> group, most recently deleted first, a row each with no thumbnail or kind icon: its title (<code class="docutils literal notranslate"><span class="pre">strong</span></code>, cut with …) over what happens to it (<code class="docutils literal notranslate"><span class="pre">small</span></code>, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, never cut, a line per part with its date kept with the words before it: “Deleted 20 Oct” then “deleted for good after 19 Nov”, “deleted for good at the next nightly clean-up” once due, “kept: Claude won’t make this topic again” for a topic page; the “ · “ between them is in the text for a screen reader, not drawn), and at its end <strong>Restore</strong> (a secondary button with the retry icon, 48 high, accessible name “Restore <title>”). Under the list, when it stops short of the trash (100 rows), a hint line “Showing the 100 most recently deleted of 130.” Restore brings the page back, the row goes (focus moves to the next row’s Restore, or to the heading once the last row has gone and the empty state shows), and a toast “Restored: <title>” offers <strong>Open</strong>; a failure toasts why and the button works again. Empty: the trash icon, “The trash is empty.” A trashed page has no page view, so a row is not a link. No nav tab is current; Back (the phone’s) goes back to where it was opened from</p></td> </tr> <tr class="row-even"><td><p>Done fold</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.donefold</span></code> (<code class="docutils literal notranslate"><span class="pre">details</span></code>)</p></td> <td><p>Now’s done and cancelled todos, folded at the end: a 48 px summary in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> with the chevron of <code class="docutils literal notranslate"><span class="pre">.claude-text</span></code></p></td> </tr> <tr class="row-odd"><td><p>Search sections</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.groups</span></code>, <code class="docutils literal notranslate"><span class="pre">h2.label</span></code></p></td> <td><p>the search screen’s grouped results (Group row in Filters: Type, Now, Due, Area, None, A–Z, Tag; the list’s own first; for <code class="docutils literal notranslate"><span class="pre">is:topic</span></code> Area is the default and the row reads Area, None, A–Z), also drawn by the Topics screen. A–Z is one continuous card: each letter a slim divider row with no count, sticky under the top bar; the other groups keep their counts, drawn as Now is: an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> heading with its count (“To watch · 4”) over each section’s <code class="docutils literal notranslate"><span class="pre">.list</span></code>, done and cancelled in a <code class="docutils literal notranslate"><span class="pre">.donefold</span></code> at the end. The server orders by section, so a page of results never splits one and Show more only appends. A Type heading is a link to that type alone (<code class="docutils literal notranslate"><span class="pre">later:watch</span></code>) unless one is asked. Later rows end with the type (<code class="docutils literal notranslate"><span class="pre">.media</span></code>: “Book”); the editor’s <strong>Type</strong> fold (a <code class="docutils literal notranslate"><span class="pre">select</span></code>, on Later pages) sets it</p></td> </tr> <tr class="row-even"><td><p>Row tick</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.list</span> <span class="pre">.tick</span></code></p></td> <td><p>a todo’s or a Later’s row: a 48 px toggle (<code class="docutils literal notranslate"><span class="pre">aria-pressed</span></code>, <code class="docutils literal notranslate"><span class="pre">aria-label</span></code> “Done: title”) at its end, beside the row’s link rather than in it, so tapping it opens nothing; an empty box in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> (the tick icon), ticked in <code class="docutils literal notranslate"><span class="pre">--accent-ink</span></code> when done, a bar across it (<code class="docutils literal notranslate"><span class="pre">aria-pressed="mixed"</span></code>, <code class="docutils literal notranslate"><span class="pre">--accent-ink</span></code>) while only some of the page’s task boxes are ticked, its row saying “2 of 5”; a cancelled one has a grey dash in its box, and pressing it marks it done; it slides with the row, and a drag that starts on it swipes the row</p></td> </tr> <tr class="row-odd"><td><p>Claude card</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.claude</span></code>, <code class="docutils literal notranslate"><span class="pre">.enrich</span></code> (<code class="docutils literal notranslate"><span class="pre">ingest.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">writeup.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">enrich.ts</span></code>)</p></td> <td><p>everything Claude makes, so it never reads as the owner’s text: a <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> card, <code class="docutils literal notranslate"><span class="pre">--r-md</span></code>, at two sizes: <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code> for all its text (its summary and a write-up’s headings too, semibold) and <code class="docutils literal notranslate"><span class="pre">--t-xs</span></code> for its by-lines and kinds. Its first line (<code class="docutils literal notranslate"><span class="pre">.ingest</span></code>) is its title (<code class="docutils literal notranslate"><span class="pre">.ctitle</span></code>, bold <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>), saying what it holds: <strong>About this page</strong> (one, after the page’s text and its files; under each photo or file is the Claude’s reading fold instead, below), and at that line’s end its action: <strong>Generate</strong> the first time (a button), <strong>Regenerate</strong> after (<code class="docutils literal notranslate"><span class="pre">button.link</span></code>, quiet: rarely wanted), <strong>Retry</strong> when it failed, <strong>Resume</strong> when paused. Under the title, only while Claude has it, where Claude is up to as a line of words, no pill (<code class="docutils literal notranslate"><span class="pre">.state</span></code>, <code class="docutils literal notranslate"><span class="pre">role="status"</span></code>, <code class="docutils literal notranslate"><span class="pre">.failed</span></code> in <code class="docutils literal notranslate"><span class="pre">--danger</span></code>: “Waiting for Claude”, “Claude is reading it…”, “Paused (reason): nothing is lost”, “Claude couldn’t read it: …” in <code class="docutils literal notranslate"><span class="pre">--danger</span></code>). Never read, a line in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> says what Claude would do (not once it failed); then its parts as folds (<code class="docutils literal notranslate"><span class="pre">.claude-text</span></code>: a chevron, a semibold <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> heading, a <code class="docutils literal notranslate"><span class="pre">--line</span></code> rule above): In detail (a board’s write-up or a topic’s synthesis, in About this page: a by-line “Claude · 25 Oct, 22:00 · v2”, the model by name in its tooltip, then its text, with Guidance for Claude and Versions as folds inside), a board’s folded until opened, a topic’s open until closed (it is what the page is for); Summary (a board’s is its write-up’s lead), then Topics (a source’s, below), Sources (a topic’s, below) and Related, open until closed; each remembered on the phone. Claude’s links (a source’s Topics, a topic’s Sources, Related, a write-up’s) are the app’s links (Links, above): <code class="docutils literal notranslate"><span class="pre">--ink</span></code>, regular, underlined in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> (<code class="docutils literal notranslate"><span class="pre">--line-strong</span></code> is under 3:1 on <code class="docutils literal notranslate"><span class="pre">--sunk</span></code>), each a 48 px target; amber links are the owner’s text’s. Related is a list of pages under its heading’s text: each a line of its own, indented 22 px past the chevron with its kind’s icon (16 px, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>) hung in the indent so a wrapped title aligns, 4 px between items (each link’s 48 px target is its inline padding-block, overlapping its neighbours’, not a gap), and its note (· links here, · in a write-up, · in a topic) <code class="docutils literal notranslate"><span class="pre">nowrap</span></code>, running on after the title. About this page’s is Ask (it reads the page again and, on a board, writes it up and finds related pages again too), named for the write-up on a board; its state line is the write-up’s state, else the page’s (a pause always the page’s, with Resume)</p></td> </tr> <tr class="row-even"><td><p>Topic parts</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.enrich</span> <span class="pre">.topics</span></code>, <code class="docutils literal notranslate"><span class="pre">.enrich</span> <span class="pre">.sources</span></code> (<code class="docutils literal notranslate"><span class="pre">topic.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">topic.css</span></code>)</p></td> <td><p>Claude’s topic pages (#261), in About this page. On a source, <strong>Topics</strong> is a fold (open until closed) drawn as Related is, straight before it: a line per topic, the topic icon hung in the indent, its title an <code class="docutils literal notranslate"><span class="pre">--ink</span></code> link, underlined. A card with only topics to show is still the card, with that one fold. Related marks a topic citing it “· in a topic”. On a topic, after In detail, <strong>Sources · 3</strong> (a fold, open until closed): a 48 px row per source, its title (an <code class="docutils literal notranslate"><span class="pre">--ink</span></code> link, underlined) then “· Note” (its kind) in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> at <code class="docutils literal notranslate"><span class="pre">--t-xs</span></code>, and a 48 px ghost ✕ (the close icon in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, “Take out <title>”); one taken out stays, its title struck through in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, “· taken out”, and <strong>Put back</strong> (<code class="docutils literal notranslate"><span class="pre">button.link</span></code>) in the ✕’s place; taking out toasts “Taken out: <title>” with <strong>Undo</strong>, and focus goes to that source’s new button. A failure toasts why, the button usable again. Offline the ✕ and Put back are disabled, their tooltip saying why (as a page’s task boxes are). Its Related leaves its sources out. The page’s notes come before the card: empty, one <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> line (<code class="docutils literal notranslate"><span class="pre">.page</span> <span class="pre">></span> <span class="pre">.notes</span></code>) says where they go</p></td> </tr> <tr class="row-odd"><td><p>Link card</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.md</span> <span class="pre">></span> <span class="pre">.linkcard</span></code></p></td> <td><p>on a Later page, a paragraph that is only a link to the web (<code class="docutils literal notranslate"><span class="pre">cards()</span></code> in <code class="docutils literal notranslate"><span class="pre">render.ts</span></code>) is drawn as a file card: its site (no <code class="docutils literal notranslate"><span class="pre">www.</span></code>) over its path, tidied to words (“wiki / Integrated information theory”: no escapes, trailing slash or <code class="docutils literal notranslate"><span class="pre">.html</span></code>; or the link’s own text, if it was given one), each cut to one line, and <strong>Open</strong> (the open icon) with a button’s look at its end. The whole card is the link, one tab stop</p></td> </tr> <tr class="row-even"><td><p>Draft row</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.capture</span> <span class="pre">.draft</span></code></p></td> <td><p>the composer’s kept draft (left by Back), one quiet line on <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> over Capture’s tiles, the whole width, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>: the kind’s icon (a photo’s for photos), “Draft: “, its first line and what it holds (“Trip · 2 photos, 1 file”, <code class="docutils literal notranslate"><span class="pre">says.ts</span></code>, as a waiting upload’s row says it), cut with …; tap to carry on; a 48 px ✕ (“Discard draft”) at its end, with <strong>Undo</strong> in a toast. The tiles always start a fresh capture; beside a kept draft it is saved as typed under a key of its own (<code class="docutils literal notranslate"><span class="pre">fresh.ts</span></code>, one per composer, held by a Web Lock while open), and takes the kept one’s place, with <strong>Undo</strong>, when left by Back or when found after the app was killed (its lock let go: another tab’s open composer is left alone)</p></td> </tr> <tr class="row-odd"><td><p>Offline line</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.strip</span> <span class="pre">.offline</span></code></p></td> <td><p>Capture offline, in Latest’s place: “Offline: showing what’s on this phone”, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>; Latest draws itself again when the phone is back online</p></td> </tr> <tr class="row-even"><td><p>Figure</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.orig</span></code></p></td> <td><p>a page’s photo as a preview, with no caption: a tap opens the viewer, which names it (<code class="docutils literal notranslate"><span class="pre">.viewer</span> <span class="pre">.name</span></code>, a pill at the top left, cut with … when long) and has its <strong>Download</strong>. A file the server can’t preview is a row with its name and <strong>Download</strong> (<code class="docutils literal notranslate"><span class="pre">.save</span></code>, whole words)</p></td> </tr> <tr class="row-odd"><td><p>Claude’s reading</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.reading</span></code>, <code class="docutils literal notranslate"><span class="pre">.reading-fold</span></code>, <code class="docutils literal notranslate"><span class="pre">.reading-state</span></code> (<code class="docutils literal notranslate"><span class="pre">reading.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">css/reading.css</span></code>)</p></td> <td><p>under each photo, image or file that has a reading (#521): one fold, always closed when the page opens and not remembered, its line as every other fold’s (the chevron before the words “Claude’s reading”, semibold <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>, a 48 px target); open, Claude’s card (<code class="docutils literal notranslate"><span class="pre">.claude</span></code>) with the description (<code class="docutils literal notranslate"><span class="pre">--t-md</span></code>) and the text read (<code class="docutils literal notranslate"><span class="pre">pre</span></code>), a PDF’s text fetched on the first open, and at the card’s end, right, <strong>Regenerate</strong> (<code class="docutils literal notranslate"><span class="pre">button.link</span></code>, quiet; it asks before replacing a drawing the owner edited) or <strong>Generate</strong> for one never sent (a button). Where the reading is up to is outside the fold, above it, as words with no pill (<code class="docutils literal notranslate"><span class="pre">.state</span></code>, <code class="docutils literal notranslate"><span class="pre">role="status"</span></code>, <code class="docutils literal notranslate"><span class="pre">.failed</span></code> in <code class="docutils literal notranslate"><span class="pre">--danger</span></code>), Retry or Resume at the line’s end; “couldn’t read” too. An image Claude made (<code class="docutils literal notranslate"><span class="pre">add_svg</span></code>) has nothing under it. Public pages publish the same fold, closed, without its buttons</p></td> </tr> <tr class="row-even"><td><p>File row</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.filerow</span></code> (<code class="docutils literal notranslate"><span class="pre">filerow.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">files.css</span></code>)</p></td> <td><p>a file below a page’s text that is not a photo (a PDF, a text file): a card with the file icon, its name (wrapping) and, once read, a PDF’s page count under it (<code class="docutils literal notranslate"><span class="pre">small</span></code>, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>: “12 pages”); the card opens the file in a new tab (in Android’s home-screen app, in place, so the phone’s Back returns to the page: #443), <strong>Download</strong> beside it. Under the name, the quiet hint “Text couldn’t be read; not searchable” (<code class="docutils literal notranslate"><span class="pre">.hint</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>) only when reading failed or found no text: nothing while it waits or is read. No Claude panel</p></td> </tr> <tr class="row-odd"><td><p>Thumbnail</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.list</span> <span class="pre">img</span></code>, <code class="docutils literal notranslate"><span class="pre">.thumbs</span> <span class="pre">img</span></code>, <code class="docutils literal notranslate"><span class="pre">.originals</span> <span class="pre">img</span></code></p></td> <td><p>fixed size or 4:3 frame reserved before load, shimmering <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> placeholder</p></td> </tr> <tr class="row-even"><td><p>Field</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">input</span></code>, <code class="docutils literal notranslate"><span class="pre">textarea</span></code></p></td> <td><p>surface, strong edge, 2 px accent-ink focus ring</p></td> </tr> <tr class="row-odd"><td><p>Search field</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.field-search</span></code></p></td> <td><p>pill with a search icon, the Filters button (above) at its end, drawn inside it; on the search screen a ✕ (<code class="docutils literal notranslate"><span class="pre">.clear</span></code>, a 48 px ghost button, “Clear search”) at its end while it has text, in place of the browser’s own</p></td> </tr> <tr class="row-even"><td><p>Recent searches</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.recents-top</span></code>, <code class="docutils literal notranslate"><span class="pre">.list.recents</span></code></p></td> <td><p>under the search field while it is empty and focused (gone once it has text, or loses focus): an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> “Recent” with <strong>Clear all</strong> (<code class="docutils literal notranslate"><span class="pre">button.link</span></code>) at its end, then up to 10 past searches as one <code class="docutils literal notranslate"><span class="pre">.list</span></code> group (<code class="docutils literal notranslate"><span class="pre">role="group"</span></code>, “Recent searches”), newest first (a search is kept when run: Enter, a tap on a result, or 3 s with results; run again it moves to the top, kept once, its spaces tidied and its case kept): a search icon and the query as typed, start-aligned and cut with … on one line, then its pin (a 48 px ghost icon, “Pin <query>”; once pinned, filled in <code class="docutils literal notranslate"><span class="pre">--ink</span></code>, “Unpin <query>”) and a 48 px ghost ✕ (“Remove <query>”), both <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>. Under the kind row, the newest pages under an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> “Latest”, as on Capture. A tap on the list keeps the field focused until the choice is made; a choice runs it and the keyboard goes. No history: no Recent block (Pinned, if any, still shows alone). There are no saved searches beyond these two: a recent one, and a pinned one (the Pinned page’s link). On this device only (<code class="docutils literal notranslate"><span class="pre">scrap.searches</span></code>, through <code class="docutils literal notranslate"><span class="pre">kept()</span></code> in <code class="docutils literal notranslate"><span class="pre">api.ts</span></code>, the one guard every <code class="docutils literal notranslate"><span class="pre">localStorage</span></code> use goes through: with storage blocked it keeps nothing and never throws)</p></td> </tr> <tr class="row-odd"><td><p>Editor sheet</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.editor</span></code></p></td> <td><p>full screen over the keyboard; top bar Cancel / Preview / Save. The text box asks for what the kind needs: “Note or link”, “What needs doing?”, “Link or title”, “What the board is about”, “What the document is”. Just above the keyboard, one line of seven equal keys (<code class="docutils literal notranslate"><span class="pre">.keys</span></code>, on <code class="docutils literal notranslate"><span class="pre">--bg</span></code> under a <code class="docutils literal notranslate"><span class="pre">--line</span></code> hairline, gone in Preview; ghost buttons at least 48 px tall, an icon drawn in <code class="docutils literal notranslate"><span class="pre">editor.ts</span></code> over a <code class="docutils literal notranslate"><span class="pre">--t-xs</span></code> word; where seven words won’t fit, the icons alone: a container query on the row, under about 413 px at 100% text, so a phone (320, 360, 390 px) is icons and 430 px words, and wider at a larger text): <strong>Task</strong> (the line, or each line selected, a task, <code class="docutils literal notranslate"><span class="pre">-</span> <span class="pre">[</span> <span class="pre">]</span> </code>, a bullet or number keeping its marker; or plain items again if all are tasks), <strong>Bullet</strong> (<code class="docutils literal notranslate"><span class="pre">-</span> </code>, or plain again), <strong>Number</strong> (<code class="docutils literal notranslate"><span class="pre">1.</span></code> <code class="docutils literal notranslate"><span class="pre">2.</span></code> <code class="docutils literal notranslate"><span class="pre">3.</span></code> in order at each indent, on from a number just above; or plain again), <strong>Indent</strong> and <strong>Outdent</strong> (a level in or out: nested at the parent item’s text column, 2 spaces under <code class="docutils literal notranslate"><span class="pre">-</span> </code>, 3 under <code class="docutils literal notranslate"><span class="pre">1.</span> </code>, as CommonMark needs, a numbered item renumbered in its new place; in a code block, 2 spaces, and the other keys do nothing), <strong>Link</strong> (a chain, drawn as the shell’s link icon is; <code class="docutils literal notranslate"><span class="pre">[[]]</span></code> at the caret, the caret between the brackets, and the Link picker, below: a page picked is <code class="docutils literal notranslate"><span class="pre">[[slug]]</span></code>, a selection its words, <code class="docutils literal notranslate"><span class="pre">[[slug|words]]</span></code> (in a table’s row, every bar in it escaped with a backslash), the caret after the <code class="docutils literal notranslate"><span class="pre">]]</span></code>; closed with none, the brackets stay with the caret inside, a selection as it was, and Link pressed again there uses them; nothing in a code block), and last <strong>Undo</strong> (the browser’s own undo, <code class="docutils literal notranslate"><span class="pre">execCommand("undo")</span></code>, as Ctrl+Z, for Android keyboards that have none; typing and the other keys share its history, so it takes them back in order; no Redo; on every screen the row shows, so on an iPhone too. Nothing reports what the history holds, so it is judged against a floor: disabled, at the usual 0.5 opacity, on a fresh editor or a draft brought back, enabled by input typed in the text box, disabled again when an undo brings the text back to the floor or changes nothing. The floor is the text it opened with, or the text as it was at the last typing in another text field (not the Link picker’s box, whose steps leave the history when it closes) (the history is the page’s: past that an undo would take back a tag or the title), the last text set by code (a file added, which the history doesn’t hold, so a file added can’t be undone) or <strong>Discard it</strong>; a kind, date or select picked doesn’t move it. The phone’s or computer’s own undo (Ctrl+Z, a shake) is judged the same way. So Undo stops at the floor even if the text was deleted and retyped before it; older text is Cancel’s or a saved revision’s. An undo that reaches another field anyway is redone at once and the key goes off); a tap keeps the keyboard up. In the text, Enter continues a bullet, numbered or task list, GitHub-style, and Enter on an empty item ends it (<code class="docutils literal notranslate"><span class="pre">listedit.ts</span></code>)</p></td> </tr> <tr class="row-even"><td><p>Link picker</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.linkpick</span></code> (<code class="docutils literal notranslate"><span class="pre">linkpick.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">linkpick.css</span></code>)</p></td> <td><p>the Link key’s page picker, a chunk of its own loaded on the first press: a modal <code class="docutils literal notranslate"><span class="pre">dialog</span></code> (“Link to a page”) at the top of what the keyboard leaves (the visual viewport), at most 32rem wide, <code class="docutils literal notranslate"><span class="pre">--surface</span></code> with <code class="docutils literal notranslate"><span class="pre">--shadow-2</span></code> over a 40 % backdrop; the search field (“Find a page”, focused as it opens) with a 48 px ✕ (“Close”) at its end, an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> “Recent” (the 8 pages changed last) or, as you type, “Matching” (the search’s first 8), then a 48 px ghost row each: the kind’s icon in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> and the title, on up to two lines, cut with …. Enter picks the first match for what is typed, nothing with the box empty (searching first if the rows are still for an earlier query); a second press while it loads or is open does nothing. The page being edited is not offered. Back (the phone’s close request), Escape, a tap on the backdrop or ✕ close it with nothing picked; a computer’s Back closes it and leaves the editor open. Focus returns to the text box with the caret placed. Offline, one quiet line instead of rows</p></td> </tr> <tr class="row-odd"><td><p>Embed chips</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.editor</span> <span class="pre">></span> <span class="pre">.embeds</span></code></p></td> <td><p>the page editor and the composer show each of the page’s own images and files (the composer’s not yet uploaded: a thumbnail from the phone’s own bytes, <code class="docutils literal notranslate"><span class="pre">pending.ts</span></code>) as a short token (<code class="docutils literal notranslate"><span class="pre">⟦image</span> <span class="pre">1⟧</span></code>, <code class="docutils literal notranslate"><span class="pre">⟦file</span> <span class="pre">2:</span> <span class="pre">scan.pdf⟧</span></code>; <code class="docutils literal notranslate"><span class="pre">embeds.ts</span></code>), and under the fields, above the keys, a chip each (<code class="docutils literal notranslate"><span class="pre">.embed</span></code>: a thumbnail, or the file icon, and its name, cut with …) in one line that scrolls sideways; a tap on a chip selects its token (to cut and paste it elsewhere), its ✕ (“Remove image 1”; 40 px wide, 48 high) takes the token out, with its line if alone on one. Saved, the tokens become the Markdown they stood for, byte for byte where none was touched; a deleted or mangled token is no embed. Gone in Preview</p></td> </tr> <tr class="row-even"><td><p>Editor fold</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.fold</span></code> (<code class="docutils literal notranslate"><span class="pre">details</span></code>)</p></td> <td><p>in the composer and editor, the rarely changed parts folded to one 48 px line each above the text, so the text stays the hero: <strong>Due</strong>, first, while the kind is Todo (the calendar, “24 Oct · high”, or “none”), then <strong>Tags</strong> (the tag icon, “3 · #work #garden…”, or “none”), in that order in the composer and the page editor. The page editor is where a page’s due date, priority and tags are changed: its due chip opens it with Due open; the name in <code class="docutils literal notranslate"><span class="pre">--ink</span></code>, semibold, what it holds in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, cut with …, a chevron at its end (down folded, up open); tapped, the controls open in place under it and nothing takes focus. Two folds read as one group. Under the text, the composer’s and the page editor’s <strong>Camera</strong>, <strong>Gallery</strong> and <strong>File</strong> (the group “Add photos and files”) are ghost buttons in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> at <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>, three equal ones on one line that never wraps: where an icon beside each word won’t fit (a narrow phone at a large text size: a container query on the row) each has its icon over its word, as the editor’s keys do; in the page editor the text gives their row (and the embeds’ row) its room, so they show whole above the keys. On a computer <strong>Add files</strong> (<code class="docutils literal notranslate"><span class="pre">.desk</span></code>) alone in their place</p></td> </tr> <tr class="row-odd"><td><p>Share menu</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.share-menu</span></code></p></td> <td><p>Share’s choice, a popover just above it (below it where it does not fit above, as on a computer, whose tools are at the top; capped to the room there, scrolling past it): <strong>Link</strong>, the content (<strong>Text</strong> on a page, <strong>Photo</strong> in the viewer), and on a page <strong>Print / Save as PDF</strong>, <strong>HTML</strong> (<strong>Download HTML</strong> where the file can’t be shared), <strong>Zip</strong> and <strong>Download</strong> (a link drawn as the rows are; tapping it closes the menu). On a computer with no share sheet a page’s menu copies instead: <strong>Copy link</strong>, <strong>Copy as Markdown</strong> and <strong>Copy as rich text</strong> in place of Link and Text, each saying so in a toast. Ghost rows on <code class="docutils literal notranslate"><span class="pre">--surface</span></code> with <code class="docutils literal notranslate"><span class="pre">--shadow-2</span></code>; a tap elsewhere, Back or Share again closes it. What Share and Download do is a chunk of its own (<code class="docutils literal notranslate"><span class="pre">sharing.ts</span></code>), loaded on the first tap</p></td> </tr> <tr class="row-even"><td><p>Tag line</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.tags</span></code> (<code class="docutils literal notranslate"><span class="pre">pagehead.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">chipfold.ts</span></code>)</p></td> <td><p>one row of same-size chips under a page’s meta line, read only, drawn inside 48 px targets and wrapping to at most two rows: a todo’s due chip first (<code class="docutils literal notranslate"><span class="pre">.duechip</span></code>, the calendar icon; always shown on an open todo; a tap opens the page editor at its Due fold), then its tags, <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code> pills that each link to their search (<code class="docutils literal notranslate"><span class="pre">/search?q=tag:car</span></code>, which includes its children), each saying its <strong>full path</strong> (“#kind/project-management”, drawn by <code class="docutils literal notranslate"><span class="pre">taglabel.ts</span></code>, shared with the tag picker and the Filters on chips; never shortened), wrapping inside the chip after a “/” (<code class="docutils literal notranslate"><span class="pre"><wbr></span></code>) when longer than the row, never sideways, its accessible name and tooltip “Tag kind/project-management”. Beside the due chip the tags are grouped in a <code class="docutils literal notranslate"><span class="pre">.tagset</span></code>, so they go together to the next line when they do not all fit beside it (at 360 px the due chip is alone on a line). No + and no in-place editor: tags are changed in the editor (Edit). Pills pad by <code class="docutils literal notranslate"><span class="pre">0.6em</span></code>, so a larger text size grows them with the text, not more. When the chips need more than two rows (measured, and again when the row’s width or a chip’s size changes), the tags that don’t fit are hidden and a <strong>+N</strong> pill (“+3”, “Show 3 more tags”: how many more, a plain <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> pill, semibold, never a ringed + that would read as “add”) after the last one shown shows them all in place; then a <strong>−</strong> pill at the end (“Show fewer tags”) folds them to two rows again. Focus stays on it; a focused tag folded away hands focus to +N. All fitting in two rows: neither. Shown in full, a page stays so while it is open (redrawn too), not on another page. The first tag always shows. A tag Claude added (<code class="docutils literal notranslate"><span class="pre">a.ai</span></code>) is the same pill with “✦” before its name, its accessible name adding “added by Claude” (the tag picker says and marks Claude’s the same way). A page with no tags and no due chip has no row. A <code class="docutils literal notranslate"><span class="pre">--line</span></code> hairline (<code class="docutils literal notranslate"><span class="pre">hr.headend</span></code>) under the header ends it on a phone (16 px above, 20 px below, across the text column); on a computer there is none, the action row under the header having its own; the meta and dates lines clip sideways (<code class="docutils literal notranslate"><span class="pre">overflow-x:</span> <span class="pre">clip</span></code>, with 4 px of padding and a matching negative margin, so a focus ring or the 48 px “by Claude” target is not cut)</p></td> </tr> <tr class="row-odd"><td><p>Tag picker</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.pick</span></code> (<code class="docutils literal notranslate"><span class="pre">tagpick.ts</span></code>), <code class="docutils literal notranslate"><span class="pre">.tagedit</span></code>, <code class="docutils literal notranslate"><span class="pre">.ftags</span></code></p></td> <td><p>one picker wherever tags are picked: the tag editor (<code class="docutils literal notranslate"><span class="pre">tagedit.ts</span></code>: the composer’s and the page editor’s Tags fold) and Search’s Filters → Tags (<code class="docutils literal notranslate"><span class="pre">refine.ts</span></code>). Every chip says the tag’s <strong>full path</strong> (“#kind/project-management”, <code class="docutils literal notranslate"><span class="pre">taglabel.ts</span></code>), wrapping inside the chip after a “/” when longer than the row; its tooltip and accessible name too (“Add #kind/project-management”, “Filter by #…”, “Remove #…”). <strong>Chosen</strong> tags are filled <code class="docutils literal notranslate"><span class="pre">.facet.on</span></code> chips with a ✕, which wrap; Claude’s marked ✦ as on the page (in Search the chosen are the Filters on chips under the kind row). <strong>Offered</strong> tags are outlined <code class="docutils literal notranslate"><span class="pre">.facet</span></code> chips (a <code class="docutils literal notranslate"><span class="pre">--line-strong</span></code> ring, never dashed), one tap each, with their counts as <code class="docutils literal notranslate"><span class="pre">small</span></code> <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> numbers in Search only; they wrap to <strong>at most two rows</strong> (<code class="docutils literal notranslate"><span class="pre">.offers</span></code>, measured: a chip that would start a third row is left out, again when the width or the text size changes; no “+N” and nothing scrolls: typing narrows them). Up to six in the editor, eight in Search, those of them that fit. With nothing typed, the editor offers the most used tags not on it yet (a group “Most used tags”), Search its results’ top tags (“Tags in these results”); as you type, the tags in use that match, those starting with it first, a part after a / counting (“Matching tags”), and a tap on one replaces what was typed; Enter adds what is typed (the editor) or the first match (Search). The box (“Add a tag”) is a <code class="docutils literal notranslate"><span class="pre">.chipfield</span></code>, drawn as the chips. A second tap within 400 ms is ignored (the chip that slid under the finger); focus goes to the chip now in the tapped one’s place. Offline, none are offered. Our own row, not a native <code class="docutils literal notranslate"><span class="pre">datalist</span></code>, whose menu covers the keyboard on a phone. Not on a page: a page’s chips are read only</p></td> </tr> <tr class="row-even"><td><p>Toast</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.toast</span></code></p></td> <td><p>dark slab above what is under the thumb, amber text action. A capture saved online says “Saved” at once (offline, on another screen than Capture, “Saved on the phone: uploads when online”); if the server then refuses it, a second toast says “Kept on this phone” (its row says why, with Retry). “Update available” with <strong>Reload</strong> appears when a new version of the app has installed behind the one running; it has no timeout and stays through navigation until tapped, and Reload swaps it in and reloads the app</p></td> </tr> <tr class="row-odd"><td><p>Banner</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.conflict</span></code>, <code class="docutils literal notranslate"><span class="pre">.restored</span></code> (<code class="docutils literal notranslate"><span class="pre">css/banners-toast.css</span></code>)</p></td> <td><p>in-place notices, never colour alone. <strong>Conflict</strong> (<code class="docutils literal notranslate"><span class="pre">.conflict</span></code>: <code class="docutils literal notranslate"><span class="pre">--danger-soft</span></code>, a 4 px <code class="docutils literal notranslate"><span class="pre">--danger</span></code> edge at the start, <code class="docutils literal notranslate"><span class="pre">--r-lg</span></code>), a save refused because it changed elsewhere. The page editor’s (<code class="docutils literal notranslate"><span class="pre">pageedit.ts</span></code>) is a medium-weight <code class="docutils literal notranslate"><span class="pre"><p></span></code> (“This page was changed elsewhere. Merge their text below into yours and save again, or discard yours.”), their title and text in a <code class="docutils literal notranslate"><span class="pre">--surface</span></code> blockquote (<code class="docutils literal notranslate"><span class="pre">--r-sm</span></code>, its <code class="docutils literal notranslate"><span class="pre">pre</span></code> in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>) and <strong>Discard mine, show theirs</strong>, scrolled into view. The drawing viewer’s (<code class="docutils literal notranslate"><span class="pre">board/viewer.ts</span></code>, in <code class="docutils literal notranslate"><span class="pre">.drawing-notes</span></code>) is one plain line, “This drawing was changed elsewhere (now version N). Nothing is lost: saving yours adds it as a new version, theirs stays in History.”, with <strong>Save mine</strong> and <strong>Discard mine, show theirs</strong>; no blockquote, no scrolling. <strong>Restored</strong> (<code class="docutils literal notranslate"><span class="pre">.restored</span></code>: <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code>, <code class="docutils literal notranslate"><span class="pre">--r-md</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>), an info line: in the composer and page editor (<code class="docutils literal notranslate"><span class="pre">editor.ts</span></code>) “Restored your unsaved draft.” with <strong>Discard it</strong> (<code class="docutils literal notranslate"><span class="pre">button.link</span></code> in <code class="docutils literal notranslate"><span class="pre">--ink</span></code>, underline <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>: <code class="docutils literal notranslate"><span class="pre">--line-strong</span></code> is under 3:1 on <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code>); in the drawing viewer “Restored your unsaved drawing.” with <strong>Discard it</strong>, and, when a draft is kept but the viewer is not editing, “You have an unsaved edit of this drawing: Edit to carry on.” with no button (Edit carries the <code class="docutils literal notranslate"><span class="pre">.dot</span></code>)</p></td> </tr> <tr class="row-even"><td><p>Settings sheet</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.editor.settings</span></code>, <code class="docutils literal notranslate"><span class="pre">.fields</span></code> (<code class="docutils literal notranslate"><span class="pre">settings.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">topbar.css</span></code>)</p></td> <td><p>from the top bar’s cog (above): a <code class="docutils literal notranslate"><span class="pre">role="dialog"</span></code> <code class="docutils literal notranslate"><span class="pre">aria-modal</span></code> sheet named “Settings” over the app (the rest inert), a <code class="docutils literal notranslate"><span class="pre">.bar</span></code> with an <code class="docutils literal notranslate"><span class="pre">h2</span></code> “Settings” (<code class="docutils literal notranslate"><span class="pre">--t-lg</span></code>) and <strong>Done</strong> (<code class="docutils literal notranslate"><span class="pre">.primary</span></code>), then <code class="docutils literal notranslate"><span class="pre">.fields</span></code> with <code class="docutils literal notranslate"><span class="pre">--s-2</span></code> gaps. Each setting is a <code class="docutils literal notranslate"><span class="pre">h3.label</span></code> over a <code class="docutils literal notranslate"><span class="pre">.chips</span></code> radio group (<code class="docutils literal notranslate"><span class="pre">role="radiogroup"</span></code>, the kind row’s look) and a <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code> hint: <strong>Text size</strong> (Phone, 80%, 90%, 100%, 115%, 130%; 100% the default, 17 px; a percentage sets the root to 17 px times it, inline on <code class="docutils literal notranslate"><span class="pre"><html></span></code>, by <code class="docutils literal notranslate"><span class="pre">textsize.ts</span></code>, by <code class="docutils literal notranslate"><span class="pre">main.ts</span></code> at the start and when the app comes back, and before the first paint by <code class="docutils literal notranslate"><span class="pre">theme-init.js</span></code>; Phone marks <code class="docutils literal notranslate"><span class="pre">data-text="phone"</span></code> and <code class="docutils literal notranslate"><span class="pre">base.css</span></code> follows the phone’s font size, never under 17 px; all six on one row at 308 px up to 115%, wrapping to two rows at 130%), <strong>Maximum column width</strong> (Narrow 720px, Medium 800px, Wide 880px, Wider 960px; 800 the default, kept as nothing; sets <code class="docutils literal notranslate"><span class="pre">--column</span></code> (<code class="docutils literal notranslate"><span class="pre">tokens.css</span></code>) inline on <code class="docutils literal notranslate"><span class="pre"><html></span></code>, by <code class="docutils literal notranslate"><span class="pre">column.ts</span></code>, by <code class="docutils literal notranslate"><span class="pre">main.ts</span></code> at the start and when the app comes back, and before the first paint by <code class="docutils literal notranslate"><span class="pre">theme-init.js</span></code>; on a wide enough window (760 px and up) — a narrower one still shrinks the column to fit, with the usual <code class="docutils literal notranslate"><span class="pre">--s-4</span></code> gutter, so nothing scrolls sideways), <strong>App theme</strong> (System, Light, Dark; “System follows the phone’s light or dark setting.”), <strong>Drawings theme</strong> (Light, Dark, Same as app), <strong>Photos</strong> (“Smaller” or “Full size”: a photo at most 2560 px on its long edge before it is saved), and <strong>Store</strong> (<strong>Store health</strong> and <strong>Trash</strong>, links with a button’s look, each over a hint). A choice applies at once and is kept on this device. Focus starts on the first checked choice (Text size) and Tab stays inside; Done, Escape or Back close it, returning focus to the cog</p></td> </tr> <tr class="row-odd"><td><p>Pinned list</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.recents-top</span></code>, <code class="docutils literal notranslate"><span class="pre">.list.recents.pins</span></code> (<code class="docutils literal notranslate"><span class="pre">searches.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">pins.ts</span></code>)</p></td> <td><p>under the empty search box, above Recent, only when something is pinned: an <code class="docutils literal notranslate"><span class="pre">h2.label</span></code> “Pinned” with <strong>Edit pins</strong> at its end (a link to the Pinned page, <code class="docutils literal notranslate"><span class="pre">/p/pinned</span></code>, drawn as <strong>Clear all</strong> is: 48 px high, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>), then the pins as one <code class="docutils literal notranslate"><span class="pre">.list</span></code> group (<code class="docutils literal notranslate"><span class="pre">role="group"</span></code>, “Pinned”), in the page’s order: each a whole-row link (<code class="docutils literal notranslate"><span class="pre">a.again</span></code>, 48 px, <code class="docutils literal notranslate"><span class="pre">--ink</span></code>, no underline) with the search icon for a search or the note icon for a page, and its name cut with …. The pins are the Pinned page’s own list of links (a page, so edited and reordered like any, and synced); pinning from a search’s results header (<strong>Pin search</strong>) or a page’s Share menu toasts “Pinned” with <strong>Undo</strong>, and unpinning “Unpinned” with <strong>Undo</strong></p></td> </tr> <tr class="row-even"><td><p>Empty state</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.empty</span></code> (<code class="docutils literal notranslate"><span class="pre">empty()</span></code> in ui.ts)</p></td> <td><p>icon in a <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> circle, one plain sentence in <code class="docutils literal notranslate"><span class="pre">--ink</span></code> (“Nothing here.”, “No matches”, “No todos yet.”), and at most one extra: a <code class="docutils literal notranslate"><span class="pre">small</span></code> hint line (Capture’s “Nothing captured yet.” adds “Photos, notes and links you save show up here.”) or one action (<strong>Back to capture</strong> on “Not found: it may have been deleted.”; <strong>Retry</strong> under a problem, on every screen search included: “Offline: this needs the server.” or “Can’t reach the server. Check your connection or VPN.”). A view still loading after 150 ms is a plain “Loading…” line (<code class="docutils literal notranslate"><span class="pre">role="status"</span></code>) in the same block, without an icon; a quick answer shows no flash</p></td> </tr> <tr class="row-odd"><td><p>Match highlight</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">mark</span></code></p></td> <td><p>the matched words in a search hit’s snippet: <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code> with <code class="docutils literal notranslate"><span class="pre">--ink</span></code> text and a 2 px <code class="docutils literal notranslate"><span class="pre">--accent</span></code> underline drawn inside the box</p></td> </tr> <tr class="row-even"><td><p>Build line</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.build</span></code></p></td> <td><p>the running build (the service worker’s cache name, <code class="docutils literal notranslate"><span class="pre">scrap-…</span></code>) at the foot of the search screen: centred, <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-xs</span></code>. Five taps turn the debug panel (<code class="docutils literal notranslate"><span class="pre">debug.ts</span></code>) on or off</p></td> </tr> <tr class="row-odd"><td><p>By Claude</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.meta</span> <span class="pre">.written</span></code></p></td> <td><p>on a page Claude made, in its meta line right after the kind word: “by Claude”, a quiet <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> link with no pill, underlined as the app’s links are (its target 48 px high): on a page written over MCP (“Note · by Claude”, named “Written by Claude”), to the search <code class="docutils literal notranslate"><span class="pre">is:claude</span></code>, its tooltip the page’s <code class="docutils literal notranslate"><span class="pre">origin</span></code> field (“Written by Claude over MCP” if none); on a topic (“Topic · by Claude”, named “Topic by Claude”), to the Topics screen</p></td> </tr> <tr class="row-even"><td><p>Topic word</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.list</span> <span class="pre">.sub</span> <span class="pre">.topic</span></code></p></td> <td><p>a search hit that is a topic page: “· Topic” after its date, in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> as a Later’s medium is; its row’s icon is the open book</p></td> </tr> <tr class="row-odd"><td><p>Draft dot</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.dot</span></code></p></td> <td><p>a 9 px <code class="docutils literal notranslate"><span class="pre">--accent</span></code> dot ringed in <code class="docutils literal notranslate"><span class="pre">--bg</span></code> on the corner of a button’s icon: a page’s Edit when a draft of it is kept (its word becomes “Edit draft”, its name “Edit draft (unsaved)”) and, on the button’s own corner, the drawing viewer’s Edit (no room there for words; on its amber fill the dot is inverted, <code class="docutils literal notranslate"><span class="pre">--on-accent</span></code> ringed in <code class="docutils literal notranslate"><span class="pre">--accent</span></code>)</p></td> </tr> <tr class="row-even"><td><p>File card</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.file-panel</span></code> (<code class="docutils literal notranslate"><span class="pre">filecard.ts</span></code>)</p></td> <td><p>under a PDF’s or text file’s row (<code class="docutils literal notranslate"><span class="pre">.filerow</span></code>: its name, which opens it, and <strong>Download</strong>), in the shape of a photo’s: the row, then a Claude card’s shape (<code class="docutils literal notranslate"><span class="pre">.claude</span></code>: <code class="docutils literal notranslate"><span class="pre">--sunk</span></code>, <code class="docutils literal notranslate"><span class="pre">--t-sm</span></code>) though no model reads the file, titled <strong>What’s in the file</strong>; while it waits or is read, the state line in its words, never naming Claude (“Waiting to be read”, “Reading the text…”, “Couldn’t read it: …” in <code class="docutils literal notranslate"><span class="pre">--danger</span></code>); once read, a line in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> with its pages and whether search finds it (“12 pages · Searchable”, or why not); for a PDF only, <strong>Text read from it</strong> as a <code class="docutils literal notranslate"><span class="pre">.claude-text</span></code> fold, fetched when first opened (a text file’s own link opens its text); its action at the title line’s end, named as the photo card’s: <strong>Retry</strong> (failed), <strong>Regenerate</strong> (read, <code class="docutils literal notranslate"><span class="pre">button.link</span></code>), <strong>Generate</strong> (never queued). No new sizes</p></td> </tr> <tr class="row-odd"><td><p>Photo panel</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.photo-panel</span></code>, <code class="docutils literal notranslate"><span class="pre">.pair</span></code>, <code class="docutils literal notranslate"><span class="pre">.tabs</span></code>, <code class="docutils literal notranslate"><span class="pre">.drawing-card</span></code> (<code class="docutils literal notranslate"><span class="pre">excalidraw.css</span></code>)</p></td> <td><p>under a photo’s page: the photo and, if Claude drew one, its drawing, as a <code class="docutils literal notranslate"><span class="pre">.pair</span></code> showing one at a time under <strong>Photo</strong> / <strong>Drawing</strong> <code class="docutils literal notranslate"><span class="pre">.tabs</span></code> at every width (<code class="docutils literal notranslate"><span class="pre">role="tablist"</span></code>, “Show”; each a <code class="docutils literal notranslate"><span class="pre">role="tab"</span></code> with <code class="docutils literal notranslate"><span class="pre">aria-selected</span></code>; a segmented control like <code class="docutils literal notranslate"><span class="pre">.chips</span></code>: <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> track, the chosen tab raised on <code class="docutils literal notranslate"><span class="pre">--surface</span></code>; 42 px segments in a 48 px track; Drawing chosen to start with). The drawing is a preview (<code class="docutils literal notranslate"><span class="pre">.drawing-card</span></code>: a button in the drawings theme, not the app’s (<code class="docutils literal notranslate"><span class="pre">data-scheme</span></code>, from Settings: paper <code class="docutils literal notranslate"><span class="pre">#ffffff</span></code> or Excalidraw’s dark <code class="docutils literal notranslate"><span class="pre">#121212</span></code>, with <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> and <code class="docutils literal notranslate"><span class="pre">--line</span></code> set to match; updated live), with a <code class="docutils literal notranslate"><span class="pre">--line</span></code> edge and <code class="docutils literal notranslate"><span class="pre">--r-lg</span></code> corners, the drawing’s own shape, at most 75 % of the screen high, inert to gestures so a drag scrolls the page); a tap opens the viewer. While it renders it says “Loading drawing…”; if it can’t, “Can’t show the drawing here: tap to open it”, or offline “Offline: the drawing needs the server”</p></td> </tr> <tr class="row-even"><td><p>Drawing viewer</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.drawing-layer</span></code>, <code class="docutils literal notranslate"><span class="pre">.drawing-bar</span></code></p></td> <td><p>the full-screen drawing (<code class="docutils literal notranslate"><span class="pre">board/viewer.ts</span></code>), a <code class="docutils literal notranslate"><span class="pre">role="dialog"</span></code> <code class="docutils literal notranslate"><span class="pre">aria-modal</span></code> sheet over the app (the nav is hidden) that rises in over <code class="docutils literal notranslate"><span class="pre">--d-3</span></code>, on <code class="docutils literal notranslate"><span class="pre">--bg</span></code>. A top bar of ghost text buttons (no boxes, as the editor’s Cancel and Preview) sharing the width by their labels (<strong>Close</strong> is the exception: a 48 px ✕ icon button named “Close”, as the photo viewer’s, <code class="docutils literal notranslate"><span class="pre">.close</span></code>): <strong>Close</strong>, <strong>Fit</strong>, <strong>Photo</strong> (a toggle, <code class="docutils literal notranslate"><span class="pre">aria-pressed</span></code> on <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code> with an <code class="docutils literal notranslate"><span class="pre">--accent</span></code> edge: the photo beside the drawing in landscape or from 900 px wide, over it in portrait), <strong>More</strong> (a menu: History, Export .excalidraw, Export PNG, Export SVG) and <strong>Edit</strong> (<code class="docutils literal notranslate"><span class="pre">.primary</span></code>); while editing, <strong>Cancel</strong>, <strong>Undo</strong>, <strong>Photo</strong> and <strong>Save</strong> (<code class="docutils literal notranslate"><span class="pre">.primary</span></code>, <code class="docutils literal notranslate"><span class="pre">aria-busy</span></code> while it saves). Under the bar, a live-region <code class="docutils literal notranslate"><span class="pre">.drawing-notes</span></code> (at most 40 % of the height) holds the restored-draft and conflict banners (<code class="docutils literal notranslate"><span class="pre">.restored</span></code>, <code class="docutils literal notranslate"><span class="pre">.conflict</span></code>) and History (<code class="docutils literal notranslate"><span class="pre">.versions</span></code>: a 48 px row per version, <strong>Restore</strong>). The canvas and its tools are Excalidraw’s own</p></td> </tr> <tr class="row-odd"><td><p>Photo viewer</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.viewer</span></code></p></td> <td><p><code class="docutils literal notranslate"><span class="pre">--viewer</span></code>; close top right, count pill top centre, tools on a scrim that fades from clear to <code class="docutils literal notranslate"><span class="pre">--overlay</span></code></p></td> </tr> <tr class="row-even"><td><p>Icons</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">icons.ts</span></code></p></td> <td><p>24 px strokes (1.8), currentColor, <code class="docutils literal notranslate"><span class="pre">aria-hidden</span></code></p></td> </tr> <tr class="row-odd"><td><p>Markdown</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">.md</span></code> (page text, Preview)</p></td> <td><p>headings a step down a level (see Type), all under the page title; on a Later page, a link alone on a line is a link card (above); code and a table’s head on <code class="docutils literal notranslate"><span class="pre">--sunk</span></code>; a table in a <code class="docutils literal notranslate"><span class="pre">.table</span></code> box with <code class="docutils literal notranslate"><span class="pre">--line</span></code> hairlines that hugs it, at the start of the line, and scrolls sideways on its own (the page never does); task boxes in <code class="docutils literal notranslate"><span class="pre">--accent</span></code>, in the bullet’s place, the list as tight as a bulleted one; each box in a <code class="docutils literal notranslate"><span class="pre">.taskbox</span></code> label with a target <code class="docutils literal notranslate"><span class="pre">--tap</span></code> (48 px) wide and one line high, so no two overlap; each box is named by its item’s text (<code class="docutils literal notranslate"><span class="pre">aria-label</span></code>); on a page they tick in place (<code class="docutils literal notranslate"><span class="pre">tasks.ts</span></code>: each box’s <code class="docutils literal notranslate"><span class="pre">data-line</span></code> is its item’s line in the text, from markdown-it’s map), off while offline and in Preview; strikethrough, quotes and footnotes in <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> (a quote’s edge <code class="docutils literal notranslate"><span class="pre">--line-strong</span></code>: no decorative amber); a Mermaid <code class="docutils literal notranslate"><span class="pre">.diagram</span></code> in its dark or neutral theme, following the system’s</p></td> </tr> </tbody> </table> </div> </section> <section id="states"> <h2>States<a class="headerlink" href="#states" title="Link to this heading">#</a></h2> <div class="pst-scrollable-table-container"><table class="table"> <thead> <tr class="row-odd"><th class="head"><p>State</p></th> <th class="head"><p>How it shows</p></th> </tr> </thead> <tbody> <tr class="row-even"><td><p>Pressed</p></td> <td><p>scale 0.97 over <code class="docutils literal notranslate"><span class="pre">--d-1</span></code> and a <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> fill (primary: 6 % darker; tiles: <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code>); the browser’s tap flash is off</p></td> </tr> <tr class="row-odd"><td><p>Focus (keyboard)</p></td> <td><p>2 px <code class="docutils literal notranslate"><span class="pre">--accent-ink</span></code> outline, 2 px offset; fields draw it inside their edge</p></td> </tr> <tr class="row-even"><td><p>Selected</p></td> <td><p>segmented: raised <code class="docutils literal notranslate"><span class="pre">--surface</span></code> with <code class="docutils literal notranslate"><span class="pre">--shadow</span></code> and an amber icon; nav: <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code> pill, ink label, bold</p></td> </tr> <tr class="row-odd"><td><p>Disabled</p></td> <td><p>50 % opacity (exempt from contrast, and never the only sign: the label says why)</p></td> </tr> <tr class="row-even"><td><p>Busy</p></td> <td><p>the label reads “Preparing…” and taps are ignored; the label is never dimmed, so it stays at full contrast. Only the control’s icon pulses (opacity down to 0.3 and back, 0.8 s, looping), so the state shows even where labels are hidden; it stops under reduced motion</p></td> </tr> <tr class="row-odd"><td><p>Loading image</p></td> <td><p>the reserved box shimmers between <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> and <code class="docutils literal notranslate"><span class="pre">--line</span></code> until the image loads or fails, then stops</p></td> </tr> <tr class="row-even"><td><p>Loading view</p></td> <td><p>after 150 ms without an answer, a plain “Loading…” line in place of the last screen (a quick answer shows none; the same page again does not blank)</p></td> </tr> <tr class="row-odd"><td><p>Not found</p></td> <td><p>an empty state with the search icon, “Not found: it may have been deleted.” and <strong>Back to capture</strong>; any other failure is the offline icon, its reason and <strong>Retry</strong></p></td> </tr> <tr class="row-even"><td><p>Failed</p></td> <td><p><code class="docutils literal notranslate"><span class="pre">--danger</span></code> text in words, a <code class="docutils literal notranslate"><span class="pre">--danger-soft</span></code> badge; never colour alone</p></td> </tr> <tr class="row-odd"><td><p>Overdue</p></td> <td><p>a todo’s due line in <code class="docutils literal notranslate"><span class="pre">--danger</span></code>, semibold, saying “overdue” (AA on <code class="docutils literal notranslate"><span class="pre">--bg</span></code>, <code class="docutils literal notranslate"><span class="pre">--surface</span></code> and <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> in both themes, as <code class="docutils literal notranslate"><span class="pre">--danger</span></code> is checked); back to <code class="docutils literal notranslate"><span class="pre">--ink-2</span></code> once done</p></td> </tr> </tbody> </table> </div> </section> <section id="the-filters-panel-in-place-not-a-modal"> <h2>The Filters panel: in place, not a modal<a class="headerlink" href="#the-filters-panel-in-place-not-a-modal" title="Link to this heading">#</a></h2> <p>The refine row kept gaining controls at its left end (Clear, Tags, Sort, the count, then every filter chosen), pushing what you came for off the screen. Those controls are used now and then, not on every search, so they moved behind one <strong>Filters</strong> button. For a while it opened a bottom sheet (a modal <code class="docutils literal notranslate"><span class="pre"><dialog></span></code>), but a sheet hides the list its choices change, needs its own Back handling (a history entry of its own, stepped over on Forward) and costs a tap to close after every few taps. So the filters open in place instead:</p> <ul class="simple"> <li><p><strong>Filters</strong> is a slider icon at the end of the search field, where the thumb already is (no count on it: the chips under the kind row say what is on). A tap opens or folds the panel (<code class="docutils literal notranslate"><span class="pre">aria-expanded</span></code>, <code class="docutils literal notranslate"><span class="pre">aria-controls</span></code>); open, the icon sits in a 36 px <code class="docutils literal notranslate"><span class="pre">--accent-soft</span></code> disc with a 2 px <code class="docutils literal notranslate"><span class="pre">--accent-ink</span></code> ring (6.5:1 on <code class="docutils literal notranslate"><span class="pre">--surface</span></code> light, 9.2:1 dark), a shape and a fill, not hue alone; it stays open or folded while the app is open, the next search too.</p></li> <li><p>The panel opens under the search field, above the kind row and the list, so each tap in it shows its effect at once. A row per kind of choice (Tags, When, Arrange), each a small heading over chips that wrap (never scrolling sideways), keeps it short. The tags it offers are capped at 8, and at two rows with the box (the tag picker). Typing matches the tags of the results (of every tag in use before a search), so there is no separate screen to browse them (the Tags screen is retired; <code class="docutils literal notranslate"><span class="pre">/tags</span></code> opens Search).</p></li> <li><p>What is on is always in view, panel open or not: a row of chips under the kind row, each with a ✕, then the results header. The kind row is always shown.</p></li> <li><p>A tap changes the query at once, so the panel is never a form to submit and never holds state the address doesn’t: Back, a reload or a shared link show the same list. A control keeps its focus as the panel is drawn again.</p></li> <li><p>It is <code class="docutils literal notranslate"><span class="pre">refine.ts</span></code>, a chunk of its own loaded with the search screen and kept offline by the service worker, so the app shell stays small.</p></li> </ul> </section> <section id="layout-and-breakpoints"> <h2>Layout and breakpoints<a class="headerlink" href="#layout-and-breakpoints" title="Link to this heading">#</a></h2> <ul class="simple"> <li><p>Portrait phones (300 px and up): the default. Under 380 px wide a segmented control drops its icons, except the kind rows (search’s and the Todos screen’s) and the editor’s kind picker, which keep theirs over the word.</p></li> <li><p>A wide window (760 px and up: a laptop, a tablet on its side): one centred column, at most 800 px by default (the top bar, the screen, the nav and the editor), the background still to the edges; Settings’ Maximum column width raises or lowers it (720 to 960 px), and a window narrower than the chosen width still shrinks the column to fit, with the usual gutter. On a computer a list screen’s top bar has a 960 px column of its own, so its four tabs clear the wordmark and Settings; a page’s top bar stays on the centred column.</p></li> <li><p>Larger text (iPhone’s Larger Text at 150 % or 200 %): the kind rows and the kind picker wrap instead of clipping, the Filters tag box fits, and the nav grows with its labels. Left for later at 200 %: the page toolbar wraps Delete to a second row, the editor’s top bar is tight, its folds push the kind picker off-screen, and Capture’s tiles leave about two rows of the list in view.</p></li> <li><p>Short screens (under 500 px high, i.e. landscape or a large text size): top bar and nav 48 px, and the page toolbar scrolls with the page instead of sticking. Landscape (also 560 px and wider): the nav’s label sits beside its icon and the capture tiles are 64 high.</p></li> <li><p>Very narrow (under 300 px: a small phone at its largest text size): the capture cluster scrolls with the list instead of covering it (it is static and moves to the top, <code class="docutils literal notranslate"><span class="pre">order:</span> <span class="pre">-1</span></code>, so Camera is on the first screen and the list follows), tiles go two by two, labels and list titles wrap, list thumbnails shrink to 32, and toolbars show icons only (the words remain their accessible names).</p></li> <li><p>Toasts sit just above whatever is under the thumb (the capture cluster, the page toolbar, the viewer’s tools or the nav), clear of the newest row and the title; in the editor, just under its top bar.</p></li> <li><p>Text that may be right-to-left (titles, page text, snippets, the editor) has <code class="docutils literal notranslate"><span class="pre">dir="auto"</span></code>, and spacing uses logical properties (<code class="docutils literal notranslate"><span class="pre">margin-inline-*</span></code>, <code class="docutils literal notranslate"><span class="pre">padding-inline-start</span></code>).</p></li> <li><p>A page photo reserves a 4:3 box while it loads, then takes the photo’s own shape (<code class="docutils literal notranslate"><span class="pre">aspect-ratio:</span> <span class="pre">auto</span> <span class="pre">4</span> <span class="pre">/</span> <span class="pre">3</span></code>), at most 75 % of the screen high.</p></li> </ul> </section> <section id="print"> <h2>Print<a class="headerlink" href="#print" title="Link to this heading">#</a></h2> <p>A page prints (the browser’s Print, or Save as PDF) as paper, not as the app: <code class="docutils literal notranslate"><span class="pre">css/print.css</span></code>, imported last in <code class="docutils literal notranslate"><span class="pre">style.css</span></code>, so it is laid over everything else.</p> <ul class="simple"> <li><p><strong>What prints:</strong> the title, the text with the photos in it (a board’s photo, not its drawing), and the text of the page’s Claude card, About this page: its summary, a write-up, topics, sources and related pages, with every fold open.</p></li> <li><p><strong>What is hidden:</strong> the top bar and nav, toasts and the debug panel, the page’s meta line, dates, tags and tools, the topic-notes prompt, the files listed under the text (<code class="docutils literal notranslate"><span class="pre">.originals</span></code>), each photo’s and file’s own Claude card, a photo’s Photo / Drawing tabs, and every control and prompt in the card (Generate, Regenerate, the state pill, the guidance box). A card with no text is left out.</p></li> <li><p><strong>Where breaks fall:</strong> each top-level block of the text asks not to be split from the next, except the one before a rule or an <code class="docutils literal notranslate"><span class="pre">h1</span></code> / <code class="docutils literal notranslate"><span class="pre">h2</span></code>, so a sheet ends between sections (a question on a test paper) when it can. Paragraphs, code, list items without a nested list, short quotes, table rows, images, displayed maths and diagrams are not split; a long list or quote may be, but never between an item’s own line and the list nested in it. Wide code wraps and a wide table shows whole.</p></li> <li><p><strong>The cost:</strong> a section that does not fit in what is left of a sheet starts the next one, so a sheet can end with blank space; one longer than a sheet still breaks inside.</p></li> <li><p>Chrome does not keep a heading with a <code class="docutils literal notranslate"><span class="pre">details</span></code> after it, so in print the card’s title is drawn in its first fold’s heading, its short folds are kept whole, and a write-up’s heading sits on its byline.</p></li> </ul> </section> <section id="high-contrast"> <h2>High contrast<a class="headerlink" href="#high-contrast" title="Link to this heading">#</a></h2> <p>Under <code class="docutils literal notranslate"><span class="pre">forced-colors:</span> <span class="pre">active</span></code> the system replaces fills and drops shadows, so the selected segment and the current nav item get a 2 px <code class="docutils literal notranslate"><span class="pre">Highlight</span></code> outline, and the borderless tiles and badge get a <code class="docutils literal notranslate"><span class="pre">ButtonText</span></code> edge. In the drawing viewer, the chosen Photo / Drawing tab and a pressed toggle in its bar get a 3 px <code class="docutils literal notranslate"><span class="pre">Highlight</span></code> outline.</p> </section> <section id="icons"> <h2>Icons<a class="headerlink" href="#icons" title="Link to this heading">#</a></h2> <p>Sizes: 20 in buttons, 24 in tiles, the nav and list placeholders, 18 in the segmented control, 16 on a photo’s remove button, 52 (in a circle) in empty states. Icons: camera, image, note, tag (the Tags fold), todo (a checklist: the Todo kind, so a row’s kind icon never reads as a ticked box beside its tick box), tick (check in a square: a row’s tick, which hides the check until done, and the Todos empty state; the nav’s Todos tab draws the same box inline), done (a check alone: Mark done, so a box always means a tick you can see), add (a plus: New todo, the tag row’s + circle), due (a calendar: a todo’s due pill, the Due fold, Upcoming), now (a clock: Now), all (four tiles: All, on the kind row and the Todos screen), later (bookmark: Later), board (a whiteboard on its stand), topic (an open book: the Topics tab, a topic’s row in search, the Topics empty state), link, open (an arrow out of a box: a link card’s Open), file, download, share, edit, trash, search, filter (two sliders: the Filters chip), close, back, next, fit, retry (also Reopen), offline; the editor’s keys draw their own (an empty box, a bulleted and a numbered list, lines moved in and out, Link’s chain, Undo’s arrow turning back); the nav’s plus, check box, magnifier and book are inline in <code class="docutils literal notranslate"><span class="pre">index.html</span></code>. The app icon (<code class="docutils literal notranslate"><span class="pre">web/public/icon.svg</span></code>, PNGs by <code class="docutils literal notranslate"><span class="pre">npm</span> <span class="pre">run</span> <span class="pre">icons</span></code>) is the paper scrap on amber; the header mark is the same scrap in amber.</p> </section> <section id="public-pages"> <h2>Public pages<a class="headerlink" href="#public-pages" title="Link to this heading">#</a></h2> <p>A public page (#497) wears scrap’s look outside the app: <code class="docutils literal notranslate"><span class="pre">web/src/public/page.css</span></code>, built after <code class="docutils literal notranslate"><span class="pre">tokens.css</span></code> into <code class="docutils literal notranslate"><span class="pre">/assets/page.css</span></code> (<code class="docutils literal notranslate"><span class="pre">vite.config.ts</span></code>, <code class="docutils literal notranslate"><span class="pre">publicAssets</span></code>), so the paper and ink tokens and <code class="docutils literal notranslate"><span class="pre">prefers-color-scheme</span></code> are the app’s own, with no theme switch (the reader’s device chooses). It styles only what the publish mode (<code class="docutils literal notranslate"><span class="pre">publish.ts</span></code>) writes: the site’s name over the page (<code class="docutils literal notranslate"><span class="pre">.site.top</span></code>), the page’s header, text, files and Claude’s card as the app draws them, and the footer “Published 1 Nov 2026 from scrap · All pages” (<code class="docutils literal notranslate"><span class="pre">.site.foot</span></code>). Tags are <code class="docutils literal notranslate"><span class="pre">--sunk</span></code> pills as in the app; links follow the app’s rule (the owner’s text <code class="docutils literal notranslate"><span class="pre">--accent-ink</span></code>, the rest ink, every one underlined). There is no script and no <code class="docutils literal notranslate"><span class="pre"><style></span></code>: KaTeX’s CSS and fonts come beside it, as <code class="docutils literal notranslate"><span class="pre">/assets/katex.css</span></code>.</p> </section> <section id="what-we-don-t-do"> <h2>What we don’t do<a class="headerlink" href="#what-we-don-t-do" title="Link to this heading">#</a></h2> <ul class="simple"> <li><p>No web font, icon font, CSS framework or component library in the shell. The drawing viewer is a separate lazily loaded chunk on React and Excalidraw, with Excalidraw’s bundled fonts, and is outside the 16.5 kB shell budget.</p></li> <li><p>No colour outside <code class="docutils literal notranslate"><span class="pre">tokens.css</span></code> (the debug panel aside), and no second accent; no gradients except the photo viewer’s scrim and the fade mask at the end of a sideways-scrolling row (<code class="docutils literal notranslate"><span class="pre">.tags</span></code>, <code class="docutils literal notranslate"><span class="pre">.refine</span></code>: a 32 px <code class="docutils literal notranslate"><span class="pre">mask-image</span></code> fade to transparent, not a colour). High-contrast mode uses the system’s colour keywords.</p></li> <li><p>No colour as the only signal: selection also raises, failure also says so in words.</p></li> <li><p>No amber decorative text, no amber link in the app’s chrome (counts, headers, Claude’s cards), no text link without an underline, no amber-soft that doesn’t mean selected or active, and no filled amber but the screen’s one hero.</p></li> <li><p>No sheet or dialog for choices: the Filters panel opens in place (above), the browser’s <code class="docutils literal notranslate"><span class="pre">confirm()</span></code> stays for destructive questions, and the share menu is a light-dismiss popover. The full-screen photo viewer and drawing viewer (<code class="docutils literal notranslate"><span class="pre">role="dialog"</span></code>, <code class="docutils literal notranslate"><span class="pre">aria-modal</span></code>) are the only modal surfaces of our own.</p></li> <li><p>No transition or entrance longer than 260 ms, no motion that carries meaning, none under reduced motion. The exceptions are the two looping indicators, which stop under reduced motion: the busy icon’s pulse (0.8 s) and the image placeholder’s shimmer (1.2 s).</p></li> <li><p>No touch target under 48 px where it can be avoided (the 36 px badge, the 30 px remove button and the 42 px <code class="docutils literal notranslate"><span class="pre">.chips</span></code> segments extend their hit area with an invisible <code class="docutils literal notranslate"><span class="pre">::after</span></code>). The exceptions: the embed chip’s ✕ is 40 px wide (48 high); and the Photo / Drawing tabs are 42 px segments in a 48 px track, with no extension. (Chips and the fields beside them are 32 px drawn in a 48 px target: Chip, above.) No text under 12.75 px (<code class="docutils literal notranslate"><span class="pre">--t-xs</span></code>); no text pair under AA.</p></li> <li><p>No dimming to mean “busy”: the label (the text) is never dimmed, since opacity would break contrast; it says “Preparing…” in words, and only the control’s icon pulses.</p></li> </ul> </section> </section> </article> <footer class="prev-next-footer d-print-none"> <div class="prev-next-area"> <a class="left-prev" href="architecture/deployment.html" title="previous page"> <i class="fa-solid fa-angle-left"></i> <div class="prev-next-info"> <p class="prev-next-subtitle">previous</p> <p class="prev-next-title">Deployment and operations</p> </div> </a> <a class="right-next" href="ingest.html" title="next page"> <div class="prev-next-info"> <p class="prev-next-subtitle">next</p> <p class="prev-next-title">Claude ingest</p> </div> <i class="fa-solid fa-angle-right"></i> </a> </div> </footer> </div> <dialog id="pst-secondary-sidebar-modal"></dialog> <div id="pst-secondary-sidebar" class="bd-sidebar-secondary bd-toc"><div class="sidebar-secondary-items sidebar-secondary__inner"> <div class="sidebar-secondary-item"> <div id="pst-page-navigation-heading-2" class="page-toc tocsection onthispage"> <i class="fa-solid fa-list"></i> On this page </div> <nav id="pst-page-toc-nav" class="page-toc" aria-labelledby="pst-page-navigation-heading-2"> <ul class="pst-show_toc_level nav section-nav flex-column"> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#principles">Principles</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#tokens">Tokens</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#components">Components</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#states">States</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#the-filters-panel-in-place-not-a-modal">The Filters panel: in place, not a modal</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#layout-and-breakpoints">Layout and breakpoints</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#print">Print</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#high-contrast">High contrast</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#icons">Icons</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#public-pages">Public pages</a></li> <li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#what-we-don-t-do">What we don’t do</a></li> </ul> </nav></div> <div class="sidebar-secondary-item"> <div class="tocsection editthispage"> <a href="https://github.com/gilesknap/scrap/edit/1.7.0/docs/explanations/design-system.md"> <i class="fa-solid fa-pencil"></i> Edit on GitHub </a> </div> </div> <div class="sidebar-secondary-item"> <div class="tocsection sourcelink"> <a href="../_sources/explanations/design-system.md.txt"> <i class="fa-solid fa-file-lines"></i> Show Source </a> </div> </div> </div></div> </div> <footer class="bd-footer-content"> </footer> </main> </div> </div> <!-- Scripts loaded after <body> so the DOM is not blocked --> <script defer src="../_static/scripts/bootstrap.js?digest=18bd87630a240e9c25be"></script> <script defer src="../_static/scripts/pydata-sphinx-theme.js?digest=18bd87630a240e9c25be"></script> <footer class="bd-footer"> <div class="bd-footer__inner bd-page-width"> <div class="footer-items__end"> <div class="footer-item"> <p class="theme-version"> <!-- # L10n: Setting the PST URL as an argument as this does not need to be localized --> Built with the <a href="https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html">PyData Sphinx Theme</a> 0.23.0. </p></div> </div> </div> </footer> </body> </html>