Skip to content

Commit 4c6a74d

Browse files
peasclaude
andcommitted
Document newsletter pipeline + manual mockups as voice reference
Add a "Newsletter pipeline (F3)" section to CLAUDE.md covering: - newsletter-template.ts and generate-newsletter.ts - tool_use approach for structured output - Anti-fabrication validator - Email best practices baked into the template - Source priority (telegram-editorial vs chat) - Cost (~$0.55/edition with Opus 4.6) - Lessons from edition 1: manual mockups in public/tmp/format-*.html beat Opus on quote curation, rhythm, closing surprise. Hybrid is the realistic path — LLM for plumbing, human for taste. - Prompt iteration ideas for next time Also fixes the site URL (builders.hipsters.tech, not hipsters.builders). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 45d57b4 commit 4c6a74d

1 file changed

Lines changed: 85 additions & 1 deletion

File tree

CLAUDE.md

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Community portal for the **Hipsters Network** (Alura podcasts + community). Publishes episode summaries with quotes, short-form "curtas" (best quotes from podcasts and WhatsApp groups), and a weekly newsletter. All content comes from the Stromae vault at build time — this is a read-only consumer, not a content producer.
66

7-
**Site**: https://hipsters.builders
7+
**Site**: https://builders.hipsters.tech
88

99
## Owner
1010

