STORY-F-004: finnest_web Phoenix endpoint + /health + /ready + DaisyUI assets¶
Epic: Foundation Priority: Must Have Story Points: 2 Status: Not Started Assigned To: Unassigned Created: 2026-04-17 Sprint: 1
User Story¶
As an Ops SRE (represented by the deploy pipeline + load balancer),
I want the Finnest web edge serving /health and /ready endpoints with a working asset pipeline for DaisyUI/Tailwind,
so that Kamal health checks pass, the ALB can route traffic, and subsequent LiveView work has a styled baseline to build on.
Description¶
Background¶
finnest_web is the Phoenix endpoint — the HTTP edge. Without this, nothing is reachable. This story sets up the endpoint shell (router, plug stack, LiveView scaffold), the health check endpoints required by Kamal (IN-08), and the Tailwind v4 + DaisyUI 5 asset pipeline (AR-09, FE-03, FE-08) so styled LiveView pages work.
Two health endpoints per architecture §Reliability:
- /health — shallow (process up, <50ms) — used by load balancer
- /ready — deep (DB reachable, Oban reachable, primary AI provider reachable, <200ms) — used by deployment gate
Scope¶
In scope:
FinnestWeb.Endpoint— Phoenix endpoint with plug stack: RequestId → CorrelationId (F-003) → Logger → ParseRequest → MethodOverride → Head → Session → RouterFinnestWeb.Router— scopes:/(public),/api/v1/(API),/admin/(later). Pipelines::browser,:api,:authenticated(skeleton only)FinnestWeb.HealthController—/healthreturns{status: "ok", version: <app_vsn>}in <50ms; no DB checkFinnestWeb.ReadinessController—/readyreturns 200 only ifFinnest.Reporeachable (viaEcto.Adapters.SQL.query!("SELECT 1")), else 503 with structured error bodyFinnestWeb.LayoutView+layouts/root.html.heex+layouts/app.html.heexwith DaisyUI 5 base- Tailwind v4 + DaisyUI 5 build config via Phoenix's esbuild / tailwind tools
assets/css/app.csswith Tailwind imports + DaisyUI plugin; dark mode default per FE-08- Landing page at
/— simple "Finnest" splash with DaisyUI button linking nowhere yet (proves styles load) mix phx.serverstarts;curl http://localhost:4000/healthreturns 200 OK JSONmix phx.server+ browser at/shows styled page
Out of scope:
- Auth routes (F-006)
- Agent chat UI (F-017)
- Cmd+K overlay (F-018)
- WebSocket Channels (F-017)
- Alpine.js (add when LiveView hooks need local state; not yet)
Technical Notes¶
- Use
mix phx.new --no-ecto finnest_webpattern but drop into existing umbrella —finnest_webalready exists from F-001, so add Phoenix deps manually - DaisyUI 5 requires Tailwind v4 — ensure
tailwindcssversion constraint matches - Dark mode via CSS custom properties on
<html data-theme="finnest-dark">(DaisyUI semantic tokens, FE-08) - Health check response should not touch DB (IN-08 —
/healthis shallow);/readydoes touch DB /readytimeout: wrap the DB query in a 150msTask.yield— if it doesn't complete, return 503 to avoid false readiness under load- Security headers:
Plug.SSLfor HSTS,put_secure_browser_headersfor X-Frame-Options etc. (SE-04) — set in router pipeline - No PII in health/ready response bodies (Commandment #24)
Dependencies¶
- Blocked by: STORY-F-003 (Repo + CorrelationId plug must exist)
Acceptance Criteria¶
-
mix phx.serverstarts; Phoenix listens on port 4000 (dev) / 4000 (prod, bound to all interfaces) -
GET /healthreturns 200 +{"status":"ok","version":"..."}in <50ms p95 under synthetic load (10 parallel requests) -
GET /readyreturns 200 when Postgres reachable; 503 with{"error":{"code":"repo_unreachable"}}when DB stopped -
/renders a DaisyUI-styled landing page (dark mode, Finnest wordmark, a btn, a card) — styles load correctly - Security headers present on every response:
Strict-Transport-Security,X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: strict-origin-when-cross-origin,Content-Security-Policy(baseline) - Every response carries
x-correlation-idheader (populated by F-003 plug) -
mix assets.deployproduces minified CSS + JS bundle; sizes reasonable (<200 KB CSS, <100 KB JS) -
mix format --check-formatted,mix credo --strict,mix dialyzerall green forfinnest_web
Testing Requirements¶
- Controller test:
test FinnestWeb.HealthControllerTest— 200 on GET /health; <50ms in test env - Controller test:
test FinnestWeb.ReadinessControllerTest— 200 on healthy DB; 503 when Repo.query!/2 mocked to raise - Integration test: boot Phoenix;
curl /returns styled HTML containing expected DaisyUI classes - Plug test: security headers present on every response (parameterised for several routes)
References¶
../architecture/architecture.md§API Design, §Reliability (health/ready endpoints)../10-GUARDRAILS.mdAR-09, FE-03, FE-08, IN-08, SE-04../brainstorms/brainstorm-09-ui-ux-vision.md§Design System