--- name: ui description: Build or change knotless's web pages (web/) so they look right, not only work. Use for any change to a page, component or style under web/. Look at every state of what you touch with `just look` before pushing, hold it to the house rules here, and have it critiqued by eyes that did not write it. --- # Building knotless's pages The shots check says what moved; it does not say whether what moved is good, and it only ever draws the states the recorded world happens to hold. A page nobody looked at shows it: a filter that empties a list leaves a bare sentence about the wrong thing, jammed against the controls above it. So look, at every state, before pushing. AGENTS.md has the mechanics (tokens, component layers, popovers, inline text). This is how the pages should read, and how to check they do. ## Looking `just look` draws pages as this checkout does, from `harness/shots/fixtures.json`, with no PDS, and writes pictures to `target/look` for you to open with Read. It builds `web/` first; run `node harness/shots/look.mjs ...` to skip the build when it is fresh. ```sh just look pull-open # a page by name, desktop light + phone dark just look '/alice.test/tally/issues?state=closed' # any path just look / --signed-in # as alice.test, logged in just look / --set 'getFeed.events=[]' # what a method answers, changed just look issues --set 'listItems.items.0.title="A title long enough to wrap twice over on a phone and then some"' just look repository --signed-in --click 'header details.popover summary' just look pull --element .merge-box --all # one element, in all four just look check --as bob.test --click 'details.rerun-menu>summary' # as another account just look / --set 'getFeed.events=@feed.json' # an answer from a file: every kind of line at once just look / --set 'getViewer.login=true' --click 'header [popovertarget=login]' --type '#login-handle=chadtm' # typed into, then shot just look org-members --as bob.test --click '.org-invitation .btn.primary' --hold setOrgMembership # a write left under way: its button busy just look issue --phone --dark --webkit # in WebKit, as Safari and every iPhone browser draw it ``` `--webkit` draws in Playwright's WebKit rather than Chromium, for a layout that wraps or overflows only on an iPhone; its pictures end `-webkit`. That WebKit is Linux's, not iOS's: it paints no custom highlights (code is drawn uncoloured), so a highlighting bug seen on an iPhone can't be seen in it. It says when a page is wider than the screen, naming the element that runs past the edge: fix that before anything else, as the page then scrolls sideways on a phone. With `--click` it centres what it clicks and shoots the screen, not the whole page, as a menu is seen. Quote `--set`'s argument: the shell takes `[]` for a glob. An answer `--set` changed is held to its method's lexicon, as the server's are, and one that is not as it says fails the run, naming what is wrong: a page is only judged on an answer the server could give. The tool names the methods each page asked for; a `--set` of any other changes nothing, and it says so and exits 1. A path that was not recorded is answered from the same method's answer for another path, which it also says. A page new to the world needs it in `harness/shots/world.mjs` and `just shots-record`; until then, `--set` can fill in its answers. ### Without a checkout In a chat with only the knotless MCP, the runner looks instead, at a branch: commit the change to it, then ask for the `look` workflow there with `LOOK` as `just look`'s arguments, quoted as a shell would, and read its pictures back. ``` rerun repo: chadtmiller.com/knotless, ref: refs/heads/, workflow: look, env: {LOOK: "profile --phone --set 'getProfile.links=[{\"url\":\"https://example.com\"}]'"} checks repo: chadtmiller.com/knotless, ref: refs/heads/, workflow: look, wait: 60, log: true, images: true ``` It installs and builds the branch's `web/` first, so a look takes about two minutes: ask `knotless run list` again, waiting 60 seconds at a time, as a longer wait can outlast the MCP call itself. The pictures come back 12 at most: ask for that many. What `look` says, a page wider than the screen or a `--set` that changed nothing, is at the end of the log. Answers go inline in `--set`; one too long for that can be committed as a file and given as `@path`, and taken out before the pull lands. A page whose query is new has no recorded answers: give them with `--set` until `just shots-record` records them. ## The states to look at For every list, page or component you touch, each that applies: - **Full**, as recorded, and **many**: rows that wrap, a long run. - **Empty**, and empty under each filter, tab or chip it has. - **One**, where a plural or a rollup could read wrong. - **Long text**: a long handle, repository name, title, branch, label. - **Signed out and signed in** (`--signed-in`), owner and not. - **Phone** and **dark**: the defaults draw desktop light and phone dark. - **Busy**: each button that writes, pressed and its write held (`--hold `): the spinner and its words in place, the rest dimmed. - **Open**: every menu, popover and dialog it has (`--click`), on a phone as well. There a menu with much in it (a list to search, a form) is a sheet over the dimmed page; a few actions drop down under their button. Judge where it opens against what was tapped, not only that it fits. - **Every kind** of row a list draws, when its rows differ by kind (the feed's pushes, rollups, stars, comments): one `--set ...=@file.json` answer holding each, with long names, on a phone. Look at a sibling page beside yours (below), so the new one matches it rather than inventing. ## House rules **Case.** Sentence case everywhere: tabs, chips, buttons, menu items, links in the header and menus, headings, column heads, field labels, dialog titles, as much as notes and errors ("New issue", "Getting started", "Log out", "Blocked accounts"). Only the first word and proper nouns take a capital: days and months, Atmosphere, Bluesky, PDS, DID. Labels end with no full stop; what is said, a sentence, ends with one. Names stay as they are written: knotless, handles, repositories, branches, files, commands, labels such as `kind/bug`. No `text-transform` to change case: write the label as it should read. **Empty states.** Say what is empty, in the terms the reader chose: with Issues picked, "No issues yet" or "No issues in the last N events", not the page's general blurb. Offer the way out when there is one (all kinds, everyone's, a clear filter). A list's empty state goes where its rows would, as `.empty` does in ItemList's box, not as loose text. **Space.** Every block stacked on another has a gap from the space scale between them; text never touches a row of controls. Things that belong together sit closer than things that do not: a heading to its list, less than the list to the next section. **Consistency.** A thing done on two pages is done the same way on both: the same component where one exists (look in `web/src/lib` before making one), the same words for the same act, the same order of controls. Counts, times, people and repositories are drawn by their atoms and molecules, never by hand. **Icons.** Where people go and act: tabs, menu items, header controls, and buttons such as Fork and Star, each with its Octicon from `lib/icons.ts`, the same icon for the same thing on every page (a pull is `pull` wherever it is). Not in sentences, nor on every row of a list: there, the avatar or state mark is enough. An icon shown without its word still says it, by `aria-label` or a hidden label. Tabs keep their words beside their icons on a phone too, and a row too long scrolls. **Weight.** One primary button on a view at most. Quiet what is said about a thing (`--mute`, `--text-sm`) next to the thing itself. ## Pages to match - A list with filters: `issues`, `pulls` (ItemList in its box, the search and filter menus above). - One item with its discussion: `issue`, `pull`. - A repository's pages: `repository`, `commits`, `file`, under RepoPage's header and tabs. - An account: `profile`, `profile-repos`, `profile-activity`. - Settings: the `settings` routes under SettingsPage. ## Critique You read your own pictures kindly. Before pushing a change that moves a page, hand the pictures to an agent that did not write it: the Agent tool, a fresh one, given the paths under `target/look` (and a sibling page's), this file's House rules, and only this question: "What looks wrong, out of place, or unlike its siblings here? Be specific: element, what is wrong, which rule." Not the code, nor what you meant: it judges what is drawn. Fix what it finds, or say in the pull why not. ## Then Push, and the pull's `shots` check draws what moved against main. Name the pages you meant to move on its `Shots-moved:` line; any other that moved is a mistake to fix. A state the check does not draw (a menu open, an edit box, a list emptied by a filter) it cannot show the reviewer: give the pull your `just look` pictures of it, each as an `--image` to `knotless pull create` or `knotless pull edit`, as AGENTS.md says.