Skip to content

docs(website): seven audit passes of accessibility, performance, and craft polish#1010

Merged
cpcloud merged 27 commits into
micasa-dev:mainfrom
cpcloud:docs-website-polish
May 23, 2026
Merged

docs(website): seven audit passes of accessibility, performance, and craft polish#1010
cpcloud merged 27 commits into
micasa-dev:mainfrom
cpcloud:docs-website-polish

Conversation

@cpcloud
Copy link
Copy Markdown
Collaborator

@cpcloud cpcloud commented May 22, 2026

Summary

Seven /impeccable audit passes converged the docs site to 20/20 across accessibility, performance, theming, responsive design, and anti-pattern dimensions. Twenty-four commits, all scoped to docs/ plus the generator behind cli.md and a one-line AGENTS.md codification.

Accessibility

  • WCAG AA contrast across body text, links, badges, and tertiary labels; AAA approached via prefers-contrast: more token overrides.
  • <main id="main"> landmark on the home page, skip link on every layout, nav aria-labels everywhere.
  • Search overlay becomes a real role="dialog" with focus trap and focus restoration.
  • Menu/search toggle buttons get aria-expanded / aria-controls, JS-synced.
  • Hero ASCII house is keyboard-activatable (or fully decorative under prefers-reduced-motion).
  • Demo video gets descriptive aria-label; reduced-motion silences the cursor blink and the testimonial reveal.
  • Touch targets bumped to ≥44×44 on coarse pointers without affecting desktop spacing.

Performance

  • Drop <link rel="preload" as="video"> on the hero; switch the <video> to preload="metadata" with a poster.
  • Re-encode screenshots from 2400px lossless to 1800px lossy q=85: 4.4MB → 790KB.
  • CSS @import chains replaced with parallel <link> tags.
  • Externalize all five inline scripts from baseof.html (menu-toggle, search, toc-scrollspy, mermaid-init, theme-toggle); zero onclick= attributes remain.
  • Preload all three woff2 fonts (Source Serif 4, DM Serif Display, JetBrains Mono) on every layout.
  • Gate Mermaid CDN import on actual .mermaid presence; ~30 docs pages no longer fetch the bundle.
  • Pause CSS animations (clouds, stars, cursor blink) on hidden tabs via visibilitychange.

Anti-patterns

  • Replace four side-stripe borders (blockquote, testimonial, TOC active, pager card) with typographic curly-quote glyphs or color-only emphasis.
  • Sweep / &mdash; / -- (including end-of-line edge cases the Hugo typographer was converting to en-dash) from all prose, the home title, the CLI reference generator, and the testimonials JS.
  • Hero h1 dot moves to ::after; sidebar and blog brand dots get aria-hidden.

Theming + responsive

  • 404 page links shared variables.css + theme.js; dark mode finally works there.
  • Switch html { font-size: 18px } to font-size: 112.5% so browser default scaling works.
  • <meta name="color-scheme" content="light dark"> on every layout.
  • New @media print stylesheet hides chrome, forces high contrast on paper, annotates link URLs.

SEO + sharing

  • Set absolute baseURL = "https://micasa.dev/" so og:url and og:image finally produce absolute URLs (the social preview surface was previously dark).
  • Dedicated 1200×630 JPEG OG card; per-post params.image override.
  • Per-page OG metadata on docs templates (was missing).
  • enableRobotsTXT = true with a robots.txt template pointing at the absolute Sitemap URL.
  • Limit RSS to the blog section (the site-wide feed was emitting docs pages with 0001-01-01 dates).

Test plan

