Commit a251ca2f07
Verified · cmc
docs/specs/2026-09-19-desktop-layout-design.md added +163
| @@ -0,0 +1,163 @@ | ||
| 1 | # Desktop layout | |
| 2 | ||
| 3 | Ref #226. The phone layout landed in v1.23.0 through v1.29.0; this is | |
| 4 | the desktop half. Measured on gitbay.org at 1a40b22 (v1.30.0 head) at | |
| 5 | 1600 and 1920 wide, and compared against GitHub, GitLab, Codeberg and | |
| 6 | SourceHut dashboards at the same window. | |
| 7 | ||
| 8 | ## Findings | |
| 9 | ||
| 10 | Three content widths, all pinned to the left edge. Nothing centers and | |
| 11 | nothing grows past its cap, so a wide screen puts every page in its | |
| 12 | top-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 | ||
| 36 | SourceHut uses the same share of the window as gitbay and reads as | |
| 37 | designed, because it centers and splits into two columns. GitLab's stat | |
| 38 | tiles are gitbay's three "0" lines done as a strip. Codeberg's one-line | |
| 39 | feed rows are the row format the lists should adopt. | |
| 40 | ||
| 41 | ## Rules | |
| 42 | ||
| 43 | 1. **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. | |
| 47 | 2. **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. | |
| 52 | 3. **Text keeps its measure.** Comments, descriptions, README prose and | |
| 53 | wiki pages stay at 48rem or 78ch. Width goes to tables and lists. | |
| 54 | 4. **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. | |
| 58 | 5. **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. | |
| 62 | 6. **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. | |
| 66 | 7. **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 | ||
| 88 | Facet links carry the other active filters, the way `activeFilters` | |
| 89 | builds its clear links. The milestones and labels pages stay for | |
| 90 | management; 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` | |
| 160 | entry in `.claude/launch.json`: `option1-dashboard.html`, | |
| 161 | `option1-blob.html`, the rejected `option2-*` pair, and `layouts.html` | |
| 162 | with one wireframe per page type. `desktop.css` there is the override | |
| 163 | layer the rules above were checked against. | |