johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
---
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/<branch>, workflow: look,
env: {LOOK: "profile --phone --set 'getProfile.links=[{\"url\":\"https://example.com\"}]'"}
checks repo: chadtmiller.com/knotless, ref: refs/heads/<branch>, 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 <method>`): 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.