What is built, what is not, and what the rest is waiting on.

Generated from the repository’s roadmap file at build time, so this page cannot drift from the work. Every line below is either settled or says what it needs.

76 of 76 shipped

100%

01Foundation

07/07
  • Shipped: Next.js 15 (App Router) + TypeScript + Tailwind scaffold — PR #1

  • Shipped: Cloudflare domain → Vercel deploy pipeline verified with a blank page — PR #2, live at financial-demo.nyxiontech.com

  • Shipped: Supabase project created, env vars wired — project fsboxdlbncegnhcjniaf, local .env.local wired and verified against the live API

Delivered alongside, not originally planned

  • Shipped: Public repo hardened: pre-commit secret scan, CI (gitleaks + semgrep OWASP + npm audit), weekly sweep, GitHub push protection

  • Shipped: main protected — PR required, 4 status checks, linear history, admins included

  • Shipped: Baseline security headers in next.config.ts (CSP deferred to Phase 3, see README)

  • Shipped: Dependency overrides for the postcss/sharp advisories create-next-app ships with

02Auth & Data Layer

08/08
  • Shipped: profiles table with auto-create trigger on signup

  • Shipped: watchlist_items table with RLS (user_id = auth.uid() on all ops)

  • Shipped: price_alerts, quote_cache, news_cache — full data model migrated ahead of schedule

  • Shipped: Add-on #2 delivered early: 12 cross-user RLS isolation tests (npm run test:rls)

  • Shipped: Migrations applied to the remote project — pushed via supabase db push, history in sync, isolation suite re-run against production (12/12)

  • Shipped: Supabase SSR clients (browser / server / middleware split) — getUser() everywhere, never getSession()

  • Shipped: Supabase Auth: email/password + Google OAuth — server actions, /auth/callback code exchange, open-redirect guard unit-tested

  • Shipped: Auth-gated routes (middleware redirect if not logged in) — route policy extracted and unit-tested, redirect verified against a running server

03Landing + Shell

24/24

Landing page

  • Shipped: Design tokens: colour system promoted to global scope (gold accent; green/red reserved for price only); type scale settles with the landing page

  • Shipped: Capture lib/fixtures/xau-daily.json — 160 real XAU/USD daily bars, integrity-checked

  • Shipped: Hero chart: progressive draw-in, dynamically imported, reduced-motion → final frame

  • Shipped: Hero copy: headline, subhead, primary + secondary CTA, server-rendered as LCP

  • Shipped: Contrast verified against the rendered chart, not the bare ground

  • Shipped: Feature section: watchlist, live charts, price alerts

  • Shipped: Footer: tech-stack credits, GitHub link, /roadmap link

  • Shipped: Responsive at 375 / 768 / 1440 — verified with Playwright, which sets a real layout viewport; zero horizontal overflow across all 8 routes

  • Shipped: Accessibility pass — global :focus-visible ring, reduced-motion honoured throughout, colourblind-safe deltas (validated ΔE 10.7 plus filled/hollow candles), aria-live on toasts and search status, semantic landmarks. Contrast verified by calculation, not yet by an automated audit run.

Shell

  • Shipped: App shell: shared nav across dashboard, company and roadmap, with active-route state and a signed-out variant

  • Shipped: Dark-mode toggle with a purpose-built light theme (not an inversion)

  • Shipped: Loading / empty / error states as reusable components

  • Shipped: Toast system (dismissible, deduplicated by error class, accessible via aria-live)

  • Shipped: Stream the shell — prices sit in their own Suspense boundary so a rate-limited feed delays numbers, not the page

  • Shipped: Cache-first render with an as of HH:MM badge; stale values marked in gold rather than hidden

  • Shipped: Anti-flash timing — skeletons hold at opacity 0 for 200ms then fade over 400ms, so content that streams in faster replaces them before they were ever visible

  • Shipped: Skeletons composed from the same layout primitives as the real components

  • Shipped: Placeholders shaped like their data (digit-width ch units in the mono face)

  • Shipped: Opacity pulse, not shimmer sweep; no animation at all under prefers-reduced-motion

  • Shipped: Hero chart draw-in is its loading state — no spinner; the chart is absolutely positioned in a min-height section so nothing shifts when it mounts

  • Shipped: Search keeps previous results on screen while fetching; spinner sits inside the input

