Skip to content

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 → Router
  • FinnestWeb.Router — scopes: / (public), /api/v1/ (API), /admin/ (later). Pipelines: :browser, :api, :authenticated (skeleton only)
  • FinnestWeb.HealthController/health returns {status: "ok", version: <app_vsn>} in <50ms; no DB check
  • FinnestWeb.ReadinessController/ready returns 200 only if Finnest.Repo reachable (via Ecto.Adapters.SQL.query!("SELECT 1")), else 503 with structured error body
  • FinnestWeb.LayoutView + layouts/root.html.heex + layouts/app.html.heex with DaisyUI 5 base
  • Tailwind v4 + DaisyUI 5 build config via Phoenix's esbuild / tailwind tools
  • assets/css/app.css with 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.server starts; curl http://localhost:4000/health returns 200 OK JSON
  • mix 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_web pattern but drop into existing umbrella — finnest_web already exists from F-001, so add Phoenix deps manually
  • DaisyUI 5 requires Tailwind v4 — ensure tailwindcss version 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 — /health is shallow); /ready does touch DB
  • /ready timeout: wrap the DB query in a 150ms Task.yield — if it doesn't complete, return 503 to avoid false readiness under load
  • Security headers: Plug.SSL for HSTS, put_secure_browser_headers for 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.server starts; Phoenix listens on port 4000 (dev) / 4000 (prod, bound to all interfaces)
  • GET /health returns 200 + {"status":"ok","version":"..."} in <50ms p95 under synthetic load (10 parallel requests)
  • GET /ready returns 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-id header (populated by F-003 plug)
  • mix assets.deploy produces minified CSS + JS bundle; sizes reasonable (<200 KB CSS, <100 KB JS)
  • mix format --check-formatted, mix credo --strict, mix dialyzer all green for finnest_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