Commit a251ca2f07

a251ca2f07acfca883a5156c169ac640f00390cb

parent: a32f7f2001

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-19 15:19 UTC

docs: desktop layout spec

Ref #226
docs/specs/2026-09-19-desktop-layout-design.md added +163
@@ -0,0 +1,163 @@
1# Desktop layout
2
3Ref #226. The phone layout landed in v1.23.0 through v1.29.0; this is
4the desktop half. Measured on gitbay.org at 1a40b22 (v1.30.0 head) at
51600 and 1920 wide, and compared against GitHub, GitLab, Codeberg and
6SourceHut dashboards at the same window.
7
8## Findings
9
10Three content widths, all pinned to the left edge. Nothing centers and
11nothing grows past its cap, so a wide screen puts every page in its
12top-left corner.
13
14| Width class | Pages | Content edge at 1920 | Used |
15|---|---|---|---|
16| `reading` (72rem, the default) | dashboard, issue and MR lists, explore, notifications, profile, refs, releases, milestones, labels | 1216px | 63% |
17| `reading`, then 48rem comments + 18rem aside | issue, MR conversation | 1184px | 62% |
18| `bounded` (48rem) | account settings, admin, new repository, login, 404 | 640px | 33% |
19| `wide` | tree, blob, blame, log, commit, compare, diff, builds, build, search | 1920px | 100% |
20
21- Text columns are at the right measure. A comment at 48rem is about 90
22 characters a line; wider hurts. Issue and MR pages need centering and
23 a stronger aside, not wider text.
24- Lists are laid out as prose. Issue, MR, build and explore rows are two
25 to four lines inside the 72rem cap: 14 issues a screen at 1080 tall,
26 9 repositories on explore.
27- The dashboard is one column. Queues stack, the feed follows at 809px,
28 below the fold at 1080 tall.
29- The repository header is 182px on the code tab (identity, description,
30 website, the toggles hint, tabs) and 110px on task tabs.
31- Account settings at 48rem wraps SSH fingerprints onto two lines beside
32 1280px of unused width.
33- The overview README card is 1216px wide while its prose caps at 78ch,
34 so paragraphs and code blocks have different right edges.
35
36SourceHut uses the same share of the window as gitbay and reads as
37designed, because it centers and splits into two columns. GitLab's stat
38tiles are gitbay's three "0" lines done as a strip. Codeberg's one-line
39feed rows are the row format the lists should adopt.
40
41## Rules
42
431. **One centered container.** `--container: 100rem`. The repository
44 header's inner content, `main` and the footer share it, so all three
45 align at every width. `reading` (72rem) and `bounded` (48rem) stay as
46 caps inside it, centered. `wide` is the container.
472. **A left column is a feature.** A page gets a left column only when
48 the column carries something the page already has as links, query
49 parameters or sections. No site navigation in it; the top bar is the
50 navigation. Every column is 15rem, sticky, and reads as one system.
51 No column persists across pages.
523. **Text keeps its measure.** Comments, descriptions, README prose and
53 wiki pages stay at 48rem or 78ch. Width goes to tables and lists.
544. **Lists are rows.** Above 64rem an issue, MR, build, explore, search
55 and notification row is one line: title, then labels, then the meta
56 pushed right, state or check badges at the far edge. The log keeps
57 two lines; subject over author and date is the convention there.
585. **Counts are tiles.** Where the dashboard now prints an empty queue
59 as a heading with a zero, a strip of four tiles carries the counts
60 and links to the lists. A non-zero count uses `--warn`: what wants
61 you. Only non-empty queues list rows.
626. **The repository header is two rows.** Row one: owner/name, the
63 description with topics and website inline in muted text, the
64 Pin/Watch/Bookmark/Fork buttons at the right. Row two: the tabs. The
65 toggles hint moves to `title` text on the three buttons.
667. **Below 64rem** a facet or section column folds into a `details`
67 element above the content, the file navigator disappears (the tree
68 page exists), and the dashboard's pinned column returns to the chip
69 row. Nothing the phone layout fixed moves.
70
71## Pages
72
73### With a left column
74
75| Page | Column | Source of its contents |
76|---|---|---|
77| Dashboard | Pinned repositories with open issue count, open MR count, last build state | `Rail.Pinned` plus per-repository counts (new store read) |
78| Blob, blame, edit | File navigator: the file's directory, parent link, current file marked with `aria-current` | `gitutil.ListTree` on the directory, the call the tree page makes |
79| Issues list, MR list | State; labels with counts; milestones with open counts; assignees (issues) | The `state`, `label`, `milestone`, `assignee` parameters `activeFilters` already handles; counts from the labels and milestones pages' reads |
80| Builds list | Status; job; branch | The `status`, `job`, `ref` parameters `buildFilter` already handles; branches from the refs read |
81| Explore | Topics with counts | `?q=<topic>`, the link the topic chips already use |
82| Site search | Kind: repositories, issues, merge requests | `?kind=repo\|issue\|mr` |
83| Repository settings | Section nav: Identity, Access, Merge gates, Protected branches, Protected tags, Dependencies, Runners, Lifecycle | Anchors on the existing `h2` headings |
84| Account settings | Section nav: Profile, SSH keys, Email addresses, OpenPGP keys, Notifications, Appearance, Export | Same |
85| Admin | Section nav: Webhook deliveries, Mail, Mirrors, Builds, Dependency checks | Same |
86| Wiki | Page nav | Already there; adopt the 15rem width |
87
88Facet links carry the other active filters, the way `activeFilters`
89builds its clear links. The milestones and labels pages stay for
90management; the "milestones · labels" links leave the list head.
91
92### Layout of the pages that gain a column
93
94- **Dashboard**: `15rem | 1fr | 20rem`. Pinned column, then the tile
95 strip and queue rows, then the activity feed as a sticky aside with
96 two-line entries (the aside is too narrow for one).
97- **Blob, blame, edit**: `15rem | 1fr`. Navigator entries in mono, the
98 current file on a `--surface` ground with a 2px `--mark` edge.
99- **Lists**: `15rem | 1fr`. Rows fill the container.
100- **Settings and admin**: `15rem | minmax(0, 56rem)`. Forms and tables
101 at up to 56rem, so the key table stops wrapping.
102
103### Without one
104
105- **Repository overview**: the facts column stays on the right at
106 20rem; the README card caps at 88ch so prose and code share an edge.
107- **Issue, MR**: centered at 72rem; text at 48rem; the aside widens to
108 20rem so the two sit balanced in the container.
109- **Log, commit, compare, refs, releases, build, code search**: the
110 container width, no column.
111- **Notifications, bookmarks, snippets, profile, org**: centered
112 `reading`; notification rows one line.
113- **Landing, login, register, new repository, 404, privacy**: centered
114 `bounded`.
115
116## Implementation
117
118- **CSS only where it can be.** The container, centering, header rows,
119 row format, tiles, column grid and breakpoints are `style.css` rules
120 on tokens that exist. New tokens: `--container` only.
121- **Templates** touched: `layout.html` (header rows, container wrapper),
122 `dashboard.html`, `issues.html`, `mrs.html`, `builds.html`,
123 `explore.html`, `globalsearch.html`, `blob.html`, `blame.html`,
124 `edit.html`, `settings.html`, `account.html`, `admin.html`,
125 `notifications.html`, `tree.html` (README cap). Width classes: the
126 list pages and the dashboard move from the default `reading` to
127 `wide`.
128- **Handlers**: `blob`, `blame` and the edit form gain the directory
129 listing; the issue, MR and build list handlers gain facet counts; the
130 dashboard gains per-pinned-repository counts. Every new read is a
131 store query or a `gitutil` call that exists; no new control command,
132 since none of this is a capability the CLI lacks.
133 `TestReadOnlyCommandsWriteNothing` is unaffected.
134- **Facet counts** span only what the viewer can read, the same rule
135 `control.ReadableOrgRepoIDs` applies to org label counts.
136
137## Tests
138
139- `TestEveryTemplateClassHasARule` covers every new class.
140- A template test asserts the width class of each list page and the
141 dashboard is `wide`, and that `layout.html` wraps the header in the
142 container.
143- e2e: the blob page's navigator lists the file's directory and marks
144 the file; a facet link on the issues list keeps `state` and the other
145 active filters; the dashboard's tile count equals the queue length.
146- Recapture the `.claude/screenshots` set at 1280 and 1920 and rerun the
147 axe scan; zero violations is the bar the last release set.
148
149## Out of scope
150
151- A persistent context rail (option 2 in the mockups). Revisit if the
152 dashboard's pinned column proves worth having on every page.
153- A density preference.
154- One-line log rows.
155- Explore facets beyond topics; an `owner` parameter would be new.
156
157## Mockups
158
159`.claude/mock/desktop/` in the main checkout, served by the `mockups`
160entry in `.claude/launch.json`: `option1-dashboard.html`,
161`option1-blob.html`, the rejected `option2-*` pair, and `layouts.html`
162with one wireframe per page type. `desktop.css` there is the override
163layer the rules above were checked against.