/roadmap page

  • Shipped: Public route rendering this file's phases and checkbox state, parsed from ROADMAP.md at build time — single source of truth, no hand-maintained duplicate

  • Shipped: Per-phase completion and an overall figure

  • Shipped: Curated public view — only checkbox lines and add-on names are ever read, so build-strategy notes cannot reach the live page

04Financial Data Integration

07/07
  • Shipped: lib/market-data/ provider interface + Twelve Data provider (quotes, time series, symbol search)

  • Shipped: Finnhub provider (company news, fundamentals) behind the same interface — keys stay server-side

  • Shipped: Verify both free tiers against real keys before building UI on top of them — Twelve Data time_series + quote, Finnhub company-news + metrics all confirmed working

  • Shipped: Response caching (quote_cache / news_cache table) with stale-serve fallback

  • Shipped: Batched quote fetching — one request per render, and only for tickers whose cache is stale

  • Shipped: Ticker search + autocomplete against Twelve Data's symbol search endpoint — debounced, request-aborted, results persist while refetching

  • Shipped: Error taxonomy (rate_limited / unavailable / not_entitled / not_found) + stale-cache fallback

05Watchlist Core

03/03
  • Shipped: Add/remove company to watchlist (CRUD wired to Supabase) — no ownership clause in app code; RLS is the enforcement

  • Shipped: Dashboard: card per watched company — price, daily change, links through to the detail page

  • Shipped: Company detail page: candlestick chart with crosshair + OHLC readout, key stats (market cap, P/E, 52w range), recent headlines — four independent Suspense boundaries

06Polish Pass

27/27
  • Shipped: Responsive check (mobile/tablet/desktop) — automated, 7 tests

  • Shipped: Skeleton loaders on all async data (dashboard cards, price, stats, news)

  • Shipped: Fault-injection pass — not_entitled and not_found forced live with real invalid credentials; rate_limited and unavailable covered by unit tests rather than by burning the daily budget. Note: the "invalid credentials" half of this now classifies as misconfigured, not not_entitled — see the 401/403 split below. The pass tested the path; it could not tell the two causes apart, which is why the defect survived it.

  • Shipped: README with architecture + decisions

  • Shipped: Canonical URLs + metadataBase — previews canonicalise to production, ?next= variants collapse to one URL

  • Shipped: robots.txt + sitemap.xml — all crawlers allowed including AI; sitemap lists only the two indexable URLs

  • Shipped: noindex on /login and /signup — thin auth pages; follow kept so the crawl passes through

  • Shipped: Structured data — WebSite + Person JSON-LD, server-rendered; 7 tests, escape validated by breaking it

  • Shipped: HSTS with includeSubDomains + preload — set in next.config.ts; preload still needs submitting at hstspreload.org

  • Shipped: Theme toggle on the landing page — the one route with no nav, so the control had no home; sits in the hero's top row opposite the instrument label

  • Shipped: Charts follow the theme — both lightweight-charts instances observe data-theme and re-apply their palette, since a canvas inherits no CSS. Previously a switch left dark grid lines on the light page

  • Shipped: Hero draw-in re-timed to 4.2s and driven by elapsed time, not frames — the old per-frame bar count ran the animation twice as fast on a 120Hz display as on a 60Hz one

  • Shipped: misconfigured split out of not_entitled (401 vs 403) — a deployment with no API key was telling visitors the data sat behind a paid plan; missing keys now fail before the request and name the variable in the server log

  • Shipped: Chart history — leading page raised 180 → 750 bars, and panning near the left edge pages in the next 750 through an auth-gated /api/candles. Immutable historical pages are cached in memory; end-of-history stops the requests and is stated in the readout

  • Shipped: Geist actually applied — @theme resolved --font-sans against :root, where the next/font variable was not defined, so every sans surface fell through to the system stack while Geist was downloaded and used for nothing

  • Shipped: color-scheme declared — native <select> popups, autofill and scrollbars were rendering from the light system palette on the dark theme

  • Shipped: --field-border token — a control's border is often the only thing marking where it is, which puts it under the 3:1 non-text floor; --rule-strong measured 1.69:1

  • Shipped: Alerts can be created from /alerts — the page whose subject is alerts was read-only and sent you elsewhere to make one. Form shared with the company page rather than copied

  • Shipped: /about/project and /about/author — stack, providers, failure taxonomy, data model and release process for a reader evaluating the work; two-tab section with a marker that travels

  • Shipped: Auth pages carry the nav and footer — nav revealed on scroll, and on focus so it is not a keyboard trap

  • Shipped: Alert mail fixed and the alerts page stopped overclaiming — the sender fell back to finance-demo… while the site is financial-demo…, so once Resend's verified domain changed, three consecutive alerts were rejected with a 403 that creates no email record. Nothing surfaced it, because the page labelled triggered_at as "emailed" and asserted mail that never left. Sender corrected, label now says triggered, which is what the column actually knows

  • Shipped: Every email is readable on /about/project — all four framed from the same files that get sent, so a reviewer sees them without triggering anything and the page cannot drift from the inbox. Needed X-Frame-Options narrowed to SAMEORIGIN for /mockups/ alone; the blanket DENY blocked the frames silently, which only rendering the page revealed

  • Shipped: Price alerts are actually evaluated — the schedule existed only as a sentence in the README, so alerts sat pending for days and the single alert that ever fired was fired by hand. Now an hourly pg_cron job committed as a migration (one Twelve Data credit per sweep, 24/day), plus an immediate scoped check when an alert is created so an already-met threshold does not look broken for an hour

  • Shipped: Alert claiming made a compare-and-set — running hourly and on creation lets two evaluations meet on one row, and the previous blind write would have let both send. Proven by racing three concurrent claims and confirming one winner

  • Shipped: Transactional emails designed — alert, signup confirmation and password reset share one dark shell built to email's constraints (tables, inline styles, VML buttons, declared colour scheme, preheader). Alerts gained a text/plain part, and muted tones were lifted above --faint, which measured 3.90:1 and failed AA in a medium with no theme toggle

  • Shipped: Ticker escaped before it reaches email HTML — price_alerts only enforces upper-case and a length cap, both of which <B> satisfies; the raw value was reaching the markup, and the <title> leak survived the first fix until a hostile-input render caught it

  • Shipped: Reviewer demo account — created pre-confirmed through the Admin API and seeded with six companies and two alerts, because a reviewer who signs up normally waits on a confirmation email and then lands on the empty state instead of the product. npm run seed:demo restores it, which is what makes publishing the password affordable. Its address is on RFC 2606's reserved example.com: an earlier draft used a real company's domain, which would have made the published password a way for any visitor to send that company automated mail

Beyond the core build

Extensions considered once the product above is complete.

  • Shipped: Price alerts (threshold-based) + Resend email via scheduled edge function shipped

  • Shipped: RLS tests proving cross-user isolation — 12 shipped in Phase 2

  • Shipped: Search debounce + polished autocomplete UX shipped in Phase 5

  • Shipped: Optimistic UI on watchlist add/remove shipped

  • Not started: Weekly digest email (cron + Resend, summarizing watchlist movers) — deferred (ran out of time)

  • Shipped: Sentiment tag on news headlines shipped

  • Shipped: E2E test (Playwright) for signup → add ticker → view dashboard shipped — 6 tests