# UI

## Philosophy

A developer tool people use daily to browse code and manage repos. Optimise for:

1. **Speed-first** — minimise clicks, maximise information density
2. **Scannable** — find what you need in under a second
3. **Quiet confidence** — premium without flashy; the UI should disappear
4. **Code-centric** — code is the hero, everything else supports it

| Principle | Meaning |
|---|---|
| Readable density | Compact without cramped. Maximise info per viewport. |
| Clear hierarchy | Strong contrast between labels, content, and muted elements |
| Functional spacing | 8px on items, 12–16px on sections. No wasted space. |
| Keyboard-first | Every action reachable without a mouse |
| Dark-mode primary | Developers live in dark mode. Light mode is supported, secondary. |

**Do:** small type (`text-xs` / `text-sm` for most UI), small buttons, monospace for
paths, SHAs, and branch names, opacity modifiers for text hierarchy, tight spacing.

**Don't:** large type outside page titles, shadows in dark mode (use borders), bright
backgrounds, hover animations that move or resize things.

## Where the portfolio framing bites

Steid is portfolio-first, not a Gitea clone. The profile page is the product — repos
are one kind of thing on it, alongside writing and projects. Any layout inherited from
a GitHub-shaped forge needs checking against that before it's copied.

## The profile page

Settled 2026-08-29, after looking at the built page with real data rather than
imagining it. Three sections of equal weight — Repositories, Writing, Projects — two of
them empty boxes, repositories sorted alphabetically so `dotfiles` outranked `steid`.
A third of the page was content and two thirds advertised incompleteness.

**Flat, app-style navigation. No cards, no boxes.**

```
James Gill
@jamesgill
<bio>
<links>
─────────────────────────────────────────────
Overview   Repositories 3   Writing
─────────────────────────────────────────────
CURRENTLY BUILDING
steid  Rust
A self-hostable gitforge where the profile is the product.
421 tests · updated 2 hours ago

REPOSITORIES                                All 3 →
──────────────────────────────────────────
topcoat-notes                                    3d
Notes on building with Topcoat 0.5.
──────────────────────────────────────────
dotfiles                                         2w
```

- **A tab bar, not stacked sections.** Empty sections stop being visible failures and
  become destinations that simply have nothing in them yet. It is also what makes
  Writing shippable without redesigning the page around it.
- **Tabs are links.** No JavaScript, and each tab is a real URL that can be shared.
- **One lead item with genuine typographic weight**, then a compact list. This is the
  editorial layer portfolio-first requires: without it the page has no opinion about
  what matters, and ordering alone cannot carry that.
- **Hairlines and hover states, never bordered containers.** The row is the unit.
- **Recency, not alphabetical.** Alphabetical is a filing rule; a portfolio needs an
  editorial one.
- **Sections with nothing in them are hidden from visitors** and shown to the owner as
  an affordance.

### This deliberately breaks the density rules above

[Philosophy](#philosophy) says small type and nothing large outside page titles. That
still holds — for the **tool** surfaces, where code is the hero and density wins:
browsing a tree, reading a blob, the log, settings. The **profile** is the portfolio
surface, and it is the one page whose job is to make an impression rather than to be
operated. The lead item gets editorial weight there and nowhere else.

If a third kind of surface appears, decide which of the two it is rather than
splitting the difference.

### What it needs that does not exist yet

- **`updated_at` on a repository.** The design promises recency and nothing stores it.
  Asking git costs one fork per repository — a twelve-repo profile would be ~150ms of
  `execve` before rendering. So it is a column, touched when a push is authorized.
  Slightly wrong if a push then fails; the alternative is wrong more expensively.
- **A `/{handle}/repos` index.** The Repositories tab needs a destination; only
  `/{handle}/repos/{name}` exists.
- **A way to choose the lead.** A `pinned` flag on the repository, set from the settings
  page that now exists. No pin, no lead section — automatic "most recent" would put a
  dotfiles tweak at the top of a portfolio.

### Mocking is harder than it looks here

**Tailwind classes the app does not already use are not in the built stylesheet.**
`build.rs` scans the real sources, so a mockup written against the served CSS silently
loses any new utility — `gap-7` and `tracking-widest` collapsed a nav into
`OverviewRepositories3WritingProjects` before this was understood. Iterate either in the
app itself or, for throwaway exploration, in plain CSS against the theme's custom
properties (`--background`, `--foreground`, `--muted-foreground`, `--border`, `--surface`).

Dark mode is a `.dark` class on an ancestor, not `prefers-color-scheme`; a mockup
without it renders light.

## Stack

Settled in [0005](decisions/0005-tailwind-and-copied-components.md).

| Concern | Choice |
|---|---|
| Styling | Tailwind via Topcoat's build script — no Node |
| Theme | `styles.css` at the package root: design tokens, dark-first |
| Components | `topcoat ui add` for primitives, hand-written for Steid's own |
| Mono | IBM Plex Mono, for paths, hashes, branches, handles |

### Tokens

Defined in `styles.css` on `:root` (light) and `.dark` (dark, the default):

```
background  surface  foreground  muted-foreground
primary     primary-foreground
success     success-foreground      confirmations, git additions
warning     warning-foreground      caution, git modifications
destructive destructive-foreground  errors, git deletions
border      ring      shadow-xs  shadow-sm
```

`surface`, `success` and `warning` are Steid's additions to Topcoat's `neutral` theme;
the rest were retuned to the palette below.

**Components reference tokens, never raw colours.** A hardcoded colour will not follow
a palette change and will not adapt to the colour scheme. This is the convention to
enforce in review.

### Working on it

```bash
topcoat dev              # rebuilds CSS and re-bundles on change
topcoat asset bundle     # manual builds only; a stale bundle serves stale CSS
topcoat ui list          # what the registry offers
topcoat ui add <name>    # copies source into src/components/
```

`components.toml` is the record of what is installed; don't duplicate that list here.

**A registry component can need more than a copy.** `select` draws its chevron with an
Iconify icon, which needs the `icon-iconify` feature on `topcoat` *and* the icon set
staged in `build.rs`:

```rust
topcoat::icon::iconify::BuildConfig::new().icon_set("feather").stage().unwrap();
```

The build fails with a message naming the missing set, so it is discoverable — but the
build.rs edit is easy not to expect from a command that only advertises copying a file.
Icons are embedded at build time; nothing is fetched at runtime.

`topcoat ui add` also rewrites the module list in `src/components.rs` by appending, so
re-alphabetise it afterwards.

## Prior art

Attempt #1 (`steid-backup/AGENTS/UI.md`) has a complete 643-line design system —
OKLCH light/dark palettes with concrete token values, a type scale, spacing scale, and
component markup for sidebar, file tree, commit bar, breadcrumbs, badges, empty
states. It was written for Tailwind + DaisyUI.

Its palette has been mined already — the dark-first OKLCH values at hue 260 are what
`styles.css` was retuned to. Its component markup has not, and comes with two caveats:
it specifies DaisyUI, which is a separate choice from Tailwind and not bundled by
Topcoat; and it was written for a GitHub-shaped forge rather than a portfolio-first
one.

It is also, on its own, longer than every other doc in this directory combined — for
an app that had about nine pages. Take the palette and the principles. Don't
re-specify components before there are components.
