Plan: Browse Frontend Polish

plan-2026-02-26-browse-frontend-polish Plan

plan completed

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>&lt;code&gt;slug-name&lt;/code&gt;</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: phase-2026-02-26-1-mermaid-fix
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
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) replacing innerHTML for XSS safety
  • PR #35 — CI: switched Woodpecker test step to official mcr.microsoft.com/playwright/python image 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: phase-2026-02-26-2-xss-fix
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
Delivered:
  • PR #39 — server-side HTML sanitization via nh3. Uses nh3 default safe tags (no custom tag allowlist). Custom attribute allowlist for class on 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.pysanitize_html() function, uses nh3 defaults for tags, only customizes attributes and URL schemes
  • browse_note route sanitizes content before passing sanitized_content to template
  • Template uses {{ sanitized_content | safe }}| safe still 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.
Slug: phase-2026-02-26-3-auto-link-slugs
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
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.pyautolink_slugs() function + get_known_slugs() with thread-safe TTL cache. Returns frozenset for immutability.
  • browse_note route calls autolink_slugs() after sanitization, before template rendering
  • Skips <code> inside <pre> blocks and already-linked <code>
  • Sanitizer allowlist updated: class added to <a> attributes for future-proofing
  • Shared create_test_note() helper extracted to conftest.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: phase-2026-02-26-4-typography
Goal: 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 via display: block; overflow-x: auto). Pre block styling (background, padding, border-radius, overflow-x). pre code reset to prevent double styling. pre.mermaid explicit overrides (padding: 0, border-radius: 0). .note-content h3 and h4 styles. Nav flex-wrap: wrap for 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 &lt;a&gt; 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 browser passes 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-workflow page.
  • [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 to RollingUpdate (tracked in todo-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_startendtag calls 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 + frozenset return is cheap insurance.
  • Extract shared test helpers early. Duplicate _create_note helpers 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
  • project-pal-e-docs — parent project
  • plan-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)