docs(02-02): complete markdown renderer plan — REND-01 through REND-10 satisfied
- 02-02-SUMMARY.md: documents render_markdown() pipeline, code block borders, GFM table grid, decisions, deviations - STATE.md: advance to plan 02 done, update decisions log (02-02 entries), resolve REND Widget trait blocker - ROADMAP.md: mark 02-02 complete (2/3 plans done for Phase 2)
This commit is contained in:
@@ -45,10 +45,10 @@ Plans:
|
||||
3. User lands on index.md when the app starts; when index.md is missing, a readable error page is shown instead of a crash
|
||||
4. User can scroll content longer than the terminal with j/k, arrow keys, and PgUp/PgDn
|
||||
5. User sees keyboard hints in the status bar, box-drawing borders on panels, and a CGA-era retro color theme; the layout reflows correctly when the terminal is resized
|
||||
**Plans:** 3 plans
|
||||
**Plans:** 2/3 plans complete
|
||||
Plans:
|
||||
- [ ] 02-01-PLAN.md — Dependencies, vault file loading, and syntax highlighter
|
||||
- [ ] 02-02-PLAN.md — Markdown-to-styled-lines renderer (all constructs)
|
||||
- [x] 02-01-PLAN.md — Dependencies, vault file loading, and syntax highlighter
|
||||
- [x] 02-02-PLAN.md — Markdown-to-styled-lines renderer (all constructs)
|
||||
- [ ] 02-03-PLAN.md — App integration: scrolling, status bar, error screen, startup wiring
|
||||
|
||||
### Phase 3: Navigation and Links
|
||||
@@ -82,6 +82,6 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Safety Foundation | 3/3 | Complete | 2026-02-28 |
|
||||
| 2. Vault Core and Rendering | 0/3 | Not started | - |
|
||||
| 2. Vault Core and Rendering | 2/3 | In Progress | - |
|
||||
| 3. Navigation and Links | 0/TBD | Not started | - |
|
||||
| 4. BBS Polish and Live Content | 0/TBD | Not started | - |
|
||||
|
||||
+17
-14
@@ -5,33 +5,33 @@
|
||||
See: .planning/PROJECT.md (updated 2026-02-28)
|
||||
|
||||
**Core value:** Users can connect via SSH and seamlessly browse a vault of linked markdown documents with retro BBS aesthetics
|
||||
**Current focus:** Phase 2 in progress — Plan 01 complete (vault core + highlighter foundation)
|
||||
**Current focus:** Phase 2 in progress — Plans 01-02 complete (vault core + highlighter + renderer)
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 2 of 4 (Vault Core and Rendering) — IN PROGRESS
|
||||
Plan: 1 of 3 in current phase (plan 01 done)
|
||||
Status: Phase 2 Plan 01 complete — Plan 02 (markdown renderer) next
|
||||
Last activity: 2026-02-28 — Plan 01 complete (deps, vault.rs, highlighter.rs)
|
||||
Plan: 2 of 3 in current phase (plans 01-02 done)
|
||||
Status: Phase 2 Plan 02 complete — Plan 03 (app integration + scrolling) next
|
||||
Last activity: 2026-02-28 — Plan 02 complete (renderer.rs with full markdown pipeline)
|
||||
|
||||
Progress: [████░░░░░░] 33%
|
||||
Progress: [█████░░░░░] 50%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 4
|
||||
- Average duration: 2.2 min
|
||||
- Total execution time: 0.14 hours
|
||||
- Total plans completed: 5
|
||||
- Average duration: 2.6 min
|
||||
- Total execution time: 0.21 hours
|
||||
|
||||
**By Phase:**
|
||||
|
||||
| Phase | Plans | Total | Avg/Plan |
|
||||
|-------|-------|-------|----------|
|
||||
| 01-safety-foundation | 3 | 7 min | 2.3 min |
|
||||
| 02-vault-core-and-rendering | 1 | 2 min | 2.0 min |
|
||||
| 02-vault-core-and-rendering | 2 | 6 min | 3.0 min |
|
||||
|
||||
**Recent Trend:**
|
||||
- Last 5 plans: 2 min, 3 min, 2 min, 2 min
|
||||
- Last 5 plans: 2 min, 3 min, 2 min, 2 min, 4 min
|
||||
- Trend: Stable
|
||||
|
||||
*Updated after each plan completion*
|
||||
@@ -56,8 +56,11 @@ Recent decisions affecting current work:
|
||||
- 01-03: app_config stored in App struct for Phase 2 access — vault_path accessible inside event loop
|
||||
- 01-03: Quit prompt cleared on any non-Ctrl+C key — pressing any key dismisses double-press window
|
||||
- 02-01: LinesWithNewlines iterator added to preserve trailing \n for syntect grammar correctness
|
||||
- 02-01: mod declarations omitted from main.rs — Plan 03 handles wiring for all Phase 2 modules
|
||||
- 02-01: mod declarations added to main.rs during Plan 02 (not Plan 03) — compiler needs them to check renderer.rs
|
||||
- 02-01: syntect default-fancy feature uses pure-Rust fancy-regex (avoids Oniguruma C lib)
|
||||
- 02-02: Full emit_code_block and emit_table written in Task 1 (not stubs) — avoids throwaway code
|
||||
- 02-02: Box width for code blocks capped at terminal width — prevents border overflow on long lines
|
||||
- 02-02: Table n_cols derived from max(alignments.len(), max row length) — handles malformed GFM tables
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -65,12 +68,12 @@ None.
|
||||
|
||||
### Blockers/Concerns
|
||||
|
||||
- **REND**: ratatui 0.30 Widget trait confirmed: `impl Widget for &Foo` pattern required (NOT WidgetRef) — Plan 02 must use reference-based impl
|
||||
- **REND blocker resolved**: renderer.rs uses no Widget trait at all — `render_markdown()` returns `Vec<Line<'static>>` which Plan 03 passes to `Paragraph::new()`. No custom Widget needed.
|
||||
- **NAV**: Path traversal via wiki-links must be addressed in Phase 3 link resolver — canonicalize and prefix-check every resolved path
|
||||
- **LIVE**: notify 8.x API must be verified at integration time; watch only current file (not full vault) to avoid inotify exhaustion
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-02-28
|
||||
Stopped at: Completed 02-01-PLAN.md — vault.rs and highlighter.rs created, cargo build clean
|
||||
Resume file: .planning/phases/02-vault-core-and-rendering/02-02-PLAN.md
|
||||
Stopped at: Completed 02-02-PLAN.md — renderer.rs created, cargo check clean
|
||||
Resume file: .planning/phases/02-vault-core-and-rendering/02-03-PLAN.md
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
phase: 02-vault-core-and-rendering
|
||||
plan: 02
|
||||
subsystem: renderer
|
||||
tags: [pulldown-cmark, ratatui, syntax-highlighting, cga-colors, box-drawing, markdown-rendering]
|
||||
|
||||
dependency_graph:
|
||||
requires:
|
||||
- phase: 02-01
|
||||
provides: highlight_code(), OnceLock-based syntect initialization, CGA color mapping
|
||||
provides:
|
||||
- render_markdown() — markdown string to Vec<Line<'static>> conversion pipeline
|
||||
- H1-H6 heading rendering with CGA colors and ═/─ decorators
|
||||
- Bold/italic/inline code inline styling
|
||||
- Ordered and unordered list rendering with ●/◦/▪ bullets
|
||||
- Blockquote rendering with yellow │ border
|
||||
- Horizontal rule rendering (full-width ─ in DarkGray)
|
||||
- Image placeholder rendering [IMAGE: alt]
|
||||
- Fenced code block rendering with ╭─╮│╰─╯ borders + syntect highlighting
|
||||
- GFM table rendering with ┌┬┐├┼┤└┴┘ full box-drawing grid
|
||||
affects: [02-03-app-integration]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- RenderState struct with style_stack (Vec<Style>) for nested inline formatting
|
||||
- Two-pass table rendering: accumulate all cells, then emit box-drawing grid
|
||||
- TextMergeStream wrapping Parser::new_ext to coalesce adjacent Text events
|
||||
- emit_code_block() and emit_table() as standalone free functions called by RenderState methods
|
||||
- All Span content uses .to_string() for owned strings — Vec<Line<'static>> with no lifetime leakage
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- src/renderer.rs
|
||||
modified:
|
||||
- src/main.rs
|
||||
|
||||
key-decisions:
|
||||
- "Wrote full emit_code_block and emit_table implementations in Task 1 rather than stubs — stubs would have been discarded in Task 2; single pass was cleaner"
|
||||
- "Removed unused push_fg and push_fg_mod helper methods — start_heading() constructs styles inline, keeping RenderState API minimal"
|
||||
- "emit_code_block box_width computed as max(content_width + 4, lang.len() + 6) capped at terminal width — prevents border overflow on long lines"
|
||||
- "Table n_cols derived from max(alignments.len(), max row length) — handles malformed tables with mismatched column counts gracefully"
|
||||
|
||||
patterns-established:
|
||||
- "RenderState pattern: internal struct with push/pop style_stack, flush_line() accumulates then emits, finish() returns owned Vec<Line<'static>>"
|
||||
- "Box-drawing code blocks: ╭─ lang ─╮ / │ content padding │ / ╰─╯ with DarkGray borders and Yellow lang label"
|
||||
- "Box-drawing tables: ┌┬┐├┼┤└┴┘ with Cyan border_style and LightCyan+BOLD header row"
|
||||
|
||||
requirements-completed: [REND-01, REND-02, REND-03, REND-04, REND-05, REND-06, REND-07, REND-08, REND-09, REND-10]
|
||||
|
||||
duration: 4min
|
||||
completed: 2026-02-28
|
||||
---
|
||||
|
||||
# Phase 02 Plan 02: Markdown Renderer Summary
|
||||
|
||||
**pulldown-cmark event pipeline producing Vec<Line<'static>> with CGA-colored headings, syntax-highlighted fenced code blocks with ╭─╮│╰─╯ borders, and GFM tables with ┌┬┐├┼┤└┴┘ box-drawing grids**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 4 min
|
||||
- **Started:** 2026-02-28T21:15:20Z
|
||||
- **Completed:** 2026-02-28T21:19:00Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 2 (src/renderer.rs created, src/main.rs updated)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Complete `render_markdown(input: &str, width: u16) -> Vec<Line<'static>>` public function
|
||||
- All Phase 2 REND-01 through REND-10 requirements implemented in one module
|
||||
- Code blocks render with rounded box-drawing borders (╭─ lang ─╮ / │ │ / ╰──╯), language label in Yellow, content via `crate::highlighter::highlight_code()`
|
||||
- GFM tables render with full DOS-style box grid (┌┬┐├┼┤└┴┘), bold LightCyan header row, Left/Right/Center column alignment
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1: Build core markdown renderer with inline and block elements** - `9e6f79c` (feat)
|
||||
2. **Task 2: Add code block borders with syntax highlighting and GFM table grid** - `8690d2a` (feat)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `src/renderer.rs` — Complete markdown-to-styled-lines conversion pipeline (717 lines); `render_markdown()` public API; `RenderState` internal struct with style stack, blockquote depth, list counters, two-pass table collection; `emit_code_block()` and `emit_table()` free functions
|
||||
- `src/main.rs` — Added `mod highlighter; mod renderer; mod vault;` declarations (wiring for Plan 03)
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Implemented full emit_code_block and emit_table in Task 1 rather than writing throwaway stubs — since both functions were already designed in the plan, writing real code once was cleaner than writing placeholder code twice
|
||||
- Removed `push_fg` and `push_fg_mod` helper methods from RenderState after they proved unused — `start_heading()` constructs styles directly with `.fg().add_modifier()` inline
|
||||
- Box width for code blocks capped at terminal `width` parameter to prevent overflow when code lines are longer than the terminal
|
||||
- Table column count derived from `max(alignments.len(), max row cell count)` to handle pathological GFM tables with mismatched header/data column counts
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] Fixed type inference error in list item bullet computation**
|
||||
- **Found during:** Task 1 (cargo check feedback)
|
||||
- **Issue:** `let (bullet_str, new_counter) = match ...` with `None` literals caused `E0282: type annotations needed for Option<_>` because Rust could not infer the Option type parameter from the unit arm
|
||||
- **Fix:** Simplified the match to return only `bullet_str: String`, eliminating the unused `new_counter` binding entirely
|
||||
- **Files modified:** src/renderer.rs
|
||||
- **Verification:** cargo check passes without error
|
||||
- **Committed in:** 9e6f79c (Task 1 commit, fixed inline during implementation)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 1 auto-fixed (1 type inference bug caught during cargo check)
|
||||
**Impact on plan:** Minor Rust type inference issue resolved immediately; no scope creep.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None — plan executed cleanly after the type inference fix.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None — no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- `render_markdown()` is ready for Plan 03 app integration
|
||||
- `src/highlighter::init_highlighter()` must be called in `main()` before any `render_markdown()` call (Plan 03 handles this)
|
||||
- All Phase 2 rendering requirements (REND-01 through REND-10) are satisfied
|
||||
- Dead-code warnings from unwired modules will resolve when Plan 03 wires `renderer`, `vault`, and `highlighter` into the event loop
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- [x] src/renderer.rs exists with render_markdown(), emit_code_block(), emit_table(), RenderState
|
||||
- [x] src/main.rs contains mod highlighter; mod renderer; mod vault;
|
||||
- [x] Commit 9e6f79c exists in git log (Task 1: core renderer)
|
||||
- [x] Commit 8690d2a exists in git log (Task 2: code block borders + table grid)
|
||||
- [x] cargo check passes (19 expected dead_code warnings, 0 errors)
|
||||
- [x] 02-02-SUMMARY.md created at .planning/phases/02-vault-core-and-rendering/
|
||||
|
||||
---
|
||||
*Phase: 02-vault-core-and-rendering*
|
||||
*Completed: 2026-02-28*
|
||||
Reference in New Issue
Block a user