Skip to content

feat: newspaper-style homepage experiment#74

Merged
oddur merged 4 commits intomainfrom
experiment/newspaper-homepage
Apr 16, 2026
Merged

feat: newspaper-style homepage experiment#74
oddur merged 4 commits intomainfrom
experiment/newspaper-homepage

Conversation

@oddur
Copy link
Copy Markdown
Owner

@oddur oddur commented Apr 16, 2026

Summary

  • Redesigns the homepage as a broadsheet newspaper front page with masthead, lead story, dispatches sidebar, and multi-column archives grid
  • Stores AI summary in the review index so each archive article shows a lede excerpt
  • Polished through five design skill passes: /critique, /typeset, /clarify, /onboard, /arrange

What it looks like

Masthead: Centered "GNOSIS" with Oxford double rules, edition number, dateline, and user avatar

Above the fold: Latest unread review as a lead story (with time-gated labels: Breaking → New → Unread) alongside a dispatches sidebar for pending review requests

The Newsroom: Compose form with progressive disclosure for model, instructions, and preferences

The Archives: Three-column grid with column rules. First article spans full width as a featured piece. Article ledes (AI summaries) clamped to 3 lines in grid, shown in full for the featured article.

Design passes applied

  • /critique: Identified 5 priority issues (28/40 heuristics score, 20/25 anti-pattern scan)
  • /typeset: Tightened masthead, clarified headline scale (four clean tiers), bumped lede readability, added hover states
  • /clarify: Dropped forced metaphors (correspondent, classified, Gazette, editions), made error messages actionable, specific delete button labels, toggle descriptions in sans-serif
  • /onboard: Welcome hero now auto-focuses input on dismiss, above-the-fold hidden for first-time users, example URL in placeholder, improved empty states
  • /arrange: Eliminated all inline styles, consolidated spacing into CSS, fixed vertical rhythm, simplified featured article layout, fixed sidebar hover layout shift

Test plan

  • First-time user flow: sign in → welcome hero → dismiss → PR input is focused, above-the-fold is hidden
  • Returning user with unread: lead story shows with correct time-gated label
  • Generate a new review: summary appears as article lede in the archives grid
  • Old reviews: summaries backfilled from full JSON on first load
  • Delete confirmation: "Clear all" and "Clear closed" show confirmation dialogs
  • Responsive: 3-col → 2-col → 1-col grid, sidebar stacks on mobile
  • Dark mode: all newspaper borders, hovers, and rules render correctly

🤖 Generated with Claude Code

oddur and others added 2 commits April 16, 2026 12:19
Redesign the homepage as a broadsheet front page with masthead,
lead story, dispatches sidebar, and multi-column archives grid.

Layout:
- Centered masthead with Oxford double rules and dateline
- Above-the-fold split: lead story (latest unread) + dispatches sidebar
- Newsroom section (compose form) with progressive disclosure
- Three-column archives grid with column rules and featured first article
- Three-tier headline scale: lead (3rem) > featured (1.375rem) > regular (1.125rem)

Data:
- Store AI summary in ReviewHistoryEntry for article ledes
- Backfill existing reviews from full JSON on first load
- Article ledes clamped to 3 lines in grid, full in featured

Polish (from /critique, /typeset, /clarify, /onboard, /arrange):
- Delete confirmation dialogs with specific button labels
- Time-gated lead labels: Breaking (<1h) → New (<24h) → Unread
- Dropped forced metaphors (correspondent, classified, Gazette, editions)
- Kept good ones (Archives, Dispatches, masthead, Oxford rules)
- Welcome hero auto-focuses PR input on dismiss
- Above-the-fold hidden for first-time users (no empty-state noise)
- Input placeholder shows example URL format
- Toggle descriptions in sans-serif (not monospace)
- All inline styles eliminated, spacing consolidated into CSS
- Sidebar hover no longer causes layout shift

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the update-available banner into the newspaper layout as an
"EXTRA" notice between the masthead and content. The generic
UpdateBanner is hidden on the home page to avoid duplication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Redesigns the app’s home screen into a broadsheet/newspaper-style “front page”, and persists AI-generated review summaries into the reviews index so the homepage can show lede excerpts without reloading full review JSON.

Changes:

  • Reworks HomePage layout into masthead + lead story + dispatches sidebar + newsroom composer + multi-column archives grid, and adds delete confirmation dialogs.
  • Adds update availability handling on the homepage via a newspaper-style “Extra” notice; hides the global update banner on the home page.
  • Stores summary on ReviewHistoryEntry, backfills it from per-review JSON on first read, and writes it into the reviews index.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/pages/HomePage.tsx Implements the newspaper layout, update “Extra” notice, archive ledes, and new delete-confirm UX.
src/main.ts Backfills and persists summary into reviews-index.json; stores summary on review completion.
src/globals.css Adds the broadsheet/homepage CSS (masthead rules, columns, archive grid, typography).
src/App.tsx Prevents UpdateBanner from rendering on the home page (homepage uses “Extra” instead).
lib/types.ts Extends ReviewHistoryEntry with optional summary for homepage ledes.

