Skip to content

Sprint 3 — News inbox#76

Merged
KevinBigham merged 4 commits into
mainfrom
goal/sprint-3-news-inbox
May 15, 2026
Merged

Sprint 3 — News inbox#76
KevinBigham merged 4 commits into
mainfrom
goal/sprint-3-news-inbox

Conversation

@KevinBigham
Copy link
Copy Markdown
Owner

@KevinBigham KevinBigham commented May 15, 2026

Summary

Sprint 3 ships the News inbox the audit found unwired: getNews(limit?) and markNewsRead(newsId) were exposed in useWorker() and powered by sim-core/narrative/newsFeed.ts, but no UI consumed them.

  • 📰 /news route, lazy-loaded under AppLayout. Renders worker NewsItem objects newest-first with headline, body excerpt, category badge, priority indicator, optional tag chip, timestamp, related team chips. Mobile-survivable at 375×667.
  • 🔍 Filters. All / Unread toggle + category multi-select. Client-side over the worker's getNews(100) result.
  • 👀 Mark-read on view. Opening an item calls markNewsRead(id) and persists through the existing IndexedDB save path. IDB proof: unread 580 → 579 after read, survives reload at the data layer.
  • 🧭 Sidebar entry. Lucide Inbox icon. Newspaper stays with Press Room.
  • 🔔 TopBar unread chip. Subscribes to a small news-read event so the count refreshes after any read across the app.

Schema stays at v33. No new deps. No sim-core, contracts, or worker edits. Bundle: new lazy chunk at 10.08 KB raw / 3.39 KB gzip, all other chunks unchanged, bundleBudget.test.ts passes.

Verification

pnpm typecheck   → 9/9 tasks clean (7.669s)
pnpm test        → 8/8 tasks, web 99 files / 624 tests (+2 files / +6 tests), sim-core 137/1610, contracts 1/20, UI 1/1
pnpm build       → 5/5 tasks, vite 4.69s, PWA 120 entries

Browser smoke (screenshots under apps/web/docs/screenshots/sprint-3/):

  1. Dashboard after a simmed month
  2. News inbox unread list
  3. Category filter applied (Trade)
  4. Item opened → read state + TopBar chip decrement
  5. Mobile 375×667 — no horizontal overflow
  6. Hard-reload behavior (pre-existing app-wide; see below)

On the hard-reload Done When item

The GOAL.md included a Done When item: "/MBD/news hard-reload survives Sprint 2's BrowserRouter basename (should be free)". That claim was wrong.

Sprint 2's BrowserRouter basename fix solved URL parsing/MBD/news now correctly resolves to the /news route table entry. It did NOT solve state hydration. AppLayout:446 has if (!isInitialized) return <Navigate to="/" replace />, and useGameStore is a plain Zustand store with no persistence middleware, so isInitialized resets to false on every hard reload. This redirect fires on every in-game route — /dashboard, /roster, /trade, /news — not just news.

Codex correctly paused at this Done When item because the fix requires modifying protected files (useGameStore, AppLayout, or App). Sprint 3 ships the news inbox feature complete; the auto-resume-on-reload polish is its own sprint (Sprint 3.5 in the queue) that affects the entire app shell.

The IDB evidence confirms the data layer is correct: markNewsRead → save writes through and read state survives the reload. Only the routing-to-Save-Hub redirect masks it visually.

How the work was sliced

4 commits on goal/sprint-3-news-inbox:

  1. docs(goal): — Sprint 3 mission contract
  2. feat(news): — NewsPage feature, route, lib
  3. feat(layout): — Sidebar nav entry + TopBar unread chip
  4. docs(news): — STATUS.md + .logs/goal-progress.md + 6 browser-smoke screenshots

Process

  • Codex GPT-5 executed milestones 1–7 (route, list, filters, read state, persistence, nav, badge, tests, browser smoke).
  • Codex correctly paused at the hard-reload Done When item because the fix is outside the GOAL.md allowed write scope.
  • Claude Code reviewed Codex's diff for scope + quality, reframed the pause as a pre-existing app-wide issue (not a Sprint 3 regression), and queued Sprint 3.5 to fix it.

