rshow 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +50 -2
- package/dist/main.js +8500 -0
- package/package.json +48 -4
- package/skills/rshow/SKILL.md +144 -0
- package/skills/rshow/reference/cli.md +263 -0
- package/skills/rshow/reference/dashboard.md +122 -0
- package/skills/rshow/reference/mockups.md +113 -0
- package/skills/rshow/reference/page-contract.md +83 -0
- package/skills/rshow/reference/pages.md +174 -0
- package/skills/rshow/reference/poster.md +137 -0
- package/skills/rshow/reference/project-page.md +132 -0
- package/skills/rshow/reference/reports.md +158 -0
- package/skills/rshow/reference/sketch.md +78 -0
- package/skills/rshow/reference/terminal.md +148 -0
- package/skills/rshow/reference/themes.md +139 -0
- package/skills/rshow-implement/SKILL.md +112 -0
- package/skills/rshow-implement/reference/compare.md +156 -0
- package/skills/rshow-implement/reference/handoff.md +125 -0
- package/skills/rshow-implement/reference/map.md +151 -0
- package/skills/rshow-slides/SKILL.md +136 -0
- package/skills/rshow-slides/reference/layouts.md +251 -0
- package/themes/default/dashboard.css +521 -0
- package/themes/default/dashboard.js +1390 -0
- package/themes/default/deck.css +586 -0
- package/themes/default/deck.js +741 -0
- package/themes/default/example/capture.ans +1 -0
- package/themes/default/example/dashboard.html +106 -0
- package/themes/default/example/demo.cast +14 -0
- package/themes/default/example/drawing.excalidraw +377 -0
- package/themes/default/example/index.html +311 -0
- package/themes/default/example/mockup-desktop.html +331 -0
- package/themes/default/example/mockup-platforms.html +742 -0
- package/themes/default/example/mockup.html +416 -0
- package/themes/default/example/page.html +153 -0
- package/themes/default/example/poster-figure.svg +58 -0
- package/themes/default/example/poster.html +181 -0
- package/themes/default/example/project.html +229 -0
- package/themes/default/example/report.html +404 -0
- package/themes/default/example/results.csv +61 -0
- package/themes/default/example/sketch.html +178 -0
- package/themes/default/example/teaser.svg +66 -0
- package/themes/default/example/terminal.html +231 -0
- package/themes/default/example/tui.html +223 -0
- package/themes/default/fonts/IBMPlexMono-Regular-Latin1.woff2 +0 -0
- package/themes/default/fonts/IBMPlexSans-Light-Latin1.woff2 +0 -0
- package/themes/default/fonts/IBMPlexSans-Regular-Latin1.woff2 +0 -0
- package/themes/default/fonts/IBMPlexSans-SemiBold-Latin1.woff2 +0 -0
- package/themes/default/fonts/OFL.txt +93 -0
- package/themes/default/fonts.css +63 -0
- package/themes/default/index.html +573 -0
- package/themes/default/mockup.css +1078 -0
- package/themes/default/mockup.js +1433 -0
- package/themes/default/mode.js +242 -0
- package/themes/default/page.css +320 -0
- package/themes/default/poster.css +480 -0
- package/themes/default/poster.js +223 -0
- package/themes/default/project.css +482 -0
- package/themes/default/project.js +116 -0
- package/themes/default/report.css +776 -0
- package/themes/default/report.js +427 -0
- package/themes/default/sketch.css +166 -0
- package/themes/default/sketch.js +681 -0
- package/themes/default/skins/adwaita.css +669 -0
- package/themes/default/skins/custom.css +596 -0
- package/themes/default/skins/egui.css +639 -0
- package/themes/default/skins/fluent.css +1093 -0
- package/themes/default/skins/fluent.js +458 -0
- package/themes/default/skins/mac.css +647 -0
- package/themes/default/skins/material.css +692 -0
- package/themes/default/skins/sketch.css +436 -0
- package/themes/default/skins/sketch.js +494 -0
- package/themes/default/skins/wire.css +561 -0
- package/themes/default/terminal.css +292 -0
- package/themes/default/terminal.js +433 -0
- package/themes/default/theme.json +149 -0
- package/themes/default/tokens.css +255 -0
- package/themes/default/tui.css +703 -0
- package/themes/default/tui.js +472 -0
- package/worker/index.js +6918 -0
- package/worker/migrations/0001_init.sql +52 -0
- package/worker/migrations/0002_blobs.sql +5 -0
- package/worker/migrations/0003_projects.sql +31 -0
- package/worker/migrations/0004_page_data_blob.sql +19 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Compare
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- The command
|
|
6
|
+
- The three checks
|
|
7
|
+
- Pixels
|
|
8
|
+
- Structure
|
|
9
|
+
- Tokens
|
|
10
|
+
- The report
|
|
11
|
+
- Thresholds
|
|
12
|
+
- TUI screens
|
|
13
|
+
- Native screens
|
|
14
|
+
- Publishing
|
|
15
|
+
- A failing run, in order
|
|
16
|
+
|
|
17
|
+
## The command
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rshow compare --against app/design/gallery --map rshow.map.json
|
|
21
|
+
rshow compare --against app/design/gallery --map rshow.map.json --publish
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| flag | default | what it does |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `--against` | none, required | the handoff folder that holds `spec.json` and `baseline/` |
|
|
27
|
+
| `--map` | `rshow.map.json` in the current folder | the map from handoff names to app paths and urls |
|
|
28
|
+
| `--threshold` | `0.1` | how far two pixels must differ before a pixel counts as different |
|
|
29
|
+
| `--max-diff-ratio` | `0.02` | the share of differing pixels a screen may have, so 2% |
|
|
30
|
+
| `--native-max-diff-ratio` | `0.15` | the same share for a native screen, whose window frame and font stack cannot line up |
|
|
31
|
+
| `--tui-colors` | off | keeps ANSI colour when a tmux pane is compared, instead of dropping it |
|
|
32
|
+
| `--publish` | off | publishes the report as `<slug>-compare`, private by default |
|
|
33
|
+
|
|
34
|
+
The run opens every screen that has a url in `map.screens`, at the widths and modes the handoff recorded.
|
|
35
|
+
A screen with no url is skipped with a note, and a skipped screen does not fail the run.
|
|
36
|
+
That is why an empty value in `screens` is a hole in the check and not a pass.
|
|
37
|
+
A `tmux:` entry is captured as text, a `file:` or `window:` entry as a native screenshot, and the rest as a url.
|
|
38
|
+
|
|
39
|
+
## The three checks
|
|
40
|
+
|
|
41
|
+
All three run in the same pass and every finding lands in one report.
|
|
42
|
+
|
|
43
|
+
- Pixels, which catches layout, spacing and colour that moved.
|
|
44
|
+
- Structure, which catches a normative component that is missing, the wrong size or the wrong style.
|
|
45
|
+
- Tokens, which catches a raw value that should have been a token.
|
|
46
|
+
|
|
47
|
+
## Pixels
|
|
48
|
+
|
|
49
|
+
Every screen is screenshotted at the same width, height and mode as the baseline.
|
|
50
|
+
The diff is a per pixel colour distance in YIQ, close to pixelmatch, so a pixel under `--threshold` does not count.
|
|
51
|
+
Anti-aliasing is ignored, so a text edge does not fail a screen.
|
|
52
|
+
The failing line is the share of differing pixels against `--max-diff-ratio`, so 2% of a 1440 wide screen is about 26k pixels.
|
|
53
|
+
A size mismatch is reported in the note as one size against the other, and it is nearly always a scrollbar, a zoom or a viewport default.
|
|
54
|
+
|
|
55
|
+
## Structure
|
|
56
|
+
|
|
57
|
+
Every component the handoff marks normative must exist in the implementation and match it.
|
|
58
|
+
|
|
59
|
+
- It is matched by `data-component` first, then by role and text.
|
|
60
|
+
Put `data-component="PrimaryButton"` on the element in the app when the match is otherwise ambiguous.
|
|
61
|
+
- The box must land within 4px or 2% of the larger side.
|
|
62
|
+
- `color`, `fontSize`, `fontWeight` and `radius` must equal the record, or the value its mapped token resolves to.
|
|
63
|
+
- A finding reads `component property expected actual`, for example `PrimaryButton box 96,412 168x40` against `96,414 168x40`.
|
|
64
|
+
|
|
65
|
+
A component in the app that the handoff does not name is not a finding, so a build may hold more than the design.
|
|
66
|
+
|
|
67
|
+
## Tokens
|
|
68
|
+
|
|
69
|
+
A value that equals a token passes.
|
|
70
|
+
A raw colour within 0.08 of a token colour is reported as drift, because the eye reads it as the token and the next theme change will miss it.
|
|
71
|
+
The finding names the token and the value, so the fix is to use the token.
|
|
72
|
+
|
|
73
|
+
## The report
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
app/design/gallery/compare-report/
|
|
77
|
+
index.html the run, one row per screen and state, with the three images and the mismatch table
|
|
78
|
+
report.json the same data, for a script
|
|
79
|
+
images/ <screen>__<state>__<width>__<mode>-baseline.png, -actual.png and -diff.png
|
|
80
|
+
rubric-<screen>.md one native screen, a checklist to tick beside the two images
|
|
81
|
+
app/design/gallery/diff/
|
|
82
|
+
<screen>__<state>__<width>__<mode>.png the same diff image, one folder up
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Open `index.html` and read the row before the image.
|
|
86
|
+
The row says pass or fail with the diff ratio and the note.
|
|
87
|
+
The mismatch table names the property, the expected value and the actual value.
|
|
88
|
+
The diff image is the third opinion, and it is worth a look when the ratio sits just over the limit.
|
|
89
|
+
|
|
90
|
+
## Thresholds
|
|
91
|
+
|
|
92
|
+
The defaults are set for a build that uses the design tokens.
|
|
93
|
+
Loosening either flag hides a real difference, and the person reading the report still sees the design.
|
|
94
|
+
So never change `--threshold` or `--max-diff-ratio` without asking.
|
|
95
|
+
When a person agrees a screen cannot match, say which screen and why in the reply, then pass the flag on that run only.
|
|
96
|
+
Never write the looser value into a script or a Makefile target that runs on every build.
|
|
97
|
+
|
|
98
|
+
## TUI screens
|
|
99
|
+
|
|
100
|
+
A terminal UI cannot be screenshotted by a browser, so the map uses a tmux target and the compare captures text cells.
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{ "screens": { "cli": "tmux:mysession:0.1" } }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `mysession` is the session, `0` the window and `.1` the pane, and the pane must be running the TUI when the run starts.
|
|
107
|
+
- The pane is read with `tmux capture-pane -pet`, so every character cell is kept.
|
|
108
|
+
- The first run writes `baseline/<screen>.txt` from the live pane and passes, which makes it the reference.
|
|
109
|
+
Read that file before you trust it, because a baseline taken from a broken build locks the break in.
|
|
110
|
+
- Later runs compare cell by cell, and a finding is a row number with the expected and the actual text.
|
|
111
|
+
- ANSI colour is dropped unless `--tui-colors` is passed, so a style only run uses that flag.
|
|
112
|
+
- `new-session -A -s mysession`, then `select-pane -t 0.1`, gets a pane with a stable target for CI.
|
|
113
|
+
|
|
114
|
+
A tmux baseline is text, so it diffs in a review and it bootstraps nothing from the network.
|
|
115
|
+
|
|
116
|
+
## Native screens
|
|
117
|
+
|
|
118
|
+
A native desktop app has no DOM, so a screenshot is the capture.
|
|
119
|
+
An Electron, Tauri, egui, Qt, GTK or SwiftUI window compares this way.
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{ "screens": { "desktop": "file:shots/desktop.png", "settings": "window:My App" } }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- `file:` is a screenshot you or the agent took, and the path is relative to `rshow.map.json`.
|
|
126
|
+
Take it at the baseline width or at 2x that width, because the compare scales it.
|
|
127
|
+
- `window:` captures the window itself.
|
|
128
|
+
On macOS it asks `CGWindowListCopyWindowInfo` through `osascript -l JavaScript` for the window id of the owner name or the title, then runs `screencapture -x -o -l <windowID>`.
|
|
129
|
+
On Linux it runs `xdotool search --name` and `import -window`.
|
|
130
|
+
Anywhere else, and when no window matches, the run exits 2 and asks for `file:`.
|
|
131
|
+
- The capture is scaled to the baseline width with a box filter, so a Retina 2x shot averages 2x2 into one pixel.
|
|
132
|
+
The diff is then written like a browser run.
|
|
133
|
+
- The pixel limit is `--native-max-diff-ratio`, 0.15 by default, because an OS window frame, a WebView and another font stack cannot line up.
|
|
134
|
+
- The palette check takes the dominant colours of the capture, k of 8 by histogram and k-means, then compares them in Lab.
|
|
135
|
+
A token colour that a normative component uses and that is absent from the capture, at a delta E 76 under 10, is a finding.
|
|
136
|
+
A large area that sits within 10 of a token but not on it is reported as drift.
|
|
137
|
+
- `compare-report/rubric-<screen>.md` is a checklist built from `spec.json`.
|
|
138
|
+
It holds every state, every normative component with its text, its colour, its size and its place against the sibling before it, every annotation note and the palette findings.
|
|
139
|
+
The compare cannot answer those lines, so you do, with the baseline and the capture open side by side.
|
|
140
|
+
- Such a screen reports `review` and the run exits 0, with the report saying the rubric needs a pass.
|
|
141
|
+
Exit 7 only when the pixels or a missing token colour fail hard.
|
|
142
|
+
|
|
143
|
+
## Publishing
|
|
144
|
+
|
|
145
|
+
`--publish` publishes `compare-report` as `<slug>-compare`, private by default, then prints the version url plus the live url.
|
|
146
|
+
Send it to the person with the screens that still differ named in the same message.
|
|
147
|
+
Ask before making it public, because the report holds a screenshot of every screen and state.
|
|
148
|
+
|
|
149
|
+
## A failing run, in order
|
|
150
|
+
|
|
151
|
+
1. Read the failing rows and write down the screen, the state and the note.
|
|
152
|
+
2. Read the mismatch table, and fix a missing component or a wrong token first, because that finding often clears several pixel rows.
|
|
153
|
+
3. Look at the diff image, and check whether the whole canvas moved, such as a scrollbar, a zoom or a font that did not load.
|
|
154
|
+
4. Fill in the rubric of every `review` screen, and write the reason on each line you could not tick.
|
|
155
|
+
5. Fix, then run the same command again.
|
|
156
|
+
6. Report the run that exits 0, and never report a run that only passed after a flag moved.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Handoff
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- The command
|
|
6
|
+
- What it writes
|
|
7
|
+
- HANDOFF.md
|
|
8
|
+
- spec.json
|
|
9
|
+
- A component record
|
|
10
|
+
- Normative and placeholder
|
|
11
|
+
- Screens and states
|
|
12
|
+
- Remote version or local folder
|
|
13
|
+
- Commit the folder
|
|
14
|
+
|
|
15
|
+
## The command
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
rshow handoff <slug>@v<n> --out app/design/gallery
|
|
19
|
+
rshow handoff ./mockups/gallery --out app/design/gallery
|
|
20
|
+
rshow handoff gallery@v3 --widths 1440,768,390 --modes light,dark
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`--out` defaults to `./handoff-<slug>-v<n>`, or `./handoff-<slug>` for a local folder.
|
|
24
|
+
`--widths` defaults to `1440,390` and `--modes` defaults to `light,dark`.
|
|
25
|
+
The first width is the primary width, and the component records are taken there.
|
|
26
|
+
Chrome or Chromium runs headless, and `CHROME` picks the binary when the default one is wrong.
|
|
27
|
+
|
|
28
|
+
## What it writes
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
app/design/gallery/
|
|
32
|
+
HANDOFF.md the source, the screens, the component table, the rules, the compare command
|
|
33
|
+
spec.json per screen, state and mode, every component with its box and computed style
|
|
34
|
+
tokens.json every custom property on :root, in W3C DTCG 2025.10 form
|
|
35
|
+
baseline/ <screen>__<state>__<width>__<mode>.png, one screenshot per variant
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The folder is a snapshot, so it is safe to commit and safe to diff.
|
|
39
|
+
A second handoff into the same `--out` overwrites it, which is how a design change arrives.
|
|
40
|
+
|
|
41
|
+
## HANDOFF.md
|
|
42
|
+
|
|
43
|
+
Read it first, because it is short and it holds the four things you need at the start.
|
|
44
|
+
|
|
45
|
+
- The immutable source url, the width and mode list and the shape it shot, such as a mockup, a deck, a report or a page.
|
|
46
|
+
- A screen table with the id, the title, the frame, the states and the size.
|
|
47
|
+
- The flows from one screen to another and the annotation notes with their x and y.
|
|
48
|
+
- A component table with the name, the role, the box, the token each value matches and whether it must match.
|
|
49
|
+
- The exact `rshow compare` line for this folder.
|
|
50
|
+
|
|
51
|
+
## spec.json
|
|
52
|
+
|
|
53
|
+
`spec.json` is the machine form of the same thing, and it is what the compare reads.
|
|
54
|
+
|
|
55
|
+
| field | holds |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `schema` and `generator` | the handoff schema number and `rshow handoff` |
|
|
58
|
+
| `source` | `slug`, `version`, `url`, the `kind` and the `skin` of the mockup |
|
|
59
|
+
| `widths`, `modes`, `primaryWidth` | what was shot, plus the width the components were recorded at |
|
|
60
|
+
| `screens` | one record per screen, with `id`, `title`, `frame`, `kind`, `width`, `height`, `states` |
|
|
61
|
+
| `screens.flows` | `from` and `to` pairs, and the flow is part of the design |
|
|
62
|
+
| `screens.annotations` | the note text with its `x` and `y`, and this is the design intent in words |
|
|
63
|
+
| `screens.focus` | the focus order, in the order the mockup records it |
|
|
64
|
+
| `screens.variants` | one record per `state` and `mode`, holding the `components` |
|
|
65
|
+
| `baselines` | the `file` for each screen, state, width and mode |
|
|
66
|
+
|
|
67
|
+
A screen id is the artboard id for a mockup or the slide number for a deck.
|
|
68
|
+
The id `page` covers a page or a report.
|
|
69
|
+
The map uses the same id, so a screen the map does not name is a screen nobody compares.
|
|
70
|
+
|
|
71
|
+
## A component record
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"name": "PrimaryButton",
|
|
76
|
+
"role": "button",
|
|
77
|
+
"x": 96, "y": 412, "w": 168, "h": 40,
|
|
78
|
+
"color": "#ffffff",
|
|
79
|
+
"background": "#0f62fe",
|
|
80
|
+
"fontFamily": "IBM Plex Sans",
|
|
81
|
+
"fontSize": "14px",
|
|
82
|
+
"fontWeight": "400",
|
|
83
|
+
"radius": "0px",
|
|
84
|
+
"normative": true,
|
|
85
|
+
"text": "Invite",
|
|
86
|
+
"padding": "0px 16px",
|
|
87
|
+
"tokens": { "background": "--accent", "fontSize": "--type-body" }
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`name` comes from `data-component` first, then from the `wf-*` class, then from the role or the tag.
|
|
92
|
+
`gap` and `padding` appear on a container, and `tokens` names the token each value matched.
|
|
93
|
+
A box must land within 4px or 2% of the larger side, and `color`, `fontSize`, `fontWeight` and `radius` must match the record or the token it names.
|
|
94
|
+
|
|
95
|
+
## Normative and placeholder
|
|
96
|
+
|
|
97
|
+
- `data-normative` means the text, the box and the computed style are the design and they must match.
|
|
98
|
+
- `data-placeholder`, and anything the mockup does not mark, is free.
|
|
99
|
+
Text, data, names, prices and images are placeholder by default, so put real content in them.
|
|
100
|
+
- A real product needs a real empty state, a real error string and a real long name, because those live in the app.
|
|
101
|
+
Only the shape of the block is fixed.
|
|
102
|
+
|
|
103
|
+
When a placeholder value and a normative value disagree, the normative one is a finding and the other is not.
|
|
104
|
+
|
|
105
|
+
## Screens and states
|
|
106
|
+
|
|
107
|
+
A mockup is shot artboard by artboard, through focus mode when `mockup.js` exposes one.
|
|
108
|
+
A deck, a report and a page are one screen each, while a deck is one screen per `section.slide`.
|
|
109
|
+
Every artboard state list starts with `default`, so a screen with `data-states="empty,loading"` gives three baselines.
|
|
110
|
+
Build and compare each one, because the empty state is where the design is thin and where a build drifts first.
|
|
111
|
+
|
|
112
|
+
## Remote version or local folder
|
|
113
|
+
|
|
114
|
+
- A remote target needs a public version, such as `gallery@v3`.
|
|
115
|
+
A private version cannot be read headless, so make it public for the run or shoot the local folder.
|
|
116
|
+
- A local folder needs an `index.html` at its root, and rshow serves it for the run.
|
|
117
|
+
`rshow handoff ./mockups/gallery` is the right call while the mockup is still being argued about.
|
|
118
|
+
- Both write the same four files, so the compare line is the same.
|
|
119
|
+
|
|
120
|
+
## Commit the folder
|
|
121
|
+
|
|
122
|
+
Commit `HANDOFF.md`, `spec.json`, `tokens.json` and `baseline/` next to the code that implements them.
|
|
123
|
+
A frozen folder answers a review question six months later, and a live link does not.
|
|
124
|
+
Publish the mockup again to a new version when the design changes, and freeze the new version over the old one in one commit.
|
|
125
|
+
That commit is the design change, and the diff is the conversation.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Map
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- The file
|
|
6
|
+
- components
|
|
7
|
+
- tokens
|
|
8
|
+
- screens
|
|
9
|
+
- The starter map
|
|
10
|
+
- One example per stack
|
|
11
|
+
- Empty values
|
|
12
|
+
|
|
13
|
+
## The file
|
|
14
|
+
|
|
15
|
+
`rshow.map.json` sits next to the app and holds three objects, all of them name to string maps.
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"components": {
|
|
20
|
+
"wf-button": "src/ui/Button.tsx",
|
|
21
|
+
"PrimaryButton": "src/ui/Button.tsx#primary"
|
|
22
|
+
},
|
|
23
|
+
"tokens": {
|
|
24
|
+
"--accent": "theme.colors.accent",
|
|
25
|
+
"--line": "--border-muted"
|
|
26
|
+
},
|
|
27
|
+
"screens": {
|
|
28
|
+
"gallery": "http://localhost:5173/",
|
|
29
|
+
"share": "http://localhost:5173/a/demo#share",
|
|
30
|
+
"cli": "tmux:mysession:0.1",
|
|
31
|
+
"desktop": "file:shots/desktop.png"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`rshow compare` reads it from `--map`, or from `./rshow.map.json` when the flag is absent.
|
|
37
|
+
Keys are sorted on write, and a hand edit keeps working because the file is only read.
|
|
38
|
+
`screens` is the only object the compare reads, so a screen missing from it is skipped.
|
|
39
|
+
`components` and `tokens` are the record of the translation, and they are what a reviewer reads to see that each design name landed somewhere in the app.
|
|
40
|
+
|
|
41
|
+
## components
|
|
42
|
+
|
|
43
|
+
The key is the component name exactly as the handoff table writes it, such as `PrimaryButton` or `wf-card-header`.
|
|
44
|
+
The value is a path from the repo root, with `#anchor` when one file holds several components.
|
|
45
|
+
|
|
46
|
+
- Find the app component that already does the job before you write a new one.
|
|
47
|
+
A handoff name often lands on a shared primitive, and two names may point at one file.
|
|
48
|
+
- Map every name in the component table that is not the role or the tag, because a name that is a bare tag such as `button` is not a component.
|
|
49
|
+
- Put `data-component` on the element when the app markup cannot be told apart by role and text.
|
|
50
|
+
That is the first thing the structural check matches on.
|
|
51
|
+
- A new component gets a line here in the same commit, and the reply says the app had nothing that fit.
|
|
52
|
+
|
|
53
|
+
## tokens
|
|
54
|
+
|
|
55
|
+
The key is the token name with its leading `--`, exactly as `tokens.json` groups it.
|
|
56
|
+
The value is the path to the app's own token, in whatever form the app reads it.
|
|
57
|
+
|
|
58
|
+
- A CSS app maps to a variable or a Tailwind key, such as `"--accent": "--color-accent"` or `"--accent": "theme.colors.accent"`.
|
|
59
|
+
- A Swift app maps to a colour asset or a struct field, such as `"Color.accent"`.
|
|
60
|
+
- A Kotlin or Compose app maps to a theme field, such as `"MaterialTheme.colorScheme.primary"`.
|
|
61
|
+
- A ratatui app maps to a style field, such as `"Style::new().fg(ACCENT)"`.
|
|
62
|
+
- A dark value lives in the `dark` group of `tokens.json` and in the app's own dark scope, so it needs no second map line when the app swaps the token.
|
|
63
|
+
|
|
64
|
+
A token the design uses and the app does not have is a change to the app token system, and it is worth one sentence in the reply.
|
|
65
|
+
|
|
66
|
+
## screens
|
|
67
|
+
|
|
68
|
+
The key is the screen id from `spec.json`, such as an artboard id, a slide number or `page`.
|
|
69
|
+
The value is a url that renders that screen and that state, with the hash or the query the app needs.
|
|
70
|
+
It may also be a tmux target or a native capture, and the compare picks the path from the prefix.
|
|
71
|
+
|
|
72
|
+
- Use the dev server url and the route that renders the screen, such as `http://localhost:5173/a/demo#share`.
|
|
73
|
+
- A url may hold a hash, so a router that reads it lands on the right screen.
|
|
74
|
+
- A terminal UI uses a tmux target, which is `tmux:<session>:<window>.<pane>`.
|
|
75
|
+
The pane must already run the TUI, and the first compare writes `baseline/<screen>.txt` from it.
|
|
76
|
+
- A native desktop app uses `file:<path.png>` for a screenshot of the window, relative to `rshow.map.json`.
|
|
77
|
+
It uses `window:<app or title>` to capture the window itself, which works on macOS and on Linux with `xdotool` and ImageMagick.
|
|
78
|
+
The capture is scaled to the baseline width with a box filter, so a Retina 2x screenshot is fine.
|
|
79
|
+
The report then marks the screen `review` and writes `compare-report/rubric-<screen>.md` to tick beside the two images.
|
|
80
|
+
- Several screens may share one url when the app switches on a hash, and the compare opens the url once for all of them.
|
|
81
|
+
- A screen with no url is skipped, and the run still exits 0, so check that every id in `spec.json` has a value here.
|
|
82
|
+
|
|
83
|
+
## The starter map
|
|
84
|
+
|
|
85
|
+
`rshow handoff` writes a starter `./rshow.map.json` when the current folder has none.
|
|
86
|
+
It lists every component name, every token name and every screen id from the handoff, all with an empty value.
|
|
87
|
+
Fill the values in and delete nothing, because a key that the handoff writes and the map drops is a name the next handoff adds back.
|
|
88
|
+
Run the handoff from the app repo root so the starter lands where the compare looks for it.
|
|
89
|
+
|
|
90
|
+
## One example per stack
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"components": { "wf-nav-item": "src/components/NavItem.tsx", "wf-avatar": "src/components/Avatar.tsx" },
|
|
95
|
+
"tokens": { "--accent": "--accent", "--bg": "--surface", "--line": "--border" },
|
|
96
|
+
"screens": { "gallery": "http://localhost:5173/gallery", "share": "http://localhost:5173/share" }
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A React app with a Tailwind theme reads `tokens.json` into `theme.extend` once, and the map points at the theme keys.
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"components": { "wf-button": "ios/UI/Button.swift", "wf-row": "ios/UI/Row.swift" },
|
|
105
|
+
"tokens": { "--accent": "Color.accent", "--line": "Color.separator" },
|
|
106
|
+
"screens": { "gallery": "http://localhost:8080/#gallery" }
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A Swift app cannot match pixels through a browser, so the screens entry is a screenshot of the running app or the structural compare by hand.
|
|
111
|
+
Say which one you did.
|
|
112
|
+
|
|
113
|
+
A Compose app maps to the theme, and a screen url needs an emulator bridge, a screenshot of the running app or the structural compare instead.
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"components": { "wf-button": "app/src/main/kotlin/ui/Button.kt" },
|
|
118
|
+
"tokens": { "--accent": "MaterialTheme.colorScheme.primary" },
|
|
119
|
+
"screens": { "gallery": "http://localhost:8080/#gallery" }
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A ratatui app or any other terminal app maps `screens` to tmux, and the components and tokens the same way.
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"components": { "wf-log-row": "src/ui/log_row.rs" },
|
|
128
|
+
"tokens": { "--accent": "styles::ACCENT", "--line": "styles::BORDER" },
|
|
129
|
+
"screens": { "cli": "tmux:rshow:0.1" }
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A native desktop app, whether it is Electron, Tauri, egui, Qt, GTK or SwiftUI, maps `screens` to a screenshot or to the window itself.
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"components": { "wf-sidebar": "src/Sidebar.tsx", "PrimaryButton": "src/Button.tsx#primary" },
|
|
138
|
+
"tokens": { "--accent": "--color-accent", "--line": "--color-border" },
|
|
139
|
+
"screens": { "desktop": "file:shots/desktop.png", "settings": "window:My App" }
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Take `shots/desktop.png` at the baseline width or at twice it and keep it next to the map.
|
|
144
|
+
Re-take it after each fix.
|
|
145
|
+
`window:My App` matches the owner name first, then the window title, so the app name is usually enough.
|
|
146
|
+
|
|
147
|
+
## Empty values
|
|
148
|
+
|
|
149
|
+
An empty value is not a neutral default, it is a name nobody translated.
|
|
150
|
+
Write the value, or write the reason it stays empty in the reply, such as a component the design names and the app renders inline.
|
|
151
|
+
The next handoff adds every new name with an empty value, so a diff of this file is the list of what still needs a home.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rshow-slides
|
|
3
|
+
description: Load before you build or revise a talk, deck or presentation. Load it even for a quick question about one or for a deck already published at an art.<domain>/<name> link. Use it for any new talk or deck unless an editable .pptx, a Keynote file or a company master is asked for, which belongs to slides. That covers a lab meeting, a reading group, a paper turned into slides, a link you share in Slack instead of a pptx, speaker notes and slides you click through in a browser with arrow keys in our group template. Publishing and non-slide pages belong to rshow, implementing a design in code belongs to rshow-implement, a claude.ai Artifact belongs to the Artifact tool, while visual design direction belongs to frontend-design.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rshow slides
|
|
7
|
+
|
|
8
|
+
A deck is a folder with one `index.html` in it, published to `https://art.example.com/<slug>/`.
|
|
9
|
+
It links the deck half of a published theme, so the folder holds no CSS, no font and no script of its own.
|
|
10
|
+
The theme does the layout, and the deck only picks a layout per slide.
|
|
11
|
+
The look is IBM Carbon on IBM Plex, and `themes/DESIGN.md` in the rshow repo holds the whole system.
|
|
12
|
+
|
|
13
|
+
## 1. Outline first
|
|
14
|
+
|
|
15
|
+
Write the outline before any HTML and put it in the reply.
|
|
16
|
+
One line per slide, and each line is a claim that states the takeaway.
|
|
17
|
+
A topic on its own is not a slide.
|
|
18
|
+
|
|
19
|
+
- Size the deck from the talk.
|
|
20
|
+
Ten minutes is 10 to 12 slides, and five minutes is 5 to 6.
|
|
21
|
+
- One idea per slide, readable in about 3 seconds.
|
|
22
|
+
When a slide needs two ideas, it is two slides.
|
|
23
|
+
- Open with a `title` slide and close with a `title` slide or a `big-number` that says what to do next.
|
|
24
|
+
- Put one `section` slide before each move in the argument.
|
|
25
|
+
- Give the evidence slides the layout that carries the claim: `figure`, `chart`, `table` or `figure-two` for a research or systems talk.
|
|
26
|
+
- In a research talk the figure, the plot and the table are the hero.
|
|
27
|
+
The title says what the evidence shows and the type stays out of the way.
|
|
28
|
+
- Say which slide carries the headline number, and put the number in it once.
|
|
29
|
+
- A number you cannot measure now is `[PLACEHOLDER]`, and it stays out of the talk until it is real.
|
|
30
|
+
- Agree the outline with the person, then write the file.
|
|
31
|
+
|
|
32
|
+
## 2. Skeleton
|
|
33
|
+
|
|
34
|
+
Replace `<name>@<n>` with the `theme` value that `rshow config <dir>` prints, which the server resolves to the current version. Use `default@3` only when `rshow config` warns that it cannot resolve the theme.
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<!doctype html>
|
|
38
|
+
<html lang="en">
|
|
39
|
+
<head>
|
|
40
|
+
<meta charset="utf-8">
|
|
41
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
42
|
+
<title>Talk title</title>
|
|
43
|
+
<link rel="stylesheet" href="/_themes/default@3/fonts.css">
|
|
44
|
+
<link rel="stylesheet" href="/_themes/default@3/tokens.css">
|
|
45
|
+
<link rel="stylesheet" href="/_themes/default@3/deck.css">
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<main class="deck">
|
|
49
|
+
|
|
50
|
+
<section class="slide title">
|
|
51
|
+
<h1>Talk title</h1>
|
|
52
|
+
<p class="subtitle">One line that says what the room gets.</p>
|
|
53
|
+
<p class="byline">[PLACEHOLDER] name, [PLACEHOLDER] group, [PLACEHOLDER] venue, [PLACEHOLDER] date</p>
|
|
54
|
+
<aside class="notes"><p>Who you are and what the room gets out of this.</p></aside>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
<section class="slide figure">
|
|
58
|
+
<h2>The title of the slide is the takeaway</h2>
|
|
59
|
+
<figure>
|
|
60
|
+
<img src="figure.svg" alt="What to look at in the figure.">
|
|
61
|
+
<figcaption>One line of source or caption.</figcaption>
|
|
62
|
+
</figure>
|
|
63
|
+
<aside class="notes"><p>Point at the line that carries the argument.</p></aside>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
</main>
|
|
67
|
+
<script src="/_themes/default@3/deck.js"></script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The three stylesheet links and the script carry the same `<n>` on purpose, because a mixed version renders somewhere between two designs.
|
|
73
|
+
The deck needs no `mode.js`, because `deck.js` reads the same key and `t` does the same work.
|
|
74
|
+
Override a token in one `:root` block after the theme links when the deck needs its own accent, and keep it to a few declarations.
|
|
75
|
+
|
|
76
|
+
## 3. Layouts
|
|
77
|
+
|
|
78
|
+
Every slide is a `<section class="slide ...">`, and the second class is one of twelve.
|
|
79
|
+
|
|
80
|
+
- `title` for the opener, `section` for a divider and `statement` for one claim set large.
|
|
81
|
+
- `bullets` for a takeaway title and at most five short points.
|
|
82
|
+
- `figure` for one diagram or image, `figure-two` for two side by side and `chart` for an inline SVG plot.
|
|
83
|
+
One series is drawn in the accent blue and two or more take `--chart-1`, `--chart-2` and so on in order.
|
|
84
|
+
- `table` for a clean data table and `code` for one command.
|
|
85
|
+
- `two-col` for two blocks of text, `quote` for one sentence and `big-number` for one number.
|
|
86
|
+
|
|
87
|
+
A sparse slide is centred a little above the middle of the canvas, so the air sits under the block.
|
|
88
|
+
A bullets slide puts its points under the title at 28px with a 1.4 line, five at most.
|
|
89
|
+
A figure slide starts at the top and lets the figure take the rest.
|
|
90
|
+
A figure is `<figure>` with a `figcaption`, and two figures are `<div class="pair">` with two `<figure>` inside.
|
|
91
|
+
A big number is `<p class="value">` plus `<p class="caption">`, and the value is a short statement with the figure inside it, never a bare digit.
|
|
92
|
+
Do not invent a thirteenth layout and do not write the layout CSS yourself, because the theme owns it and a copy drifts.
|
|
93
|
+
[layouts](reference/layouts.md) holds one markup example per layout. Read it before you write a slide that is not `title`, `bullets` or `section`.
|
|
94
|
+
|
|
95
|
+
## 4. Notes and steps
|
|
96
|
+
|
|
97
|
+
- Every slide gets one `<aside class="notes">`, including the title slide, because a slide without one shows nothing in the notes window.
|
|
98
|
+
- Keep a note under 60 words.
|
|
99
|
+
It is what you say, not what the room reads.
|
|
100
|
+
- Press `n` for the notes window, which also names the next slide.
|
|
101
|
+
- Press `p` for the presenter view: the current slide, the next slide, the notes and an elapsed timer, kept in step with the deck.
|
|
102
|
+
- Put `class="step"` on an `<li>` to reveal it one press later.
|
|
103
|
+
- Every step is a reveal, so a slide with four steps needs four presses.
|
|
104
|
+
Five is the ceiling before the slide drags.
|
|
105
|
+
- Press `f` for fullscreen and arrows or space to advance.
|
|
106
|
+
Press `Ctrl` and `P` for a print sheet when someone wants a pdf.
|
|
107
|
+
- Press `t` to cycle the mode: system, light, dark, with the mode shown in the counter for a moment.
|
|
108
|
+
The choice is remembered for every rshow document and print is always light.
|
|
109
|
+
|
|
110
|
+
## 5. Check, preview, publish
|
|
111
|
+
|
|
112
|
+
1. Run `rshow check <dir>` and fix every error until it exits 0.
|
|
113
|
+
2. Run `rshow preview <dir>` and open the printed `http://127.0.0.1:4321` link.
|
|
114
|
+
Click through the whole deck.
|
|
115
|
+
3. Read one slide at 360px wide, because the room may open the link on a phone.
|
|
116
|
+
4. Press `n` and read the notes once, out loud, so a note that is too long shows up before the talk.
|
|
117
|
+
5. Run `rshow publish <dir> --slug <slug> --label "<what changed>"`.
|
|
118
|
+
6. Report the printed `live-url` and say who can see it.
|
|
119
|
+
|
|
120
|
+
A talk lives at `art.example.com/<slug>/`, and `art.example.com/<slug>/#7` opens slide 7 directly.
|
|
121
|
+
Publish again after a fix instead of making a second slug, because the link in the calendar invite keeps working.
|
|
122
|
+
|
|
123
|
+
## Never
|
|
124
|
+
|
|
125
|
+
- Never write an eyebrow, a kicker or a small caps label with wide tracking above a title.
|
|
126
|
+
- Never set a label in mono or in capitals.
|
|
127
|
+
- Never write a meta string such as `A / B / C` above a title.
|
|
128
|
+
- Never number a slide or a card 01, 02, 03.
|
|
129
|
+
- Never ship three equal cards with a coloured top rule.
|
|
130
|
+
- Never build an identity from cream and terracotta, from beige and brass or from near black plus one bright accent.
|
|
131
|
+
- Never put a coloured bar on a slide edge or a rule down the side of a quote.
|
|
132
|
+
- Never use a hairline rule as decoration.
|
|
133
|
+
- Never use Inter, Space Grotesk, Geist, Fraunces or Instrument Serif as the gesture.
|
|
134
|
+
- Never use gradient text, glass, a large shadow or an emoji.
|
|
135
|
+
- Never animate a slide in, because a reveal is the only motion a deck needs.
|
|
136
|
+
- Never invent a number and never present example data without saying so in the caption.
|