Conversation
Implements the Layer 3 core from AGENTS.md §10 as additive modules on the existing crate, without workspace restructuring or touching parser/analyzer/ model (owned by the in-flight Layer 1/2 worktrees). Rust core: - ir/: UiNode (12 core + 6 domain), SourceRange+LineIndex, registry allowlist, validate (schema + allowlist + sourceRange bounds + low-confidence flag) - generator/: Generator trait + GenInput, deterministic RulesGenerator (task lists→Checklist, tables→DataTable, mermaid→Diagram, config→ConfigViewer, GFM alerts→Callout), ClaudeGenerator scaffold behind feature="llm" (offline fallback to rules) - cache/: content-hash key (markdown + generator + schema version) + store - gui.rs: generate→validate→cache facade; `mdpeek gen <file>` CLI subcommand Web (Preact): - 2-layer component registry + Render dispatcher, all 18 node kinds, 3-pane layout (Outline/Content/Generated UI) with SourceRangeLink jump, hand-maintained ir.ts mirror. tsc + vite build clean. Tests: 18 unit + tests/gen_output.rs integration; clippy clean (default + llm). Deferred integration points (server /api/gui, ts-rs, #16 diff) documented in docs/layer3.md to avoid worktree interference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
takeshiD
marked this pull request as ready for review
July 5, 2026 00:07
…ative-ui # Conflicts: # Cargo.lock # Cargo.toml # src/main.rs
… model/effort Adds selectable LLM backends to the Layer 3 generator, configurable via [llm] in config.toml or `mdpeek gen` CLI flags: - provider: "claude_code" (shells `claude` CLI) and "codex" (shells `codex` CLI) work in the default build (std::process, no extra crates); "anthropic_api" (direct HTTP) stays behind feature="llm". Default provider is anthropic_api. - model: backend-specific model id (--model / [llm] model). - effort: low|medium|high — codex -> model_reasoning_effort; claude_code -> thinking keyword (think/ultrathink); anthropic advisory. Wiring: - src/generator/llm/: LlmProvider/Effort/LlmBackendConfig + build() factory, claude_code.rs, codex.rs, anthropic.rs (renamed from claude.rs); prompt gains extract_json_array for noisy CLI stdout. llm module no longer feature-gated. - config.rs: [llm] gains provider/model/effort; llm_backend_config()/llm_enabled(). - cli.rs: `gen` gains --llm/--provider/--model/--effort (override config). - gui.rs: generate_with_llm() with rules fallback on any backend failure. - config.example.toml + docs/layer3.md updated. Tests: config parsing for provider/model/effort, prompt extraction, and a CLI integration test that --llm falls back to rules when the backend is unavailable. cargo test + clippy (default and --features llm) + web build all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
The TOC sidebar (and the whole page) inherited the body font stack, which had
no Japanese font — so CJK text fell back to whatever the OS/browser substituted.
Add Hiragino Sans / Yu Gothic / Meiryo / Noto Sans CJK JP before the generic
`sans-serif` in both server themes (static/css/github-{light,dark}.css) and the
Layer 3 web island (web/src/styles.css). Latin still prefers the system UI font;
only CJK glyphs pick the new fonts.
Verified: rebuilt server serves the updated embedded CSS for both themes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
The font stack lives on `.markdown-body` (the content <article>), but the TOC,
file sidebar, toolbar and front-matter panel are siblings outside it, so they
fell back to the browser default (serif) — English and Japanese both looked
different from the content. Set the same stack on `body` (inherited by all the
chrome) and add `input,button,select,textarea { font-family: inherit }` so the
TOC search box matches too. Placed in the index.html inline style (theme-
independent), kept identical to `.markdown-body`.
Verified: served page exposes the body font rule for both themes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
…and)
Completes Layer 3's "動く Generative UI" deliverable: opening `mdpeek serve` and
clicking the ✨ toolbar button reveals a Generated UI pane rendered from the IR,
co-existing with the existing SSR content (design 論点 A).
- Extract Layer 3 core into `crates/mdpeek-gui` (ir + generator + cache +
pipeline; design §2 mdpeek-core) so the CLI and server share one impl. Root
binary now depends on it; `llm` feature forwards to mdpeek-gui/-server.
- Server: `GET /api/gui` generates validated IR for the active file (rules by
default, or the configured LLM backend, on a blocking thread) and returns
{ nodes, markdown }. `serve()` gains an optional LlmBackendConfig, resolved in
main.rs from [llm] config (enabled → backend).
- Web: new `panel.tsx` island (Generated UI pane only, not the full 3-pane
harness) fetches /api/gui and renders via the shared registry; scoped
`panel.css` (no global resets). Built as a stable-named IIFE and committed to
web/dist, embedded by the server via include_bytes! (論点 C).
- Template: toolbar ✨ toggle + panel container + /static/gui asset refs.
Verified end-to-end: page ships the toggle/panel/assets, /static/gui/mdpeek-gui.
{js,css} serve 200, /api/gui returns IR. cargo test --workspace + clippy
(default and --features llm) + web build all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
The panel overlapped the ✨ toolbar toggle when open, so there was no way to dismiss it. Add a sticky panel header with a ✕ close button that clears the `mdpeek-gui-open` state (and resets the toggle's aria-pressed), plus an Escape key handler. Panel padding moved into the scrollable body so the header stays flush at the top. Verified: rebuilt server serves the updated island JS/CSS with the close button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
Pressing the Generated-UI toggle hits /api/gui but the terminal stayed silent, so it was unclear what was happening. Log the start (file + backend: rules vs LLM provider/model/effort) and the result (node count, elapsed ms, and a kind×count breakdown), plus clearer error/panic logs. Example: INFO gui: generating UI for 'SAMPLE.md' [rules (offline)] INFO gui: generated 8 node(s) for 'SAMPLE.md' in 0 ms [Checklist×1, DataTable×1, Callout×5, Diagram×1] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
The panel only showed static "Generating UI…" text. Add an indeterminate spinner + an animated progress bar under the panel header while /api/gui is in flight (single fetch = no % progress, so indeterminate). Respects prefers-reduced-motion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
Connect the Layer 3 pipeline to Layer 2's rules analyser and add a planner that emits semantic, document-type-aware UI nodes alongside the structural ones — so rules generation now produces kinds it previously couldn't (RiskPanel, Timeline, review Checklists). - mdpeek-gui depends on mdpeek-analyzer; new `planner` module maps `Analysis` (DocumentModel + SemanticPanel) → IR: - `## Risk` sections → RiskPanel - open questions → an unchecked Checklist - DesignDoc/Readme → missing-section review Checklist (§9.1) - Adr/Changelog/Minutes → Timeline from the outline - pipeline: `generate`/`generate_with_llm`/`generate_json` now take `filename` (sharpens doctype inference); structural rules nodes + planner nodes are concatenated then validated; cache entry records the real doctype. - cache key now includes the filename basename (it can change the output). - callers (mdpeek gen, server /api/gui) pass the active file name. Verified: `mdpeek gen docs/sample-design-doc.md` and `/api/gui` now include a RiskPanel + review/open-question checklists; README gets a section checklist. cargo test --workspace + clippy (default and --features llm) all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
-54) Reframe generated UI as reading lenses (design doc §8) instead of re-listing body content. Addresses the "not actually a reading aid" critique. IR (mdpeek-gui/ir): - Add Confidence enum (low/medium/high, §14) + per-item confidence. - New lens node kinds: SemanticOutline, SummaryCards, DecisionLog, ActionItems, OpenQuestions; extend RiskPanel (assumptions/likelihood/mitigation) and Glossary (aliases/inferredDefinition). Registered in the allowlist. - Cache SCHEMA_VERSION → 2 (invalidates old body-node entries). Pipeline: - #45: drop body-reprint nodes (DataTable/ConfigViewer/Diagram/Callout) from the lens pane — those stay in the Markdown Body (§7.2). - #46: LLM-first — LLM backend is primary, deterministic planner is the fallback; every node re-validated (schema + allowlist + sourceRange). No ANTHROPIC_API_KEY now errors (→ falls back to planner) instead of emitting body nodes. Prompt rewritten to steer toward reading lenses. - planner rewritten to emit lenses from Layer 2 analysis: SemanticOutline (BlockClass groups), DecisionLog (markers), ActionItems (todos), OpenQuestions, RiskPanel, Glossary (acronyms). #48/#50/#51/#52/#53/#54. Web: ir.ts + components + registry for all new lenses; confidence badges; panel.css styling; dev fixture regenerated. Verified end-to-end: `/api/gui` on the sample design doc returns [SemanticOutline, ActionItems, OpenQuestions, RiskPanel, Glossary] with no body reprints. cargo test --workspace + clippy (default and --features llm) + web build all clean. Refs #44 #45 #46 #48 #50 #51 #52 #53 #54 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
Add planner::recommended_order / order_lenses (design §13 / §9.3): the Generated UI pane now leads with the lenses that matter for the doctype (design doc → SemanticOutline/DecisionLog/RiskPanel…; ADR → DecisionLog first; meeting notes → Decisions/ActionItems/Timeline). Unlisted lenses keep their order after the recommended ones. Applied to both the rules planner and LLM output. Refs #47 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
- config.example.toml: default [llm] strategy → "llm_first" with clarified note (LLM is primary for lenses; the rules planner is the fallback). - docs/layer3.md: describe the reading-lens model, the lens set, body/lens separation, Lens Selector, and per-item Confidence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
10 tasks
The `[llm] effort` config was ignored by the Anthropic-API backend (advisory only). Map it to `output_config.effort` (GA, no beta header) on the Messages request, so low/medium/high now control reasoning depth for anthropic_api the same way they do for codex. Effort also participates in the cache key (anthropic-<model>-<effort>) since it changes output. A model without effort support (e.g. Haiku 4.5) returns 400 → handled by the rules fallback. cargo build (default + --features llm) + clippy + tests all clean. Refs #46 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
…ut of the box) The anthropic_api backend needed `--features llm`, which was a friction point. Make `llm` a default feature so `cargo build` / `cargo install` include it and `--provider anthropic_api` works with no flag. For a lean fully-offline build (no reqwest/tokio/TLS), use `--no-default-features` — rules + claude_code/codex still work there. Verified: `cargo build` (default, includes llm) and `cargo build --no-default-features` (lean) both succeed; tests + clippy clean; `mdpeek gen --llm --provider anthropic_api` with no key falls back to rules instead of erroring on the missing feature. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXGWTTALewHoUjGfVkDwEV
…an docs)
A new web reading mode ("Guided Reading") for design/planning documents that
reduces cognitive load via segmenting + attention cueing, kept reader-paced
(never auto-driven) so critical review stays active.
- `/api/scrolly`: one LLM call → whole-doc overview + per-section commentary
(additive, non-restating), aligned to rendered heading anchors. Offline-safe
rules fallback; disk cache keyed by content+model.
- `mdpeek-gui::complete_text_blocking`: raw completion across the existing
anthropic/claude_code/codex backends (no IR contract).
- `mdpeek-render-html::heading_anchor`: expose the emitter's slug so server
section anchors match SSR `<h*>` ids.
- Preact/DOM mode: toolbar toggle, grey-out + spotlight the reached section,
side panel with overview + typewriter-revealed commentary, prev/next nav,
reduced-motion aware.
Deferred to later slices: in-section Q&A, resumable/saved sessions, true
server-side token streaming, inline anchored commentary.
Verified: cargo build + tests (scrolly section-split, fallback, json-extract),
web typecheck+build, and end-to-end `/api/scrolly` (origin=llm via ClaudeCode,
anchors resolve to SSR ids, cache hits on re-request).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdRbWDkNYQFPVGYeWA6BQF
…fallback - In-section Q&A: `POST /api/scrolly/ask` answers a reader's question grounded in the current section's text (+ whole-doc context) and prior turns; panel gets a chat log + input with a "thinking" spinner and typewriter reveal. - Language control: auto / 日本語 / English selector (persisted in localStorage), threaded through `/api/scrolly?lang=` prompts + cache key. - Loading spinner while the guide generates (replaces the plain text state). - LLM-only: removed the rules/offline fallback per feedback — `generate` now returns an error (surfaced in the panel) when no backend or on failure. Verified end-to-end (ClaudeCode backend): lang=en yields English overview, Q&A on the 'architecture' section answers in Japanese and correctly notes the section doesn't cover double-charging while pointing to the Risks section. cargo build + scrolly tests + web typecheck/build all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdRbWDkNYQFPVGYeWA6BQF
…ailures)
The single-JSON contract broke on larger docs (e.g. README, 22 sections):
free-text commentary with literal newlines/quotes invalidates the JSON, and a
truncated response loses everything — which, since the offline fallback was
removed, surfaced as a hard failure.
Switch the model contract to a newline-safe marker format:
@@overview@@
<text>
@@section 0@@
<commentary>
...
`parse_guide_output` is tolerant of code fences, stray prose, and a cut-off
tail (every section before the cut is kept; missing sections degrade to empty
rather than failing the whole guide).
Verified: README now guides reliably — 22 sections, 0 empty commentaries,
Japanese overview. New unit tests cover multiline commentary, truncated tails,
code fences, and marker parsing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EdRbWDkNYQFPVGYeWA6BQF
The chat could only see the original document text, so readers couldn't ask about the guide's own explanation. Q&A is now scoped to BOTH the guide (its overview + current-section commentary) and the original source text. - `AskRequest` gains optional `guide_overview` + `guide_commentary`; the answer prompt presents them alongside the section text and whole-doc context, and the system prompt tells the model either is fair game. - Client sends the loaded guide's overview and the active section's commentary with each question (stores overview in runtime). Verified: a question about the guide's "foreshadowing" remark is answered from the commentary and correctly tied back to the document's Risks section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdRbWDkNYQFPVGYeWA6BQF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Layer 3 — Generated UI
Implements the Layer 3 core from
AGENTS.md§10, as additive modules on the existing single-binary crate. To avoid interfering with the in-flight Layer 1 (parser) and Layer 2 (analyzer/model) worktrees, this PR does not restructure into a workspace (論点 B) and does not touch those areas.Rust core
src/ir/—UiNode(12 core + 6 domain kinds),SourceRange+LineIndex, registry allowlist, andvalidate(schema + allowlist + sourceRange bounds + low-confidence flagging). This is the security boundary (§3.5/§8): an LLM cannot emit a component outside the registry or a fabricated range.src/generator/—Generatortrait + lightweightGenInput(stand-in for Layer 2'sDocumentModel); deterministic offlineRulesGenerator(task lists→Checklist, tables→DataTable, mermaid→Diagram, json/yaml/toml/env→ConfigViewer, GFM alerts→Callout);ClaudeGeneratorscaffold behindfeature = "llm"with offline fallback.src/cache/— content-hash key (markdown + generator + schema version) +.cache/mdpeek/*.gui.jsonstore.src/gui.rs— generate→validate→cache facade. New CLI:mdpeek gen <file>(emits validated IR JSON;--no-cache).Web (Preact)
Renderdispatcher, components for all 18 node kinds, 3-pane layout (Outline / Content / Generated UI) with SourceRangeLink jump, hand-maintainedir.tsmirror.Verification
cargo test— 18 Layer-3 unit tests +tests/gen_output.rsintegration, all pass.cargo clippyclean on default and--features llm.cd web && npm run build(tsc --noEmit + vite) builds clean (~28 KB JS).mdpeek gen SAMPLE.mdproduces Checklist / DataTable / Callout×5 / Diagram with valid sourceRanges.Deferred (documented in
docs/layer3.md)Integration points that touch shared files or depend on other worktrees: server
/api/guiroute + island mount +web/distembed (論点 A/C), Layer 2DocumentModel/planneradapter,ts-rsauto-gen, and #16 live diff.🤖 Generated with Claude Code