Plan: Browse Frontend Polish
Vision
pal-e-docs is on a resume and shown in interviews. The browse frontend must look professional, render diagrams correctly, and give visitors a smooth experience on any screen size. Diagrams are a key value-add — they must be first-class citizens, not broken afterthoughts.
Projects & Repos Touched
| Project/Repo | Platform | Role in this plan |
|---|---|---|
| pal-e-docs (app) | Forgejo | Frontend templates, CSS, mermaid rendering, playwright test infrastructure |
Context
The site is publicly accessible and functional. Content is strong — 75+ notes covering SOPs, architecture, plans, project pages. But mermaid diagrams had a rendering bug, overflowed on mobile, and were untestable. This plan fixes all of that and adds professional polish.
Architecture constraint: Server-side rendered Jinja2. Note HTML sanitized via
nh3 then injected via | safe. Mermaid loads from CDN and transforms <pre class="mermaid"> into SVG client-side. All CSS inline in base.html. Playwright browser tests close the JS verification gap.Previous Plan
plan-2026-02-25-private-notes-auth — auth is done, now polish the public experience.Depends On
None.
Decisions Made
| Decision | Rationale |
|---|---|
| Graph visualization (Obsidian-style) is out of scope | Not urgent, high complexity, low interview impact |
| Search bar is deferred | Nice to have, not needed for interview readiness |
| Mermaid interaction: scrollable container + click-to-expand lightbox | Simple CSS gets 80% (scrolling), lightweight inline JS gets the rest (full-screen overlay). No new routes, no dependencies. |
| QA approach: pytest + playwright baked into repo | Zero LLM token cost to run. Agents can verify their own mermaid work. Runs in CI. |
| Playwright test infra scaffolded in Phase 1 | The agent needs to verify mermaid rendering — existing TestClient can't execute JS. |
| CI uses official Microsoft playwright image | <code>mcr.microsoft.com/playwright/python</code> — has Python + playwright + Chromium pre-installed. No custom image needed. |
| XSS sanitizer: <code>nh3</code> (Rust-based), default safe tags | Modern replacement for deprecated <code>bleach</code>. Uses nh3's built-in safe tag defaults — don't maintain a custom allowlist when the library already knows which tags are safe. Only customize attributes (class on pre/code for mermaid) and URL schemes (http/https/mailto). |
| Sanitize at render time, not write time | API stays "raw" for agents — they might store HTML that looks suspicious but is valid. Only the browser rendering path needs protection. API is behind Tailscale anyway. |
| Only <code>note.html</code> needs sanitization | Landing page mermaid diagram is hardcoded in <code>landing.html</code> (not from DB). Only <code>note.html</code> uses <code>| safe</code> on DB content. All other template variables use Jinja2 auto-escaping. |
| Auto-link slugs server-side, not change authoring | 75+ existing notes reference slugs as <code><code>slug-name</code></code> text, not links. Server-side auto-linking fixes all existing notes retroactively. Changing how agents write content would only fix future notes. |
| Font: Atkinson Hyperlegible | Free (Google Fonts), designed for low-vision readability. Professional appearance — doesn't scream "accessibility font." Clean, highly legible sans-serif. |
Phases
Phase 1: Playwright test infra + mermaid fix + diagram UX ✓ COMPLETE
Slug:
Goal: Add browser-level test infrastructure. Fix mermaid rendering. Make diagrams responsive and expandable. Tests prove it works.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — deployed and verified live
phase-2026-02-26-1-mermaid-fixGoal: Add browser-level test infrastructure. Fix mermaid rendering. Make diagrams responsive and expandable. Tests prove it works.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — deployed and verified live
Delivered:
- PR #31 — playwright test infra (live server fixture, 3 browser tests), mermaid newline fix, responsive CSS, click-to-expand lightbox
- PR #33 — review follow-up: fixture scope mismatch fix,
cloneNode(true)replacinginnerHTMLfor XSS safety - PR #35 — CI: switched Woodpecker test step to official
mcr.microsoft.com/playwright/pythonimage so browser tests run in CI - PR #37 — CI: ruff formatting fix to unblock pipeline
Verified: Pipeline #38 succeeded. Image
4093596 deployed via ArgoCD Image Updater. Lightbox, responsive containers, and mermaid rendering confirmed working on live site.Phase 2: XSS sanitization ✓ COMPLETE
Slug:
Goal: Note HTML content is sanitized before rendering to prevent cross-site scripting attacks.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — PR #39 merged
phase-2026-02-26-2-xss-fixGoal: Note HTML content is sanitized before rendering to prevent cross-site scripting attacks.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — PR #39 merged
Delivered:
- PR #39 — server-side HTML sanitization via
nh3. Uses nh3 default safe tags (no custom tag allowlist). Custom attribute allowlist forclasson pre/code (mermaid). URL schemes restricted to http/https/mailto. 26 unit tests + 5 integration tests (93 total passing).
Key implementation:
src/pal_e_docs/sanitize.py—sanitize_html()function, uses nh3 defaults for tags, only customizes attributes and URL schemesbrowse_noteroute sanitizes content before passingsanitized_contentto template- Template uses
{{ sanitized_content | safe }}—| safestill needed to prevent Jinja2 double-escaping
Review-fix loop: 3 rounds. Round 1 found: type annotation mismatch, missing data: URI test, over-restrictive custom tag allowlist, weak assertion. Round 2 verified fixes, flagged allowlist design. Round 3 verified simplification to nh3 defaults — clean approval.
Phase 3: Auto-link slug references ✓ COMPLETE
Slug:
Goal: Inline slug references in note content become clickable links to the referenced notes.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — PR #41 merged, deployed and verified live
phase-2026-02-26-3-auto-link-slugsGoal: Inline slug references in note content become clickable links to the referenced notes.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — PR #41 merged, deployed and verified live
Delivered:
- PR #41 — server-side auto-linking of slug references.
HTMLParser-based state machine scans<code>elements, wraps known slugs in<a href="/browse/notes/{slug}" class="auto-link">links. Thread-safe slug cache with 60s TTL. CSS for linked code (blue text, light blue background, hover underline). 26 unit tests + 5 integration tests (124 total passing).
Key implementation:
src/pal_e_docs/autolink.py—autolink_slugs()function +get_known_slugs()with thread-safe TTL cache. Returnsfrozensetfor immutability.browse_noteroute callsautolink_slugs()after sanitization, before template rendering- Skips
<code>inside<pre>blocks and already-linked<code> - Sanitizer allowlist updated:
classadded to<a>attributes for future-proofing - Shared
create_test_note()helper extracted toconftest.py
Review-fix loop: 3 rounds. Round 1 (agent internal): self-closing tag corruption, html_escape on slug href, attribute entity decoding. Round 2 (QA): thread safety (Lock), frozenset return, cache tests, sanitizer allowlist, conftest helper. Round 3: clean approval.
Verified: Deployed via ArgoCD Image Updater. Live site confirmed:
agent-workflow page renders 8 auto-linked slugs (agent-spawn-conventions, agent-paradigm, hook-events-reference, enforcement-architecture, pr-lifecycle) as clickable blue links with class="auto-link".Phase 4: Typography & CSS polish ✓ COMPLETE
Slug:
Goal: Professional, accessible typography. Readable tables. Polished mobile experience.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — PR #43 merged
phase-2026-02-26-4-typographyGoal: Professional, accessible typography. Readable tables. Polished mobile experience.
Owner: Agent (worktree, pal-e-docs repo)
Status: COMPLETE — PR #43 merged
Delivered:
- PR #43 — Atkinson Hyperlegible font (400, 400i, 700, 700i) from Google Fonts with preconnect and
display=swap. Table styling (border-collapse, borders, cell padding, header backgrounds, alternating row colors, full width, mobile horizontal scroll viadisplay: block; overflow-x: auto). Pre block styling (background, padding, border-radius, overflow-x).pre codereset to prevent double styling.pre.mermaidexplicit overrides (padding: 0, border-radius: 0)..note-content h3andh4styles. Navflex-wrap: wrapfor mobile. +54/-3 lines, single file.
Review: 1 round. QA reviewer approved — all acceptance criteria met, no regressions to mermaid/lightbox/auto-links. Two non-blocking suggestions noted (code comment on table
display: block pattern, nth-child counting with thead).Key Files
| Phase | File | Repo | Change |
|---|---|---|---|
| 1 ✓ | <code>pyproject.toml</code> | pal-e-docs | Add pytest-playwright to dev deps |
| 1 ✓ | <code>tests/conftest.py</code> | pal-e-docs | Live server fixture, browser marker, scope fix |
| 1 ✓ | <code>tests/test_frontend_browser.py</code> | pal-e-docs | Playwright tests for mermaid rendering |
| 1 ✓ | <code>src/pal_e_docs/templates/base.html</code> | pal-e-docs | Mermaid newline fix, responsive CSS, lightbox JS (cloneNode) |
| 1 ✓ | <code>.woodpecker.yaml</code> | pal-e-docs | Switched to playwright CI image for browser tests |
| 2 ✓ | <code>pyproject.toml</code> | pal-e-docs | Add <code>nh3</code> dependency |
| 2 ✓ | <code>src/pal_e_docs/sanitize.py</code> | pal-e-docs | New — sanitize_html() with nh3 defaults |
| 2 ✓ | <code>src/pal_e_docs/routes/frontend.py</code> | pal-e-docs | Sanitize html_content before rendering |
| 2 ✓ | <code>src/pal_e_docs/templates/note.html</code> | pal-e-docs | Use sanitized_content, updated security comment |
| 2 ✓ | <code>tests/test_sanitize.py</code> | pal-e-docs | 26 unit tests for sanitization |
| 2 ✓ | <code>tests/test_sanitize_integration.py</code> | pal-e-docs | 5 integration tests for browse_note route |
| 3 ✓ | <code>src/pal_e_docs/autolink.py</code> | pal-e-docs | New — autolink_slugs() with HTMLParser state machine + thread-safe TTL cache |
| 3 ✓ | <code>src/pal_e_docs/routes/frontend.py</code> | pal-e-docs | Auto-link slug references after sanitization |
| 3 ✓ | <code>src/pal_e_docs/sanitize.py</code> | pal-e-docs | Added class to <a> allowlist |
| 3 ✓ | <code>src/pal_e_docs/templates/base.html</code> | pal-e-docs | CSS for auto-linked code elements |
| 3 ✓ | <code>tests/test_autolink.py</code> | pal-e-docs | 26 unit tests for auto-linking + caching |
| 3 ✓ | <code>tests/test_autolink_integration.py</code> | pal-e-docs | 5 integration tests for browse_note route |
| 4 ✓ | <code>src/pal_e_docs/templates/base.html</code> | pal-e-docs | Font import, table styling, pre blocks, mobile, spacing |
Verification
- [x] Phase 1:
pytest -m browserpasses in CI. Mermaid renders. Scrollable containers. Lightbox works. No regressions. Deployed and verified live. - [x] Phase 2: XSS payloads stripped. Mermaid still renders. Existing note content unaffected. 93 tests passing. 3-round review-fix loop completed. PR #39 merged.
- [x] Phase 3: Slug references in note content are clickable links. Non-slug code unaffected. Pre blocks unaffected. 124 tests passing. 3-round review-fix loop completed. PR #41 merged. Verified live — 8 auto-linked slugs on
agent-workflowpage. - [x] Phase 4: Atkinson Hyperlegible font active. Tables styled with borders, padding, headers, alternating rows. Pre blocks styled. Mobile responsive. No regressions. PR #43 merged.
Lessons Learned
Phase 1
- Always run review agent before merging. Self-review missed fixture scope mismatch and innerHTML XSS — the fresh review agent caught both.
- CI must match dev deps. Adding playwright to dev deps without updating the CI image broke the pipeline for 4 PRs. The official Microsoft playwright Docker image is the right solution.
- ArgoCD Image Updater has a ~2min polling interval. Don't panic if deploy doesn't happen immediately after image push.
- Deploy strategy is
Recreate— causes downtime on every deploy. Should move toRollingUpdate(tracked intodo-deployment-safety).
Phase 2
- Trust library defaults. Initial implementation defined a custom tag allowlist that was more restrictive than nh3's built-in safe defaults. This silently stripped valid HTML like
<details>,<del>,<kbd>. Lesson: don't maintain a custom list when the library already knows which tags are safe. Only customize what you actually need to (attributes, URL schemes). - 3-round review-fix loop catches real issues. Round 1 found type annotation mismatch + test gap. Round 2 flagged over-restrictive design. Round 3 verified the simplified approach. Fresh eyes matter.
Phase 3
- HTMLParser needs careful handling of self-closing tags and entity encoding. Default
handle_startendtagcalls starttag+endtag which produces<br></br>. Must override to emit self-closing format. Also, HTMLParser decodes entities in attribute values — must re-encode when rebuilding tags. - Cache thread safety matters even for simple cases. A bare global variable cache works under CPython GIL but is a code smell.
threading.Lock+frozensetreturn is cheap insurance. - Extract shared test helpers early. Duplicate
_create_notehelpers across integration test files were caught in review. Shared conftest helper is cleaner.
Phase 4
- CSS-only changes are low-risk, high-impact. +54 lines of CSS transformed the entire site's appearance. Single-file scope made review trivial.
- Clean first-round approval is possible when the scope is tight and well-defined. Phase 4 had the clearest spec of all phases — no ambiguity in acceptance criteria.
Next Plan Seeds
- Browse search bar
- CSRF protection on login form
- Deployment strategy: switch from Recreate to RollingUpdate
Related
project-pal-e-docs— parent projectplan-2026-02-25-private-notes-auth— predecessor (auth done, now polish)issue-mermaid-newline-bug— resolved, fixed in Phase 1 (PR #31)issue-xss-safe-filter— resolved, fixed in Phase 2 (PR #39)issue-pal-e-docs-auto-link-slugs— resolved, fixed in Phase 3 (PR #41)issue-pal-e-docs-playwright-mermaid-fix— resolved (PR #31)issue-pal-e-docs-playwright-review-fixes— resolved (PR #33)issue-pal-e-docs-typography-css-polish— resolved, fixed in Phase 4 (PR #43)