Raxx · internal docs

internal · gated

QA Report — PR #1431 (Tailwind build-step + favicon.ico)


Summary

PR replaces the dev cdn.tailwindcss.com script tag with a build-time-compiled tailwind.css shipped from the slug, and adds a /favicon.ico Flask redirect plus a generated multi-size .ico asset. Build pipeline now runs Tailwind CLI before the git-subtree split. All artifacts ship via the standard Heroku slug — no new infra or secrets.

Pre-merge verification on the PR branch passes every check available without a deployed instance: - Tailwind build is deterministic (local rebuild matches manifest SHA exactly) - Asset manifest entries match committed file SHAs byte-for-byte - Flask test_client routes return correct status codes (301/200/200) - Existing console test suite (62 tests) passes with no regressions - All 12 CI checks on the PR are SUCCESS or SKIPPED (none failing) - Layer A asset-manifest checksum guard passed (validates committed entries match disk)

The two ACs that strictly require a deployed instance — the absence of the CDN warning in a real browser console and the live 200 on console.raxx.app/favicon.ico — must be re-checked post-merge once the operator dispatches the manual deploy-console.yml workflow per ADR-0020.


Per-AC results

Issue #1426 — replace Tailwind CDN (4 ACs)

AC1: Replace CDN script tag with compiled output.css shipped from build pipeline — PASS

