-
Notifications
You must be signed in to change notification settings - Fork 1
docs(e2e): VitePress + Collision Tour + Playwright (split PR 4) #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9ef8f5f
a2136f6
7bf5662
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,41 @@ | ||
| SHELL := /bin/bash | ||
|
|
||
| .PHONY: hooks | ||
| # Default docs port; override with: make docs PORT=5180 | ||
| PORT ?= 5173 | ||
|
|
||
| .PHONY: hooks docs docs-build docs-ci | ||
|
|
||
| hooks: | ||
| @git config core.hooksPath .githooks | ||
| @chmod +x .githooks/* 2>/dev/null || true | ||
| @echo "[hooks] Installed git hooks from .githooks (core.hooksPath)" | ||
|
|
||
| # Start VitePress dev server and open the browser automatically. | ||
| docs: | ||
| @echo "[docs] Ensuring deps..." | ||
| @npm install --silent | ||
| @echo "[docs] Starting VitePress on http://localhost:$(PORT) ..." | ||
| # Start server in background and record PID | ||
| @ (npm run --silent docs:dev -- --port $(PORT) &) ; \ | ||
| server_pid=$$! ; \ | ||
| echo "[docs] Waiting for server to become ready..." ; \ | ||
| for i in {1..80}; do \ | ||
| if curl -sSf "http://localhost:$(PORT)/" >/dev/null ; then \ | ||
| echo "[docs] Server is up at http://localhost:$(PORT)/" ; \ | ||
| scripts/docs-open.sh "http://localhost:$(PORT)/" ; \ | ||
| wait $$server_pid ; \ | ||
| exit 0 ; \ | ||
| fi ; \ | ||
| sleep 0.25 ; \ | ||
| done ; \ | ||
| echo "[docs] Timed out waiting for VitePress." ; \ | ||
| exit 1 | ||
|
|
||
| # Build static site | ||
| docs-build: | ||
| @npm run --silent docs:build | ||
|
|
||
| # Build docs without installing dependencies (for CI caches) | ||
| docs-ci: | ||
| @echo "[docs] CI build (no npm install)" | ||
| @npm run --silent docs:build | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { defineConfig } from 'vitepress' | ||
|
|
||
| export default defineConfig({ | ||
| title: 'Echo', | ||
| description: 'Deterministic, multiverse-aware ECS', | ||
| cleanUrls: true, | ||
| themeConfig: { | ||
| nav: [ | ||
| { text: 'Home', link: '/' }, | ||
| { text: 'Collision Tour', link: '/guide/collision-tour' }, | ||
| ], | ||
| sidebar: { | ||
| '/guide/': [ | ||
| { | ||
| text: 'Guide', | ||
| items: [ | ||
| { text: 'Collision Tour', link: '/guide/collision-tour' }, | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| }) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Collision/CCD DPO Diagrams | ||
|
|
||
| - Base CSS: `diagrams.css` | ||
| - Static SVGs: | ||
| - `dpo_build_temporal_proxy.svg` | ||
| - `dpo_broad_phase_pairing.svg` | ||
| - `dpo_narrow_phase_discrete.svg` | ||
| - `dpo_narrow_phase_ccd.svg` | ||
| - `dpo_contact_events.svg` | ||
| - `dpo_gc_ephemeral.svg` | ||
| - `scheduler_phase_mapping.svg` | ||
| - `legend.svg` | ||
|
|
||
| Each SVG uses semantic classes: | ||
| - Nodes: `node`, variants: `interfaceK`, `added`, `removed` | ||
| - Edges: `edge`, variants: `added`, `removed` | ||
| - Other: `title`, `label`, `caption`, `scope` | ||
| - Optional animations: `pulse-add`, `pulse-remove` | ||
|
|
||
| ## Animations | ||
| - Extend `diagrams.css` with your own `@keyframes` and apply classes to elements. | ||
| - For step-by-step tutorials, use the `_step1.svg`, `_step2.svg`, `_step3.svg` variants or overlay multiple SVGs in the docs site. | ||
|
Comment on lines
+20
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add blank line before heading. You've got a heading smashed right up against the previous content. Markdown isn't assembly—we use whitespace for readability here. Apply this diff: - Optional animations: `pulse-add`, `pulse-remove`
+
## AnimationsBunBun 🧰 Tools🪛 markdownlint-cli2 (0.18.1)20-20: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 🤖 Prompt for AI Agents |
||
|
|
||
| ## Mermaid Sources | ||
| - Source `.mmd` files live alongside these SVGs. You can generate SVGs locally: | ||
|
|
||
| ``` | ||
| # Requires Node.js | ||
| npm i -g @mermaid-js/mermaid-cli | ||
| mmdc -i build_temporal_proxy.mmd -o build_temporal_proxy.svg | ||
| ``` | ||
|
|
||
| CI also compiles `.mmd` files to SVG artifacts (see `.github/workflows/ci.yml` job `diagrams`). | ||
|
Comment on lines
+24
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix heading spacing and code fence language. Two issues here:
The static analysis whining about "github" casing is a false positive— Apply this diff: - Optional animations: `pulse-add`, `pulse-remove`
## Animations
- Extend `diagrams.css` with your own `@keyframes` and apply classes to elements.
- For step-by-step tutorials, use the `_step1.svg`, `_step2.svg`, `_step3.svg` variants or overlay multiple SVGs in the docs site.
+
## Mermaid Sources
- Source `.mmd` files live alongside these SVGs. You can generate SVGs locally:
-```
+```bash
# Requires Node.js
npm i -g @mermaid-js/mermaid-cli
mmdc -i build_temporal_proxy.mmd -o build_temporal_proxy.svgBunBun 🧰 Tools🪛 LanguageTool[uncategorized] ~33-~33: The official name of this software platform is spelled with a capital “H”. (GITHUB) 🪛 markdownlint-cli2 (0.18.1)24-24: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 27-27: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,154 @@ | ||||||||||||||||||||||||||||||||||||
| (function(){ | ||||||||||||||||||||||||||||||||||||
| const els = Array.from(document.querySelectorAll('.fade-seed')); | ||||||||||||||||||||||||||||||||||||
| if (!('IntersectionObserver' in window)) { | ||||||||||||||||||||||||||||||||||||
| els.forEach(el => el.classList.add('fade-in')); | ||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| const io = new IntersectionObserver((entries) => { | ||||||||||||||||||||||||||||||||||||
| entries.forEach(entry => { | ||||||||||||||||||||||||||||||||||||
| if (entry.isIntersecting) { | ||||||||||||||||||||||||||||||||||||
| entry.target.classList.add('fade-in'); | ||||||||||||||||||||||||||||||||||||
| io.unobserve(entry.target); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }, { rootMargin: '0px 0px -10% 0px', threshold: 0.1 }); | ||||||||||||||||||||||||||||||||||||
| els.forEach(el => io.observe(el)); | ||||||||||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Carousel pager for each rule's step-grid | ||||||||||||||||||||||||||||||||||||
| (function(){ | ||||||||||||||||||||||||||||||||||||
| function buildPager(rule) { | ||||||||||||||||||||||||||||||||||||
| const grid = rule.querySelector('.step-grid'); | ||||||||||||||||||||||||||||||||||||
| if (!grid) return; | ||||||||||||||||||||||||||||||||||||
| const slides = Array.from(grid.querySelectorAll('figure')); | ||||||||||||||||||||||||||||||||||||
| if (slides.length < 2) return; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Build overlay captions with step counts from figcaptions | ||||||||||||||||||||||||||||||||||||
| slides.forEach((fig, i) => { | ||||||||||||||||||||||||||||||||||||
| const cap = fig.querySelector('figcaption'); | ||||||||||||||||||||||||||||||||||||
| const text = cap ? cap.textContent.trim() : ''; | ||||||||||||||||||||||||||||||||||||
| const ov = document.createElement('div'); | ||||||||||||||||||||||||||||||||||||
| ov.className = 'overlay'; | ||||||||||||||||||||||||||||||||||||
| ov.innerHTML = `<strong>Step ${i + 1} of ${slides.length}</strong><div class="ov-t">${text}</div>`; | ||||||||||||||||||||||||||||||||||||
| fig.appendChild(ov); | ||||||||||||||||||||||||||||||||||||
| fig.classList.add('has-overlay'); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Avoid innerHTML; keep it DOM-safe. No reason to template HTML here. Build nodes; assign textContent. Safer and cheaper. - const ov = document.createElement('div');
- ov.className = 'overlay';
- ov.innerHTML = `<strong>Step ${i + 1} of ${slides.length}</strong><div class="ov-t">${text}</div>`;
- fig.appendChild(ov);
+ const ov = document.createElement('div');
+ ov.className = 'overlay';
+ const strong = document.createElement('strong');
+ strong.textContent = `Step ${i + 1} of ${slides.length}`;
+ const ovT = document.createElement('div');
+ ovT.className = 'ov-t';
+ ovT.textContent = text;
+ ov.append(strong, ovT);
+ fig.appendChild(ov);
fig.classList.add('has-overlay');📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const nav = document.createElement('div'); | ||||||||||||||||||||||||||||||||||||
| nav.className = 'pager'; | ||||||||||||||||||||||||||||||||||||
| const prev = document.createElement('button'); | ||||||||||||||||||||||||||||||||||||
| prev.className = 'btn'; prev.textContent = '◀ Prev'; | ||||||||||||||||||||||||||||||||||||
| const next = document.createElement('button'); | ||||||||||||||||||||||||||||||||||||
| next.className = 'btn'; next.textContent = 'Next ▶'; | ||||||||||||||||||||||||||||||||||||
| const toggle = document.createElement('button'); | ||||||||||||||||||||||||||||||||||||
| toggle.className = 'btn'; toggle.textContent = 'Show all'; | ||||||||||||||||||||||||||||||||||||
| const world = document.createElement('button'); | ||||||||||||||||||||||||||||||||||||
| world.className = 'btn'; world.textContent = 'World view: On'; | ||||||||||||||||||||||||||||||||||||
| nav.append(prev, next, toggle, world); | ||||||||||||||||||||||||||||||||||||
| grid.after(nav); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let mode = 'all'; // default to all frames visible | ||||||||||||||||||||||||||||||||||||
| let idx = 0; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Helpers to find neighbor rule blocks | ||||||||||||||||||||||||||||||||||||
| const prevRule = (el) => { let r = el.previousElementSibling; while (r && !r.classList.contains('rule')) r = r.previousElementSibling; return r; }; | ||||||||||||||||||||||||||||||||||||
| const nextRule = (el) => { let r = el.nextElementSibling; while (r && !r.classList.contains('rule')) r = r.nextElementSibling; return r; }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| function render() { | ||||||||||||||||||||||||||||||||||||
| if (mode === 'all') { | ||||||||||||||||||||||||||||||||||||
| slides.forEach(el => el.classList.remove('hidden')); | ||||||||||||||||||||||||||||||||||||
| toggle.textContent = 'Carousel mode'; | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| slides.forEach((el, i) => { | ||||||||||||||||||||||||||||||||||||
| el.classList.toggle('hidden', i !== idx); | ||||||||||||||||||||||||||||||||||||
| // ensure visible slide is faded in | ||||||||||||||||||||||||||||||||||||
| if (!el.classList.contains('fade-in')) el.classList.add('fade-in'); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| toggle.textContent = 'Show all'; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| // Enable/disable edges. If at first slide and no previous rule, disable Prev. | ||||||||||||||||||||||||||||||||||||
| // If at last slide and no next rule, disable Next. | ||||||||||||||||||||||||||||||||||||
| if (mode === 'all') { | ||||||||||||||||||||||||||||||||||||
| // Keep navigation enabled in 'all' mode so users/tests can enter carousel via Prev/Next. | ||||||||||||||||||||||||||||||||||||
| prev.disabled = false; next.disabled = false; | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
|
flyingrobots marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
| const atFirst = idx === 0; | ||||||||||||||||||||||||||||||||||||
| const atLast = idx === slides.length - 1; | ||||||||||||||||||||||||||||||||||||
| prev.disabled = atFirst && !prevRule(rule); | ||||||||||||||||||||||||||||||||||||
| next.disabled = atLast && !nextRule(rule); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| prev.addEventListener('click', () => { | ||||||||||||||||||||||||||||||||||||
| // If in all mode, enter carousel at first slide | ||||||||||||||||||||||||||||||||||||
| if (mode === 'all') { mode = 'one'; idx = 0; render(); return; } | ||||||||||||||||||||||||||||||||||||
| if (idx > 0) { idx -= 1; render(); return; } | ||||||||||||||||||||||||||||||||||||
| // At first slide: navigate to previous rule, show its first slide | ||||||||||||||||||||||||||||||||||||
| const pr = prevRule(rule); | ||||||||||||||||||||||||||||||||||||
| if (pr && pr._pager) { | ||||||||||||||||||||||||||||||||||||
| pr._pager.setIndex(0); | ||||||||||||||||||||||||||||||||||||
| pr.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| render(); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| next.addEventListener('click', () => { | ||||||||||||||||||||||||||||||||||||
| if (mode === 'all') { mode = 'one'; idx = 0; render(); return; } | ||||||||||||||||||||||||||||||||||||
| if (idx < slides.length - 1) { idx += 1; render(); return; } | ||||||||||||||||||||||||||||||||||||
| // At last slide: navigate to next rule, show its first slide | ||||||||||||||||||||||||||||||||||||
| const nr = nextRule(rule); | ||||||||||||||||||||||||||||||||||||
| if (nr && nr._pager) { | ||||||||||||||||||||||||||||||||||||
| nr._pager.setIndex(0); | ||||||||||||||||||||||||||||||||||||
| nr.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+87
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Prev should go to previous rule’s last slide, not its first. Current behavior is jarring. Jumping to the previous rule should land on its last slide; mirror “Next → next rule first slide”. - if (pr && pr._pager) {
- pr._pager.setIndex(0);
+ if (pr && pr._pager) {
+ pr._pager.setIndex(pr._pager.length - 1);
pr.scrollIntoView({ behavior: 'smooth', block: 'start' });
}Expose rule._pager = {
setIndex: (i) => { mode = 'one'; idx = Math.max(0, Math.min(slides.length - 1, i)); render(); },
setMode: (m) => { mode = m; render(); },
+ length: slides.length,
};
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Respect prefers-reduced-motion (optional). Use instant scroll if users prefer reduced motion. - nr._pager.setIndex(0);
- nr.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ nr._pager.setIndex(0);
+ const smooth = !window.matchMedia || !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ nr.scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'start' });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| render(); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| toggle.addEventListener('click', () => { | ||||||||||||||||||||||||||||||||||||
| mode = (mode === 'all') ? 'one' : 'all'; | ||||||||||||||||||||||||||||||||||||
| render(); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Picture-in-picture container with tabs (World / Graph) | ||||||||||||||||||||||||||||||||||||
| slides.forEach((fig) => { | ||||||||||||||||||||||||||||||||||||
| const srcWorld = fig.getAttribute('data-pip'); | ||||||||||||||||||||||||||||||||||||
| const srcGraph = fig.getAttribute('data-graph'); | ||||||||||||||||||||||||||||||||||||
| if (!srcWorld && !srcGraph) return; | ||||||||||||||||||||||||||||||||||||
| const wrap = document.createElement('div'); | ||||||||||||||||||||||||||||||||||||
| wrap.className = 'pip'; | ||||||||||||||||||||||||||||||||||||
| const tabs = document.createElement('div'); | ||||||||||||||||||||||||||||||||||||
| tabs.className = 'pip-tabs'; | ||||||||||||||||||||||||||||||||||||
| const tabWorld = document.createElement('div'); tabWorld.className = 'tab active'; tabWorld.textContent = 'World'; | ||||||||||||||||||||||||||||||||||||
| const tabGraph = document.createElement('div'); tabGraph.className = 'tab'; tabGraph.textContent = 'Graph'; | ||||||||||||||||||||||||||||||||||||
| tabs.append(tabWorld, tabGraph); | ||||||||||||||||||||||||||||||||||||
| const imgWorld = document.createElement('img'); imgWorld.alt = 'World view'; if (srcWorld) imgWorld.src = srcWorld; else imgWorld.style.display='none'; | ||||||||||||||||||||||||||||||||||||
| const imgGraph = document.createElement('img'); imgGraph.alt = 'Graph view'; if (srcGraph) imgGraph.src = srcGraph; else imgGraph.style.display='none'; imgGraph.classList.add('hidden'); | ||||||||||||||||||||||||||||||||||||
| wrap.append(tabs, imgWorld, imgGraph); | ||||||||||||||||||||||||||||||||||||
| fig.appendChild(wrap); | ||||||||||||||||||||||||||||||||||||
| function show(which){ | ||||||||||||||||||||||||||||||||||||
| if (which==='world') { tabWorld.classList.add('active'); tabGraph.classList.remove('active'); imgWorld.classList.remove('hidden'); imgGraph.classList.add('hidden'); } | ||||||||||||||||||||||||||||||||||||
| else { tabGraph.classList.add('active'); tabWorld.classList.remove('active'); imgGraph.classList.remove('hidden'); imgWorld.classList.add('hidden'); } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| tabWorld.addEventListener('click', ()=>show('world')); | ||||||||||||||||||||||||||||||||||||
| tabGraph.addEventListener('click', ()=>show('graph')); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| let worldOn = true; | ||||||||||||||||||||||||||||||||||||
| world.addEventListener('click', () => { | ||||||||||||||||||||||||||||||||||||
| worldOn = !worldOn; | ||||||||||||||||||||||||||||||||||||
| world.textContent = worldOn ? 'World view: On' : 'World view: Off'; | ||||||||||||||||||||||||||||||||||||
| slides.forEach(fig => { | ||||||||||||||||||||||||||||||||||||
| const pip = fig.querySelector('.pip'); | ||||||||||||||||||||||||||||||||||||
| if (pip) pip.classList.toggle('hidden', !worldOn); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Expose simple API for cross-rule navigation | ||||||||||||||||||||||||||||||||||||
| rule._pager = { | ||||||||||||||||||||||||||||||||||||
| setIndex: (i) => { mode = 'one'; idx = Math.max(0, Math.min(slides.length - 1, i)); render(); }, | ||||||||||||||||||||||||||||||||||||
| setMode: (m) => { mode = m; render(); }, | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| render(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| document.querySelectorAll('.rule').forEach(buildPager); | ||||||||||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| flowchart LR | ||
| subgraph LHS | ||
| PA((TemporalProxy(a,n))):::KClass | ||
| PB((TemporalProxy(b,n))):::KClass | ||
| PA -- overlaps --> PB | ||
| end | ||
|
|
||
| subgraph RHS | ||
| PA2((TemporalProxy(a,n))):::KClass | ||
| PB2((TemporalProxy(b,n))):::KClass | ||
| PP[[PotentialPair(a,b,n)]]:::Add | ||
| PA2 -- pair_of --> PP | ||
| PB2 -- pair_of --> PP | ||
| end | ||
|
|
||
| classDef KClass stroke:#ffd166,stroke-width:2; | ||
| classDef Add stroke:#00c853,stroke-width:2,fill:#0d2a1a,color:#dfe7ff; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| flowchart LR | ||
| subgraph LHS | ||
| E[Collider(e)] --- T[Transform(e,n)] | ||
| V[Velocity(e)] | ||
| K[Tick(n)] | ||
| T -- at --> K | ||
| E -- produced_in --> K | ||
| end | ||
|
|
||
| subgraph RHS | ||
| E2[Collider(e)]:::KClass --- T2[Transform(e,n)]:::KClass | ||
| K2[Tick(n)]:::KClass | ||
| P((TemporalProxy(e,n))):::Add | ||
| E2 -- has_proxy --> P | ||
| P -- produced_in --> K2 | ||
| end | ||
|
|
||
| classDef KClass stroke:#ffd166,stroke-width:2; | ||
| classDef Add stroke:#00c853,stroke-width:2,fill:#0d2a1a,color:#dfe7ff; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| flowchart LR | ||
| subgraph LHS | ||
| C1((Contact(pair,n-1))):::KClass | ||
| C2((Contact(pair,n))):::KClass | ||
| end | ||
| subgraph RHS | ||
| E((ContactEvent(kind,pair,n))):::Add | ||
| E -- event_of --> C2 | ||
| end | ||
|
|
||
| classDef KClass stroke:#ffd166,stroke-width:2; | ||
| classDef Add stroke:#00c853,stroke-width:2,fill:#0d2a1a,color:#dfe7ff; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| /* Echo collision/CCD DPO diagrams — base styles and optional animations */ | ||
| :root { | ||
| --bg: #0b1020; | ||
| --panel: #121a33; | ||
| --text: #dfe7ff; | ||
| --muted: #9fb0d6; | ||
| --edge: #6aa0ff; | ||
| --k: #ffd166; | ||
| --added: #00c853; | ||
| --removed: #ff5252; | ||
| --scope: #ffa600; | ||
| } | ||
|
|
||
| svg { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif; } | ||
| .panel { fill: var(--panel); rx: 8px; } | ||
| .title { fill: var(--text); font-weight: 600; font-size: 16px; } | ||
| .caption { fill: var(--muted); font-size: 12px; } | ||
| .node rect { fill: #182347; stroke: #3356a6; stroke-width: 1.2; rx: 6px; } | ||
| .node.interfaceK rect { fill: #2b2746; stroke: var(--k); stroke-width: 2; } | ||
| .node.added rect { fill: #0d2a1a; stroke: var(--added); stroke-width: 2; } | ||
| .node.removed rect { fill: #2a0d0d; stroke: var(--removed); stroke-width: 2; } | ||
| .node text { fill: var(--text); font-size: 12px; } | ||
| .edge line, .edge path { stroke: var(--edge); stroke-width: 1.6; fill: none; } | ||
| .edge.added line, .edge.added path { stroke: var(--added); stroke-width: 2; } | ||
| .edge.removed line, .edge.removed path { stroke: var(--removed); stroke-width: 2; } | ||
| .edge text { fill: var(--muted); font-size: 11px; } | ||
| .scope rect { fill: none; stroke: var(--scope); stroke-width: 2; stroke-dasharray: 6 4; rx: 8px; } | ||
| .label { fill: var(--text); font-size: 13px; } | ||
|
|
||
| /* Optional animation hooks */ | ||
| .pulse-add { animation: pulseAdd 1.6s ease-in-out infinite; } | ||
| .pulse-remove { animation: pulseRemove 1.6s ease-in-out infinite; } | ||
| @keyframes pulseAdd { 0%{opacity:0.5} 50%{opacity:1} 100%{opacity:0.5} } | ||
| @keyframes pulseRemove { 0%{opacity:1} 50%{opacity:0.6} 100%{opacity:1} } | ||
|
|
||
| /* Fade-in on scroll (tour page) */ | ||
| .fade-seed { opacity: 0; transform: translateY(12px); transition: opacity 400ms ease, transform 400ms ease; } | ||
| .fade-in { opacity: 1; transform: translateY(0); } | ||
|
|
||
| /* Carousel pager */ | ||
| .pager { display: flex; gap: 8px; margin-top: 8px; } | ||
| .btn { background: #1b2446; color: var(--text); border: 1px solid #3356a6; border-radius: 6px; padding: 6px 10px; cursor: pointer; font-size: 12px; } | ||
| .btn:hover { background: #223060; } | ||
| .btn:disabled { opacity: 0.5; cursor: default; } | ||
| .hidden { display: none; } | ||
|
|
||
| /* Overlay caption inside figures */ | ||
| .has-overlay figcaption { display: none; } | ||
| .overlay { position: absolute; left: 12px; right: 12px; bottom: 12px; background: rgba(18,26,51,0.85); border: 1px solid #3356a6; border-radius: 6px; padding: 8px 10px; backdrop-filter: blur(2px); } | ||
| .overlay strong { color: var(--text); font-size: 12px; letter-spacing: 0.2px; } | ||
| .overlay .ov-t { color: var(--muted); font-size: 12px; margin-top: 2px; } | ||
| .has-overlay { padding-bottom: 68px; } | ||
| .slide-explain { margin-top: 10px; font-size: 13px; color: var(--text); } | ||
| .slide-explain p { margin: 0 0 6px; } | ||
| .slide-explain ul { margin: 6px 0 0 18px; } | ||
| .slide-explain li { margin: 3px 0; color: var(--muted); } | ||
|
|
||
| /* Picture-in-picture world view */ | ||
| .pip { position: absolute; right: 12px; top: 12px; width: 32%; max-width: 240px; border: 1px solid #3356a6; background: #0b1020; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.35); overflow: hidden; } | ||
| .pip img { width: 100%; display: block; } | ||
| .pip-tabs { display: flex; gap: 0; border-bottom: 1px solid #2a3c72; } | ||
| .pip-tabs .tab { flex: 1; text-align: center; font-size: 11px; color: var(--muted); padding: 6px 4px; cursor: pointer; background: #121a33; } | ||
| .pip-tabs .tab.active { color: var(--text); background: #1b2446; } | ||
|
Comment on lines
+1
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial From: Linus Torvalds torvalds@linux-foundation.org Subject: Re: [PATCH] docs: add collision diagram styles The CSS is competent. You have a consistent color scheme, your animations aren't obnoxious, and you're not doing anything insane with z-indexes or position:absolute soup. Minor gripe: Your Not blocking the PR, but it's sloppy accessibility hygiene. BunBun Add focus states: .btn:focus-visible {
outline: 2px solid #6aa0ff;
outline-offset: 2px;
}
.pip-tabs .tab:focus-visible {
outline: 2px solid #6aa0ff;
outline-offset: -2px;
}🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots
Cc: code-rabbit@vger.kernel.org
Subject: Re: [PATCH] Makefile: add docs target - BROKEN PID CAPTURE
What the hell is this subshell nonsense on line 19?
You're wrapping the backgrounded npm command in a subshell that runs synchronously and exits immediately.
$$!will NOT capture the npm process PID—it'll either be empty or capture something completely unrelated. Then on line 26 youwait $$server_pidon a bogus PID, which will either fail immediately or wait forever on nothing.This is broken. Just do:
No subshell. No nonsense.
$$!captures the actual npm background job PID.Second issue: If your polling loop times out (line 31), you print an error and exit 1, but you never kill the orphaned VitePress server that's still running in the background. Every time someone hits the timeout, they leak a server process. You need to trap the timeout and
kill $$server_pidbefore exiting.Third issue: Your static analysis is crying about an 18-line function body when it wants 5. I don't care about that warning—Make targets can be as long as they need to be—but I do care about the PID capture bug and the resource leak.
Fix the PID capture or this target is useless.
BunBun
Apply this diff:
🧰 Tools
🪛 checkmake (0.2.2)
[warning] 14-14: Target body for "docs" exceeds allowed length of 5 (18).
(maxbodylength)
🤖 Prompt for AI Agents