Skip to content

Commit b1686f4

Browse files
cpcloudclaude
andauthored
docs(website): theme Mermaid diagrams and add inline code formatting (#875)
## Summary - **Inline code formatting**: Post-render JS scans Mermaid SVG text nodes for backtick-delimited segments and replaces them with monospace-styled `<tspan>` elements (JetBrains Mono). Function calls, API endpoints, CLI commands, and SQL keywords get monospace; prose stays in Source Serif 4. - **CSS custom property theming**: All diagram colors — sequence diagrams (actors, arrows, notes, lifelines, labels, activations) and ER diagrams (entity boxes, relationship lines, markers, edge labels) — are overridden via external CSS using `var(--linen)`, `var(--terracotta)`, `var(--charcoal-soft)`, etc. with `!important`. Theme toggles update CSS variables instantly with zero JS re-rendering. - **Rect group backgrounds**: Post-processing replaces hardcoded `fill` attributes with `style.fill = "var(--linen)"` so they adapt to theme toggles. - **Actor labels**: DM Serif Display font via CSS to match site headings. - Applied to all three sequence diagrams in relay architecture docs. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 61082aa commit b1686f4

3 files changed

Lines changed: 203 additions & 71 deletions

File tree

docs/content/docs/development/relay-architecture.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ sequenceDiagram
4949
participant A as Device A
5050
participant R as Relay
5151
participant B as Device B
52-
A->>R: secretbox.Seal(op, hhKey) then push
52+
A->>R: `secretbox.Seal(op, hhKey)` then push
5353
R->>R: store ciphertext
5454
B->>R: pull after seq N
5555
R->>B: serve ciphertext
56-
B->>B: secretbox.Open(op, hhKey)
56+
B->>B: `secretbox.Open(op, hhKey)`
5757
```
5858

5959
### Device tokens at rest ([AES-256-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode))
@@ -78,20 +78,20 @@ sequenceDiagram
7878
participant R as Relay
7979
participant J as Joiner
8080
81-
I->>R: POST /households/{id}/invite
81+
I->>R: `POST /households/{id}/invite`
8282
R-->>I: invite code
8383
8484
Note over I,J: share code out-of-band
8585
86-
J->>R: POST /households/{id}/join (code)
86+
J->>R: `POST /households/{id}/join` (code)
8787
R-->>J: exchange ID
8888
89-
I->>R: GET /households/{id}/pending-exchanges
89+
I->>R: `GET /households/{id}/pending-exchanges`
9090
R-->>I: joiner public key
9191
92-
I->>R: POST /key-exchange/complete (encrypted household key)
92+
I->>R: `POST /key-exchange/complete` (encrypted household key)
9393
94-
J->>R: GET /key-exchange
94+
J->>R: `GET /key-exchange`
9595
R-->>J: encrypted key + device token
9696
Note over R: credentials scrubbed
9797
```
@@ -162,13 +162,13 @@ sequenceDiagram
162162
participant R as Relay (Postgres)
163163
participant B as Device B
164164
165-
Note over A: User runs micasa pro init
165+
Note over A: User runs `micasa pro init`
166166
167167
rect rgb(240, 235, 228)
168168
Note over A,R: Household creation
169-
A->>A: GenerateDeviceKeyPair()
170-
A->>A: GenerateHouseholdKey()
171-
A->>R: POST /households (public key)
169+
A->>A: `GenerateDeviceKeyPair()`
170+
A->>A: `GenerateHouseholdKey()`
171+
A->>R: `POST /households` (public key)
172172
R-->>A: household ID + device token
173173
A->>A: save keys to secrets directory
174174
end
@@ -178,46 +178,46 @@ sequenceDiagram
178178
179179
rect rgb(240, 235, 228)
180180
Note over A,R: Push
181-
A->>A: secretbox.Seal(op, hhKey) per entry
182-
A->>R: POST /sync/push (encrypted ops)
181+
A->>A: `secretbox.Seal(op, hhKey)` per entry
182+
A->>R: `POST /sync/push` (encrypted ops)
183183
R->>R: assign sequence numbers, store ciphertext
184184
end
185185
186-
Note over A,B: User runs micasa pro invite, shares code
186+
Note over A,B: User runs `micasa pro invite`, shares code
187187
188188
rect rgb(240, 235, 228)
189189
Note over A,B: Key exchange
190-
A->>R: POST /households/{id}/invite
190+
A->>R: `POST /households/{id}/invite`
191191
R-->>A: invite code (4h expiry)
192-
B->>R: POST /households/{id}/join (invite code + public key)
192+
B->>R: `POST /households/{id}/join` (invite code + public key)
193193
R-->>B: exchange ID
194-
A->>R: GET /households/{id}/pending-exchanges
194+
A->>R: `GET /households/{id}/pending-exchanges`
195195
R-->>A: joiner public key
196-
A->>A: box.Seal(hhKey, joinerPubKey)
197-
A->>R: POST /key-exchange/complete
198-
R->>R: encryptToken(deviceToken, serverKey)
199-
B->>R: GET /key-exchange (15min expiry)
200-
R->>R: decryptToken(stored, serverKey)
196+
A->>A: `box.Seal(hhKey, joinerPubKey)`
197+
A->>R: `POST /key-exchange/complete`
198+
R->>R: `encryptToken(deviceToken, serverKey)`
199+
B->>R: `GET /key-exchange` (15min expiry)
200+
R->>R: `decryptToken(stored, serverKey)`
201201
R-->>B: encrypted household key + device token
202202
R->>R: scrub credentials
203-
B->>B: box.Open(encKey, privateKey) = household key
203+
B->>B: `box.Open(encKey, privateKey)` = household key
204204
end
205205
206206
rect rgb(240, 235, 228)
207207
Note over B,R: Pull
208-
B->>R: GET /sync/pull?after=0
208+
B->>R: `GET /sync/pull?after=0`
209209
R-->>B: all encrypted ops
210-
B->>B: secretbox.Open(op, hhKey) per op
211-
B->>B: apply INSERT/UPDATE/DELETE to local SQLite
210+
B->>B: `secretbox.Open(op, hhKey)` per op
211+
B->>B: apply `INSERT`/`UPDATE`/`DELETE` to local SQLite
212212
end
213213
214214
rect rgb(240, 235, 228)
215215
Note over A,B: Blob sync
216-
A->>A: secretbox.Seal(docData, hhKey)
217-
A->>R: PUT /blobs/{household}/{sha256}
218-
B->>R: GET /blobs/{household}/{sha256}
216+
A->>A: `secretbox.Seal(docData, hhKey)`
217+
A->>R: `PUT /blobs/{household}/{sha256}`
218+
B->>R: `GET /blobs/{household}/{sha256}`
219219
R-->>B: encrypted blob
220-
B->>B: secretbox.Open(blob, hhKey)
220+
B->>B: `secretbox.Open(blob, hhKey)`
221221
end
222222
223223
Note over A,B: Both devices now have identical data

docs/layouts/_default/baseof.html

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -172,49 +172,68 @@
172172
</script>
173173

174174
<script type="module">
175-
import mermaid from 'mermaid';
176-
177-
function getMermaidTheme() {
178-
var dark = document.documentElement.getAttribute('data-theme') === 'dark';
179-
return {
180-
startOnLoad: false,
181-
theme: 'base',
182-
themeVariables: dark ? {
183-
fontFamily: '"Source Serif 4", Georgia, serif',
184-
fontSize: '14px',
185-
primaryColor: '#3d3730',
186-
primaryBorderColor: '#d4764e',
187-
primaryTextColor: '#e8ddd0',
188-
lineColor: '#706760',
189-
secondaryColor: '#242019',
190-
tertiaryColor: '#242019'
191-
} : {
192-
fontFamily: '"Source Serif 4", Georgia, serif',
193-
fontSize: '14px',
194-
primaryColor: '#f0ebe4',
195-
primaryBorderColor: '#c05e3c',
196-
primaryTextColor: '#2d2a26',
197-
lineColor: '#9e958a',
198-
secondaryColor: '#faf6f1',
199-
tertiaryColor: '#faf6f1'
175+
import mermaid from "mermaid";
176+
177+
mermaid.initialize({
178+
startOnLoad: false,
179+
theme: "base",
180+
themeVariables: {
181+
fontFamily: '"Source Serif 4", Georgia, serif',
182+
fontSize: "14px",
183+
background: "transparent"
184+
}
185+
});
186+
await mermaid.run();
187+
188+
// Yield one frame so the browser commits rendered SVGs to the DOM
189+
// before we walk their text nodes.
190+
await new Promise(function(resolve) { requestAnimationFrame(resolve); });
191+
192+
// Post-process rendered SVGs once — CSS custom properties in docs.css
193+
// handle all color changes on theme toggle without re-rendering.
194+
document.querySelectorAll(".mermaid svg").forEach(function(svg) {
195+
// Replace backtick-delimited text with monospace tspans
196+
var walker = document.createTreeWalker(svg, NodeFilter.SHOW_TEXT, null);
197+
var nodes = [];
198+
var node;
199+
while ((node = walker.nextNode())) {
200+
if (node.textContent.includes("`")) {
201+
nodes.push(node);
200202
}
201-
};
202-
}
203-
204-
mermaid.initialize(getMermaidTheme());
205-
mermaid.run();
206-
207-
document.addEventListener('theme-changed', async function () {
208-
mermaid.initialize(getMermaidTheme());
209-
var els = document.querySelectorAll('.mermaid[data-processed]');
210-
for (var i = 0; i < els.length; i++) {
211-
var el = els[i];
212-
var source = el.getAttribute('data-original-source');
213-
if (!source) continue;
214-
el.removeAttribute('data-processed');
215-
el.innerHTML = source;
216203
}
217-
await mermaid.run();
204+
var mono = '"JetBrains Mono", "Fira Code", "Consolas", monospace';
205+
nodes.forEach(function(textNode) {
206+
var parts = textNode.textContent.split(/`([^`]+)`/g);
207+
if (parts.length <= 1) {
208+
return;
209+
}
210+
var parent = textNode.parentNode;
211+
var frag = document.createDocumentFragment();
212+
for (var i = 0; i < parts.length; i++) {
213+
if (parts[i] === "") {
214+
continue;
215+
}
216+
if (i % 2 === 0) {
217+
frag.appendChild(document.createTextNode(parts[i]));
218+
} else {
219+
var tspan = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
220+
tspan.style.fontFamily = mono;
221+
tspan.textContent = parts[i];
222+
frag.appendChild(tspan);
223+
}
224+
}
225+
parent.replaceChild(frag, textNode);
226+
});
227+
228+
// Replace hardcoded rect group fills with a CSS variable so they
229+
// adapt to theme toggles without re-rendering.
230+
svg.querySelectorAll("rect").forEach(function(rect) {
231+
var fill = (rect.getAttribute("fill") || "").replace(/\s/g, "");
232+
if (fill === "rgb(240,235,228)") {
233+
rect.removeAttribute("fill");
234+
rect.style.fill = "var(--linen)";
235+
}
236+
});
218237
});
219238
</script>
220239

docs/static/css/docs.css

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,119 @@ h2:hover .badge-experimental .badge-label {
746746
font-size: 0.9em;
747747
}
748748

749+
/* Sequence diagram colors — CSS custom properties auto-update on theme
750+
toggle, eliminating the need to re-render Mermaid diagrams. */
751+
752+
.docs-main pre.mermaid .actor {
753+
stroke: var(--terracotta) !important;
754+
fill: var(--linen) !important;
755+
}
756+
757+
.docs-main pre.mermaid text.actor > tspan {
758+
fill: var(--charcoal) !important;
759+
font-family: "DM Serif Display", Georgia, serif !important;
760+
}
761+
762+
.docs-main pre.mermaid .actor-line {
763+
stroke: var(--rule) !important;
764+
}
765+
766+
.docs-main pre.mermaid .messageLine0,
767+
.docs-main pre.mermaid .messageLine1 {
768+
stroke: var(--charcoal-soft) !important;
769+
}
770+
771+
.docs-main pre.mermaid [id^="arrowhead"] path,
772+
.docs-main pre.mermaid [id^="crosshead"] path {
773+
fill: var(--charcoal-soft) !important;
774+
stroke: var(--charcoal-soft) !important;
775+
}
776+
777+
.docs-main pre.mermaid .messageText {
778+
fill: var(--charcoal) !important;
779+
}
780+
781+
.docs-main pre.mermaid .labelBox {
782+
stroke: var(--rule) !important;
783+
fill: var(--cream) !important;
784+
}
785+
786+
.docs-main pre.mermaid .labelText,
787+
.docs-main pre.mermaid .labelText > tspan,
788+
.docs-main pre.mermaid .loopText,
789+
.docs-main pre.mermaid .loopText > tspan {
790+
fill: var(--charcoal-soft) !important;
791+
}
792+
793+
.docs-main pre.mermaid .loopLine {
794+
stroke: var(--rule) !important;
795+
fill: var(--rule) !important;
796+
}
797+
798+
.docs-main pre.mermaid .note {
799+
stroke: var(--rule) !important;
800+
fill: var(--cream) !important;
801+
}
802+
803+
.docs-main pre.mermaid .noteText,
804+
.docs-main pre.mermaid .noteText > tspan {
805+
fill: var(--charcoal) !important;
806+
}
807+
808+
.docs-main pre.mermaid .activation0,
809+
.docs-main pre.mermaid .activation1,
810+
.docs-main pre.mermaid .activation2 {
811+
fill: var(--linen) !important;
812+
stroke: var(--terracotta) !important;
813+
}
814+
815+
/* ER diagram colors */
816+
817+
.docs-main pre.mermaid .entityBox,
818+
.docs-main pre.mermaid .node rect,
819+
.docs-main pre.mermaid .node circle,
820+
.docs-main pre.mermaid .node ellipse,
821+
.docs-main pre.mermaid .node polygon {
822+
fill: var(--linen) !important;
823+
stroke: var(--terracotta) !important;
824+
}
825+
826+
.docs-main pre.mermaid .relationshipLabelBox {
827+
fill: transparent !important;
828+
opacity: 1 !important;
829+
}
830+
831+
.docs-main pre.mermaid .labelBkg {
832+
background-color: transparent !important;
833+
}
834+
835+
.docs-main pre.mermaid .relationshipLine {
836+
stroke: var(--warm-gray) !important;
837+
}
838+
839+
.docs-main pre.mermaid .marker {
840+
stroke: var(--charcoal-soft) !important;
841+
}
842+
843+
.docs-main pre.mermaid .marker path {
844+
stroke: var(--charcoal-soft) !important;
845+
stroke-width: 2 !important;
846+
}
847+
848+
.docs-main pre.mermaid .marker circle {
849+
fill: var(--cream) !important;
850+
stroke: var(--charcoal-soft) !important;
851+
stroke-width: 2 !important;
852+
}
853+
854+
.docs-main pre.mermaid .edgeLabel .label {
855+
fill: var(--charcoal) !important;
856+
}
857+
858+
.docs-main pre.mermaid .label {
859+
color: var(--charcoal) !important;
860+
}
861+
749862
/* ── Screenshots ───────────────────────────────────────────── */
750863

751864
.docs-main img,

0 commit comments

Comments
 (0)