Evidence: - console/app/templates/base.html line 24 (PR diff): <link rel="stylesheet" href="{{ url_for('static', filename='tailwind.css') }}?v=2"> - CDN <script> tag removed (was line 19 in the prior version) - Compiled file shipped at console/app/static/tailwind.css (49,802 bytes, SHA dadbe0cf...) - Repo-wide grep for cdn.tailwindcss in deployable code: only match is the comment in base.html warning future maintainers not to restore it. (Other matches are docs/design/*/mockups/*.html design artifacts, not deployed.)

AC2: Either Tailwind CLI build step in CI, or PostCSS plugin — PASS

Evidence: - .github/workflows/deploy-console.yml lines 310-340 (PR diff) adds two new steps before the subtree-split commit: - Setup Node (Tailwind build)actions/setup-node@v4, Node 20, npm cache keyed on console/package-lock.json - Build Tailwind CSS — runs npm ci && npm run build:css and aborts deploy if app/static/tailwind.css is missing or empty - The post-build Stage deploy-time generated files for subtree split step now also git adds tailwind.css so the subtree split bundles the freshly-compiled file even if the committed copy drifted - Local build reproduction confirms determinism: cd console && npm ci && npm run build:css -> Done in 385ms. -> 49802 bytes -> SHA256: dadbe0cffdaad65ebde136b4e1c0eab876ec6127b3bd44b3f610f0e8b133ff1b Matches docs/asset-manifest.json exactly.

AC3: Warning gone from console on production pages — PARTIAL (pending deploy)

Evidence (pre-merge, code-level): - console/app/templates/base.html no longer emits <script src="https://cdn.tailwindcss.com">. With the CDN script gone, the runtime warning cannot fire. - Local Flask test_client probe of /static/tailwind.css returns HTTP 200, 49802 bytes — the replacement asset serves correctly.

Evidence (post-deploy, blocked): console.raxx.app is gated by Cloudflare Access (302 → CF login). Operator-assisted browser auth was offered for this run, but the PR is not yet merged and the console has no preview surface (Antlers preview build doesn't cover console — confirmed via PR Preview / Antlers check SKIPPED). Manual deploy + browser console re-check required post-merge.

→ Labeled qa:partial-pending-deploy for this AC.

AC4: No visible style regression (audit dashboard, secrets, flags, billing) — PARTIAL (pending deploy)

Evidence (pre-merge): - Tailwind config (console/tailwind.config.js) scans ./app/templates/**/*.html and ./app/static/js/**/*.js for class names, so JIT emits all classes used in current templates - 49 KB minified output is consistent with a production-pruned bundle - Test_client probes for /static/tailwind.css return 200 and the file content begins with the standard Tailwind variable preamble (*,:after,:before{--tw-border-spacing-x:0;...) — confirming the file is a real compiled bundle

Evidence (post-deploy, blocked): Visual regression audit on dashboard/secrets/flags/billing pages requires the deployed surface and cannot be done from build artifacts alone. Same qa:partial-pending-deploy reason as AC3.

→ Labeled qa:partial-pending-deploy for this AC.

Issue #1427 — favicon.ico 404 (3 ACs)

AC1: favicon.ico (or proper favicon family) deployed as static asset, served at /favicon.ico — PASS

Evidence: - New file console/app/static/favicon.ico (2,301 bytes) — verified ICO format (magic 00 00 01 00) with two embedded PNG entries (16x16 + 32x32), generated from favicon-32.png per the PR body - New route in console/app/blueprints/root.py: python @bp.route("/favicon.ico") def favicon_ico(): return redirect(url_for("static", filename="favicon.ico"), code=301) - Local Flask test_client probe: GET /favicon.ico -> 301 Location: /static/favicon.ico GET /static/favicon.ico -> 200 Content-Length: 2301 Content-Type: image/x-icon

AC2: No 404 in console / Sentry for favicon — PARTIAL (pending deploy)

Evidence (pre-merge): Local route returns 301 + 200 chain; cannot 404. Slug-bundling verified by git ls-tree -r pr-1431 -- console/app/static/favicon.ico (file present, blob 75617e08...).

Evidence (post-deploy, blocked): Live probe of console.raxx.app/favicon.ico requires CF Access auth + post-merge deploy. Pre-merge prod still 404s (current state matches the issue).

→ Labeled qa:partial-pending-deploy for this AC.

AC3: in base.html references the asset — PASS

Evidence: console/app/templates/base.html line 13 (PR diff):

<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}?v=2">

Inserted as the first <link rel="icon"> in <head>, ordered before the existing svg/png/apple-touch entries so older browsers prefer it.


Asset-manifest delta (Layer A guard, ADR-0051 / #1359)

generated_at: 2026-05-07T11:47:47Z -> 2026-05-09T04:32:42Z
total assets: 19 -> 21

+favicon.ico   sha256=21a7170f39563ca180ded4b4520b259c770f3a91cf98ea41a63734041928d7ef
+tailwind.css  sha256=dadbe0cffdaad65ebde136b4e1c0eab876ec6127b3bd44b3f610f0e8b133ff1b

Verified locally — shasum -a 256 of the committed blobs matches both manifest entries exactly. CI's Asset-manifest checksum guard (Layer A) job passed.


CI checks

All 12 PR check runs reported via gh pr view --json statusCheckRollup:

Check Conclusion
Detect changed paths SUCCESS
Detect changed surfaces SUCCESS
Antlers (raxx-app) SKIPPED (no Antlers diff)
PR base-branch cleanliness (hard gate) SUCCESS
Mockups (raxx-mockups) SKIPPED (no mockups diff)
Stale-branch guard (hard gate) SUCCESS
Commit message lint (advisory) SUCCESS
Sticky PR comment SUCCESS
Sprint readiness gate SUCCESS
Flag-promotion enforcement (B1) SKIPPED (no flag YAML diff)
Asset-manifest checksum guard (Layer A) SUCCESS
PR report SUCCESS

No FAILURE conclusions. No CANCELLED runs that would indicate hidden duplicates.


Test coverage

Gap (non-blocking): No new test added for the /favicon.ico redirect route or for the presence of the Tailwind link tag in base.html. Recommend a follow-up to add a small test_root_favicon.py so the route doesn't silently regress. Filed as advisory note in the PR comment, not a blocker.


Findings (minor, non-blocking)

Minor doc inaccuracy in console/app/blueprints/root.py docstring. The new docstring at line 8 claims:

Cloudflare Access exempts /static/* and /favicon.ico via the cloudflare_origin_guard.py bypass list.

But console/app/middleware/cloudflare_origin_guard.py line 47 declares:

_ALLOWLISTED_PATHS = frozenset([
    "/health",
    "/",
    "/poller-status",
])

Neither /static/* nor /favicon.ico is in the bypass list. In practice this doesn't break anything because: 1. The CF origin guard is gated on FLAG_ENFORCE_CF_ORIGIN (default off) 2. CF-proxied requests inject CF-Connecting-IP regardless of path, so any genuine request reaching origin will pass

But the comment is misleading future readers. Not a blocker — recommend a one-line fixup (either correct the comment or add the paths to the allowlist) in a follow-up. Noted in PR comment.


  1. Merge PR #1431.
  2. Operator dispatches deploy-console.yml workflow_dispatch -> production (per ADR-0020).
  3. Re-run QA on prod (operator-assisted CF Access auth via Playwright): - Load https://console.raxx.app/dashboard - Open browser devtools console - Confirm absence of cdn.tailwindcss.com should not be used in production warning - Probe https://console.raxx.app/favicon.ico directly -> expect 301 -> 200 - Visual smoke: dashboard, secrets, flags, billing pages render with no missing styles
  4. Re-label PR (or open a follow-up note) with qa:passed once the post-deploy checks come back clean.

Issues filed

None — all ACs either PASS or are PARTIAL-pending-deploy. No failing AC.