Skip to content

feat(content): in-repo AI article drafting pipeline + dev-only /draft preview#176

Merged
iamjohnnymac merged 18 commits into
mainfrom
claude/condescending-herschel-a599bf
Jun 12, 2026
Merged

feat(content): in-repo AI article drafting pipeline + dev-only /draft preview#176
iamjohnnymac merged 18 commits into
mainfrom
claude/condescending-herschel-a599bf

Conversation

@iamjohnnymac

Copy link
Copy Markdown
Owner

What

An in-repo AI content pipeline + a dev-only preview route — draft SEO articles grounded in our own Supabase data, illustrate with Pexels stock, and preview in the real styling without publishing.

scripts/draft-article.mjs (new)

Supabase pub data → data-grounded brief (optional --keywords) → Claude draft (claude-opus-4-8, structured JSON, cached style exemplars) → humanizer pass → Pexels photos → writes drafts/<slug>.draft.{ts,md,json}.

  • BYOK: reads ANTHROPIC_API_KEY + PEXELS_API_KEY from env (or infisical run); Supabase from .env.local.
  • The .ts is a paste-ready Article object for src/lib/articles.ts; the .json feeds the preview route; photos download to public/_drafts/<slug>/ with attribution captured.
  • Flags: --dry-run (data/brief only, no LLM), --no-photos, --suburb, --liveModule, CLAUDE_MODEL override.

Dev-only /draft preview

  • Extracted the article body into src/components/ArticleView.tsx; live /articles/[slug] rewired to it (metadata + JSON-LD + canonical retained; helpers + body byte-identical — Opus-reviewed, no regression).
  • New src/app/draft/[slug] + src/app/draft render a draft in the real site styling404 in production, noindex, with a "DRAFT PREVIEW · not published" banner; slug path-traversal guarded.
  • drafts/ + public/_drafts/ gitignored; drafts excluded from tsconfig.

Verification

  • tsc --noEmit clean · 278 unit tests · ESLint clean · Opus code review LGTM (applied: query-string guard on the photo resizer, shape-guard on the draft loader).
  • Live-validated end-to-end (real Claude draft + 5 Pexels photos) on "Cheapest pints in Fremantle" — 779 words, grounded in the 12 verified Fremantle pubs, house voice intact.

Notes

  • Preview the /draft route from the main repo (full node_modules): infisical run -- node scripts/draft-article.mjs --topic "…" --suburb "…", then npm run devlocalhost:3001/draft/<slug>. (Git worktrees lack node_modules, so a worktree dev server can't resolve React/Next.)
  • Keys live in self-hosted Infisical; none committed.

🤖 Generated with Claude Code

… preview

scripts/draft-article.mjs: Supabase data -> data-grounded brief -> Claude draft
(claude-opus-4-8, structured JSON) -> humanizer pass -> Pexels photos -> writes
drafts/<slug>.draft.{ts,md,json}. BYOK (Anthropic + Pexels via env/Infisical),
grounded in proprietary pub data, ends in the human-edit/humanize step.

Preview: extracted the article body into src/components/ArticleView.tsx (live
/articles/[slug] rewired to it — SEO/JSON-LD retained, body byte-identical) and
added dev-only /draft/[slug] + /draft routes that render a draft in the real
styling: 404 in production, noindex, "DRAFT PREVIEW" banner. drafts/ +
public/_drafts/ gitignored; tsconfig excludes drafts.

Live-validated end-to-end (real Claude draft + 5 Pexels photos) on a Fremantle
cheap-pints article. tsc clean, 278 tests, lint clean, Opus review LGTM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
perthpintprices Ready Ready Preview, Comment Jun 12, 2026 3:17am

Request Review

The brief guarded hard against corny-loud (exclamation marks, stacked
slang) but not precious-quiet: aphorisms, grand openers, headings that
name the virtue instead of the content, stacked 'X, not Y' pivots.
Adds a 'Polished tells' list, a drifted example from the World Cup
draft, and widens voice-test #7 to catch both failure modes.

Driven by founder feedback on the first pipeline draft ('too nice').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
iamjohnnymac and others added 2 commits June 10, 2026 10:21
…a, stat callouts, World Cup live module

ArticleView upgrades (all articles): amber mono section numbers, first
paragraph of the first section styled as a lead, optional per-section
stat callout (ArticleSectionStat, additive). New 'worldCupFixtures'
liveModule renders the three Socceroos fixtures with live countdowns
and trading windows, linking to /world-cup.

Driven by founder feedback on the World Cup draft ('it looks boring').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
iamjohnnymac and others added 14 commits June 10, 2026 10:29
HomeWorldCup sits between the homepage header and hero for the
tournament window only (self-removes after the 19 July final). Three
Socceroos cards with kickoff in Perth time, live countdown, and a
TeamStripes flag-colour band (home left, away right) — horizontal
scroll on mobile, 3-up grid on desktop. Same stripes applied to the
/world-cup Socceroos cards. TEAM_COLOURS covers Group D with a neutral
fallback for future knockout opponents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#178)

- Move the article rail below the pub list on the homepage and /discover
- Server-render pub data on all guide/insight pages + /discover (no more spinner; ~850KB HTML, under the 2MB crawl limit) with fetch-failure empty states
- Beer Weather guide gets a visible H1 + intro; duplicate sr-only H1s removed from all 8 shell pages
- gray-mid darkened #8A8A85 -> #6E6E69 for WCAG AA body-text contrast
- /happy-hour area jump nav; PriceHistory skeleton removed (layout shift); Articles linked from homepage desktop nav
- Replace stale arvo/PintDex brand assets (og-image, logo, favicon, PWA icons) with current branding via a checked-in generator script

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
At 375px the strip header crammed both labels side-by-side (each
wrapping to two lines) and the fixture rail chopped the next card at
the container edge. The header now stacks on mobile, and the rail
scrolls full-bleed with snap points and a hidden scrollbar, matching
the /discover picks rail.

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
#179)

- Slack instant ping on every new price report + daily pending-queue
  reminder on the price-check cron (SLACK_WEBHOOK_URL env var, no-ops
  safely until set)
- Pint receipt card: happy hour schedule shown instead of contradictory
  TBC, stale check dates flagged amber, provenance jargon humanized,
  duplicate standard-pint row removed, micro-type raised to legible
  sizes, self-branding banner line dropped

https://claude.ai/code/session_01HRdu5eYTJoUZ5sPJqVUzkk
…ture rows (#181)

- "Confirmed early opens" card softened from a solid bg-ink block to amber-pale with ink text and the standard white pill button
- TEAM_COLOURS extended from Group D's 4 teams to all 48 qualified teams; fixture rows show stacked-stripe flag chips per team
- Fixture rows moved from ragged flex-wrap to a grid: fixed time column, flags beside teams, status chips right-aligned on desktop / indented on mobile
- Trading-window chips get crisper borders and stronger tints

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
…verflow (#182)

- Active/live chips go solid amber with white text; passive badges keep the pale tint with ink text; World Cup permit chip becomes amber-on-white — no amber-text-on-amber-tint pairings remain
- Global guard: rounded-pill elements never wrap; world-cup early-open button label shortened to fit one line
- Fixed pre-existing 14px horizontal overflow on /happy-hour at 320px (min-w-0 on the best-of grid cards so truncate can clip)

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
The banner was a solid bg-ink pill with amber-light text — the last
black-with-orange element on the homepage — and used a bare shadow-hard
the design system bans. Now a white pill with ink text, the green live
pulse kept, amber reserved for the price, shadow-hard-sm.

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
…eclutter + dedupe SQL (#184)

- Pub list: CHEAPEST chip (sized as a tag) replaces the amber left-bar + star; marks the genuinely cheapest priced pub in view, not row one
- Header: desktop nav cutover sm: -> md: across HomeClient/SubPageNav/MobileNav, brand wordmarks nowrap — single-line header at 375-1280px
- World Cup: kickoff labels 12am/12pm instead of midnight/midday
- /happy-hour: decluttered top — one live signal, one-line AWST stamp in the live card, quiet text jump-nav above the area sections
- scripts/dedupe-google-import-2026-06-10.sql: merges 8 duplicate pubs from the April Google import (run in Supabase SQL editor)

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
…#185)

- scripts/dedupe-google-import-2026-06-10.sql: the corrected merge script as executed against prod (frees unique place_ids first)
- docs/prototypes/pint-signal.html: interactive answer-page prototype
- docs/pint-signal-plan.md: phased build plan (MVP -> crews/push -> price-report loop)

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
…burns out (#186)

Phase 1 of the Pint Signal feature (docs/pint-signal-plan.md): one mate
lights the signal (pub + time), the crew answers a shared link with one
tap, the signal burns out 3h after the meet time.

- /signal/new: lighting flow with price/happy-hour-aware pub picker
- /signal/[id]: the answer page (dark beacon card, draining-glass timer,
  IN/OUT crew list), force-dynamic, noindexed, 30s polling
- POST /api/signal (creates + auto-INs the lighter), GET/POST
  /api/signal/[id] + /answer, ip_hash rate limiting per the
  price-report patterns
- scripts/pint-signal-schema.sql (executed against prod 11 June:
  tables live, RLS read-only, verified end-to-end on the preview)
- src/lib/signalId + src/lib/signals helpers with unit tests

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
…lete (#187)

force-dynamic does not opt supabase-js fetches out of Next's data
cache, so the 30s poll served a stale answers list and wiped the
optimistic row (the answer was always in the DB). fetchCache =
'force-no-store' on the signal page and GET route, cache: 'no-store'
on the client poll.

The daily price-check cron now sweeps signals expired more than 7 days
— dead share links show the burned-out state for a week, then the rows
delete themselves. noindex + robots disallow were already in place.

https://claude.ai/code/session_01FCyEdyeKbWCHzQP5nyfNex
…s, kill happy-hour poll (#188)

- Shared unstable_cache pubs pull (1h TTL, tag 'pubs'), raw rows cached,
  toPub() mapped fresh per render; ~20 routes + suburb/pub aggregates
  switched over, /signal/new included
- select('*') eliminated from all pubs queries; google_opening_hours
  only travels to pub-detail and happy-hour pages
- HappyHourClient 60s full-table browser poll removed (~122MB/hr/tab)
- revalidateTag('pubs') on admin approvals
- Measured: build 167.2MB -> 1.7MB; warm cache 6 pages = 1 request

https://claude.ai/code/session_01HRdu5eYTJoUZ5sPJqVUzkk
@iamjohnnymac iamjohnnymac merged commit fe4eac3 into main Jun 12, 2026
2 of 3 checks passed
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