Visual checks (CI doesn't render).

  • Home page in light and dark themes; toggle works.
  • Docs page: search opens with /, Escape closes and returns focus.
  • Mobile viewport: hamburger opens sidebar, taps hit ≥44px targets.
  • Hard-reload /blog/: brand reads "micasa." (single period) in both themes.
  • Cmd+P on a docs page: preview shows just the article in light theme.
  • Paste https://micasa.dev/ into a social platform after deploy; verify OG card shows.
  • DevTools → Application → Visibility "hidden": <html> gains animations-paused.

@cpcloud cpcloud added documentation Improvements or additions to documentation ux User experience website Documentation website labels May 22, 2026
cpcloud added 24 commits May 22, 2026 06:16
Audit found warm-gray text on cream at 2.75:1 (fails AA 4.5:1) across
nav, footer, sidebar labels, eyebrows, timestamps, and install hints.
Body links and inline kbd/badge accents using terracotta (3.97:1) also
failed. Mode badges and tab-pill at 0.7rem cream-on-terracotta failed.

- Darken --warm-gray to #736961 (light, 5.1:1) and #9a907f (dark, 5.2:1)
- Add --terracotta-darker for hover, --sage-strong for badge backgrounds
- Body links, kbd, table code, badges now use --terracotta-dark (6.5:1)
- Hover lands on --terracotta-darker (6.8:1) for consistent feedback
- mode-normal uses sage-strong; mode-edit + tab-pill use terracotta-dark
- 404 inline styles get the same darker warm-gray
Address the structural a11y gaps surfaced by the audit:

- Add <main id="main"> landmark on the home page and a skip link on
  every layout (home, docs, blog).
- Switch html { font-size: 18px } to 112.5% across all stylesheets so
  user font-size preferences scale through.
- Search overlay becomes a real dialog: role="dialog" aria-modal="true",
  focus trap inside the modal, focus returns to the trigger on close.
- menu-toggle and search-hint buttons gain aria-expanded / aria-controls,
  synced from JS instead of inline onclick.
- Hero house scene is now keyboard-activatable (role="button",
  tabindex="0", Enter/Space simulates a click from the scene center).
- Visible focus ring on the house scene; reduced-motion now silences the
  blinking cursor block too.
- nav landmarks gain aria-labels (Primary, Footer, Site, Documentation).
- Demo video gets an aria-label describing what's happening.
- 404 page links shared variables.css + fonts.css and loads theme.js so
  dark mode finally works there; smoke color reads from the CSS var.
- Externalize the four inline scripts from baseof.html into
  static/js/{menu-toggle,search,toc-scrollspy,mermaid-init}.js.
The four banned side-stripe accents (>1px colored left/right borders
on cards, callouts, blockquotes) get distilled treatments:

- Blockquote in docs: drop the 3px terracotta left bar plus rounded
  right corners; full linen background with a typographic opening
  quote glyph in the corner.
- Testimonial on the home page: drop the 3px rule left bar; same
  opening-quote treatment in terracotta-dark.
- TOC active state: drop the 2px terracotta indicator; rely on the
  already-colored active link plus font-weight: 600.
- Pager prev/next cards: drop the 2px transparent-to-terracotta
  border accents; hover shifts the card 4px in its arrow direction
  for the affordance.
Em dashes are banned by the project writing rules. Replace each
occurrence with the right punctuation for its role:

- Layout titles use &middot; (matches the hero-nav separator)
- docs/list separator between link and description becomes a colon
- Prose em dashes (self-hosting, relay-architecture, navigation)
  switch to semicolons or colons based on grammatical role
- Definition-style list items (**Term** -- desc) become colons
- "Not applicable" table cells use (none) instead of the dash
- Schema description block uses ": " separator
- testimonials.js drops the leading em dash on each citation
- One CSS comment loses its em dash for a period
Three of the four optimize items land cleanly. The Mermaid CDN stays
for now (see follow-up below).

- Drop <link rel="preload" as="video"> on the home page; switch the
  <video> to preload="metadata" and add a poster from dashboard.webp.
  Autoplay still kicks off when the element is reached; we no longer
  race the document parse with a 1.3MB asset.
- Replace CSS @import url("variables.css") / @import url("fonts.css")
  in website.css, docs.css, and blog.css with explicit <link> tags in
  the HTML so the browser fetches them in parallel instead of through
  the parent stylesheet.
- Re-encode the ten WebP screenshots from 2400px lossless to 1800px
  lossy q=85. Visual quality is preserved for terminal text; total
  asset weight drops from ~4.4MB to ~790KB (-82%).

Mermaid self-host attempted but reverted: nixpkgs mermaid-cli pulls
pkgs.chromium, which on nixos-unstable-small is not in the binary
cache and would build from source (~3.5GB download, ~12GB unpack,
hours). The audit's P2 stays open as a follow-up; SRI on the
jsdelivr URL remains in place.
Several labels and tertiary text were below the readability threshold
at the 18px html base (e.g. 0.6rem = 10.8px). Floor the uppercase
tracked labels at 0.7rem (~12.6px) and any readable text at 0.8rem
(~14.4px):

- Label floor (0.7rem): sidebar-section, toc-label, docs-pager eyebrow,
  badge-experimental label (was a tiny 0.4em scaled off the parent h2).
- Readable floor (0.8rem): TOC links, sidebar external links, docs
  topbar nav, search modal buttons, hero repo line, testimonial
  citation, footer nav, footer copy, blog nav, blog index dates,
  blog post dates.
Final pass picking up P2 + P3 items from the audit:

- Bump .cursor::after opacity 0.3 -> 0.6 so the blinking block on the
  "asked" heading is actually visible (still pulses to 0 mid-cycle).
- Move the hero h1 colored dot from <span class="dot">.</span> to a
  ::after pseudo-element. Screen readers no longer hear "micasa dot".
- Add Open Graph + Twitter meta tags on the home page so shared links
  get a preview card (matches the docs/blog single-page template).
- Remove the per-section HTML comments (<!-- Hero -->, <!-- Demo -->,
  etc.) from index.html; class names are self-describing.
- Drop the explicit <hr class="section-rule"> between features and
  install. The CSS `section + section` rule already draws the line;
  having both was redundant code, even though it didn't visually
  double up.
Scope larger hit areas to touch devices via @media (pointer: coarse),
(hover: none). Desktop spacing stays compact for mouse + keyboard.

- theme-toggle: 10px padding around the 30x24 SVG -> 44x44 hit area
- menu-toggle and search-hint: min 44x44 with proportional padding
- sidebar nav links: 0.6rem vertical padding -> ~44px tall taps
- hero-nav and footer-nav: vertical padding plus wrap to keep dense
  rows from forcing 44px-tall single rows on narrow viewports
- docs topbar nav, sidebar external links, and TOC anchors all get
  comparable touch padding

Sidebar section labels and external padding bumped slightly so the
larger anchor padding doesn't crash into them.
- Add a collapsible "what's in the demo" transcript below the hero
  video so deaf users (or anyone) get a textual walkthrough of the
  five actions the clip cycles through.
- house-crumble.js: under prefers-reduced-motion, also strip the
  role="button" / tabindex / aria-label / cursor:pointer from the
  hero scene so a keyboard user doesn't focus a button that no-ops.
- render-table render hook: replace inline style="text-align: ..."
  attributes with .ta-left / .ta-center / .ta-right utility classes.
  Tokens live in variables.css so docs and blog both pick them up.
- og:image: blog/baseof.html and _default/baseof.html now honor
  .Params.image per page, falling back to dashboard.webp. _default
  also gains the full OG + Twitter card set (was missing entirely).
Social platforms (Slack, LinkedIn, older WhatsApp) have spotty WebP
support. Generate og-card.jpg as a center-crop of dashboard.webp at
the 1200x630 recommended size and point every og:image at it.

65KB JPEG vs 264KB WebP for a thumbnail that was always going to be
previewed at ~600px wide anyway. Per-post params.image still takes
precedence on blog and docs pages.
Sidebar brand link and blog brand link both had <span class="dot">.</span>
in their markup. Hero h1 already uses ::after. Bring the other two in line:
drop the spans, add ::after with the same terracotta color. Screen readers
now announce "micasa" in those positions, not "micasa dot".
The ::after approach caused double-dot artifacts when the server cached
an old template alongside new CSS. Revert blog and sidebar dots back to
the span, add aria-hidden='true' so screen readers skip the punctuation.
Home h1 keeps its ::after (already stable there).
The mono is on the critical path for first paint everywhere: hero nav,
mode badges, install commands, testimonial citations, every label
across docs/blog/topbar/pager/toc. Preloading it eliminates the
brief fallback-to-Consolas flash before woff2 arrives.

Also fills in the Source Serif 4 + DM Serif Display preloads on
blog/baseof.html, which were missing entirely (the blog had been
relying on the CSS font-display: swap path).

Lazy loading on docs screenshots is already in place via the existing
render-image hook (no change needed).
- <meta name="color-scheme" content="light dark"> on every layout so
  the browser draws native form controls, scrollbars, and autofill in
  the matching theme. theme.js still drives [data-theme="dark"] for
  everything we style.
- Switch the .cursor::after blink from animating opacity to animating
  transform: scaleY(). step-end timing keeps the hard on/off feel; the
  paint cost moves off the main thread.
For users who request high contrast at the OS level, push tertiary
text and links from AA (5:1) to AAA (>=7:1):

  light: --warm-gray #544c45 (7.9:1), --terracotta-dark #7a3a23 (8.0:1)
  dark : --warm-gray #beb09e (7.6:1), --terracotta-dark = terracotta-darker (7.0:1)

Heading accents and identity colors stay put -- they pass at heading
sizes already and changing them would alter the brand voice.
baseURL was "/" which made Hugo's .Permalink and absURL produce
root-relative paths. og:url and og:image in the generated HTML were
"/" and "/images/og-card.jpg" -- social platforms reject relative
URLs, so the OG card we built was invisible to Facebook, LinkedIn,
and Slack.

Set baseURL = "https://micasa.dev/" (matching the CNAME). Internal
links use relURL and stay root-relative, so the site still works on
any host (including localhost via hugo server, which overrides
baseURL automatically).
- Replace onclick="toggleTheme()" on the theme-toggle button with a
  click listener bound from js/theme-toggle.js. The button gets an
  id so the script can find it without re-querying by class.
- Enable Hugo's robots.txt generation (enableRobotsTXT = true) and
  add a layouts/robots.txt template that points crawlers at the
  existing sitemap. The Sitemap URL uses absURL, so it picks up the
  absolute baseURL from the previous commit.

That leaves zero inline event handlers across the site. CSP-ready.
Three small cleanups from the fifth audit pass:

- Gate the Mermaid import in mermaid-init.js on whether the page
  actually has a .mermaid element. Most docs pages don't, and the
  unconditional dynamic import was fetching the CDN entry module on
  every load. Now only the two pages with diagrams (relay-architecture,
  data-model) trigger the fetch.
- Delete docs/static/js/typewriter.js. It was used by an old hero
  treatment that's been replaced by house-crumble + chimney-smoke.
  No layout referenced it; 1.3KB of dead code shipping to the site.
- Hide empty .toc-rail via :empty selector. Section index pages
  (/docs/, /docs/getting-started/, etc.) don't generate a TOC and
  were leaving 200px of unused horizontal space on the right.
The earlier em-dash sweep caught `&mdash;` and `—` but missed
the `--` shorthand. The Copy rule bans both forms. Replace ` -- `
with `; ` in prose (consequence/aside contexts) and `: ` where the
text is definition-style.

- Update cliref.go to emit `:` instead of `--` between subcommand
  links and their Short descriptions; regenerate cli.md (~80 lines
  of subcommand entries).
- Update cliref_test.go assertion to match.
- Fix the home page title in content/_index.md: was "micasa --
  your house, in a terminal", now uses the same middot the visual
  title uses. Visible in the RSS title and any template that reads
  site.Home.Title.
- Bulk-replace ` -- ` with `; ` across all docs/blog markdown files,
  then spot-fix a few definition-style cases (LLM pipeline stages,
  mag mode parenthetical) where `:` or `,` reads better.
Print:
- Shared print rules in variables.css force readable colors regardless
  of theme (high-contrast charcoal-on-white), hide the skip link and
  theme toggle, and append link URLs to anchor text via a[href]::after
  for paper readers (excluding in-page anchors).
- docs.css print rules hide the topbar, sidebar, TOC rail, search hint,
  search overlay, pager, and heading-anchor symbols; switch the dark
  code-block treatment to light with a thin border; add page-break
  hints to keep headings with their content.
- blog.css print rules hide the sticky header, footer, and post-footer
  back-link; expand the main width.

RSS:
- Drop site-wide RSS by setting [outputs] in hugo.toml to HTML only
  for home / section / taxonomy / term defaults.
- Per-section override in blog/_index.md (outputs = ["HTML", "RSS"])
  keeps the blog feed at /blog/index.xml. The home /index.xml feed
  that was pulling in every docs page (most with "Mon, 01 Jan 0001"
  dates) is gone.
CSS animations (cloud drift/bob, star twinkle, cursor blink) keep
painting in backgrounded tabs even though they're invisible. RAF
loops are throttled by the browser; CSS animations aren't.

Add idle-pause.js: tiny visibilitychange listener that toggles
.animations-paused on <html>. CSS rule in variables.css sets
animation-play-state: paused on the affected selectors when the
class is present. Loaded with defer (no DOM dependency).
Future audits should not flag cdn.jsdelivr.net for Mermaid; the
trade-off (vendor ~600KB of chunks or build Chromium for mermaid-cli)
has been weighed and dismissed.
The prior sweep matched ' -- ' (spaces both sides). Hugo's goldmark
typographer also converts ' --\\n' (line-break ending) to en-dash.
Five remaining instances in sorting-and-columns, documents, and
llm-plumbing produced &ndash; in the rendered HTML.

Replace with ; or : per context. Rebuild confirms zero dash entities
across the entire generated site.
@cpcloud cpcloud force-pushed the docs-website-polish branch from f38f3ad to 693df67 Compare May 22, 2026 10:36
cpcloud added 3 commits May 22, 2026 06:47
13 new CVEs were disclosed against `golang.org/x/crypto@v0.51.0`
(GO-2026-5005, 5006, 5013-5021, 5023, 5033). Bump to 0.52.0 to
clear them, along with the transitive bump of `x/sys` to 0.45.0.

Also drop the now-stale `GO-2025-3548` ignore from
`osv-scanner.toml` -- the upstream ollama bump in main already
moved past the affected version.
`lipgloss.HasDarkBackground` writes an OSC sequence to stderr and
waits to read the terminal's response from stdin. After the
`x/sys` bump to 0.45.0 (transitive from `x/crypto` 0.52.0) the read
blocks forever on Windows when stdin is not a TTY, causing every
`status` CLI test to hang until the 5-minute timeout.

`status.go` now defers the query until it knows styled output will
be rendered (which already requires the output to be a TTY).
`main.go`'s startup-warning path had the same latent bug -- piping
stderr would hang on Windows -- and gets the same guard.

The pattern matches the one already used in
`internal/ftseval/report.go`.
6 CVEs disclosed against `golang.org/x/net@v0.54.0` (GO-2026-5025
through GO-2026-5030). Bumping to 0.55.0 clears them.
@cpcloud cpcloud merged commit 2882f35 into micasa-dev:main May 23, 2026
38 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation ux User experience website Documentation website

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant