Docs › Design sheet
Design sheet
One visual language for the two places a person sees Sparehand: the Mac menu panel (SwiftUI) and the dashboard (the web page, on a Mac, iPad or phone). Both show the same machines, jobs and states, so they use the same words, the same colours for the same meanings, and the same patterns for waiting, failing and confirming. When one of them needs something new, add it here first.
Names
- This Mac is the computer the person is sitting at (the hub). In the menu panel and the dashboard say "this Mac". Say "the hub" only in the docs, where the reader may be on Linux, and in the CLI's own help. Never "this computer" or "the dashboard" for the machine. In a sentence it is always "this Mac" ("Why jobs ran on this Mac", "3h of work taken off this Mac"); the name it was given ("Studio") stands only where it is listed as a machine (its card, its row, its avatar) and where a sentence speaks from another Mac's side (Travel mode: "MacBook tells you when Studio can't be reached").
- Workers are the other machines. A worker is called by its name ("MacBook"); "the worker" when there is no name at hand.
- Jobs are what runs on them: "a job", "2 jobs", never "task" (Crew has tasks) or "process".
- The app is the Mac app; the dashboard is the web page; Settings is the app's window and Preferences does not exist.
- Add-ons are named plainly and carry a badge: Crew (Beta), Studio (Beta), People (Beta).
A machine's state, in one word
The same list in both places, in this order of precedence (the first that applies wins). The colour belongs to the meaning, and the word is always next to the colour: nothing is said by colour alone.
| Word | Meaning | Colour | Plain? |
|---|---|---|---|
| Off | switched off on this Mac (disable) | grey | plain |
| Stopped | a cloud machine that isn't running (costs only its disk) | grey | plain |
| Starting… / Stopping… | a cloud machine on its way | orange | |
| Not reachable | off, asleep or on another network | red | |
| Paused | pause: finishes what it runs, takes no new jobs | orange | |
| Too hot | no new jobs until it cools down | red | |
| GPU in use | someone's game or other GPU work; no GPU jobs meanwhile | purple | |
| Restarting… | a Windows restart that heals its GPU bridge | orange | |
| N jobs | working: "1 job", "3 jobs" | green | plain |
| Busy | CPU at 80% or more with no Sparehand job | orange | |
| Ready | up and has room | green | plain |
"Plain" means the word is drawn in the normal text colour with a coloured dot; the others draw the word itself in the colour, because they say something the person should know. Health notes ("memory is tight, so it takes no new jobs", "only 4 GB of disk free") go under the word as a sentence, never as a second badge.
Colours
The dashboard's tokens are the reference; the menu panel maps them to system colours.
| Meaning | Dashboard (light / dark) | Menu panel |
|---|---|---|
| ok, ready, working | --ok #1f8a4c / #4cc77f | .green |
| warn: busy, paused, starting | --warn #b7791f / #e3b25a | .orange |
| bad: not reachable, too hot, failed | --bad #c4372f / #f07067 | .red |
| GPU in use | --c3 #9b3fb5 / #d29bf0 | .purple |
| off, stopped, idle text | --idle #7a8394 / #8a93a5 | .secondary |
| accent: links, primary buttons, the selected tab | --accent #2b55d6 / #7d9bff | .accentColor |
| per-machine colours | --c0…--c7, none of them red or orange: those two only ever mean failed and warning | the same eight hex values |
Meters (CPU, memory, GPU) fill green below 60%, orange from 60%, red from 80%, in both places.
Waiting, failing, done
- Loading: a tab that is fetching shows a light skeleton of what will come (three grey bars in a card), never a blank area and never the word "Loading…" alone. Numbers that are not in yet show "–".
- Can't reach: when a fetch fails the page keeps what it has and shows one banner under the tab strip, "Couldn't reach this Mac's dashboard. Trying again…", with the time of the last good answer. The banner is the same for every tab. The menu panel shows the same sentence in its error row.
- An error from the engine stays until dismissed, in a row with the message, a Copy button and, when a doc explains it, "What to do". Nothing that tells the person something went wrong vanishes by itself.
- Done: after a change that closes a dialog (removed, renamed, saved) a toast in the corner says what happened in two or three words ("Removed MacBook", "Saved") and goes after four seconds.
- Confirm only what can't be undone: removing a machine, deleting a chat, stopping a job. The button names the action ("Remove MacBook", not "OK"); the safe choice is "Cancel".
- Asking for a value (a name, a folder, a number) uses the page's own box (
ask) with the field inside it, never the browser'sprompt.
Layout and words
- The dashboard's first row of tabs is the core: Machines, Jobs, Activity, History, Files, Screens, Sessions. Everything else (Work, Crew, Studio, Advisor, People, Chat) sits under More ▾, each with its badge. Below 520 px the tabs become one select.
- A tab's one-line tip under the strip says what the tab is for, in the second person, and can be hidden once.
- Empty states say what will appear and give the one action that makes it appear ("Nothing running right now. Heavy builds and tests you start through Sparehand show up here. Try it: …").
- Buttons are verbs ("Add a machine", "Pause", "Check connection"). Destructive buttons are red outline, never red filled. The primary action of a form is the only filled button on it.
- Numbers are written with their unit and a thin space ("208 GB free", "34% of 14 cores"). Times are relative within a day ("2 min ago") and dated after that. How long something took is the one exception to the space: "45s", "1m 5s", "2h 10m", compact, because it stands in rows and tiles beside other numbers. A part that is off has no tab: opened by its link it explains itself, and no tab is shown selected.
- Technical readings (GPU bridge speeds, DXG errors, pressure levels) are never printed raw on a card: say what they mean ("GPU path slow: Windows restart suggested") and keep the numbers behind "Details".
- Settings tabs appear when they apply: Cloud machines, Local AI and AI Tools are shown once a worker exists; before that the General, Machines and Licence tabs are all there is.
Accessibility
- Every meter has a label and a value for VoiceOver ("CPU, 34 percent"). Every icon-only button has a label ("More actions for MacBook"). State dots are decorative; the word carries the state.
- Rows that open something are buttons: focusable with the keyboard, Enter opens them.
- Text size follows the system; the dashboard's Large and Larger settings scale everything.
- Reduced motion turns off every animation, including the skeleton shimmer.
How the dashboard does it (notes for whoever changes the page)
- More ▾ is a button in the tab strip whose menu is a popover (so the strip's own sideways scrolling can't clip it); the open add-on's name and the counts of the tabs inside show on the button. Below 520 px the strip is the
#tabselectselect, kept in step bysyncTabs(). - Couldn't reach: every loader fetches through
fetchJson(), which counts failures and shows#reachbarwith the time of the last good answer;poll(fn, ms, when)replacessetIntervaland doubles its wait (4 s → 8 → 16…, at most 60 s) while that banner is up or the page is hidden. - Skeletons are
.skelcards (three bars, a shimmer that reduced motion turns off) placed in the markup of History, Activity, Crew, Studio, Advisor and People; a loader that gets no answer leaves the list as it is. - Machine cards:
stateOf()returns the word and its colour class; health goes inul.healthunder the word as sentences, and the readings (GPU path speed, pressure and heat levels, the card's temperature) indetails.techbehind "Details". - First run: while this Mac is the only machine,
tryCard()opens the Machines tab and the Jobs tab's empty state has the same button;POST /api/try(owners, with the page's header) runssparehand run --or-local sh -c '…; sleep 4'fromSTATE_DIR/try, a git checkout made once. - Asking for a value:
ask(title, text, yes, { input: { label, value, placeholder } })returns the string or null; withoutinputit returns true, false or "alt". Esc answers like Cancel.