| 1 | # UI |
| 2 | |
| 3 | ## Philosophy |
| 4 | |
| 5 | A developer tool people use daily to browse code and manage repos. Optimise for: |
| 6 | |
| 7 | 1. **Speed-first** — minimise clicks, maximise information density |
| 8 | 2. **Scannable** — find what you need in under a second |
| 9 | 3. **Quiet confidence** — premium without flashy; the UI should disappear |
| 10 | 4. **Code-centric** — code is the hero, everything else supports it |
| 11 | |
| 12 | |
| 13 | |
| 14 | | Readable density | Compact without cramped. Maximise info per viewport. | |
| 15 | | Clear hierarchy | Strong contrast between labels, content, and muted elements | |
| 16 | | Functional spacing | 8px on items, 12–16px on sections. No wasted space. | |
| 17 | | Keyboard-first | Every action reachable without a mouse | |
| 18 | | Dark-mode primary | Developers live in dark mode. Light mode is supported, secondary. | |
| 19 | |
| 20 | **Do:** small type (`text-xs` / `text-sm` for most UI), small buttons, monospace for |
| 21 | paths, SHAs, and branch names, opacity modifiers for text hierarchy, tight spacing. |
| 22 | |
| 23 | **Don't:** large type outside page titles, shadows in dark mode (use borders), bright |
| 24 | backgrounds, hover animations that move or resize things. |
| 25 | |
| 26 | ## Where the portfolio framing bites |
| 27 | |
| 28 | Steid is portfolio-first, not a Gitea clone. The profile page is the product — repos |
| 29 | are one kind of thing on it, alongside writing and projects. Any layout inherited from |
| 30 | a GitHub-shaped forge needs checking against that before it's copied. |
| 31 | |
f0afae5docs: settle the profile page on flat navigation7d | 32 | ## The profile page |
| 33 | |
| 34 | Settled 2026-08-29, after looking at the built page with real data rather than |
| 35 | imagining it. Three sections of equal weight — Repositories, Writing, Projects — two of |
| 36 | them empty boxes, repositories sorted alphabetically so `dotfiles` outranked `steid`. |
| 37 | A third of the page was content and two thirds advertised incompleteness. |
| 38 | |
| 39 | **Flat, app-style navigation. No cards, no boxes.** |
| 40 | |
| 41 | ``` |
| 42 | James Gill |
| 43 | @jamesgill |
| 44 | <bio> |
| 45 | <links> |
| 46 | ───────────────────────────────────────────── |
| 47 | Overview Repositories 3 Writing |
| 48 | ───────────────────────────────────────────── |
| 49 | CURRENTLY BUILDING |
| 50 | steid Rust |
| 51 | A self-hostable gitforge where the profile is the product. |
| 52 | 421 tests · updated 2 hours ago |
| 53 | |
| 54 | REPOSITORIES All 3 → |
| 55 | ────────────────────────────────────────── |
| 56 | topcoat-notes 3d |
| 57 | Notes on building with Topcoat 0.5. |
| 58 | ────────────────────────────────────────── |
| 59 | dotfiles 2w |
| 60 | ``` |
| 61 | |
| 62 | - **A tab bar, not stacked sections.** Empty sections stop being visible failures and |
| 63 | become destinations that simply have nothing in them yet. It is also what makes |
| 64 | Writing shippable without redesigning the page around it. |
| 65 | - **Tabs are links.** No JavaScript, and each tab is a real URL that can be shared. |
| 66 | - **One lead item with genuine typographic weight**, then a compact list. This is the |
| 67 | editorial layer portfolio-first requires: without it the page has no opinion about |
| 68 | what matters, and ordering alone cannot carry that. |
| 69 | - **Hairlines and hover states, never bordered containers.** The row is the unit. |
| 70 | - **Recency, not alphabetical.** Alphabetical is a filing rule; a portfolio needs an |
| 71 | editorial one. |
| 72 | - **Sections with nothing in them are hidden from visitors** and shown to the owner as |
| 73 | an affordance. |
| 74 | |
| 75 | ### This deliberately breaks the density rules above |
| 76 | |
| 77 | [Philosophy](#philosophy) says small type and nothing large outside page titles. That |
| 78 | still holds — for the **tool** surfaces, where code is the hero and density wins: |
| 79 | browsing a tree, reading a blob, the log, settings. The **profile** is the portfolio |
| 80 | surface, and it is the one page whose job is to make an impression rather than to be |
| 81 | operated. The lead item gets editorial weight there and nowhere else. |
| 82 | |
| 83 | If a third kind of surface appears, decide which of the two it is rather than |
| 84 | splitting the difference. |
| 85 | |
| 86 | ### What it needs that does not exist yet |
| 87 | |
| 88 | - **`updated_at` on a repository.** The design promises recency and nothing stores it. |
| 89 | Asking git costs one fork per repository — a twelve-repo profile would be ~150ms of |
| 90 | `execve` before rendering. So it is a column, touched when a push is authorized. |
| 91 | Slightly wrong if a push then fails; the alternative is wrong more expensively. |
| 92 | - **A `/{handle}/repos` index.** The Repositories tab needs a destination; only |
| 93 | `/{handle}/repos/{name}` exists. |
| 94 | - **A way to choose the lead.** A `pinned` flag on the repository, set from the settings |
| 95 | page that now exists. No pin, no lead section — automatic "most recent" would put a |
| 96 | dotfiles tweak at the top of a portfolio. |
| 97 | |
| 98 | ### Mocking is harder than it looks here |
| 99 | |
| 100 | **Tailwind classes the app does not already use are not in the built stylesheet.** |
| 101 | `build.rs` scans the real sources, so a mockup written against the served CSS silently |
| 102 | loses any new utility — `gap-7` and `tracking-widest` collapsed a nav into |
| 103 | `OverviewRepositories3WritingProjects` before this was understood. Iterate either in the |
| 104 | app itself or, for throwaway exploration, in plain CSS against the theme's custom |
| 105 | properties (`--background`, `--foreground`, `--muted-foreground`, `--border`, `--surface`). |
| 106 | |
| 107 | Dark mode is a `.dark` class on an ancestor, not `prefers-color-scheme`; a mockup |
| 108 | without it renders light. |
| 109 | |
| 110 | ## Stack |
| 111 | |
af126c7feat: tailwind theme and the first components25d | 112 | Settled in [0005](decisions/0005-tailwind-and-copied-components.md). |
| 113 | |
af126c7feat: tailwind theme and the first components25d | 114 | |
| 115 | |
| 116 | | Styling | Tailwind via Topcoat's build script — no Node | |
| 117 | | Theme | `styles.css` at the package root: design tokens, dark-first | |
| 118 | | Components | `topcoat ui add` for primitives, hand-written for Steid's own | |
| 119 | | Mono | IBM Plex Mono, for paths, hashes, branches, handles | |
| 120 | |
af126c7feat: tailwind theme and the first components25d | 121 | ### Tokens |
| 122 | |
af126c7feat: tailwind theme and the first components25d | 123 | Defined in `styles.css` on `:root` (light) and `.dark` (dark, the default): |
| 124 | |
| 125 | ``` |
| 126 | background surface foreground muted-foreground |
| 127 | primary primary-foreground |
| 128 | success success-foreground confirmations, git additions |
| 129 | warning warning-foreground caution, git modifications |
| 130 | destructive destructive-foreground errors, git deletions |
| 131 | border ring shadow-xs shadow-sm |
| 132 | ``` |
| 133 | |
| 134 | `surface`, `success` and `warning` are Steid's additions to Topcoat's `neutral` theme; |
| 135 | the rest were retuned to the palette below. |
| 136 | |
| 137 | **Components reference tokens, never raw colours.** A hardcoded colour will not follow |
| 138 | a palette change and will not adapt to the colour scheme. This is the convention to |
| 139 | enforce in review. |
| 140 | |
| 141 | ### Working on it |
| 142 | |
af126c7feat: tailwind theme and the first components25d | 143 | ```bash |
| 144 | topcoat dev |
| 145 | topcoat asset bundle |
| 146 | topcoat ui list |
| 147 | topcoat ui add <name> |
| 148 | ``` |
| 149 | |
64222c3docs: record the icon staging trap and tick the repo-creation check24d | 150 | `components.toml` is the record of what is installed; don't duplicate that list here. |
| 151 | |
| 152 | **A registry component can need more than a copy.** `select` draws its chevron with an |
| 153 | Iconify icon, which needs the `icon-iconify` feature on `topcoat` *and* the icon set |
| 154 | staged in `build.rs`: |
| 155 | |
| 156 | ```rust |
| 157 | topcoat::icon::iconify::BuildConfig::new().icon_set("feather").stage().unwrap(); |
| 158 | ``` |
| 159 | |
| 160 | The build fails with a message naming the missing set, so it is discoverable — but the |
| 161 | build.rs edit is easy not to expect from a command that only advertises copying a file. |
| 162 | Icons are embedded at build time; nothing is fetched at runtime. |
| 163 | |
| 164 | `topcoat ui add` also rewrites the module list in `src/components.rs` by appending, so |
| 165 | re-alphabetise it afterwards. |
| 166 | |
| 167 | ## Prior art |
| 168 | |
| 169 | Attempt #1 (`steid-backup/AGENTS/UI.md`) has a complete 643-line design system — |
| 170 | OKLCH light/dark palettes with concrete token values, a type scale, spacing scale, and |
| 171 | component markup for sidebar, file tree, commit bar, breadcrumbs, badges, empty |
| 172 | states. It was written for Tailwind + DaisyUI. |
| 173 | |
af126c7feat: tailwind theme and the first components25d | 174 | Its palette has been mined already — the dark-first OKLCH values at hue 260 are what |
| 175 | `styles.css` was retuned to. Its component markup has not, and comes with two caveats: |
| 176 | it specifies DaisyUI, which is a separate choice from Tailwind and not bundled by |
| 177 | Topcoat; and it was written for a GitHub-shaped forge rather than a portfolio-first |
| 178 | one. |
| 179 | |
| 180 | It is also, on its own, longer than every other doc in this directory combined — for |
| 181 | an app that had about nine pages. Take the palette and the principles. Don't |
| 182 | re-specify components before there are components. |