Lineage

  • Sprint 1 cleanup: #74 — MERGED
  • Sprint 2 revised onboarding: #75 — MERGED
  • Sprint 3 news inbox: this PR
  • Sprint 3.5 hard-reload state survival: queued next
  • Sprint 4 granular endpoints: queued
  • Sprint 5 press-conference unification: queued
  • Sprint 6 hardening: queued
  • Sprint 7 team logos: queued

Test plan

  • CI gate green
  • /news lists worker-backed items
  • Filters work
  • markNewsRead persists; IDB unread count drops
  • TopBar unread chip decrements
  • Mobile 375×667 — no horizontal overflow
  • Hard reload behavior matches every other in-game route (intentional; Sprint 3.5 fixes app-wide)

🤖 Generated with Claude Code (review/ops) and Codex GPT-5 (build).

claude and others added 4 commits May 14, 2026 19:08
Goal Packet v2.0 format. Single-mission contract for Codex.

Mission: build a /news route that surfaces the worker-backed news feed
(getNews / markNewsRead) the audit found unwired. Includes filter UI,
mark-read on view, Sidebar nav entry, and an unread badge in TopBar.

Read-first, allowed-write, protected scope, non-negotiables, milestone
loop, validation loop, evaluator-visible proof, pause conditions, done
criteria, and final report all encoded inline so the slash command can
stay thin.