Comment thread src/pages/HomePage.tsx Outdated
Comment on lines +1546 to +1553
onClick={() =>
setExpandedPRs((prev) => {
const next = new Set(prev);
if (next.has(group.prUrl)) next.delete(group.prUrl);
else next.add(group.prUrl);
return next;
})
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The “X reviews/Hide” toggle button doesn’t stop propagation, so it will also trigger the parent <article onClick> and open the latest review when the user is only trying to expand/collapse the list. Pass the click event and call e.stopPropagation() (or remove the parent click handler entirely).

Suggested change
onClick={() =>
setExpandedPRs((prev) => {
const next = new Set(prev);
if (next.has(group.prUrl)) next.delete(group.prUrl);
else next.add(group.prUrl);
return next;
})
}
onClick={(e) => {
e.stopPropagation();
setExpandedPRs((prev) => {
const next = new Set(prev);
if (next.has(group.prUrl)) next.delete(group.prUrl);
else next.add(group.prUrl);
return next;
});
}}

Copilot uses AI. Check for mistakes.
Comment thread src/pages/HomePage.tsx
Comment on lines 722 to 724
return (
<main className="min-h-screen overflow-y-auto px-10 lg:px-12 py-8">
<main className="h-screen overflow-y-auto">
<ShortcutOverlay open={shortcutsOpen} onClose={() => setShortcutsOpen(false)} />
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

<main> is now the scroll container (h-screen overflow-y-auto), but the page still calls window.scrollTo(...) in a few places (e.g., About/replay onboarding). That likely won’t scroll this container, so “scroll to top” can stop working. Consider using a ref to the <main> element and scrolling that element instead.

Copilot uses AI. Check for mistakes.
Comment thread src/pages/HomePage.tsx Outdated
Comment on lines +718 to +719
const editionNumber = Math.floor(
(today.getTime() - new Date('2024-01-01').getTime()) / 86_400_000
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

editionNumber is derived from today.getTime() (time-of-day) minus a date-only epoch, so the edition can tick over at UTC midnight and can be off-by-one relative to the local dateline. If you want “days since epoch” in local time, normalize both dates to local midnight before dividing; if you want UTC, compute both in UTC and ensure the dateline matches.

Suggested change
const editionNumber = Math.floor(
(today.getTime() - new Date('2024-01-01').getTime()) / 86_400_000
const todayAtLocalMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const editionEpoch = new Date(2024, 0, 1);
const editionNumber = Math.floor(
(todayAtLocalMidnight.getTime() - editionEpoch.getTime()) / 86_400_000

Copilot uses AI. Check for mistakes.
Comment thread src/main.ts
Comment on lines 603 to +628
function readReviewsIndex(): ReviewHistoryEntry[] {
try {
const entries = JSON.parse(fs.readFileSync(getReviewsIndexPath(), 'utf-8')) as ReviewHistoryEntry[];
// Backward compat: entries without status are completed
return entries.map((e) => ({ ...e, status: e.status ?? 'completed' }));
let dirty = false;
const result = entries.map((e) => {
const patched = { ...e, status: e.status ?? ('completed' as const) };
// Backfill summary from the full review JSON if missing
if (!patched.summary && patched.status === 'completed') {
try {
const reviewPath = path.join(getReviewsDir(), `${patched.id}.json`);
const review = JSON.parse(fs.readFileSync(reviewPath, 'utf-8')) as ReviewGuide;
if (review.summary) {
patched.summary = review.summary;
dirty = true;
}
} catch {
// Review file missing or unreadable — skip
}
}
return patched;
});
// Persist backfilled summaries so we only do this once
if (dirty) {
fs.writeFileSync(getReviewsIndexPath(), JSON.stringify(result, null, 2));
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

readReviewsIndex() now performs synchronous disk reads for each entry missing summary (and may synchronously write the index) every time it’s called. Since readReviewsIndex() is used in multiple IPC handlers, this can block the Electron main process and make the app feel janky on large histories. Consider doing a one-time migration step at startup (or caching the parsed index in memory) and/or performing backfill asynchronously.

Copilot uses AI. Check for mistakes.
Comment thread src/globals.css Outdated
Comment on lines +1272 to +1276
.newspaper-article *,
.newspaper-article--featured * {
cursor: pointer;
}

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The universal cursor: pointer on .newspaper-article * forces a pointer cursor even for nested controls (including disabled buttons) and makes text-selection cursors impossible inside the article. This can be misleading and hurts accessibility cues. Consider removing the wildcard rule and only applying the pointer cursor to the actual clickable container/CTA elements.

Suggested change
.newspaper-article *,
.newspaper-article--featured * {
cursor: pointer;
}

Copilot uses AI. Check for mistakes.
Comment thread src/pages/HomePage.tsx Outdated
Comment on lines +1430 to +1439
<article
key={group.prUrl}
data-pr-url={group.prUrl}
className={`${idx === 0 ? 'newspaper-article--featured' : 'newspaper-article'} group`}
onClick={() => isClickable && handleLoadFromHistory(latestId)}
>
{/* Headline */}
<button
type="button"
onClick={() => setPrUrl(pr.url)}
className="flex items-baseline gap-3 text-left flex-1 min-w-0"
onClick={() => isClickable && handleLoadFromHistory(latestId)}
className="text-left w-full"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

<article> is clickable and also contains a clickable <button> that calls handleLoadFromHistory. Clicking the headline will trigger both handlers due to event bubbling, which can load the review twice (and double-run side effects like marking unread). Prefer a single click target (e.g., remove the <article onClick> and keep the button, or keep the article but make inner controls call e.stopPropagation()).

Copilot uses AI. Check for mistakes.
oddur and others added 2 commits April 16, 2026 12:31
- Remove headline button to prevent double-click with article onClick
- Add stopPropagation to expand/collapse toggle
- Remove wildcard cursor:pointer on article children (accessibility)
- Use ref-based scrolling instead of window.scrollTo (main is scroll container)
- Normalize edition number to local midnight (timezone fix)
- Move summary backfill to one-time startup instead of every readReviewsIndex call

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The featured (first) article now uses a proper two-column grid:
headline + meta on the left, full AI summary on the right.
Badges and actions span full width below.

Removed line-clamp from all article ledes — show full summaries
like a real newspaper front page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oddur oddur merged commit ceb3eeb into main Apr 16, 2026
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