From 64bad7091b70780ad2277a26872a45122b595511 Mon Sep 17 00:00:00 2001 From: rzen Date: Tue, 14 Jul 2026 19:46:35 -0400 Subject: [PATCH] pipeline: EDITORIAL.md voice charter + fresh-eyes copy-edit stage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One written standard for every audience-facing word: concise, to the point, inspiring delight through specificity — with word budgets per copy type (reviews had drifted from a ~370-word median to 500-1000+). All writing skills now reference the charter, and daily-pipeline gains a stage-4 copy-edit pass: a subagent that didn't write the day's copy tightens it against the charter, prose only, diff reviewed before commit. Digest gains a copy-edit line. Claude-Session: https://claude.ai/code/session_01WZaczDJjL3xZ3u5spsN5AL --- .claude/skills/build-bundle/SKILL.md | 2 +- .claude/skills/build-review/SKILL.md | 2 +- .claude/skills/cluster-bundles/SKILL.md | 2 +- .claude/skills/daily-finds/SKILL.md | 2 +- .claude/skills/daily-pipeline/SKILL.md | 19 +++++++++++--- CLAUDE.md | 1 + EDITORIAL.md | 33 +++++++++++++++++++++++++ 7 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 EDITORIAL.md diff --git a/.claude/skills/build-bundle/SKILL.md b/.claude/skills/build-bundle/SKILL.md index f56c499..eb2bf89 100644 --- a/.claude/skills/build-bundle/SKILL.md +++ b/.claude/skills/build-bundle/SKILL.md @@ -104,7 +104,7 @@ items: --- ``` -Body: a brief audience-facing intro paragraph (1–2 sentences max) — open with "Today's catch" or a close variant; name what links these finds; vary the wording day to day. **No** numbered candidates, no headings inside the body, no list items — the grid renders from `items[]`. +Body: a brief audience-facing intro paragraph (1–2 sentences max, per `EDITORIAL.md`) — open with "Today's catch" or a close variant; name what links these finds; vary the wording day to day. **No** numbered candidates, no headings inside the body, no list items — the grid renders from `items[]`. Example body: diff --git a/.claude/skills/build-review/SKILL.md b/.claude/skills/build-review/SKILL.md index 5b9b074..048ba9d 100644 --- a/.claude/skills/build-review/SKILL.md +++ b/.claude/skills/build-review/SKILL.md @@ -63,7 +63,7 @@ Take the find's 2-3 sentence body and expand it to a full review: - 3-5 paragraphs covering: what it is, the specific problem it solves, the concrete details that make it earn its place, and any honest limitations (surfaced by step 3). - Reference the hero asset inline where it helps (relative `./hero.`; use `astro:assets` `` for JPG sources — plain Markdown images have rendered small on JPGs). - Don't pad. If the find body is already complete, the review is just it + a blockquote summary + maybe one paragraph on context. -- Match the voice of existing reviews under `src/content/reviews/` — informative, concise, no sales tone. +- Write to `EDITORIAL.md` (the voice charter): concise, to the point, delight through specificity. Budget: **250–450 words, hard ceiling 600** — narrative subjects (travel, history, story-led pieces) may earn up to ~800 when the story itself is the delight. Budgets are ceilings, not targets. Interactive mode: show the proposed body and wait. Autonomous mode: proceed. diff --git a/.claude/skills/cluster-bundles/SKILL.md b/.claude/skills/cluster-bundles/SKILL.md index b0f1c21..a01d379 100644 --- a/.claude/skills/cluster-bundles/SKILL.md +++ b/.claude/skills/cluster-bundles/SKILL.md @@ -89,7 +89,7 @@ items: Body rules (same as `build-bundle`): -- 1-2 sentence intro paragraph in a blockquote. No headings, no list items, no numbered candidates — the grid handles the items. +- 1-2 sentence intro paragraph in a blockquote, per `EDITORIAL.md`. No headings, no list items, no numbered candidates — the grid handles the items. - Audience-facing language. Never write "today's pick", "candidates", "winners". - Vary the opening across the batch. With 4-7 bundles going out the same day, repeating "Today's catch" five times feels mechanical. diff --git a/.claude/skills/daily-finds/SKILL.md b/.claude/skills/daily-finds/SKILL.md index 25c35a0..01fd91b 100644 --- a/.claude/skills/daily-finds/SKILL.md +++ b/.claude/skills/daily-finds/SKILL.md @@ -60,7 +60,7 @@ For every survivor, write one file at `src/content/find////in - Slug: kebab-case form of the name (lowercase, ASCII, no leading numbers). - Frontmatter: `name`, `subtitle` (optional, one-liner), `date` (today), `link` (external URL — required), `linkText` (optional), `source` (the source name from sources.json — required, exact match), `topics` (1-3 values chosen from the controlled vocabulary in `src/lib/topics.ts` — the `TOPIC_META` keys; judge from the item itself, the source's `topics` in sources.json are hints only and may contain retired values; the build fails on anything outside the vocabulary; `[]` if nothing genuinely fits), `tags` (free editorial tags inferred from the item — keep to 2-4; add `giftable` when the item would make a genuinely good gift), `description` (optional meta override). -- Body: 2-3 sentence note. Concrete, name a *specific* virtue. Bad: "great design". Good: "the silicone bumper means it survives drops onto concrete; the lanyard hole is positioned where your thumb naturally rests". +- Body: 2-3 sentence note (40–90 words), written to `EDITORIAL.md` — concise, and delightful through specificity: name a *specific* virtue. Bad: "great design". Good: "the silicone bumper means it survives drops onto concrete; the lanyard hole is positioned where your thumb naturally rests". - No daily cap — write every survivor. If `link` for a Reddit-discovered item points at a v.redd.it / i.redd.it URL, fetch the post's selftext or comments to find the actual product URL before writing the file. diff --git a/.claude/skills/daily-pipeline/SKILL.md b/.claude/skills/daily-pipeline/SKILL.md index 5591520..5f3ba84 100644 --- a/.claude/skills/daily-pipeline/SKILL.md +++ b/.claude/skills/daily-pipeline/SKILL.md @@ -1,6 +1,6 @@ --- name: daily-pipeline -description: The full unattended daily run for unique.rzen.dev — capture (daily-finds) → bundle (build-bundle auto) → graduate reviews (build-review auto) → set the pick of the day → build, commit, and push — ending with a digest. Use when the user runs /daily-pipeline, asks to "run the pipeline", "do the daily run", or when a scheduled/headless session needs the whole chain. +description: The full unattended daily run for unique.rzen.dev — capture (daily-finds) → bundle (build-bundle auto) → graduate reviews (build-review auto) → copy-edit pass (EDITORIAL.md) → set the pick of the day → build, commit, and push — ending with a digest. Use when the user runs /daily-pipeline, asks to "run the pipeline", "do the daily run", or when a scheduled/headless session needs the whole chain. disable-model-invocation: true --- @@ -41,7 +41,17 @@ Stage: the new find folders, `daily-finds.log.md`, `candidate-sources.md`, and ` **Commit per review or combined**: `site: review — (graduates from find)` / `site: reviews — , (graduate from finds)`. -### 4. Pick of the day +### 4. Copy edit — fresh-eyes pass over the day's new prose + +Enforce `EDITORIAL.md` (the voice charter: concise, to the point, inspiring delight) on everything stages 1–3 wrote. The point is fresh eyes — the author of a piece is the worst person to cut it — so run this as a **subagent** (sonnet) that did not write the copy: + +- Scope: find bodies, bundle blurb/intro, review bodies, subtitles and descriptions created or modified this run. Nothing older. +- The subagent gets `EDITORIAL.md` plus the file list, and edits prose only — never facts, names, prices, links, claims, or structural frontmatter (wording of `subtitle`/`description`/`blurb` is fair game). Word budgets in the charter are the hard lines. +- Review the diff in the main session before committing; revert any edit that changed meaning or lost the delight it was supposed to sharpen. + +**Commit** (only when something changed): `site: copy edit — pieces tightened`. + +### 5. Pick of the day Make an explicit pick decision **every run** — the pick is a daily editorial call, not a side effect of publishing reviews: @@ -56,13 +66,13 @@ Write `src/data/pick.json` → `{"hero": ""}`. **Commit** (only when the pick changed): `site: pick of the day — `. -### 5. Verify + push +### 6. Verify + push - `npm run build` must pass (it will also have been run by the sub-skills; this is the final gate). If it fails, fix what the pipeline broke; never push a red build. - `git push origin main`. If the push fails (auth, network), leave the commits local and flag it in the digest. - Deployment is automatic downstream: in scheduled runs, a lab CI job (`publish-unique-daily`) redeploys unique.rzen.dev from `main` after this pipeline succeeds — nothing to do here, but it means whatever is on `origin/main` goes live. -### 6. Digest +### 7. Digest End with a compact, human-readable digest — this is the only thing the user should *need* to read: @@ -72,6 +82,7 @@ Daily pipeline — YYYY-MM-DD Captured: finds ( productive sources), shortlisted Bundle: "" (<n> items) [+ week's catch / cluster run if run] Reviews: <Name> (<slug>), <Name> (<slug>) — or none +Copy edit: <N> pieces tightened — or clean Pick: <Name> — or held: <current> (<one-line why>) Amazon: <n> links added Sources: <n> maintenance changes; <n> new nominees awaiting vetting diff --git a/CLAUDE.md b/CLAUDE.md index 959abbc..8509616 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,7 @@ Unattended runs never pause for confirmation — they publish, log to `daily-fin ## Conventions that bite +- **`EDITORIAL.md` is the voice charter** — concise, to the point, inspiring delight (through specificity, never ad copy); word budgets per copy type. All audience-facing prose follows it; the pipeline runs a fresh-eyes copy-edit stage over each day's new copy. - **No tobacco or alcohol content** — hard editorial exclusion at every stage: never captured as finds, never selected into new bundles, never graduated to reviews. A few legacy alcohol finds (May–July 2026) stay published and keep their existing bundle spots, but they're ineligible for anything new. - Review heroes are auto-discovered from `src/content/reviews/<slug>/hero.<ext>` (`src/lib/hero.ts` glob) — name the file `hero.*`; no registration. - The homepage "pick of the day" is `src/data/pick.json` (`{"hero": "<review-slug>"}`); falls back to the latest review. diff --git a/EDITORIAL.md b/EDITORIAL.md new file mode 100644 index 0000000..f469fc0 --- /dev/null +++ b/EDITORIAL.md @@ -0,0 +1,33 @@ +# Editorial voice charter + +The standard for every audience-facing word on unique.rzen.dev — find bodies, review bodies, bundle blurbs and intros, subtitles, descriptions. Skills reference this file; the pipeline's copy-edit pass enforces it. + +## The voice in one line + +Concise, to the point, and above all **inspiring delight**. + +## Principles + +1. **Delight is specificity.** Name the exact detail that makes someone light up — the silicone bumper that survives concrete, the hinge that closes with a click you can feel. Generic praise ("great design", "beautifully made") is dead copy; if the sentence could describe a hundred products, cut it. +2. **Lead with the virtue, not the category.** The reader learns *why it's wonderful* before *what shelf it sits on*. +3. **Every sentence earns its place.** No throat-clearing openings ("In a world where…", "We've all been there"), no scene-setting preamble, no restating in the close what the lead already said. If a sentence can go without losing a fact or a smile, it goes. +4. **No ad copy.** Never "game-changer", "must-have", "stunning", exclamation points, or any superlative a competitor's brochure could also print. Enthusiasm comes from detail, not volume. +5. **Honest beats glossy.** Limitations stated plainly; corrections from fact-checking called out when they're interesting. Trust is the site's only currency. +6. **The forward test.** Before publishing, ask: would a friend forward this to another friend with "look at this"? If not, find the detail that would make them — or cut deeper. + +## Word budgets + +| Copy | Budget | +|---|---| +| Find body | 40–90 words (2–3 sentences) | +| Review body | 250–450 words; hard ceiling 600 | +| Review — narrative subjects (travel, history, story-led) | up to ~800 when the story itself is the delight — it must earn every paragraph | +| Bundle blurb | one sentence | +| Bundle intro | 1–2 sentences | +| Subtitle / description | one line, no trailing period needed | + +Budgets are ceilings, not targets — a 250-word review that says everything beats a 450-word one that pads. + +## For the copy editor + +The copy-edit pass (daily-pipeline stage 4) edits **prose only**: tighten, sharpen, de-pad, enforce the budgets. Never change facts, names, prices, links, claims, or frontmatter beyond wording of `subtitle`/`description`/`blurb`. Never add a claim the author didn't make. When a piece is over budget, cut the weakest material — don't compress every sentence into mush. When in doubt between two cuts, keep the one that delights.