Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cad2e27
feat: bake in a standard self-contained favicon for all generated pages
zereraz Jun 27, 2026
fddcb59
feat: managed-fork update system (on-demand sync with upstream)
zereraz Jun 27, 2026
8beaa4e
feat: reader-first hardening + navigation
zereraz Jun 28, 2026
347489f
fix: remove transform/scale on the scroll-snap target (.slide)
zereraz Jun 28, 2026
1f814e2
docs(skill): never clip straddling labels (overflow:visible on badge …
zereraz Jun 28, 2026
20e1687
reader/learning upgrades: fix resume-to-last-slide, series prev/next …
zereraz Jul 2, 2026
db1d5f6
build discipline: canonical templates/engine.js + scripts/check-deck.…
zereraz Jul 2, 2026
e17a5ee
learning-first: prediction-before-reveal, shared-vocabulary rule, sin…
zereraz Jul 2, 2026
84f2f39
interactive MCQ quizzes (instant feedback), katex overflow guard in t…
zereraz Jul 3, 2026
55205b9
SKILL: depth-first rule — derive don't assert, three layers (math/con…
zereraz Jul 4, 2026
ce6ec4c
SKILL: equation-anatomy rule — every equation needs where/why/English…
zereraz Jul 4, 2026
101400f
fact ledger (scripts/facts.py: provenance, void/verify), executable t…
zereraz Jul 4, 2026
1bd4ba1
house design language v2: Space Grotesk + Instrument Serif accents, a…
zereraz Jul 4, 2026
d004d73
design v2.1: left-anchored assertion-evidence layout (F-pattern resea…
zereraz Jul 4, 2026
ff4d1af
design v2.2: content-sized blocks (fit-content), remove left-border c…
zereraz Jul 4, 2026
30e6b7c
design v2.3: slides scroll internally instead of squashing content (f…
zereraz Jul 4, 2026
9d61bd4
design v2.4: vertical balance + presence scale locked; rule: verify l…
zereraz Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/MANAGED-FORK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Managed fork workflow

This is a **managed fork** of `nicobailon/visual-explainer`. It carries local
customizations (a standard self-contained favicon, house-style notes) on top of
upstream, and stays updatable.

## Philosophy: control over automatic

Updates are **on-demand, not automatic**. Nothing pulls upstream behind your
back — so your local modifications and pinned behavior are never disturbed by a
surprise merge. When you *do* want to update, it's one command, and a pi agent
resolves any conflicts with full repository context.

If a session is already current, everything just loads normally with zero work.

## Remotes

| Remote | Points at | Role |
|------------|------------------------------------|------------------------------|
| `origin` | `zereraz/visual-explainer` | your fork (your commits) |
| `upstream` | `nicobailon/visual-explainer` | the original project |

## Updating

From this clone (`~/Code/Zereraz/visual-explainer`):

```bash
bash scripts/update-fork.sh --check # status only; never modifies
bash scripts/update-fork.sh --apply # fetch + merge upstream + push + pi update
# if conflicts (exit 20): resolve files, then:
bash scripts/update-fork.sh --continue
```

Or, inside pi, just run the command (loaded with this plugin):

```
/update-visual-explainer
```

The command checks first, applies if behind, and if a merge conflict occurs the
agent resolves it — **keeping both** upstream's new behavior and our additive
customizations — then commits, pushes, and refreshes the install.

## Making your own changes

Commit directly on `main` (small tweaks) or via a feature branch + PR to your
own fork. Because our changes are additive, upstream merges are usually clean.
`scripts/update-fork.sh` verifies the favicon customizations survived each update.
60 changes: 60 additions & 0 deletions plugins/visual-explainer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ Generate self-contained HTML pages that explain systems, code changes, plans, da
- Write files to `~/.agent/diagrams/` or the explicit eval output path. Use descriptive filenames.
- Open generated pages in the browser when running normally. In Pi package installs, use `visual_explainer` with `prepare` for planning/context and `render` only after the complete HTML document exists.
- The final page must be a complete self-contained HTML document, including embedded CSS and any needed JS.
- Always include the standard self-contained data-URI favicon immediately after `</title>` (see "Favicon" below). Never leave a page without a favicon.

## Favicon

Every generated page must include this exact self-contained data-URI favicon, placed immediately after the `</title>` tag. It needs no external file and is a small node-graph glyph that matches the dark/accent palette:

```html
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iNyIgZmlsbD0iIzBmMTcyOSIvPjxjaXJjbGUgY3g9IjkiIGN5PSIxMC41IiByPSIzIiBmaWxsPSIjZDRhNzNhIi8+PGNpcmNsZSBjeD0iMjMiIGN5PSIxMC41IiByPSIzIiBmaWxsPSIjNjBhNWZhIi8+PGNpcmNsZSBjeD0iMTYiIGN5PSIyMi41IiByPSIzIiBmaWxsPSIjNGFkZTgwIi8+PHBhdGggZD0iTTkgMTAuNSBMMTYgMjIuNSBMMjMgMTAuNSIgc3Ryb2tlPSIjZDRhNzNhIiBzdHJva2Utd2lkdGg9IjEuNyIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMC43NSIvPjwvc3ZnPg==">
```

If math is rendered with KaTeX, escape `<` as `&lt;` inside `$$...$$` (e.g. `y_{&lt;t}`); a bare `<` makes the HTML parser truncate the formula.

## Reference routing

Expand Down Expand Up @@ -68,6 +79,7 @@ Read only the references needed for the current output:
- Good font pair families: DM Sans + Fira Code; Instrument Serif + JetBrains Mono; IBM Plex Sans + IBM Plex Mono; Bricolage Grotesque + Fragment Mono; Plus Jakarta Sans + Azeret Mono.
- Good accent directions: terracotta+sage, teal+slate, rose+cranberry, amber+emerald, deep blue+gold.
- Prevent overflow: `min-width: 0` on grid/flex children, `overflow-wrap: break-word` for long text, and scroll containers for wide tables/code.
- **Labels must never be clipped.** When a badge/tag straddles a box edge (e.g. a code-block filename pill at `top: -12px`), the box must NOT have `overflow: auto/hidden` — that slices the label. Put the scroll on an inner element instead (`overflow: visible` on the badge container, `overflow-x: auto` on the inner `<pre>`/scroller). Likewise keep SVG/mermaid `<text>` inside the viewBox and tab/section labels within their pill.
- Do not set `display: flex` directly on `<li>` when list markers matter.
- Use depth sparingly: hero/elevated only for primary sections; flat/recessed for reference material.
- Use entrance/hover animation only when it clarifies hierarchy. Respect `prefers-reduced-motion`. Do not use continuous glow, pulse, or breathing effects on static content.
Expand All @@ -83,15 +95,63 @@ Use slides only when explicitly requested or when a command asks for slides. Sli
- Do not drop content to fit a fixed slide count. Add slides instead.
- Use the 10 slide types from `slide-patterns.md`: Title, Section Divider, Content, Split, Diagram, Dashboard, Table, Code, Quote, Full-Bleed.

## Reader-first defaults (pages are made to be read and re-read)

The primary use is a reader consuming the page, often over multiple sittings. Build for that:

- **Navigation**: ship the upgraded `SlideEngine` from `slide-deck.html` — it adds deep-link hashes (`#slide-7`), resume-where-you-left-off (localStorage), reading percent in the counter, an outline overlay (press `O`), and a keyboard help panel (press `?`). Don't ship a stripped engine.
- **Comprehension**: open each section with its one-line takeaway, then expand (TL;DR-first). End a multi-section page with a short "what to remember" recap.
- **Glossary**: wrap domain jargon in `<abbr title="one-line definition">term</abbr>` so a reader can hover/tap for meaning without leaving the page.
- **Don't gate content on motion**: every slide must be fully readable even if its animation never plays; animations enhance, never reveal essential text.
- **Accessibility**: don't rely on color alone (pair with shape/label); respect `prefers-reduced-motion`; keep body text ≥ 16px with generous line-height; ensure keyboard nav reaches everything.

## House design language v2 (make it beautiful)

Default look for decks (reference implementation: any deck styled like `the-gradient-view`):

- **Layout is left-anchored assertion–evidence (research-grounded)**: eye-tracking shows readers scan from a strong left edge (F-pattern) — so slides are **left-aligned and top-anchored**, never a small centered core in empty space. The headline is a **full-sentence assertion** (left-justified, ≤2 lines, consistent position across slides = the eye's anchor); the body is **evidence that fills the canvas** (content column up to ~1160px, blocks stretch full-width, base type larger). Related pairs (equation ↔ English reading ↔ numeric trace) sit **side-by-side** in a 2-column `.duo` grid on wide screens (Mayer's spatial-contiguity principle), collapsing to one column under 980px. Slides never compress content to fit the viewport: the slide is `height:100dvh; overflow-y:auto` so tall content **scrolls within the slide** at its natural size (children `flex-shrink:0`; no forced equal-height block pairs; no max-height squeeze media-queries that shrink type). The content group is **vertically centered** (`justify-content:center` + `gap:clamp(6px,1.6vh,18px)`) so slides never leave a dead bottom half. Scale for presence: assertion headline `clamp(26px,4.4vw,52px)`, display math `clamp(19px,2.7vw,28px)` with generous card padding, callouts/traces at reading size `clamp(15px,2vw,19px)` / mono `clamp(12.5px,1.65vw,17px)` — the equation should dominate the slide, not whisper. Verify layout with a headless-Chrome screenshot (`--headless=new --screenshot ... '#slide-N'`, no virtual-time flags) and actually look at it before delivering.
- **Type system**: `Space Grotesk` for display (titles/heads, tight tracking), `Inter` body, `JetBrains Mono` for code/kickers, and `Instrument Serif` *italic* as the accent voice — `<i>` inside leads/heads renders serif-italic in gold. Title = gradient text (ink → gold → peach).
- **Depth, not flatness**: body gets two fixed layers — aurora radial glows (gold top-left, sky bottom-right, mint accent) + an SVG-noise grain at ~3% opacity. Surfaces are **glass**: `rgba(255,255,255,.035)` + `backdrop-filter: blur(12-14px)` + 1px `rgba(255,255,255,.09)` border + deep soft shadow + inset top highlight.
- **Editorial details**: huge **ghost slide numerals** (Space Grotesk, ~4% opacity) top-right of every slide; kickers flanked by gradient hairlines; progress bar is a glowing gold→peach gradient.
- **Motion**: reveals rise with slight scale + **blur-in** (`blur(6px)→0`), staggered ~90ms; MCQ options nudge right + glow on hover; correct answers get a soft green glow. All gated by `prefers-reduced-motion`.
- **Color voice**: gold = emphasis/labels, mint = "in english"/success, sky = structure/where, rose = errors/failures, dim slate for prose. Light scheme maps to warm paper (#f7f3ea).
- Callout grammar: `.eng` (mint glass, ◍ prefix) for English readings; `.trace` (dark glass, ⌁ prefix) for numeric traces; `.ww` chips for where/why. **Blocks size to their content** (`width:fit-content`, capped by the column, sensible min-widths) — never full-width slabs around two words. **No colored left-border accents** on callouts; differentiate by the small-caps label color and surface tint only.

## Learning-first rules (for teaching/explainer decks)

When the deck's purpose is to teach (concepts, math, papers, systems):

- **Honest evidence tags**: label every claim's status with a small pill — `real` (measured, from actual runs/files), `building` (in progress), `planned`, `polluted/void` (known-bad measurement). Never present illustrative numbers as real.
- **Code + concept travel together**: a formula or concept slide either includes the minimal code it corresponds to, or explicitly signposts "next slide: the code".
- **Retrieval practice**: end substantial teaching decks with a "Test yourself" slide before the final bleed — 2–3 **clickable multiple-choice questions** (`.mcq` pattern in `templates/slide-deck.html`: click an option → correct answer highlights green, wrong pick red, explanation appears). Plausible distractors, instant feedback. Recall beats re-reading; feedback beats bare recall.
- **Prediction before reveal**: for the 1–2 headline results, pose the question on its own slide first ("guess before you advance") and reveal on the next. Prediction-then-feedback beats passive presentation — delegated reading scores &lt;40% on comprehension; active inquiry scores far higher.
- **Shared vocabulary**: name each core concept once with one vivid metaphor (e.g. KL = "leash", mixed data = "soup", repair = "orthogonal fix") and reuse the exact same term + color across every deck in the series. Never introduce synonyms for an established metaphor — shared words are what let author and reader riff.
- **At most one explorable**: a single small interactive element (slider/toggle driving a formula or plot) is allowed per deck when direct manipulation genuinely teaches the idea (e.g. vary ε and watch the clip region). Never decorative; the deck must teach fully with it inert.
- **Verify-yourself ending**: decks explaining work an agent did (code, training runs, changes) end with the one command or check the reader can run themselves to confirm the headline claim — the goal is a reader deeper in the loop, not just informed.
- **Depth-first (do not hide the concept)**: for technical/teaching decks, derive — don't assert. Every mechanism gets three layers on adjacent slides: (1) the math, derived in steps small enough to follow; (2) the concept the math encodes, named plainly; (3) *why it works* — the gradient/information/geometry-level reason. Plug the run's actual numbers into the derivations (real logprobs, real dims, real lr). Status/narrative slides are allowed only as connective tissue between depth slides, never the substance of the deck.
- **Executable traces**: when a trace derives from raw values (logprobs, logits), compute it **in-page with JS** from the embedded raw data (`.xtrace[data-lps]` and `.xslider` helpers in `templates/slide-deck.html`) instead of hand-typing results — hand arithmetic can silently be wrong; computed traces can be poked.
- **Fact ledger (provenance + retraction)**: projects with evolving claims keep a ledger (`scripts/facts.py`; JSONL of id/claim/value/source/status). Deck elements citing a ledger fact carry `data-fact="id"`. Retraction = `facts.py void` (one flag), then `facts.py verify *.html` finds every deck citing dead facts. Never state a `real` number that has no source.
- **Equation anatomy (no untranslated math)**: every displayed equation must be accompanied, on the same slide, by: (a) **where it fits** — one line locating it in the pipeline; (b) **why it exists** — the problem it solves; (c) **its English reading** — the formula spoken as a sentence, symbol by symbol ("loss = how surprised you were by the correct token"); (d) **a raw numeric trace** — concrete input values flowing through the formula to a concrete output (`p=0.344 → L=−ln 0.344=1.068 nats`). An equation without its sentence and its trace is not done.
- **Series wiring**: when 3+ decks cover one topic, create/refresh an index page (card grid, reading order, one-line summaries) and wire each deck with `data-series-prev/next/index` attributes on `<body>` — the template's series-nav script renders prev/series/next links (keys `N`/`P`). The last deck's `next` points at the index.
- **Resume never lands on the end**: the engine must not restore a saved position equal to the final slide (start fresh instead); mid-deck resume stays.

## Optional generated images

If `surf` is available, generated images may be embedded as base64 for hero banners, conceptual illustrations, or educational visuals. Skip images for data-heavy, structural, or Mermaid/CSS-suitable content. Pages must stand on CSS, typography, and diagrams without images.

## Deck build discipline (agent workflow)

- **Engine source of truth**: inject the SlideEngine from `templates/engine.js` in this skill's directory (resolve relative to SKILL.md). Never depend on `/tmp` copies or retype it.
- **One validation gate**: after writing any deck, run `python3 <repo>/scripts/check-deck.py <file>.html` (managed clone: `~/Code/Zereraz/visual-explainer`). Fix every FAIL before delivering. Do not hand-roll per-deck validation snippets.

## Final checklist

Before delivery, verify:

- complete HTML document;
- standard favicon `<link rel="icon">` present immediately after `</title>`;
- decks use the upgraded SlideEngine (outline `O`, help `?`, deep-link + resume, reading %);
- each slide readable with animations disabled; no content depends on motion;
- output written to the requested path;
- no console errors when opened;
- no horizontal overflow at normal desktop width;
Expand Down
28 changes: 28 additions & 0 deletions plugins/visual-explainer/commands/update-visual-explainer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: update-visual-explainer
description: Sync this visual-explainer fork with upstream, preserving local customizations; an agent resolves any merge conflicts.
---

Update the managed `visual-explainer` fork. This is **on-demand** — only run because the user asked.

Managed clone location (try in order; use the first that exists):
- `~/Code/Zereraz/visual-explainer`
- the installed package dir: `~/.pi/agent/git/github.com/zereraz/visual-explainer`

Steps:

1. **Check first.** Run `bash scripts/update-fork.sh --check` from the managed clone.
- Exit `0` → already up to date. Tell the user "already current (fork is N commits ahead with your customizations)" and stop.
- Exit `10` → behind upstream; show the listed upstream commits, then continue.

2. **Apply.** Run `bash scripts/update-fork.sh --apply`.
- Exit `0` → clean merge, pushed to the fork, and `pi update` refreshed the install. Report what upstream commits were merged and confirm the local customizations (favicon / house-style) survived. Done.
- Exit `20` → **merge conflict**. Do NOT abandon. Resolve it:
a. Read each conflicted file (`git diff --name-only --diff-filter=U`).
b. For each conflict, **keep the intent of BOTH sides**: upstream's new behavior AND our customizations (the favicon injection in `extension.ts`, favicon lines in `templates/*.html` and `SKILL.md`, the KaTeX `&lt;` note). Our changes are additive — prefer integrating, not discarding.
c. After resolving every file, run `bash scripts/update-fork.sh --continue` to commit, push, and reinstall.
- Exit `2` → precondition failure (dirty tree / wrong branch). Read the message, fix it (commit/stash local work, or `git checkout main`), then retry.

3. **Verify** after success: confirm `grep -c ensureFavicon plugins/visual-explainer/extension.ts` is non-zero and the 4 templates still contain `rel="icon"`. If a customization was lost in the merge, re-apply it and amend.

Report concisely: what changed upstream, whether conflicts occurred, how they were resolved, and that customizations are intact. $@
39 changes: 38 additions & 1 deletion plugins/visual-explainer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,41 @@ function outputFilename(input: string) {
return /\.html?$/i.test(raw) ? raw : `${raw}.html`;
}

const STANDARD_FAVICON =
'<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iNyIgZmlsbD0iIzBmMTcyOSIvPjxjaXJjbGUgY3g9IjkiIGN5PSIxMC41IiByPSIzIiBmaWxsPSIjZDRhNzNhIi8+PGNpcmNsZSBjeD0iMjMiIGN5PSIxMC41IiByPSIzIiBmaWxsPSIjNjBhNWZhIi8+PGNpcmNsZSBjeD0iMTYiIGN5PSIyMi41IiByPSIzIiBmaWxsPSIjNGFkZTgwIi8+PHBhdGggZD0iTTkgMTAuNSBMMTYgMjIuNSBMMjMgMTAuNSIgc3Ryb2tlPSIjZDRhNzNhIiBzdHJva2Utd2lkdGg9IjEuNyIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMC43NSIvPjwvc3ZnPg==">';

// Guarantees the house-style favicon is present, regardless of what the agent emitted.
function ensureFavicon(html: string): string {
if (/<link\b[^>]*rel\s*=\s*["']?(?:shortcut\s+)?icon/i.test(html)) return html;
if (/<\/title>/i.test(html)) return html.replace(/<\/title>/i, `</title>\n${STANDARD_FAVICON}`);
if (/<head[^>]*>/i.test(html)) return html.replace(/<head[^>]*>/i, (m) => `${m}\n${STANDARD_FAVICON}`);
return html;
}

// Reader-safety: inside $$...$$ display math, escape < and > so the HTML
// parser doesn't treat e.g. y_{<t} as a tag and silently truncate the formula.
// KaTeX decodes entities, so this is semantically lossless. Scoped to $$ (very
// unlikely to false-match) and skips already-escaped content.
function fixDisplayMath(html: string): string {
return html.replace(/\$\$([\s\S]*?)\$\$/g, (_m, inner: string) => {
const fixed = inner.replace(/</g, "&lt;").replace(/>/g, "&gt;");
return `$$${fixed}$$`;
});
}

// Reader-safety: a complete, well-formed document for any browser/offline read.
function ensureDocMeta(html: string): string {
let out = html;
if (!/<html[^>]*\blang=/i.test(out)) out = out.replace(/<html\b/i, '<html lang="en"');
if (/<head[^>]*>/i.test(out)) {
if (!/<meta[^>]+name=["']viewport/i.test(out))
out = out.replace(/<head[^>]*>/i, (m) => `${m}\n<meta name="viewport" content="width=device-width, initial-scale=1.0">`);
if (!/<meta[^>]+name=["']theme-color/i.test(out))
out = out.replace(/<head[^>]*>/i, (m) => `${m}\n<meta name="theme-color" content="#0f1729">`);
}
return out;
}

function assertHtmlDocument(html: string) {
const trimmed = html.trim();
if (!trimmed) throw new Error("html is required");
Expand Down Expand Up @@ -276,7 +311,9 @@ async function renderVisualExplanation(params: VisualExplainerParams, signal?: A
}

signal?.throwIfAborted();
writeFileSync(outputPath, params.html, "utf8");
// Reader-safety pipeline: fix truncating math, complete the doc head, guarantee favicon.
const finalHtml = ensureFavicon(ensureDocMeta(fixDisplayMath(params.html)));
writeFileSync(outputPath, finalHtml, "utf8");

signal?.throwIfAborted();

Expand Down
Loading