diff --git a/finance-equity-research/.gitignore b/finance-equity-research/.gitignore
new file mode 100644
index 000000000..66750eb3e
--- /dev/null
+++ b/finance-equity-research/.gitignore
@@ -0,0 +1,42 @@
+.env.*
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/finance-equity-research/AGENTS.md b/finance-equity-research/AGENTS.md
new file mode 100644
index 000000000..643577dfa
--- /dev/null
+++ b/finance-equity-research/AGENTS.md
@@ -0,0 +1,9 @@
+
+
+# This is NOT the Next.js you know
+
+This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
+
+This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
+
+
diff --git a/finance-equity-research/CLAUDE.md b/finance-equity-research/CLAUDE.md
new file mode 100644
index 000000000..43c994c2d
--- /dev/null
+++ b/finance-equity-research/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/finance-equity-research/README.md b/finance-equity-research/README.md
new file mode 100644
index 000000000..baf07991b
--- /dev/null
+++ b/finance-equity-research/README.md
@@ -0,0 +1,54 @@
+# Upstream — equity research on first-hand signal
+
+Every research tool summarizes published coverage; by the time it's published, it's priced in. Upstream sends live TinyFish agents to the primary sources — Reddit, Trustpilot, app stores, careers pages, SEC EDGAR, Downdetector — and turns what customers and employees are doing *right now* into a directional read on a public company, with every claim citing a verbatim, timestamped source.
+
+**The signature:** the lead-time timeline. Cracker Barrel's complaint velocity turned on May 4, 2026; the CEO-departure 8-K reached EDGAR on July 27 — a measured **84-day lead**. Measured, not modeled.
+
+## Screens
+
+- **Live scan** — type any US-listed ticker; agents fan out (waves of 5), results stream in over SSE, the Direction Score assembles as each source lands.
+- **Company read** — Direction Score decomposed into Customer Sentiment (40%), Workforce (30%), Leadership (20%), Product/Ops (10%); signal tiles with baselines; an evidence table of verbatim quotes, each linked to its source and scrape time.
+- **Lead-time timeline** — the leading signal plotted against official filings on one axis.
+
+## How it uses TinyFish
+
+Escalation ladder per source: **fetch → stealth agent**. Plain fetch first (free); when a site blocks it (Reddit, Trustpilot 403 non-browser clients), the same source escalates to a stealth-profile agent with a US proxy:
+
+```ts
+// src/lib/tinyfish.ts
+const stream = await tf().agent.stream({
+ url: opts.url,
+ goal: opts.goal, // JSON shape embedded in the goal, cookbook-style
+ browser_profile: opts.stealth ? BrowserProfile.STEALTH : BrowserProfile.LITE,
+ proxy_config: { enabled: true, country_code: "US" },
+});
+for await (const event of stream) {
+ if (event.type === "STREAMING_URL") opts.onStreamingUrl?.(event.streaming_url, event.run_id);
+ else if (event.type === "PROGRESS") opts.onProgress?.(event.purpose);
+ else if (event.type === "COMPLETE") { complete = event; break; }
+}
+// COMPLETED only means the browser ran without crashing — validate content:
+const result = normalizeResult(complete.result);
+if (result != null) return { ok: true, ... };
+```
+
+Layoff intel uses **search → fetch** (targeted, ~10s) instead of browsing tracker UIs. SEC EDGAR is parsed deterministically in code — no LLM near structured data.
+
+## Anti-hallucination
+
+- Extracted quotes must appear **verbatim** in the fetched text (substring gate in code) or they're discarded.
+- Every source carries a `metricHint`; the normalizer must return `null` rather than substitute a different number.
+- Per-family scoring rubrics ("a layoff in the last 90 days never scores above 50") keep the read defensible.
+- Provenance (source URL, scraped-at) is stamped server-side, never trusted from the model.
+
+## Setup
+
+```bash
+npm install
+cp .env.example .env.local # fill in keys
+node scripts/apply-schema.mjs # idempotent; needs DATABASE_URL loaded
+node scripts/seed.mjs # demo companies + CBRL lead-time backtest
+npm run dev
+```
+
+Postgres is raw (`postgres` driver over the Supabase session pooler) — no ORM, no supabase-js. Env vars are listed in `.env.example`. Design system: `docs/DESIGN.md` (locked from a Claude Design handoff in `docs/design-handoff/`).
diff --git a/finance-equity-research/db/schema.sql b/finance-equity-research/db/schema.sql
new file mode 100644
index 000000000..bef10ab76
--- /dev/null
+++ b/finance-equity-research/db/schema.sql
@@ -0,0 +1,111 @@
+-- Upstream schema. Applied idempotently by scripts/apply-schema.mjs.
+-- Raw Postgres (Supabase session pooler). No supabase-js anywhere.
+
+create table if not exists companies (
+ id bigint generated always as identity primary key,
+ ticker text not null unique,
+ name text not null,
+ exchange text,
+ sector text,
+ -- resolved source profile: subreddits, trustpilot domain, app ids, ats board,
+ -- edgar cik, newsroom url, downdetector slug… discovered via TinyFish search
+ source_profile jsonb not null default '{}'::jsonb,
+ created_at timestamptz not null default now()
+);
+
+create table if not exists scans (
+ id bigint generated always as identity primary key,
+ company_id bigint not null references companies(id),
+ status text not null default 'running', -- running | complete | failed
+ direction_score numeric, -- 0-100, null until enough families land
+ family_scores jsonb not null default '{}'::jsonb, -- {sentiment:{score,weight,baseline}, …}
+ provisional boolean not null default true,
+ started_at timestamptz not null default now(),
+ completed_at timestamptz,
+ error text
+);
+create index if not exists scans_company_started_idx on scans (company_id, started_at desc);
+
+-- one row per TinyFish source run inside a scan
+create table if not exists source_runs (
+ id bigint generated always as identity primary key,
+ scan_id bigint not null references scans(id),
+ source_key text not null, -- reddit | trustpilot | app_store | google_play | careers | layoffs | edgar | newsroom | downdetector
+ primitive text not null, -- search | fetch | agent
+ status text not null default 'queued', -- queued | running | complete | failed
+ tinyfish_run_id text,
+ streaming_url text,
+ started_at timestamptz,
+ completed_at timestamptz,
+ duration_ms integer,
+ items_read integer,
+ result jsonb,
+ error text
+);
+create index if not exists source_runs_scan_idx on source_runs (scan_id);
+
+-- normalized, citable rows: every claim in the UI is one of these
+create table if not exists evidence (
+ id bigint generated always as identity primary key,
+ company_id bigint not null references companies(id),
+ scan_id bigint not null references scans(id),
+ family text not null, -- sentiment | workforce | leadership | ops
+ quote text not null, -- verbatim excerpt
+ source_key text not null,
+ source_label text not null, -- "r/CrackerBarrel", "Trustpilot · ★1 review"
+ source_url text,
+ published_at date,
+ scraped_at timestamptz not null default now(),
+ sentiment numeric, -- -1..1 from the classifier
+ extra jsonb not null default '{}'::jsonb
+);
+create index if not exists evidence_company_idx on evidence (company_id, scraped_at desc);
+create index if not exists evidence_scan_idx on evidence (scan_id);
+
+-- per-scan headline metrics that power the signal tiles
+create table if not exists signal_metrics (
+ id bigint generated always as identity primary key,
+ company_id bigint not null references companies(id),
+ scan_id bigint not null references scans(id),
+ metric_key text not null, -- complaint_velocity | job_postings | app_rating | exec_events | …
+ family text not null,
+ value numeric,
+ unit text, -- "/wk", "open", "★"
+ baseline numeric,
+ baseline_label text, -- "vs prior week", "vs 90-day average", "was 3.8 on Jan 5"
+ delta_pct numeric,
+ series jsonb not null default '[]'::jsonb, -- sparkline points [{t,v}]
+ sources text, -- "Reddit · Trustpilot · X"
+ scraped_at timestamptz not null default now()
+);
+create index if not exists signal_metrics_scan_idx on signal_metrics (scan_id);
+create index if not exists signal_metrics_history_idx on signal_metrics (company_id, metric_key, scraped_at desc);
+
+-- lagging official record: filings, press releases — the timeline's bottom track
+create table if not exists official_events (
+ id bigint generated always as identity primary key,
+ company_id bigint not null references companies(id),
+ event_type text not null, -- 8k_502 | press_release | earnings | other_filing
+ title text not null,
+ occurred_on date not null,
+ url text,
+ source text not null default 'sec_edgar',
+ is_key boolean not null default false, -- highlighted (rust) on the timeline
+ created_at timestamptz not null default now(),
+ unique (company_id, event_type, occurred_on, title)
+);
+
+-- measured lead-time reads: the product's headline claim, stored not derived-on-the-fly
+create table if not exists lead_time_reads (
+ id bigint generated always as identity primary key,
+ company_id bigint not null references companies(id),
+ signal_metric text not null, -- which series turned (complaint_velocity)
+ signal_start_on date not null, -- first sustained turn
+ signal_rule text not null, -- "+22% above trailing 4-week avg, 3 consecutive weeks"
+ event_id bigint references official_events(id),
+ filed_on date not null,
+ lead_days integer not null,
+ narrative text, -- "Customers turned 84 days before the filing."
+ series jsonb not null default '[]'::jsonb, -- indexed weekly points for the chart
+ created_at timestamptz not null default now()
+);
diff --git a/finance-equity-research/docs/BRIEF.md b/finance-equity-research/docs/BRIEF.md
new file mode 100644
index 000000000..21f70c703
--- /dev/null
+++ b/finance-equity-research/docs/BRIEF.md
@@ -0,0 +1,82 @@
+# Upstream — Equity Research on First-Hand Signal
+
+> Working name: **Upstream** (signal before it's priced in). Angle brief, 2026-08-21. Grounded in `docs/research/`.
+
+## The one-liner
+
+An analyst picks a company. Upstream sends live web agents to the primary sources — customer complaints, app reviews, careers pages, exec announcements — and returns a **directional read with receipts**: a decomposed score, week-over-week velocity, and a timeline proving the signal led the filing.
+
+**The thesis on screen:** every other tool summarizes coverage; by the time it's published, it's priced in. Upstream watches the sources that *precede* coverage. The filing is the lagging indicator — we plot it as one.
+
+## What it is NOT (anti-generic guardrails)
+
+- Not a report generator. No prose walls. If a screen could have come out of Gemini deep research, it's wrong.
+- No number without a baseline. Every metric carries "vs. its own trailing history" — velocity over level, always.
+- No opaque score. The composite is always decomposed into its named inputs in the same view (Quiver DC-Insider pattern).
+- No claim without a receipt. Every evidence row links to the dated, scraped source — verbatim quote, timestamp, URL, scraped-at.
+- Honest about exclusions: LinkedIn is listed as "known, excluded (ToS)" — an engineering call shown in the UI, not a silent gap.
+
+## The three screens
+
+### 1. Live Scan (the TinyFish moment)
+User enters a ticker → source-resolution step (TinyFish **search**: find the subreddit, Trustpilot domain, app-store IDs, ATS job board, EDGAR CIK, newsroom URL) → fan-out of **fetch/agent** runs, one per source, streamed to the UI as a rail of source cards flipping from "agent browsing…" (with live `streaming_url` browser view available) → evidence rows land as each completes. The audience literally watches agents walk Reddit, Trustpilot, and a careers page. This screen is the demo's opening 60 seconds.
+
+### 2. Company Read (the spine — Quiver-style entity page)
+- Header: company, ticker, price context, **Direction Score** (0–100 with ▲/▼ trend) decomposed inline into its four families:
+ - **Customer Sentiment 40%** — Reddit post/comment sentiment + velocity, Trustpilot rating trend + review velocity, app-store rating deltas, BBB complaint velocity
+ - **Workforce 30%** — job-posting count by department (ATS API), posting velocity, layoffs.fyi/WARN events
+ - **Leadership 20%** — exec departures/appointments from newsroom + 8-K 5.02, tenure churn
+ - **Product/Ops 10%** — Downdetector incident volume, status-page incidents, pricing-page diffs (Wayback baseline)
+- Signal tiles: headline delta ("+38% complaint velocity, 7d") + sparkline + baseline note, one tile per family metric.
+- **"What changed this week"** module: ranked biggest movers across all signals — the analyst's first click.
+- Evidence tables per family: verbatim quotes/excerpts, dated, sourced, one hop from every number.
+
+### 3. Lead-Time Timeline (the killer visual, Bloomberg-ALTD pattern)
+One timeline: our leading signals (sentiment velocity, hiring velocity) plotted **against** official disclosure events (8-K filings, press releases) and price. The gap between "signal moved" and "filing landed" is the product, made visually self-evident. Pre-seeded calibration case: **Cracker Barrel** — complaint/sentiment collapse predating the 2026-07-27 CEO-departure 8-K by months. The pitch line: "here's the lead time on a known case; now run it live on any company."
+
+## Demo script (Glean GO)
+
+1. Open on Cracker Barrel's Lead-Time Timeline — the backtest. 30 seconds of "this is what leading actually means."
+2. Live scan on **Etsy** (or Starbucks — both have live 2026 stories): agents fan out on stage, score assembles piece by piece.
+3. Drill one evidence row from score → verbatim seller complaint, dated last week, linked to source.
+4. Closer: "What changed this week" across the tracked watchlist.
+
+Reliability plan: every scan persists; the stage demo can replay the latest stored scan instantly while a genuinely live scan streams in parallel. Conference wifi never gets to kill the demo.
+
+## Sources per scan (escalation ladder: search → fetch → agent)
+
+| Source | Primitive | Reliability |
+|---|---|---|
+| Reddit `.json` listings | fetch | High |
+| Trustpilot (`__NEXT_DATA__`) | fetch | High |
+| Apple/Google app store pages | fetch | High |
+| Greenhouse/Lever ATS APIs | fetch | Very high |
+| layoffs.fyi | fetch | High |
+| SEC EDGAR full-text (8-K 5.02) | fetch | Very high |
+| Company newsroom | agent (lite) | High |
+| Downdetector | agent (stealth) | Medium — cached fallback |
+| BBB complaints | agent (stealth) | Medium |
+| Pricing page + Wayback diff | fetch | Medium |
+| Glassdoor | excluded live; cached snapshot only | Low |
+| LinkedIn | excluded, stated in UI | — |
+
+Concurrency: waves of 5 agent runs (plan limits). Every run validated on content, not status.
+
+## Scoring (stated, not hidden)
+
+Per family: evidence normalized by LLM (GPT-5 for classification/sentiment; Fireworks open-source for bulk labeling) → family score = trend direction × velocity (magnitude × recency) vs own trailing baseline → composite = 40/30/20/10 weighted. Weights shown in the UI with a methodology popover: "starting weights, backtested against Cracker Barrel." Sub-scores always visible next to the composite.
+
+## Architecture
+
+- Next.js (App Router, TS, Tailwind + shadcn), deployed on Vercel.
+- TinyFish via `@tiny-fish/sdk`, server-side SSE routes (`runtime nodejs`, `maxDuration 800`), district-rent-shark pattern.
+- **Raw Postgres** (`postgres` driver, Supabase session pooler via `DATABASE_URL`) — no supabase-js.
+- Tables: `companies` (resolved source profile), `scans`, `source_runs` (per-source raw result + status + streaming url), `evidence` (normalized rows: quote, sentiment, date, url, family), `signal_scores` (per scan per family), `official_events` (8-Ks, press releases — the lagging track for the timeline).
+- Deltas computed between scans; Wayback + first-scan backfill seeds baselines for new companies.
+- Logging: every scan/source run logs id, source, duration, result size, reason on failure — success and failure both.
+
+## v1 cut (Friday EOD)
+
+- Live scan + Company Read + Lead-Time Timeline for a curated six: CBRL (pre-seeded backtest), ETSY, SBUX, TSLA, Z, UNH. Arbitrary-ticker entry works via source resolution but the six are guaranteed-good.
+- Score + evidence + "what changed" module. Watchlist page if time allows, else cut.
+- Not in v1: alerts, auth, sector-peer baselines (self-baseline only), Bluesky.
diff --git a/finance-equity-research/docs/DESIGN.md b/finance-equity-research/docs/DESIGN.md
new file mode 100644
index 000000000..1b81e5d5e
--- /dev/null
+++ b/finance-equity-research/docs/DESIGN.md
@@ -0,0 +1,61 @@
+# Upstream — Design System (LOCKED)
+
+> **Locked 2026-08-21 by Edward from the Claude Design handoff in `docs/design-handoff/`.**
+> The three `.dc.html` files there are the visual source of truth — build the app to match them exactly. This file is the extracted token/pattern reference. Screens: `Live Scan`, `Company Read`, `Lead-Time Timeline`.
+> Handoff data is illustrative; production uses our researched data (`docs/research/`).
+
+## Character
+
+Warm editorial finance — FT-print-heritage on paper, not a terminal. Sharp corners (zero border-radius anywhere), flat 1px borders, no shadows, ink-on-cream, one rust accent that means "signal."
+
+## Color tokens
+
+| Role | Hex |
+|---|---|
+| page (paper) | `#f5efe3` |
+| panel / card | `#fcf9f1` |
+| hairline | `#ddd3c0` |
+| hairline, inner (tile footers) | `#eee5d2` |
+| section rule (strong) | `#201b13` — section headers/table headers underline in **ink**, not gray |
+| ink (text, default chart line) | `#201b13` |
+| muted | `#7a7060` |
+| **accent rust** | `#a8402a` — links, active nav underline, negative deltas, the signal line, the lead-time band, live/working pulse dots, blinking caret |
+| ok green | `#47694f` — completed-agent dots and "✓ Complete" labels only |
+| bar track | `#e9e0cd` |
+
+Rust = "the signal / attention here." Ink = neutral data. Green = agent success only. No other colors.
+
+## Type
+
+- **Newsreader** (serif, optical size axis): wordmark (24/600), page headlines (46–50/500, −0.01em), giant numerals (score 76px, timeline "84" at 122px in-SVG, panel score 64px), tile event titles (26/500), evidence quotes (**16.5px italic**), summary-strip numerals (24/500).
+- **IBM Plex Sans**: everything else. UI 12.5–13.5px; eyebrows 11px/600/letter-spacing 0.14–0.16em uppercase; `font-variant-numeric: tabular-nums` on all numeric text.
+- No monospace anywhere.
+
+## Signature patterns (match handoff exactly)
+
+- **Header** (all screens): serif wordmark + 10px letterspaced "PRIMARY-SOURCE RESEARCH" + nav (active = rust text + 2px rust bottom border) + "● LIVE · Aug 21, 2026 · 09:41 ET" with 2s pulsing rust dot. Bottom border 1px **ink**.
+- **Direction score block**: giant serif numeral + "▼ Falling" in rust; context line "was 52 on Jul 22 · −14 vs 30-day baseline"; four component rows `label+weight | 8px bar | score + "was N"` — bar fill ink, **rust only for the family driving the fall**; caption "Components are live; the headline score is smoothed over 7 days."
+- **Signal tiles**: panel bg, 1px hairline border, eyebrow label, 30px number + unit, delta line (rust when adverse), 120×40 sparkline (rust for the hero signal, ink otherwise), footer above inner hairline: sources + "scraped N min ago". Leadership tile variant: serif "CEO departure" + ghost serif "8-K" ornament.
+- **Evidence table**: 4-col grid (QUOTE / SOURCE / DATE / SCRAPED), header eyebrow row underlined 1px ink, rows separated by hairline; quotes serif italic; scrape column muted tabular ("09:38 ET · 2m ago").
+- **Agent rail / scan cards**: complete = green dot + "✓ Complete · 4.1 s" + result stats; working = **rust border on card**, pulsing rust dot, italic progress text ("reading reviews from the last 90 days… 34 of 57"), "Watch the agent's browser →"; queued = transparent bg + dashed hairline border, "waiting for a browser…".
+- **Live scan header**: serif ticker input with blinking rust caret (1.1s), 2px ink underline; right side "● 8 agents dispatched / scan started 6.2 s ago · 5 complete · 2 working · 1 queued".
+- **Provisional score panel**: score "61" + "provisional ±9 until all sources land"; pending families' bars at 45% opacity or empty with "—"; footnote: "Leadership is excluded until EDGAR completes; remaining weights are renormalized."
+- **THE signature — lead-time band**: headline "Customers turned 84 days before the filing." Chart: ink 2px line on hairline grid; band = rust at 6% opacity between signal-start and filing verticals (1px dashed rust), measurement bracket on top (1.5px rust with end ticks), giant serif rust "84" + letterspaced "DAYS BEFORE THE FILING" inside the band; rust dot at inflection point; official events = 6px ink squares on the baseline with thin leader lines down to labels (filing event in rust); methodology footnote under a hairline; below, three summary strips (SIGNAL START / OFFICIAL FILING / LEAD TIME) with 1px ink top borders — lead-time strip in rust. Tagline register: "Measured, not modeled."
+
+## Typography — why it looks like that (the secret sauce)
+
+1. **Newsreader is an optical-size variable font.** It was designed for on-screen news text and carries an `opsz` axis (6–72): at 46–76px the letterforms automatically get higher contrast, tighter joins, and sharper serifs — display-grade elegance for free. At quote size it relaxes back to a readable text face. This is why the big "84" and the headlines look *expensive* — the font literally changes shape with size. Keep `font-optical-sizing: auto` (default) — never disable it.
+2. **Weight restraint.** The serif never exceeds 500–600 at display sizes. Big-and-medium-weight reads editorial; big-and-bold reads like a template. The 76px score is weight 500.
+3. **Hard role separation.** Serif = what matters (headlines, verdict numerals, verbatim quotes — always *italic* for quotes). Sans = apparatus (labels, metadata, chrome). The eye learns in seconds that serif means "look here."
+4. **Tension of scale, not of color.** 76px numerals against 11px letterspaced uppercase eyebrows (0.14–0.16em tracking) — a ~7:1 size jump in one composition. Color stays almost monochrome so scale does the talking; rust appears only where the product is making its point.
+5. **Tabular numerals everywhere** (`font-variant-numeric: tabular-nums`) — numbers align vertically in tables and don't jiggle when live values update. Quiet, but it's half of what makes it feel like a financial instrument.
+6. **Negative tracking on display, positive on micro.** Headlines at −0.01em; eyebrows at +0.14em. Standard editorial practice, rarely done in dashboards.
+7. **Ink underlines, not gray.** Section headers rule off with 1px `#201b13` — newspaper section rules — while row separators stay hairline `#ddd3c0`. Hierarchy through border color.
+
+## Motion
+
+Pulse (2s / 1.4s) on live dots, caret blink 1.1s. Nothing else. `prefers-reduced-motion`: static.
+
+## Copy register
+
+Understated, factual, editorial. "Evidence, verbatim" · "Every quote links to its source and its scrape." · "Scores update as each agent lands." · "Measured, not modeled." Numbers always carry their baseline ("was 3.8 on Jan 5", "−12% vs 90-day average").
diff --git a/finance-equity-research/docs/design-handoff/Company Read.dc.html b/finance-equity-research/docs/design-handoff/Company Read.dc.html
new file mode 100644
index 000000000..400f9b5d7
--- /dev/null
+++ b/finance-equity-research/docs/design-handoff/Company Read.dc.html
@@ -0,0 +1,203 @@
+
+
+
Complaint velocity across Reddit, Trustpilot, X and app-store reviews began a sustained rise on May 4. The CEO-departure 8-K reached SEC EDGAR on Jul 27 — 84 days later. Measured, not modeled.
+
+
+
+
+
COMPLAINT VELOCITY, INDEXED · MAR–AUG 2026
+
Mar 2 = 100 · weekly
+
+
+
Line: complaint mentions per week across Reddit, Trustpilot, X and app-store reviews, indexed to the week of Mar 2, 2026 = 100. Events: company press releases and SEC filings, dated as published. Sources scraped continuously; last refresh 09:41 ET.
+
+
+
+
+
SIGNAL START
+
May 4
+
Complaint velocity +22% above its trailing 4-week average, sustained for three consecutive weeks.
+
+
+
OFFICIAL FILING
+
Jul 27
+
Form 8-K, Item 5.02 — departure of the Chief Executive Officer. View on EDGAR →
+
+
+
LEAD TIME
+
84 days
+
The measured gap between the first sustained customer signal and the official record.