Plan: Server-Side Template Rendering

plan-2026-03-09-template-rendering Plan

active plan

Plan: Server-Side Template Rendering

Vision

Any agent or client can create structured notes (plans, phases, issues) by providing structured data instead of hand-writing HTML. Templates are stored in pal-e-docs, rendered server-side via Jinja2, and exposed through the full stack (API → SDK → MCP → commands). Token cost for plan creation drops from ~3000 to ~500.

Projects & Repos Touched

Project/Repo Platform Role in this plan
pal-e-docs Forgejo API endpoint + Jinja2 rendering engine. Template storage (code blocks with <code>language: "jinja2"</code>).
pal-e-docs-sdk Forgejo New <code>create_note_from_template()</code> method.
pal-e-docs-mcp Forgejo New <code>create_note_from_template</code> MCP tool (thin SDK wrapper).
claude-custom Forgejo <code>/plan</code> command that guides agent to provide structured data → calls MCP tool.

Context

Plan creation currently requires agents to hand-write ~3000 tokens of repetitive HTML. The template-plan note documents the structure, and hooks enforce required sections, but agents still produce the full HTML by hand. Every phase repeats the same boilerplate (Slug/Goal/Owner/Repo/Issue). This is wasteful and error-prone.
Discovered during basketball project session when Betty Sue created plan-2026-03-08-tryout-prep without using the template skill (which doesn't exist yet). The hook caught compliance but couldn't prevent the token waste.
What's already done:
  • [x] Template notes exist in pal-e-docs (template-plan, template-phase, etc.)
  • [x] Hooks enforce template compliance (check-note-template.sh, check-phase-template.sh)
  • [x] Full stack exists: API → SDK → MCP → commands (Phase 8 architecture)
  • [x] Block API with anchor_ids works for all block types (PR #120, #125, #127)
  • [ ] No Jinja2 templates exist yet
  • [ ] No /plan command exists yet
  • [ ] No server-side rendering endpoint exists

Previous Plan

plan-2026-02-26-tf-modularize-postgres — Epilogue item 10 identified the need. The block API fixes (Epilogue item 9) unblocked surgical template editing.

Depends On

None. All prerequisites are met (Phase 8 stack, block API fixes).

Decisions Made

Decision Rationale
Server-side rendering in pal-e-docs API, not MCP server API is the service layer. MCP is a client. SvelteKit frontend will need the same rendering — put it where all clients can use it.
Jinja2 templates stored as code blocks (<code>language: "jinja2"</code>) in existing template notes No schema changes needed. Template notes already have a human-readable pseudo-template in a code block — add a second code block with the machine-renderable Jinja2. Both live together.
Start with <code>template-plan</code> only, extend to phases/issues later Plans have the highest token waste (~3000 per plan). Phases and issues are smaller. Prove the pattern on the biggest win first.
Full stack: API → SDK → MCP → <code>/plan</code> command Same Phase 8 pattern. Proven architecture.
<strong>DEFERRED (2026-03-09):</strong> Phases 3-4 paused after Phase 2 completion The MCP tool is live — Betty Sue can call <code>create_note_from_template</code> directly. The <code>/plan</code> command (Phase 3) would just be a prompt wrapper around something she already knows how to do. Questionable ROI. The tool exists, the ceremony doesn't need automation yet. Revisit if plan creation becomes frequent enough to justify it.

Phases

Phase 1: Jinja2 plan template + API endpoint

  • Slug: phase-2026-03-09-1-jinja2-api
  • Goal: Add a POST /notes/from-template endpoint that accepts structured JSON + template slug and returns a created note with rendered HTML
  • Owner: Dev agent
  • Repo: forgejo_admin/pal-e-docs
  • Forgejo Issue: #128 (closed), #132 QA nits (closed)
  • PR: #131 (merged, squash)
  • Status: COMPLETED
Scope
  • Add jinja2 to dependencies
  • Write the Jinja2 HTML template for plans and add it to the template-plan note as a code block with language: "jinja2"
  • New route: POST /notes/from-template — accepts {"template_slug": "template-plan", "slug": "...", "title": "...", "tags": "...", "project": "...", "data": {...}}
  • Route fetches template note → finds Jinja2 code block → renders with data → calls existing create_note logic
  • Define the data schema for plans (vision, repos, context, phases, decisions, key_files, verification, next_plan_seeds, related)
  • Tests: unit test for Jinja2 rendering, integration test for the endpoint

Phase 2: SDK + MCP tool

  • Slug: phase-2026-03-09-2-sdk-mcp
  • Goal: Expose template rendering through the SDK and MCP tool so agents can use it
  • Owner: Dev agent
  • Repo: forgejo_admin/pal-e-docs-sdk + forgejo_admin/pal-e-docs-mcp
  • Forgejo Issue: pal-e-docs-sdk #19 (closed), pal-e-docs-mcp #28 (closed)
  • PR: pal-e-docs-sdk #21 (merged), pal-e-docs-mcp #31 (merged)
  • Status: COMPLETED. Subphase 2a test hygiene also completed (PR #25, issue #24).
Scope
  • SDK: new method create_note_from_template(template_slug, slug, title, data, tags=None, project=None)
  • MCP: new tool create_note_from_template wrapping the SDK method. Tool description documents the data schema for each template type.
  • Tests: SDK integration test, MCP tool unit test

Phase 3: /plan command

  • Slug: phase-2026-03-09-3-plan-command
  • Goal: Create a /plan slash command that guides agents to provide structured data and calls the MCP tool
  • Owner: Dev agent
  • Repo: forgejo_admin/claude-custom
  • Forgejo Issue: TBD
Scope
  • Create commands/plan.md — structured prompt that:
  • Deploy: copy to ~/.claude/commands/
  • Update template-plan note to reference the command

Phase 4: Extend to phase + issue templates

  • Slug: phase-2026-03-09-4-extend-templates
  • Goal: Add Jinja2 templates for template-phase and template-issue, proving the system is extensible
  • Owner: Dev agent
  • Repo: forgejo_admin/pal-e-docs + forgejo_admin/pal-e-docs-mcp
  • Forgejo Issue: TBD
Scope
  • Write Jinja2 templates for phases and issues
  • Add to existing template notes as code blocks
  • MCP tool already supports any template_slug — just document the data schemas for phases and issues in the tool description
  • Optional: /phase and /issue commands

Key Files

Phase File Repo Change
1 <code>src/pal_e_docs/routes/notes.py</code> pal-e-docs New <code>POST /notes/from-template</code> endpoint
1 <code>src/pal_e_docs/services/template_renderer.py</code> pal-e-docs Jinja2 rendering service (new file)
1 <code>pyproject.toml</code> pal-e-docs Add jinja2 dependency
2 <code>src/pal_e_docs_sdk/client.py</code> pal-e-docs-sdk New <code>create_note_from_template()</code> method
2 <code>src/pal_e_docs_mcp/tools/notes.py</code> pal-e-docs-mcp New MCP tool
3 <code>commands/plan.md</code> claude-custom New command (new file)

Verification

  • [ ] Phase 1: curl POST /notes/from-template with plan data creates a note with correct HTML structure, all required sections present
  • [ ] Phase 1: Hook check-note-template.sh would pass on the rendered output (all ### headings present)
  • [ ] Phase 2: create_note_from_template MCP tool creates a plan with ~500 tokens of input instead of ~3000
  • [ ] Phase 3: /plan command walks agent through structured data collection and creates the note
  • [ ] Phase 4: Phase and issue templates render correctly via the same endpoint

Next Plan Seeds

  • Template versioning — what happens when a Jinja2 template changes? Notes created from v1 vs v2 will differ. May need a template_version field.
  • SvelteKit template UI — web form that maps to the same API endpoint. Deferred to the frontend migration plan.
  • Template validation — Pydantic models for each template's data schema, validated at the API layer. Would give better error messages than Jinja2 rendering failures.
  • todo-jinja2-plan-templates — the TODO that spawned this plan
  • template-plan — the existing plan template (will gain a Jinja2 code block)
  • template-phase — Phase 4 target
  • template-issue — Phase 4 target
  • phase-postgres-epilogue-cleanup — Epilogue item 10
  • plan-2026-02-26-tf-modularize-postgres — parent plan (Epilogue)
  • check-note-template.sh — existing enforcement hook (should still pass on rendered output)