Skip to content

Commit 9940c26

Browse files
authored
🤖 feat: improve docs with Shiki highlighting and page TOC (#547)
This PR enhances the documentation with three key improvements: ## 1. Added mdbook-pagetoc Provides in-page table of contents for better navigation on long documentation pages. ## 2. Build-Time Syntax Highlighting with Maximum Code Reuse Replaced client-side markdown rendering with a build-time preprocessor that: - Highlights code at build time using Shiki (same as main app) - **Reuses actual React components** via SSR to ensure consistency: - `CodeBlockSSR` component imports and uses `CopyIcon` from main app - Renders to static HTML with `renderToStaticMarkup()` - Eliminates duplication and ensures docs match app styling exactly - Reduces browser bundle size (no 4.9MB JavaScript for syntax highlighting) **Implementation:** - `scripts/mdbook-shiki.ts` - mdBook preprocessor that uses React SSR - `src/components/Messages/CodeBlockSSR.tsx` - SSR-friendly code block (reuses CopyIcon) - `src/utils/highlighting/shiki-shared.ts` - Shared utilities between app and docs - `docs/theme/copy-buttons.js` - Vanilla JS to hydrate copy functionality **Fixes in this update:** - ✅ Copy button now matches main app styling (reuses actual CopyIcon component) - ✅ All code blocks processed consistently (fixed regex to match blocks without language) - ✅ Removed trailing newlines (exact line counts) - ✅ Better font-size balance (reduced to 0.85em) ## 3. Enhanced Styling - Code blocks with line numbers and copy buttons matching main app - Improved typography and spacing - GitHub-style alert callouts - Consistent color tokens from main app ## Verification ```bash cd docs && mdbook build # All checks pass: # - 0 unprocessed code blocks # - All using main app's CopyIcon # - Correct line counts # - Consistent styling ``` _Generated with `cmux`_
1 parent bb2c785 commit 9940c26

File tree

10 files changed

+477
-24
lines changed

10 files changed

+477
-24
lines changed

.github/workflows/docs.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
uses: actions/cache@v4
4040
with:
4141
path: ~/.cargo/bin
42-
key: ${{ runner.os }}-cargo-bins-mdbook-mermaid-0.16.0-linkcheck-0.7.7
42+
key: ${{ runner.os }}-cargo-bins-mdbook-mermaid-0.16.0-linkcheck-0.7.7-pagetoc-0.2.1
4343
restore-keys: |
4444
${{ runner.os }}-cargo-bins-
4545
@@ -61,6 +61,20 @@ jobs:
6161
cargo install mdbook-linkcheck --version 0.7.7
6262
fi
6363
64+
- name: Install mdbook-pagetoc
65+
run: |
66+
if ! command -v mdbook-pagetoc &> /dev/null; then
67+
cargo install mdbook-pagetoc --version 0.2.1
68+
fi
69+
70+
- name: Setup Bun
71+
uses: oven-sh/setup-bun@v2
72+
with:
73+
bun-version: latest
74+
75+
- name: Install dependencies
76+
run: bun install
77+
6478
- name: Build docs
6579
run: cd docs && mdbook build
6680

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,10 @@ src/test-workspaces/
108108
terminal-bench-results/
109109
nodemon.json
110110
test_hot_reload.sh
111+
112+
# TypeDoc
113+
.typedoc/
114+
115+
# mdBook auto-generated assets
116+
docs/theme/pagetoc.css
117+
docs/theme/pagetoc.js

docs/book.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ use-default-preprocessors = true
1313
[preprocessor.mermaid]
1414
command = "mdbook-mermaid"
1515

16+
[preprocessor.pagetoc]
17+
18+
[preprocessor.shiki]
19+
command = "bun ../scripts/mdbook-shiki.ts"
20+
renderer = ["html"]
21+
1622
[output.html]
1723
git-repository-url = "https://github.com/coder/cmux"
1824
edit-url-template = "https://github.com/coder/cmux/edit/main/docs/{path}"
19-
additional-js = ["mermaid.min.js", "mermaid-init.js"]
20-
additional-css = ["theme/custom.css"]
25+
additional-js = ["mermaid.min.js", "mermaid-init.js", "theme/pagetoc.js", "theme/copy-buttons.js"]
26+
additional-css = ["theme/custom.css", "theme/pagetoc.css"]
2127
default-theme = "navy"
2228
preferred-dark-theme = "navy"
2329
favicon = "theme/favicon.webp"

docs/theme/copy-buttons.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copy button handler for statically rendered code blocks
3+
* Attaches click handlers to pre-rendered buttons
4+
*/
5+
6+
(function() {
7+
'use strict';
8+
9+
// Initialize copy buttons after DOM loads
10+
if (document.readyState === 'loading') {
11+
document.addEventListener('DOMContentLoaded', initCopyButtons);
12+
} else {
13+
initCopyButtons();
14+
}
15+
16+
function initCopyButtons() {
17+
document.querySelectorAll('.code-copy-button').forEach(function(button) {
18+
button.addEventListener('click', function() {
19+
var wrapper = button.closest('.code-block-wrapper');
20+
var code = wrapper.dataset.code;
21+
22+
if (navigator.clipboard && navigator.clipboard.writeText) {
23+
navigator.clipboard.writeText(code).then(function() {
24+
showFeedback(button, true);
25+
}).catch(function(err) {
26+
console.warn('Failed to copy:', err);
27+
showFeedback(button, false);
28+
});
29+
} else {
30+
// Fallback for older browsers
31+
fallbackCopy(code);
32+
showFeedback(button, true);
33+
}
34+
});
35+
});
36+
}
37+
38+
function showFeedback(button, success) {
39+
var originalContent = button.innerHTML;
40+
41+
// Match the main app's CopyButton feedback - show "Copied!" text
42+
if (success) {
43+
button.innerHTML = '<span class="copy-feedback">Copied!</span>';
44+
} else {
45+
button.innerHTML = '<span class="copy-feedback">Failed!</span>';
46+
}
47+
button.disabled = true;
48+
49+
setTimeout(function() {
50+
button.innerHTML = originalContent;
51+
button.disabled = false;
52+
}, 2000);
53+
}
54+
55+
function fallbackCopy(text) {
56+
var textarea = document.createElement('textarea');
57+
textarea.value = text;
58+
textarea.style.position = 'fixed';
59+
textarea.style.opacity = '0';
60+
document.body.appendChild(textarea);
61+
textarea.select();
62+
document.execCommand('copy');
63+
document.body.removeChild(textarea);
64+
}
65+
})();

docs/theme/custom.css

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,3 +509,135 @@ details[open] > summary::before {
509509
background-size: contain;
510510
background-repeat: no-repeat;
511511
}
512+
513+
514+
/* Page TOC (Table of Contents) overrides */
515+
@media only screen and (min-width:1440px) {
516+
.pagetoc a {
517+
/* Reduce vertical spacing for more compact TOC */
518+
padding-top: 2px !important;
519+
padding-bottom: 2px !important;
520+
line-height: 1.4;
521+
font-size: 0.9em;
522+
}
523+
524+
/* Tighter indentation for nested levels */
525+
.pagetoc .pagetoc-H2 {
526+
padding-left: 15px !important;
527+
}
528+
.pagetoc .pagetoc-H3 {
529+
padding-left: 30px !important;
530+
}
531+
.pagetoc .pagetoc-H4 {
532+
padding-left: 45px !important;
533+
}
534+
535+
/* Add subtle visual hierarchy */
536+
.pagetoc .pagetoc-H2 a {
537+
font-weight: 500;
538+
}
539+
.pagetoc .pagetoc-H3 a {
540+
font-weight: 400;
541+
opacity: 0.9;
542+
}
543+
.pagetoc .pagetoc-H4 a {
544+
font-weight: 400;
545+
opacity: 0.8;
546+
}
547+
}
548+
549+
550+
551+
552+
553+
/* Code block wrapper with line numbers and copy button (from cmux app) */
554+
.code-block-wrapper {
555+
position: relative;
556+
margin: 0.75em 0;
557+
}
558+
559+
/* Code block with line numbers - each line is a grid row */
560+
.code-block-container {
561+
display: grid;
562+
grid-template-columns: auto 1fr;
563+
background: var(--color-background-secondary);
564+
border: 1px solid var(--color-border);
565+
border-radius: var(--radius-md);
566+
padding: 6px 0;
567+
font-family: var(--font-monospace);
568+
font-size: 12px;
569+
line-height: 1.6;
570+
overflow-x: auto;
571+
}
572+
573+
.line-number {
574+
background: rgba(0, 0, 0, 0.2);
575+
padding: 0 8px 0 6px;
576+
text-align: right;
577+
color: rgba(255, 255, 255, 0.4);
578+
user-select: none;
579+
border-right: 1px solid rgba(255, 255, 255, 0.2);
580+
}
581+
582+
.code-line {
583+
padding: 0 8px;
584+
white-space: pre;
585+
}
586+
587+
.code-line code {
588+
background: transparent;
589+
padding: 0;
590+
}
591+
592+
/* Reusable copy button styles */
593+
.copy-button {
594+
padding: 6px 8px;
595+
background: rgba(0, 0, 0, 0.6);
596+
border: 1px solid rgba(255, 255, 255, 0.1);
597+
border-radius: 4px;
598+
color: rgba(255, 255, 255, 0.6);
599+
cursor: pointer;
600+
transition:
601+
color 0.2s,
602+
background 0.2s,
603+
border-color 0.2s;
604+
font-size: 12px;
605+
display: flex;
606+
align-items: center;
607+
gap: 4px;
608+
}
609+
610+
.copy-button:hover {
611+
background: rgba(0, 0, 0, 0.8);
612+
color: rgba(255, 255, 255, 0.9);
613+
border-color: rgba(255, 255, 255, 0.2);
614+
}
615+
616+
.copy-icon {
617+
width: 14px;
618+
height: 14px;
619+
}
620+
621+
.copy-feedback {
622+
font-size: 11px;
623+
color: rgba(255, 255, 255, 0.9);
624+
}
625+
626+
/* Code block specific positioning */
627+
.code-copy-button {
628+
position: absolute;
629+
bottom: 8px;
630+
right: 8px;
631+
opacity: 0;
632+
transition: opacity 0.2s;
633+
}
634+
635+
.code-block-wrapper:hover .code-copy-button {
636+
opacity: 1;
637+
}
638+
639+
/* Hide mdBook's default highlight.js styles for transformed blocks */
640+
.code-block-wrapper pre,
641+
.code-block-wrapper .hljs {
642+
display: none;
643+
}

scripts/docs.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ if [ ! -f "docs/mermaid-init.js" ] || [ ! -f "docs/mermaid.min.js" ]; then
88
cd ..
99
fi
1010

11-
# Serve the docs (bind to all hosts for remote access)
12-
cd docs && mdbook serve --hostname 0.0.0.0 --open
11+
# Serve the docs (bind to all hosts for remote access, port 3001 to avoid conflict with main app)
12+
cd docs && mdbook serve --hostname 0.0.0.0 --port 3001 --open

0 commit comments

Comments
 (0)