@@ -376,3 +376,87 @@ The vault is the source of truth. This project never writes to it.
376376
| `caelum/stromae` | Orchestrator that produces the vault content |
377377
| `caelum/stromae-vault-alura` | The vault itself (signals, drafts, voices) |
378378
| `peas/paulo.com.br` | Paulo's blog (separate project, same Astro/Tailwind stack) |
379+
380+
## Newsletter pipeline (F3 — Diálogo split)
381+
382+
Independent flow that lives alongside the stories pipeline. Reads `src/data/stories.json` (already classified + editorialized), picks candidates from a date range, and renders an HTML email in the F3 (Diálogo split) format ready for Resend.
383+
384+
### Files
385+
386+
- **`scripts/newsletter-template.ts`** — pure renderer. Function `renderNewsletterF3(data: NewsletterData): string`. Inline-styled, table-based, mobile-first, ~640px max-width, system fonts, no images. Survives Gmail/Outlook/Apple Mail without modification.
387+
- **`scripts/generate-newsletter.ts`** — LLM-based assembler. Calls `claude-opus-4-6` via **tool_use** to force structured JSON output (avoids fragile JSON-in-text parsing when editorial HTML contains nested quotes). Validates every output quote literally exists in some source `conversation` (anti-fabrication guardrail). Run via `hipsters newsletter`.
388+
389+
### Run
390+
391+
```bash
392+
hipsters newsletter --from 2026-04-01 --to 2026-04-09 --edition 1 --slug newsletter-edicao-01
393+
hipsters newsletter --from 2026-04-01 --print-prompt # see the system+user prompts (no API call)
394+
hipsters newsletter --from 2026-04-01 --print-data # see the LLM JSON output (no HTML write)
395+
hipsters newsletter --from 2026-04-01 --dry-run # full LLM call but skip the file write
396+
```
397+
398+
Outputs to `public/tmp/<slug>.html` by default. After deploy, lives at `https://builders.hipsters.tech/tmp/<slug>.html`.
399+
400+
### What's in the F3 template
401+
402+
- **Pre-header** (hidden inbox preview)
403+
- **Top utility bar**: "Recebeu de um amigo? Inscreva-se" + "Ver no navegador"
404+
- **Cold open** (literal quote with attribution, no preamble)
405+
- **Intro paragraph** (default explains the format without naming WhatsApp/Telegram by name)
406+
- **3-5 editorial blocks**, each with:
407+
- Header that carries voice (not a label)
408+
- Editorial paragraphs (HTML, with `<em>` on literal quotes and `<a>` on links)
409+
- Optional "No grupo" callout with literal community messages (handles + quote text in italic)
410+
- **Closing dark block** (the blue-bg highlight). Originally "A mensagem que ninguém respondeu" — now generic so the editor can use it for a closing curta, a forgotten note, a small but curious item. Title and eyebrow are configurable per edition.
411+
- **Sign-off** with footnote
412+
- **Email footer**: unsubscribe + preferences + view in browser + reply, permission reminder, sender address (CAN-SPAM / LGPD)
413+
414+
### Anti-fabrication rules baked into the prompt
415+
416+
1. NEVER invent quotes, authors, dates, links, numbers, or facts not in the input
417+
2. Every quote in the output must appear EXACTLY in some `conversation[].text` (validator enforces this post-call and warns)
418+
3. URLs only from `story.links[].url` or literal URLs in conversation text — never invent
419+
4. Don't normalize capitalization, fix typos, or "polish" quotes
420+
5. Skip stories that are confused or short on material
421+
6. Don't name WhatsApp/Telegram groups by name — use "no canal", "alguém anotou", "Paulo escreveu na semana passada"
422+
423+
### Source priority (telegram-editorial vs chat)
424+
425+
Stories whose `id` starts with `story-tg-` or whose `sourceGroups` matches `/Telegram/i` are tagged `source_kind: "telegram-editorial"` — these are the long editorial texts Paulo and Vinny write on the Telegram broadcast channel and they should anchor the bigger blocks. Chat stories (WhatsApp) provide reaction quotes for the "No grupo" callouts, or anchor smaller blocks when the discussion was rich.
426+
427+
The script sorts candidates with `telegram-editorial` first, then by weight desc, before sending to Opus.
428+
429+
### Cost
430+
431+
~$0.55 per edition with `claude-opus-4-6` (16k input tokens, 4k output tokens). Pricing assumed: $15/M input, $75/M output. The `--print-prompt` and `--dry-run` flags let you iterate without spending.
432+
433+
### Lessons from edition 1 (2026-04-09)
434+
435+
**The manual mockups still beat the LLM on quote curation.** The 3 manual F1/F2/F3 mockups in `public/tmp/format-{1,2,3}-*.html` were written by hand, and Paulo preferred their selection of quotes and phrasing over the Opus-generated edition 1. Reasons to internalize:
436+
437+
- **Manual picks the punchier quotes.** Heuristics like "shortest message between 25-200 chars" miss the punch line. Manual went for "todo mundo com github verdinho mas comitando onde nao precisa", "Estão sangrando MESMO", "balela demais né". Opus picked safer, more obvious quotes.
438+
- **Opus over-indexes on meta auto-reference.** Edition 1 had a full block on "essa newsletter foi feita pelo sistema do Karpathy" — too much. Cap meta references at 1 brief mention, not a whole block.
439+
- **Manual mockups have rhythm variation between blocks** (one long + one short + one editorial + one closing). Opus tends to make every block the same length and density.
440+
- **Opus headers are short and punchy** (good) but sometimes too distant from the content (ex: "Quem paga a conta é o ecossistema" works, but loses the specificity that "todo mundo com github verdinho" had as a header in the manual version).
441+
- **Opus closing pick was OK but not surprising.** "1bi commits" was the obvious closing. Manual would have picked "Mark Zuckerberg voltou a programar" — more surprising, more "ah, e mais uma".
442+
- **Manual has explicit thesis per block.** Opus has implicit thesis. Make the prompt require an explicit one-sentence thesis per block before the LLM writes the editorial.
443+
444+
**Iteration ideas for the prompt** (for next time, not a TODO yet):
445+
- Tell Opus to prefer quotes with slang, irony, or cynical observation over "safe" technical statements
446+
- Require an explicit one-line thesis per block as scratch reasoning before writing
447+
- Cap auto-meta references to 1 brief mention max
448+
- Explicitly ask for rhythm variation: not all blocks the same length
449+
- Allow human override per block via `--block-1-id`, `--block-2-id`, etc., so the editor can lock the key picks before Opus writes
450+
451+
**Hybrid is the realistic path**: LLM does plumbing (template, structure, validation, links, footer), human does the curation pass on `--print-data` JSON before rendering. Or: LLM proposes 3 versions with different selections, human picks one.
452+
453+
### Files written this session (2026-04-09 → 2026-04-10)
454+
455+
- `scripts/hipsters.ts` — unified CLI (subcommands: sync, signals, editorialize, classify, stories, newsletter, build, status)
456+
- `scripts/newsletter-template.ts` — F3 renderer
457+
- `scripts/generate-newsletter.ts` — Opus generator with anti-fabrication validator
458+
- `public/tmp/format-{1,2,3}-*.html` — 3 manual mockups (kept as canonical reference for tone)
459+
- `public/tmp/newsletter-edicao-01.html` — first Opus-generated edition
460+
- `public/tmp/index.html` — preview index
461+
462+
The 3 manual mockups in `public/tmp/format-*.html` are the canonical voice reference. Use them when calibrating future prompt versions.

0 commit comments

Comments
 (0)