1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# Quire — Style Guide
> A self-hosted personal source forge. jj-native. Text-first. JavaScript-optional.
> This guide is the canonical reference for any agent producing UI, copy, or
> markup for Quire. It is prescriptive about tokens and vocabulary, looser
> about layout — read it through before you build.
---
## 1. Product personality
Quire is a **gathering of folded leaves, sewn together** — a quire, in
bookbinding, is the smallest unit of a book that can stand on its own. The
product reflects that:
- **One person's tool.** No login, no social graph, no issues, no PRs. You
push via git or jj; you read via the browser.
- **Text-first.** Reading code and history is the primary activity. Treat
the page like a printed page, not an app surface.
- **Quiet.** No badges, no gradients, no shadows, no rounded card stacks.
Hairline rules and whitespace do the structural work.
- **Durable.** Designed to run for years on a small VPS without thinking.
The UI should feel similarly low-maintenance — no trends, no chrome.
- **jj-native.** jj's vocabulary is the primary vocabulary. Git is the
interop layer, shown secondarily.
### Voice
- Lowercase prose by default in UI chrome (`quire`, `bookmarks`, `recent
changes`). Sentence case in body copy and README content.
- Short, plain sentences. No exclamation marks. No emoji.
- Technical when it needs to be — assume the reader knows git and is
learning jj.
### Anti-patterns (do not do)
- Gradients, glassmorphism, drop shadows, neumorphism.
- Rounded cards with colored left-border accents.
- Emoji or pictographic icon sets. Use unicode glyphs sparingly (`※`, `→`,
`·`, `—`).
- Fixed sidebars. Columns scroll with the page.
- "Get started" hero blocks, marketing copy, social proof.
- Inventing new accent colors per feature. One accent per palette.
---
## 2. Vocabulary
These terms are load-bearing — use them consistently in copy, labels, and
component names.
| Term | Meaning | UI treatment |
|---|---|---|
| **change** | jj's stable identity across rewrites. 8-char letter id. | First 4 chars in `accent`, last 4 in `muted`. Monospace. |
| **commit** | Underlying git commit-id. 8-char hex. | `mutedFaint`, monospace. Secondary to change-id. |
| **bookmark** | jj's branches. | Pill prefixed with `※`. Label in `accent`. |
| **trunk** | The immutable frontier (below `trunk()` revset). | Hair-rule left rail; `immutable` flag. |
| **conflict** | Tracked on changes with conflicts. | Filled `!` badge in `bad`. |
| **divergent** | Same change-id, different commit-ids. | Split glyph; `bad` color. |
| **(no description set)** | jj's empty description. | Italic, `mutedFaint`. |
Do not write "branch" when you mean bookmark. Do not write "SHA" when you
mean commit-id. Do not write "revision" when you mean change.
---
## 3. Typography
Two families. No exceptions without a reason.
### Families
```css
--font-humanist: "iA Writer Quattro", "iA Writer Quattro V",
-apple-system, system-ui, sans-serif;
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
```
- **Humanist** is the default for body, headings, README, and any prose
surface.
- **Mono** is for: change-ids, commit-ids, bookmark names, paths, code
blocks, kbd, top-nav wordmark, section eyebrows, metadata strips, CI
rows, the footer.
When in doubt: anything that originates from the repo (a name, an id, a
path, a ref) is mono.
### Scale
| Token | Size | Line-height | Used for |
|---|---|---|---|
| `display` | 28px | 1.2 | Page-level h1 in README |
| `h2` | 19px | 1.3 | Section headings in prose |
| `body` | 16px | 1.72 | README and long-form prose |
| `ui` | 15px | 1.6 | Default UI text |
| `meta` | 13px | 1.6 | Top-nav, breadcrumbs |
| `mono-md` | 12.5px | 1.6 | Bookmarks, CI rows, change rows |
| `mono-sm` | 11.5–12px | 1.5 | Author, age, secondary mono |
| `eyebrow` | 11px / `letter-spacing: 1.2px` / uppercase | — | Section labels |
| `kbd` | 10.5px | — | Keyboard hints |
Letter-spacing: `-0.2` to `-0.3` on display-size headings; `+1.2` on
uppercase eyebrows; `+0.2` to `+0.8` on small caps and labels. Default
otherwise.
Headings: `font-weight: 500–600`. Body: 400. Mono is rarely bolded — only
the first 4 chars of a change-id (500) and matched fennel keywords (500).
---
## 4. Color
Quire ships **six palettes**, each with light + dark variants. Every
palette uses the same token shape. Pick one per instance; do not mix.
### Token shape
```ts
type Palette = {
bg: string; // page background
ink: string; // primary text
muted: string; // secondary text
mutedFaint: string; // tertiary / age / hint
rule: string; // primary hairline (between major bands)
rule2: string; // secondary hairline (between rows, dotted underlines)
code: string; // code-block & inline-code background
accent: string; // change-ids, bookmarks, links, code-block left-rail
ok: string; // CI pass, ok status
bad: string; // CI fail, conflict
};
```
### The six palettes
```ts
// Paper · graphite — the original direction. Warm paper, graphite accent.
PALETTE_PAPER.light = {
bg:'#f8f4ea', ink:'#1d1a15', muted:'#6b6257', mutedFaint:'#9a9184',
rule:'#ddd4c1', rule2:'#c7bfae', code:'#efe8d6',
accent:'#3a3a3a', ok:'#4a7a3a', bad:'#9a3a28',
};
PALETTE_PAPER.dark = {
bg:'#14120f', ink:'#e8e2d2', muted:'#8a8173', mutedFaint:'#5a5347',
rule:'#2a2721', rule2:'#1f1d18', code:'#1b1915',
accent:'#c9c2b0', ok:'#8ab378', bad:'#d47a65',
};
// Bone · ink-red — colder off-white, single inked-red accent.
PALETTE_BONE.light = {
bg:'#f4f2ec', ink:'#1a1814', muted:'#625a4f', mutedFaint:'#948b7d',
rule:'#d6cfbe', rule2:'#c0b9a7', code:'#ebe6d4',
accent:'#a8361d', ok:'#4a7a3a', bad:'#a8361d',
};
PALETTE_BONE.dark = {
bg:'#141312', ink:'#ece5d4', muted:'#8d8573', mutedFaint:'#5a5347',
rule:'#2a2822', rule2:'#1f1d18', code:'#1b1a16',
accent:'#d77560', ok:'#8ab378', bad:'#d77560',
};
// Linen · forest — linen paper, deep forest accent. Quietest palette.
PALETTE_FOREST.light = {
bg:'#f3efe3', ink:'#1c1a15', muted:'#665e52', mutedFaint:'#958c7e',
rule:'#d4cdbb', rule2:'#bfb7a4', code:'#eae3d0',
accent:'#2d5b4a', ok:'#2d5b4a', bad:'#9a3a28',
};
PALETTE_FOREST.dark = {
bg:'#11130f', ink:'#e6e2d3', muted:'#827a6d', mutedFaint:'#555045',
rule:'#242722', rule2:'#1c1f1a', code:'#181a15',
accent:'#7aaa92', ok:'#7aaa92', bad:'#d47a65',
};
// Chalk · cobalt — cooler paper, cobalt ink. Architects' notation.
PALETTE_COBALT.light = {
bg:'#f5f4ef', ink:'#181a1e', muted:'#5c6168', mutedFaint:'#8d939a',
rule:'#d5d4cd', rule2:'#bfbeb5', code:'#ebeae3',
accent:'#1f4f7a', ok:'#4a7a3a', bad:'#9a3a28',
};
PALETTE_COBALT.dark = {
bg:'#101216', ink:'#e4e6ea', muted:'#838a93', mutedFaint:'#545962',
rule:'#23262c', rule2:'#1a1d22', code:'#15181d',
accent:'#7ba4c8', ok:'#8ab378', bad:'#d47a65',
};
// Folio · sepia-gold — strongest folio nod. Warm paper, sepia-gold accent.
PALETTE_SEPIA.light = {
bg:'#f7eedc', ink:'#1c170f', muted:'#71654f', mutedFaint:'#a09278',
rule:'#dccfb3', rule2:'#c4b899', code:'#eee1c4',
accent:'#7a5d2e', ok:'#4a7a3a', bad:'#9a3a28',
};
PALETTE_SEPIA.dark = {
bg:'#14110b', ink:'#ece1cb', muted:'#8f826b', mutedFaint:'#5d5545',
rule:'#2a2720', rule2:'#1e1c16', code:'#1b1813',
accent:'#c4a76f', ok:'#8ab378', bad:'#d47a65',
};
// Ink · dark-native — dark-first; cooler ink with paper-white accent.
PALETTE_INK.light = {
bg:'#f2f1ec', ink:'#151517', muted:'#5a5a5e', mutedFaint:'#8b8b8f',
rule:'#d2d1ca', rule2:'#bbbab2', code:'#e9e8e1',
accent:'#151517', ok:'#4a7a3a', bad:'#9a3a28',
};
PALETTE_INK.dark = {
bg:'#0f0f12', ink:'#eceae4', muted:'#86858b', mutedFaint:'#545357',
rule:'#22222a', rule2:'#19191f', code:'#161619',
accent:'#eceae4', ok:'#8ab378', bad:'#d47a65',
};
```
### Color rules
- **One accent per palette.** Use it for change-id heads, bookmark labels,
links, and the left-rail of code blocks. Nowhere else.
- All accents are low-chroma so prose still reads quiet. Do not introduce
saturated hues.
- `ok`/`bad` are reserved for CI status and conflict markers. Do not use
them as decorative color.
- Hairlines use `rule` between major bands and `rule2` between rows or as
dotted underlines on links.
---
## 5. Layout
### Page rhythm
- Outer horizontal padding: **56px** on all major bands (`padding: x 56px`).
- Vertical band padding: **22–32px** top, **16–32px** bottom. Tighten for
metadata strips (10px), expand for prose (32px+).
- Major bands separated by `1px solid rule`.
- Row-level separators (inside lists) use `1px solid rule2` or
`1px dotted rule2`.
### Two-column
When a page has both a primary reading surface and ancillary metadata
(e.g. README + sidebar stack), use:
```css
display: grid;
grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
column-gap: 0;
border-right between columns: 1px solid rule;
```
Both columns scroll with the page. **No fixed sidebars.** Sidebar content
is `font-mono`, eyebrow + content blocks, separated by 24px.
### Single-column (file/code)
File views go full-width. Use a 2-column grid inside (line-number gutter
+ code body) with `gridTemplateColumns: 'auto 1fr'`. The gutter has
`background: code` and a `1px solid rule2` right border.
### Prose width
README body should max-width around **720px**, even within a wide column.
---
## 6. Components
### Top nav
A single horizontal band:
```
[Q-mark] quire / <repo> press [?] for shortcuts
```
- Padding: `14px 56px`.
- Border-bottom: `1px solid rule`.
- Wordmark in mono, 14px, weight 500, `letter-spacing: -0.2`.
- Breadcrumb separator: `/` in `rule2`.
- Right-side hint: 11px `mutedFaint`.
### Q-mark logo
16×16 SVG: a 12×12 rounded square (`rx: 1.2`, `stroke: ink`,
`stroke-width: 1.2`) containing three horizontal strokes (lines of text)
and a dashed circle overlay at 35% opacity. Render at `ink` color in any
palette. Do not redraw or restyle.
### Change-id
```jsx
<a><span color="accent" weight={500}>{id.slice(0,4)}</span><span color="muted">{id.slice(4,8)}</span></a>
```
- Mono, 12.5px, no underline.
- Optional `title="commit <hex>"` for hover affordance.
### Commit-id
Mono, `mutedFaint`, 12px. Always shown next to or after a change-id, never
in place of one as primary identity.
### Bookmark
Pill, inline:
```
[ ※ name @remote +ahead -behind ⚠ ]
```
- Border: `1px solid rule2`, `border-radius: 2`. Mono, 10.5px.
- `※` glyph in `mutedFaint` at 9px. Name in `accent`. Remote in
`mutedFaint`. Ahead/behind in `mutedFaint`, 9.5px. Tracking-broken `⚠`
in `bad`.
### BookmarkList (sidebar)
Mono 12.5px, line-height 1.8. Each row:
```
[KIND 44px][※ name (dotted-underline) ][+a −b][age]
```
- KIND uppercase, 10px, `letter-spacing: 0.8`. `trunk` kind in `accent`,
others in `mutedFaint`.
- Dotted underline on names: `border-bottom: 1px dotted rule2`.
- Numbers tabular: `font-variant-numeric: tabular-nums`.
### CI list
Mono, 12.5px:
```
[● 6px dot][#412][change(4)][age ][duration]
```
- Dot: 6×6, `border-radius: 3`. `ok` for pass, `bad` for fail.
- Change column shows the first 4 chars of the change-id in `accent`.
### ChangeList row
Grid: `3px 74px 1fr auto auto`, gap 16, padding `6px 56px 6px 53px`.
- 2px left rail (`rule2` if `immutable`, else transparent).
- ChangeId, then description (`ink`), then bookmarks (inline), then
author, then age.
- Empty description renders as `<NoDesc>` — italic, `mutedFaint`,
literal text `(no description set)`.
- Conflict flag inserts a `<ConflictMark>` after the description.
### Conflict mark
13×13 filled circle, `background: bad`, white `!` in mono 9px/600.
Optional uppercase `conflict` tag in `bad`, 10px, `letter-spacing: 0.6`.
### Code block
```css
font-family: mono;
font-size: 13px;
line-height: 1.65;
background: code;
color: ink;
padding: 14px 18px;
border-left: 2px solid accent;
overflow: auto;
```
Inline code: same `code` background, `1px 5px` padding, mono 13px.
### Keyboard hint (`kbd`)
```css
font-family: mono;
font-size: 10.5px;
padding: 1px 5px;
border: 1px solid rule2;
border-bottom-width: 2px;
border-radius: 3px;
color: muted;
```
### MetaCol / SideBlock (eyebrow + content)
Eyebrow: mono 11px, `letter-spacing: 1.2`, uppercase, `muted`. 8px below
eyebrow, then 6px to a `1px solid rule2` border-bottom on the eyebrow if
it sits inside a SideBlock. SideBlocks are stacked with `margin-bottom: 24`
(0 on last).
### Footer
`16px 56px 24px`, `border-top: 1px solid rule`. Mono 11px, `mutedFaint`,
`letter-spacing: 0.2`. Left side: version. Right side: `?` shortcut hint.
---
## 7. Glyphs
Use only these. Do not introduce icons.
| Glyph | Use |
|---|---|
| `※` | Bookmark prefix. |
| `→` | Forward pointer ("on main → kxrtpqmn"); also "more" links. |
| `·` | Inline metadata separator (in `rule2`). |
| `/` | Breadcrumb separator (in `rule2`). |
| `—` | Em-dash; submodule placeholders, prose. |
| `!` | Conflict marker (filled circle, white). |
| `⚠` | Tracking-broken bookmark. |
---
## 8. Imagery
There isn't any. Quire renders text on paper-toned backgrounds. If you
must place an image (e.g. a favicon), it is the **folio mark** — the
Q-mark SVG above, exported at 16/32/180.
---
## 9. Accessibility
- Hit-targets in keyboard hints and inline UI may be small visually but
every primary action has a keyboard shortcut shown in the footer.
- Maintain WCAG AA contrast for `ink` on `bg` and `accent` on `bg` — all
shipped palettes do. Do not lower contrast for stylistic reasons.
- Links are distinguished by color + dotted underline (`rule2`), not
color alone.
- Status (CI pass/fail, conflict) uses color + glyph, not color alone.
---
## 10. Quick checklist for any new view
- [ ] Top nav with Q-mark + breadcrumb.
- [ ] One band of identity / metadata under the nav, separated by `rule`.
- [ ] Mono for everything that came from the repo (ids, names, paths).
- [ ] Humanist for prose.
- [ ] One accent color, used only for change-id heads / bookmark labels /
links / code-block left-rail.
- [ ] 56px horizontal page padding.
- [ ] No shadows, no gradients, no rounded cards.
- [ ] Footer with version + `?` hint.
- [ ] jj vocabulary: change, commit, bookmark, trunk.
---
## 11. Machine-readable token export
```json
{
"fonts": {
"humanist": "\"iA Writer Quattro\", \"iA Writer Quattro V\", -apple-system, system-ui, sans-serif",
"mono": "\"IBM Plex Mono\", ui-monospace, monospace"
},
"scale": {
"display": { "size": 28, "lineHeight": 1.2, "weight": 600, "letterSpacing": -0.3 },
"h2": { "size": 19, "lineHeight": 1.3, "weight": 600 },
"body": { "size": 16, "lineHeight": 1.72, "weight": 400 },
"ui": { "size": 15, "lineHeight": 1.6, "weight": 400 },
"meta": { "size": 13, "lineHeight": 1.6, "weight": 400 },
"monoMd": { "size": 12.5, "lineHeight": 1.6 },
"monoSm": { "size": 11.5, "lineHeight": 1.5 },
"eyebrow": { "size": 11, "letterSpacing": 1.2, "uppercase": true },
"kbd": { "size": 10.5 }
},
"spacing": { "page": 56, "band": 22, "section": 32, "row": 6, "block": 24 },
"rules": { "major": "1px solid rule", "minor": "1px solid rule2", "dotted": "1px dotted rule2" },
"palettes": ["PAPER", "BONE", "FOREST", "COBALT", "SEPIA", "INK"],
"vocab": ["change", "commit", "bookmark", "trunk", "conflict", "divergent"],
"glyphs": { "bookmark": "※", "arrow": "→", "sep": "·", "crumb": "/", "dash": "—", "conflict": "!", "warn": "⚠" }
}
```