V1 Web PWA · Surface walkthrough

Article Analyzer · Veritas

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
9sections6 minread

On this page

Context. surface customer · domain veritas · route /domains/veritas/articles · auth signed-in · source apps/oshun/web/src/app/domains/veritas/articles/page.tsx

Last walked. 2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data (2xx), console/page-errors, expected content, screenshot verified; live screen-reader, touch, offline, and telemetry-delivery checks pending a manual AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md. 2026-06-27 Codex addendum — added granular Playwright coverage in apps/oshun/web/e2e/veritas-article-analyzer.spec.ts for input/filter/close, sample analysis, claim-source expansion, compare switching, offline URL simulation, reduced motion, and standalone mobile overflow; updated component hooks and mobile grid behaviour. 2026-06-30 Codex addendum — added and covered analyzer entry links from /domains/veritas shell tools and the /veritas broadsheet subnav; corrected the stale standalone /domains dashboard assumption.

Purpose#

The Veritas Article Analyzer — a full-screen overlay that ingests an article URL (or sample article) and produces a multi-section breakdown: readability, sentiment, named entities, claim extraction, source quality, and bias. Side panel supports a compare-against-another-article view.

Entry points#

  • /domains/veritas shell surface — More verification tools exposes an "Article Analyzer" link (data-veritas-secondary-tool-link="articles") to /domains/veritas/articles
  • /veritas broadsheet subnav — "Article analyzer" (data-lilith-subnav-item-id="articles") links to /domains/veritas/articles
  • Direct URL / bookmark — yes (auth required)

There is no standalone /domains dashboard route in the current app; domain switcher paths land on concrete domain surfaces such as /domains/veritas.

Layout regions#

page.tsx is a 'use client' component that renders an sr-only <h1> then mounts <VeritasArticleAnalyzer onClose={() => router.back()} />. The analyzer is a fixed-position overlay (position: fixed; inset: 0; zIndex: 40).

  • Sticky header: domain logo / Article Analyzer title, tablist for input | analysis | compare (only visible when view !== 'input'), Close button (X icon, accessible name "Close", ≥44×44 target)
  • Body (scrollable):
    • Input view (view === 'input'):
      • URL input card: 28px headline "Article Analyzer", lede about what it analyzes, link icon + text input (placeholder="Paste article URL..."), primary Analyze button (disabled when input empty or analyzing; "Analyzing..." pulse text on submit, ~1.8s simulated delay)
      • Sample Articles section: category chip filter (all + unique categories from SAMPLE_ARTICLES); grid of story overview cards (auto-fit, minmax(min(100%, 380px), 1fr)); each card is a keyboard reachable button and shows sentiment colour indicator and claim verification status
    • Analysis view (view === 'analysis', requires selectedArticle):
      • Multi-section analysis grouped by collapsible panels: readability, sentiment, entities, claims, source, bias (all expanded by default per expandedSections initial state)
    • Compare view (view === 'compare', requires compareArticle): two article columns laid side-by-side on wide viewports and stacked on phones
  • Footer: none beyond the overlay body padding

States#

  • Loading (initial) — the overlay renders synchronously; no loading skeleton for the input view
  • AnalyzingAnalyze button shows "Analyzing..." with the oshun-aa-pulse keyframe; URL input remains visible
  • Empty inputAnalyze button is disabled and rendered at 50% opacity; cursor not-allowed
  • Populated (sample selected) — clicking a sample card transitions to analysis view via handleSelectSample
  • Populated (URL submitted) — after the 1.8s timeout, a random SAMPLE_ARTICLES entry is picked, its url overridden with the submitted URL, and the view switches to analysis
  • Compare view with no second articlecompare tab is disabled when compareArticle is unset
  • Filter: no matches — N/A through the shipped UI because category chips are derived from categories present in SAMPLE_ARTICLES; the test verifies a real category filter and the resulting count
  • Offline — no network calls in code (uses SAMPLE_ARTICLES simulation); offline is effectively a no-op
  • Standalone PWA — full-screen overlay renders cleanly; verify safe area at the top
  • Reduced motion — pulse and scan-line keyframes are injected via <style>; verify prefers-reduced-motion honoured

Interactions#

  • Close (X icon button, top-right of header)
    • Function: invokes onCloserouter.back()
    • Keyboard: Enter/Space activates
    • Screen reader: accessible name is "Close"
    • Touch target: verified ≥ 44×44 CSS px