Builds on Sprint 1 cleanup (PR #74) and Sprint 2 revised onboarding
canonicalization (PR #75), both merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The audit found getNews(limit?) and markNewsRead(newsId) exposed in
useWorker() and powered by sim-core/narrative/newsFeed.ts, but no UI
consumed them. SettingsPage showed the unread count, full stop.

This adds:

- /news route lazy-loaded under AppLayout, wrapped in RouteErrorBoundary
  like every other feature route.
- NewsPage list rendering: headline, body excerpt, category badge,
  priority indicator, optional tag chip, timestamp (Season X · Day Y or
  Now), related team chips. Read items have a clear visual treatment.
- Filters: All / Unread toggle plus a category multi-select chip group.
  Filtering happens client-side over the worker's getNews(100) result.
- Mark-read on item open: calls markNewsRead(id), persists the active
  save through the existing IndexedDB save path, decrements the
  in-session unread count.
- Mobile-survivable at 375x667 — no horizontal overflow.
- Loading skeleton, empty-state panel, and error toast for worker
  failures.
- A small newsEvents helper module dispatches a "news-read" event so
  the TopBar unread chip can react across components without prop
  threading.

Tests: NewsPage.test.tsx (4 tests) covers list render, filter behavior,
mark-read flow, and worker mock surface.

Schema stays at v33. No sim-core or contracts edits.

Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Sidebar: new NavItem { to: '/news', label: 'News', icon: <Inbox /> }.
  Newspaper icon stays with Press Room. Sidebar.test asserts the entry
  renders.
- TopBar: new unread chip that subscribes to news-read events and
  refetches worker getNews() to recompute the count after every read.
  Decrements as the user reads items. Match the existing TopBar density
  (small chip, no emoji, lucide-only).

A new TopBar.test.tsx covers the chip render, the count reflecting the
worker mock, and the post-read recompute.

Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…creenshots

Captures the milestone-by-milestone audit trail Codex produced during
Sprint 3, the final Sprint-3-complete status report (reframed to
distinguish the news inbox feature — which fully shipped — from the
hard-reload state-hydration issue, which is pre-existing app-wide
behavior that affects every in-game route and is now queued as Sprint
3.5), and six browser-smoke screenshots:

- 01-dashboard-after-month.png   Day 31 save context
- 02-news-inbox-unread.png       /MBD/news list with worker news
- 03-news-category-filter.png    Trade-category filter applied
- 04-news-item-read.png          Read state + TopBar chip decrement
- 05-news-mobile-375.png         375x667 viewport, no overflow
- 06-news-hard-reload-blocked.png  pre-existing app-wide behavior
                                   (documented, not a Sprint 3 regression)

IndexedDB save-state proof from save-slot-2: unread 580 -> 579 after
read and survives reload at the data layer (only the routing redirect
to Save Hub masks it visually).

Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@KevinBigham KevinBigham marked this pull request as ready for review May 15, 2026 00:54
@KevinBigham KevinBigham changed the title Sprint 3 — News inbox (Codex handoff) Sprint 3 — News inbox May 15, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c64caa8a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const unreadCount = items.filter((item) => !item.read).length;
const filteredItems = useMemo(() => items.filter((item) => {
const readMatch = readFilter === 'all' || !item.read;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep opened items visible under the Unread filter

When the user has selected the Unread filter, opening any unread story immediately calls markItemRead, which optimistically flips read to true; this predicate then excludes that same item from filteredItems. In that scenario the card disappears as soon as the user tries to view it, so they cannot read the expanded body from the Unread inbox.

Useful? React with 👍 / 👎.

Comment on lines +226 to +227
const nextNews = await worker.getNews(NEWS_LIMIT);
setItems([...(nextNews ?? [])].sort(compareNewsForInbox));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Load more than unread stories for the All filter

This fetch uses worker.getNews, but the worker implementation returns only getUnreadNews(requireState().news).slice(...) (apps/web/src/workers/sim.worker.queries.ts:2666-2668). As a result, after a refresh or revisit, the All filter and category counts can never include already-read stories, so read items effectively vanish from the inbox instead of remaining available under All.

Useful? React with 👍 / 👎.

KevinBigham pushed a commit that referenced this pull request May 15, 2026
Goal Packet v2.0 format. Single-mission contract for Codex.

Mission: persist useGameStore active-save metadata to localStorage via
Zustand persist middleware, then auto-load the active save on app boot
before AppLayout's isInitialized guard fires. Hard reload at any
in-game route renders that route instead of redirecting to Save Hub.

Read-first, allowed-write, protected scope, non-negotiables, milestone
loop, validation loop, evaluator-visible proof, pause conditions, done
criteria, and final report all encoded inline.

Built on top of Sprint 3 (PR #76); rebase onto main after that merges.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@KevinBigham KevinBigham merged commit a973b57 into main May 15, 2026
1 check passed
@KevinBigham KevinBigham deleted the goal/sprint-3-news-inbox branch May 15, 2026 01:20
KevinBigham pushed a commit that referenced this pull request May 15, 2026
Goal Packet v2.0 format. Single-mission contract for Codex.

Mission: persist useGameStore active-save metadata to localStorage via
Zustand persist middleware, then auto-load the active save on app boot
before AppLayout's isInitialized guard fires. Hard reload at any
in-game route renders that route instead of redirecting to Save Hub.

Read-first, allowed-write, protected scope, non-negotiables, milestone
loop, validation loop, evaluator-visible proof, pause conditions, done
criteria, and final report all encoded inline.

Built on top of Sprint 3 (PR #76); rebase onto main after that merges.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KevinBigham added a commit that referenced this pull request May 15, 2026
* docs(goal): replace Sprint 3 GOAL.md with Sprint 3.5 mission contract

Goal Packet v2.0 format. Single-mission contract for Codex.

Mission: persist useGameStore active-save metadata to localStorage via
Zustand persist middleware, then auto-load the active save on app boot
before AppLayout's isInitialized guard fires. Hard reload at any
in-game route renders that route instead of redirecting to Save Hub.

Read-first, allowed-write, protected scope, non-negotiables, milestone
loop, validation loop, evaluator-visible proof, pause conditions, done
criteria, and final report all encoded inline.

Built on top of Sprint 3 (PR #76); rebase onto main after that merges.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(app): auto-resume active save on hard reload

Co-Authored-By: Codex GPT-5 <noreply@openai.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Codex GPT-5 <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants