feat(content): in-repo AI article drafting pipeline + dev-only /draft preview#176
Merged
Merged
Conversation
… 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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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>
# Conflicts: # docs/PROJECT-STATUS.md
…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>
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 → writesdrafts/<slug>.draft.{ts,md,json}.ANTHROPIC_API_KEY+PEXELS_API_KEYfrom env (orinfisical run); Supabase from.env.local..tsis a paste-readyArticleobject forsrc/lib/articles.ts; the.jsonfeeds the preview route; photos download topublic/_drafts/<slug>/with attribution captured.--dry-run(data/brief only, no LLM),--no-photos,--suburb,--liveModule,CLAUDE_MODELoverride.Dev-only
/draftpreviewsrc/components/ArticleView.tsx; live/articles/[slug]rewired to it (metadata + JSON-LD + canonical retained; helpers + body byte-identical — Opus-reviewed, no regression).src/app/draft/[slug]+src/app/draftrender a draft in the real site styling — 404 in production,noindex, with a "DRAFT PREVIEW · not published" banner; slug path-traversal guarded.drafts/+public/_drafts/gitignored;draftsexcluded fromtsconfig.Verification
tsc --noEmitclean · 278 unit tests · ESLint clean · Opus code review LGTM (applied: query-string guard on the photo resizer, shape-guard on the draft loader).Notes
/draftroute from the main repo (fullnode_modules):infisical run -- node scripts/draft-article.mjs --topic "…" --suburb "…", thennpm run dev→localhost:3001/draft/<slug>. (Git worktrees lacknode_modules, so a worktree dev server can't resolve React/Next.)🤖 Generated with Claude Code