Header tablist (when not in input view)#

  • input (button)
  • analysis (button)
    • Disabled when selectedArticle === null (opacity 0.4)
  • compare (button)
    • Disabled when compareArticle === null (opacity 0.4)

Input view — URL form#

  • URL input (<input type="text">)
    • Function: bound to urlInput state
    • Keyboard: Enter triggers handleAnalyze
    • Placeholder: Paste article URL...
    • inputRef autofocuses when the input view is active
  • Analyze (primary button)
    • Function: handleAnalyze → 1.8s setTimeout → switch to analysis view with a random SAMPLE_ARTICLES entry whose url is overridden
    • Disabled when input empty or already analyzing

Input view — Sample articles#

  • Category chip (per category, including all)
    • Function: setCategoryFilter(cat); capitalises label
    • Active style: accent border + 15% accent background
  • Sample article card (per filteredArticles)
    • Function: handleSelectSample(article) → switches to analysis view
    • Visual indicators: sentiment colour (SENTIMENT_COLORS), claim status (CLAIM_STATUS_CONFIG), bias position chip

Analysis view — Sections#

  • Section header (expand/collapse, per expandedSections set)
    • Function: toggleSection(id) adds/removes from expandedSections
    • All sections start expanded
    • Sections: readability, sentiment, entities, claims, source, bias

Analysis view — section content (sampled by code)#

  • Readability gauge (ReadabilityGauge) — colour-coded score (>=70 ok, >=50 warn, >=30 alert, else alert) — visual chart with score and label
  • Sentiment label badge (bdg(...)) — pill driven by SENTIMENT_COLORS[label]
  • Named entities — chips with ENTITY_CONFIG icon + colour by EntityCategory (person, organization, location, date, money, event, product, legislation, statistic)
  • Claims list — each claim shows CLAIM_STATUS_CONFIG badge (verified / disputed / unverified / false / misleading) and an evidence list
  • Bias position barBIAS_POSITION markers at 0/25/50/75/100 for left → right; current article's bias rendered as accent point

Analysis view → Compare#

  • "Compare against…" affordance (button) — startCompare(article) sets compareArticle and switches view to compare. Verify the visible label and which card surfaces it

Compare view#

  • Two article columns side by side; fields diffed include header/source, readability, sentiment, bias, claim summary, and source reliability
  • Back to analysis (button) — setView('analysis')

Data & contracts#

  • Reads: SAMPLE_ARTICLES from @/lib/veritas/veritas-extended-simulation — fixture-only; the URL input does not make a real network call
  • Writes: none externally; local state mutations only
  • Realtime: none
  • Caching: not applicable (no network); analyzer is client-only
  • Auth/role check: shell middleware enforces signed-in (verify rule for /domains/veritas/*)
  • Trust helpers: formatVeritasSignalBand, mapStatusToVeritasVerdict, resolveWebVeritasTrustRule from ./veritasTrustLabels
  • Random source: webRandomUnitInterval from @/lib/random (used to pick a fake "result" article)
  • Page metadata: not set (no Metadata export in this client-side page); the screen reader <h1> reads "Article Analyzer · Veritas · OSHUN"

Current E2E evidence#

  • apps/oshun/web/e2e/veritas-article-analyzer.spec.ts
    • Entry paths: opens the analyzer from /domains/veritas via the More verification tools link, closes back through browser history, then opens the analyzer from the /veritas broadsheet subnav.
    • Input state: autofocus, empty disabled analyze button, category filter, reduced-motion override, close button accessible name / 44px target, and keyboard close returning to the previous shell route.
    • Analysis state: canonical sample card selection, selected article contract, tab active/disabled state, all core sections, section collapse/restore, claim-source expansion, and source support flag.
    • Compare state: compare action, compare tab enablement, default second article, switching comparison source, back to analysis, and new-analysis return.
    • Failure modes: browser-offline URL submission still completes through the local simulation with the submitted URL preserved; standalone PWA launch at 390px verifies input, analysis, and compare views do not horizontally overflow.

Cross-references#

Open questions / known gaps#

  • URL submission is simulated — handleAnalyze picks a random SAMPLE_ARTICLES entry, not a real fetch. Verify whether V1 wires this to a real article-analysis endpoint
  • No telemetry calls (no trackVeritas* imports in this file); verify the design intent