feat: composition rendering — parent notes render child notes inline #62

Closed
opened 2026-03-02 17:02:30 +00:00 by forgejo_admin · 0 comments

Plan

plan-2026-03-01-note-decomposition -- Phase 4 (Browse Frontend — Composition Rendering)

Repo

forgejo_admin/pal-e-docs

User Story

As a human browsing pal-e-docs,
I want plan pages to render their child phase notes inline as one scrollable page,
So that decomposed plans look identical to monolithic plans — same UX, better data structure.

Context

Phase 2 (PR #61) added note_type, status, parent_note_id, position columns to the notes table. Phase 3 (PR #6 on pal-e-docs-mcp) exposed these in MCP tools. The schema supports parent-child note relationships, but the browse frontend still renders each note independently. A plan with child phases currently shows only the parent's html_content — the children are invisible on the parent page.

This phase makes the browse frontend compose child notes into the parent page at render time. The Note model already has a children relationship (models.py:90-95) ordered by position. The rendering pipeline (sanitize_html → autolink_slugs → wrap_tables) must run on each child independently.

Key decisions from the plan:

  • Composition is server-side (Jinja2), consistent with existing pipeline
  • Backward compatible — notes with no children render identically to today
  • Each child's content goes through the full rendering pipeline independently
  • Parent privacy gates page access; children filtered by is_public for unauthenticated users

File Targets

Files to modify:

  • src/pal_e_docs/routes/frontend.pybrowse_note() route: eager-load children, apply rendering pipeline to each, pass rendered children to template. Also: when viewing a child note directly, include parent link in context.
  • src/pal_e_docs/templates/note.html — add child rendering section after parent content. When viewing a child, show "Part of: {parent title}" breadcrumb.
  • src/pal_e_docs/templates/base.html — add CSS for .child-note section styling (border-left accent, spacing)

Files to create:

  • tests/test_composition_rendering.py — composition rendering test scenarios

Files NOT to touch:

  • sanitize.py, autolink.py, wrap_tables.py — used as-is, no changes
  • models.pychildren relationship already exists (line 90-95, ordered by position)
  • schemas.py — no API changes in this phase
  • routes/notes.py — API routes unchanged
  • Other templates (landing.html, project_notes.html, etc.) — unchanged

Acceptance Criteria

  • Parent note with children: page renders parent content followed by each child's content inline, ordered by position
  • Each child section shows the child's title as an <h2> heading
  • Each child's html_content is independently sanitized, auto-linked, and table-wrapped
  • Monolithic notes (no children): render identically to current behavior — no visual change
  • Privacy: unauthenticated users only see public children (filter by is_public)
  • Child note direct view: shows "Part of: {parent title}" link above the note title when the note has a parent_note_id
  • Mermaid diagrams in child notes render correctly (client-side mermaid.initialize runs on full page)
  • Links section (outgoing/incoming note_links) still appears after all content (parent + children)
  • Ruff clean

Test Expectations

  • Test: parent with 2-3 children — all child titles and content appear in response HTML, in position order
  • Test: monolithic note (no children) — response identical to current behavior (no .child-note divs)
  • Test: child ordering — position=2 content appears after position=1 content
  • Test: private child hidden for anonymous user — child with is_public=False not rendered when no session
  • Test: private child visible for authenticated user — child appears when logged in
  • Test: child direct view shows parent breadcrumb — response contains "Part of:" with parent title and link
  • Test: rendering pipeline runs on children — child with <code>known-slug</code> gets auto-linked, child with <table> gets wrapped
  • Run command: PALDOCS_DATABASE_PATH=:memory: python -m pytest tests/test_composition_rendering.py -v

Constraints

  • Follow existing patterns in routes/frontend.py — use joinedload for eager loading, _apply_public_filter helper for privacy
  • The Note.children relationship (models.py:90-95) is already ordered by Note.position — use it directly
  • Rendering pipeline order is immutable: sanitize_html → autolink_slugs → wrap_tables
  • Use get_known_slugs(db) for auto-linking (cached, same call for parent and all children)
  • Test patterns: follow test_browse_ux.py — use TestingSessionLocal for direct DB setup, client fixture for HTTP assertions
  • CSS should use .child-note class with a left border accent (e.g. border-left: 3px solid #0366d6) to visually distinguish child sections from parent content
  • Agent must run ruff check src/ tests/ and ruff format --check src/ tests/ before PR

Checklist

  • PR opened against main
  • All existing tests still pass (240 baseline)
  • New composition rendering tests pass
  • Ruff clean
  • Only frontend files modified (routes, templates, tests)
  • project-pal-e-docs — project this affects
  • Phase 2: PR #61 (schema columns) — prerequisite, merged
  • Phase 3: PR #6 on pal-e-docs-mcp (MCP tools) — prerequisite, merged
  • Phase 5 will add compose support to get_note MCP tool and dogfood the full flow
### Plan `plan-2026-03-01-note-decomposition` -- Phase 4 (Browse Frontend — Composition Rendering) ### Repo `forgejo_admin/pal-e-docs` ### User Story As a human browsing pal-e-docs, I want plan pages to render their child phase notes inline as one scrollable page, So that decomposed plans look identical to monolithic plans — same UX, better data structure. ### Context Phase 2 (PR #61) added `note_type`, `status`, `parent_note_id`, `position` columns to the notes table. Phase 3 (PR #6 on pal-e-docs-mcp) exposed these in MCP tools. The schema supports parent-child note relationships, but the browse frontend still renders each note independently. A plan with child phases currently shows only the parent's `html_content` — the children are invisible on the parent page. This phase makes the browse frontend compose child notes into the parent page at render time. The `Note` model already has a `children` relationship (`models.py:90-95`) ordered by `position`. The rendering pipeline (`sanitize_html → autolink_slugs → wrap_tables`) must run on each child independently. Key decisions from the plan: - Composition is server-side (Jinja2), consistent with existing pipeline - Backward compatible — notes with no children render identically to today - Each child's content goes through the full rendering pipeline independently - Parent privacy gates page access; children filtered by `is_public` for unauthenticated users ### File Targets Files to modify: - `src/pal_e_docs/routes/frontend.py` — `browse_note()` route: eager-load children, apply rendering pipeline to each, pass rendered children to template. Also: when viewing a child note directly, include parent link in context. - `src/pal_e_docs/templates/note.html` — add child rendering section after parent content. When viewing a child, show "Part of: {parent title}" breadcrumb. - `src/pal_e_docs/templates/base.html` — add CSS for `.child-note` section styling (border-left accent, spacing) Files to create: - `tests/test_composition_rendering.py` — composition rendering test scenarios Files NOT to touch: - `sanitize.py`, `autolink.py`, `wrap_tables.py` — used as-is, no changes - `models.py` — `children` relationship already exists (line 90-95, ordered by position) - `schemas.py` — no API changes in this phase - `routes/notes.py` — API routes unchanged - Other templates (`landing.html`, `project_notes.html`, etc.) — unchanged ### Acceptance Criteria - [ ] Parent note with children: page renders parent content followed by each child's content inline, ordered by `position` - [ ] Each child section shows the child's title as an `<h2>` heading - [ ] Each child's html_content is independently sanitized, auto-linked, and table-wrapped - [ ] Monolithic notes (no children): render identically to current behavior — no visual change - [ ] Privacy: unauthenticated users only see public children (filter by `is_public`) - [ ] Child note direct view: shows "Part of: {parent title}" link above the note title when the note has a `parent_note_id` - [ ] Mermaid diagrams in child notes render correctly (client-side mermaid.initialize runs on full page) - [ ] Links section (outgoing/incoming note_links) still appears after all content (parent + children) - [ ] Ruff clean ### Test Expectations - [ ] Test: parent with 2-3 children — all child titles and content appear in response HTML, in position order - [ ] Test: monolithic note (no children) — response identical to current behavior (no `.child-note` divs) - [ ] Test: child ordering — position=2 content appears after position=1 content - [ ] Test: private child hidden for anonymous user — child with `is_public=False` not rendered when no session - [ ] Test: private child visible for authenticated user — child appears when logged in - [ ] Test: child direct view shows parent breadcrumb — response contains "Part of:" with parent title and link - [ ] Test: rendering pipeline runs on children — child with `<code>known-slug</code>` gets auto-linked, child with `<table>` gets wrapped - Run command: `PALDOCS_DATABASE_PATH=:memory: python -m pytest tests/test_composition_rendering.py -v` ### Constraints - Follow existing patterns in `routes/frontend.py` — use `joinedload` for eager loading, `_apply_public_filter` helper for privacy - The `Note.children` relationship (models.py:90-95) is already ordered by `Note.position` — use it directly - Rendering pipeline order is immutable: `sanitize_html → autolink_slugs → wrap_tables` - Use `get_known_slugs(db)` for auto-linking (cached, same call for parent and all children) - Test patterns: follow `test_browse_ux.py` — use `TestingSessionLocal` for direct DB setup, `client` fixture for HTTP assertions - CSS should use `.child-note` class with a left border accent (e.g. `border-left: 3px solid #0366d6`) to visually distinguish child sections from parent content - Agent must run `ruff check src/ tests/` and `ruff format --check src/ tests/` before PR ### Checklist - [ ] PR opened against main - [ ] All existing tests still pass (240 baseline) - [ ] New composition rendering tests pass - [ ] Ruff clean - [ ] Only frontend files modified (routes, templates, tests) ### Related - `project-pal-e-docs` — project this affects - Phase 2: PR #61 (schema columns) — prerequisite, merged - Phase 3: PR #6 on pal-e-docs-mcp (MCP tools) — prerequisite, merged - Phase 5 will add compose support to `get_note` MCP tool and dogfood the full flow
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/pal-e-api#62
No description provided.