docs/specs/2026-09-19-desktop-layout-design.md
171 lines · 9331 bytes
11 symbols in this file
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 62rem** a facet or section column becomes a closed
67 disclosure before the content, headed by its word and the filters in
68 force; the file navigator disappears (the tree page exists).
69 **Below 80rem** the dashboard's pinned column returns to the chip
70 row. Nothing the phone layout fixed moves.
71
72 *Amended 2026-09-19 (#237).* The rule read "stacks after the
73 content, the way the issue aside does". An aside carries a note on
74 one thing; a facet column carries the vocabulary the list is
75 narrowed by, and a repository with two dozen labels stacked all of
76 them under the content, taller than it. A column that stops being a
77 column is a control, so it collapses to one and leads the list.
78
79## Pages
80
81### With a left column
82
83| Page | Column | Source of its contents |
84|---|---|---|
85| Dashboard | Pinned repositories with open issue count, open MR count, last build state | `Rail.Pinned` plus per-repository counts (new store read) |
86| 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 |
87| Issues list, MR list | State; labels with counts; milestones with open counts | The `state`, `label`, `milestone`, `assignee` parameters `activeFilters` already handles; counts from the labels and milestones pages' reads |
88| Builds list | Status; job; branch | The `status`, `job`, `ref` parameters `buildFilter` already handles; branches from the refs read |
89| Explore | Topics with counts | `?q=<topic>`, the link the topic chips already use |
90| Site search | Kind: repositories, issues, merge requests | `?kind=repo\|issue\|mr` |
91| Repository settings | Section nav: Identity, Access, Merge gates, Protected branches, Protected tags, Dependencies, Runners, Lifecycle | Anchors on the existing `h2` headings |
92| Account settings | Section nav: Profile, SSH keys, Email addresses, OpenPGP keys, Notifications, Appearance, Export | Same |
93| Admin | Section nav: Webhook deliveries, Mail, Mirrors, Builds, Dependency checks | Same |
94| Wiki | Page nav | Already there; adopt the 15rem width |
95
96Facet links carry the other active filters, the way `activeFilters`
97builds its clear links. The milestones and labels pages stay for
98management; the "milestones · labels" links leave the list head.
99
100### Layout of the pages that gain a column
101
102- **Dashboard**: `15rem | 1fr | 20rem`. Pinned column, then the tile
103 strip and queue rows, then the activity feed as a sticky aside with
104 two-line entries (the aside is too narrow for one).
105- **Blob, blame, edit**: `15rem | 1fr`. Navigator entries in mono, the
106 current file on a `--surface` ground with a 2px `--mark` edge.
107- **Lists**: `15rem | 1fr`. Rows fill the container.
108- **Settings and admin**: `15rem | minmax(0, 56rem)`. Forms and tables
109 at up to 56rem, so the key table stops wrapping.
110
111### Without one
112
113- **Repository overview**: the facts column stays on the right at
114 20rem; the README card caps at 88ch so prose and code share an edge.
115- **Issue, MR**: centered at 72rem; text at 48rem; the aside widens to
116 20rem so the two sit balanced in the container.
117- **Log, commit, compare, refs, releases, build, code search**: the
118 container width, no column.
119- **Notifications**: the container width, no column; rows one line.
120- **Bookmarks, snippets, profile, org**: centered `reading`.
121- **Landing, login, register, new repository, 404, privacy**: centered
122 `bounded`.
123
124## Implementation
125
126- **CSS only where it can be.** The container, centering, header rows,
127 row format, tiles, column grid and breakpoints are `style.css` rules
128 on tokens that exist. New tokens: `--container` only.
129- **Templates** touched: `layout.html` (header rows, container wrapper),
130 `dashboard.html`, `issues.html`, `mrs.html`, `builds.html`,
131 `explore.html`, `globalsearch.html`, `blob.html`, `blame.html`,
132 `edit.html`, `settings.html`, `account.html`, `admin.html`,
133 `notifications.html`, `tree.html` (README cap). Width classes: the
134 list pages and the dashboard move from the default `reading` to
135 `wide`.
136- **Handlers**: `blob`, `blame` and the edit form gain the directory
137 listing; the issue, MR and build list handlers gain facet counts; the
138 dashboard gains per-pinned-repository counts. Every new read is a
139 store query or a `gitutil` call that exists; no new control command,
140 since none of this is a capability the CLI lacks.
141 `TestReadOnlyCommandsWriteNothing` is unaffected.
142- **Facet counts** span only what the viewer can read, the same rule
143 `control.ReadableOrgRepoIDs` applies to org label counts.
144
145## Tests
146
147- `TestEveryTemplateClassHasARule` covers every new class.
148- A template test asserts the width class of each list page and the
149 dashboard is `wide`, and that `layout.html` wraps the header in the
150 container.
151- e2e: the blob page's navigator lists the file's directory and marks
152 the file; a facet link on the issues list keeps `state` and the other
153 active filters; the dashboard's tile count equals the queue length.
154- Recapture the `.claude/screenshots` set at 1280 and 1920 and rerun the
155 axe scan; zero violations is the bar the last release set.
156
157## Out of scope
158
159- A persistent context rail (option 2 in the mockups). Revisit if the
160 dashboard's pinned column proves worth having on every page.
161- A density preference.
162- One-line log rows.
163- Explore facets beyond topics; an `owner` parameter would be new.
164
165## Mockups
166
167`.claude/mock/desktop/` in the main checkout, served by the `mockups`
168entry in `.claude/launch.json`: `option1-dashboard.html`,
169`option1-blob.html`, the rejected `option2-*` pair, and `layouts.html`
170with one wireframe per page type. `desktop.css` there is the override
171layer the rules above were checked against.