pipeline: hero floor — bundles require at least 4 items with hero images

Bundles now ship only when at least four of their items have a hero.*
image, purely so the grid never renders image-thin. Clusters that miss
the floor are held back and logged rather than padded or written thin;
the weekly catch widens its window to older unbundled hero-bearing
finds instead of holding. Capture gets a nudge to try a second image
source before giving up on a hero.

Claude-Session: https://claude.ai/code/session_01WZaczDJjL3xZ3u5spsN5AL
This commit is contained in:
2026-07-14 08:08:57 -04:00
parent 7e7b5afa1f
commit f0f1d8fb61
4 changed files with 23 additions and 8 deletions
+12 -4
View File
@@ -14,7 +14,11 @@ Invoke from inside the `unique.rzen.dev` repo.
- **Interactive (default when a user is present)**: propose the cluster, wait for sign-off (step 2), then write.
- **Autonomous (`auto` argument, or when running unattended — e.g. invoked by `/daily-pipeline`, a scheduled run, or any headless session)**: skip the confirmation gate. Apply the same quality bar, write directly, and record the would-have-been proposal in the log so the user can veto after the fact. Prefer the *tighter* theme when torn — an unattended run should err conservative.
- **Weekly catch (`weekly` argument)**: a date-based safety valve, not a themed bundle. Collect every `shortlist: true` find from the last 7 days that appears in **no** existing bundle's `items[]`; if fewer than 3, widen to all unbundled finds from the window. Title: "The week's catch — <Month DD>". Blurb names the week, not a theme. Cap at 8 by shortlist rank. If nothing qualifies, do nothing and report that. Purpose: no shortlisted find stays invisible for weeks awaiting a theme (finds are only reachable through bundles).
- **Weekly catch (`weekly` argument)**: a date-based safety valve, not a themed bundle. Collect every `shortlist: true` find from the last 7 days that appears in **no** existing bundle's `items[]`; if fewer than 3, widen to all unbundled finds from the window. Title: "The week's catch — <Month DD>". Blurb names the week, not a theme. Cap at 8 by shortlist rank. The hero floor (see below) applies here too, but widen rather than hold: if the window yields fewer than 4 hero-bearing items, pull in older unbundled hero-bearing finds until the floor is met — the weekly exists so finds don't stay invisible, so holding it defeats the point. Only if the *entire* unbundled pool can't muster 4 heroes, skip and report. If nothing qualifies, do nothing and report that. Purpose: no shortlisted find stays invisible for weeks awaiting a theme (finds are only reachable through bundles).
## Hero floor (applies to every bundle)
A bundle must contain **at least 4 items that have a hero image** (`hero.<ext>` next to the find's `index.mdx` — check with `ls src/content/find/<y>/<m>/<slug>/hero.*`). This is purely aesthetic: the grid looks broken with fewer than four image cards. Hero-less finds may still ride along beyond the four. If a proposed cluster can't meet the floor, **hold the bundle back** — don't pad with weak fits and don't write it anyway; the theme waits until a fourth hero-bearing find emerges. In autonomous mode, log the held-back cluster (theme + items + which lack heroes) so the next run can revisit it.
## Inputs
@@ -33,11 +37,12 @@ Invoke from inside the `unique.rzen.dev` repo.
3. Compute the **unbundled pool**: every find that does NOT appear in the `items[]` array of any existing `src/content/bundles/*.mdx` bundle. The seed slugs may or may not be in this pool — they're the seed regardless.
4. From the unbundled pool, find every entry that fits the inferred theme. Use frontmatter signals (tags, topics, subtitle) and body content. Don't reach: three weakly-fitting items is worse than zero. If a candidate's fit feels strained, drop it.
5. Combine `seed + qualifying unbundled matches`, deduped. Cap at **8 total** (per sizing notes below). If the combined list exceeds 8, drop the weakest fits — never drop a seed slug; if the seed itself is more than 8, ask the user which to drop.
6. Produce one cluster (theme phrase + final items list). Continue to step 2 to confirm before writing.
6. Check the hero floor: if fewer than 4 items in the final list have a `hero.*` file, prefer swapping in hero-bearing theme matches; if the floor still can't be met, report that the bundle is held back (interactive: tell the user and let them decide; auto: hold and log).
7. Produce one cluster (theme phrase + final items list). Continue to step 2 to confirm before writing.
- **If the user provided a theme phrase only:** scan all finds, surface the 812 that best fit the theme.
- **If neither:** scan recent (last ~14 days) finds that are not yet referenced by any existing bundle, weighting `shortlist: true` finds first. From the unbundled pool, propose 13 thematic clusters of 510 finds each, with a candidate theme phrase per cluster. (In autonomous mode: pick the single strongest cluster and write it.)
- **If neither:** scan recent (last ~14 days) finds that are not yet referenced by any existing bundle, weighting `shortlist: true` finds first. From the unbundled pool, propose 13 thematic clusters of 510 finds each, with a candidate theme phrase per cluster. Only propose clusters that meet the hero floor. (In autonomous mode: pick the single strongest cluster that meets the floor and write it; if none does, skip the bundle this run and log why.)
Re-use is allowed — a find can appear in multiple bundles under different themes. But by default, prefer surfacing **unbundled** finds first; only reach for re-uses when the theme genuinely calls for them.
@@ -73,6 +78,8 @@ Cluster B — "<theme phrase>"
...
```
In either format, annotate items that lack a hero image with `(no hero)` so the floor is visible at a glance.
Wait for the user to confirm, refine the theme phrase, or drop/swap items. Don't write anything until confirmed.
### 3. Write the bundle
@@ -106,6 +113,7 @@ Example body:
### 4. Verify
- Confirm every slug in `items[]` exists at `src/content/find/<year>/<month>/<slug>/index.mdx` (use `find src/content/find -type d -name "<slug>"` to locate). If any is missing, abort and report — never reference a non-existent find.
- Confirm the hero floor: at least 4 of the `items[]` folders contain a `hero.*` file.
- Run `npm run build` to confirm the new bundle compiles.
### 5. Update the log
@@ -124,7 +132,7 @@ Items referenced:
## Notes
- Default to 5 finds per bundle. Use 3 for tighter themes, up to 8 if the theme really supports it. Beyond 8 the grid stops being scannable.
- Default to 5 finds per bundle, up to 8 if the theme really supports it. Beyond 8 the grid stops being scannable. The effective minimum is 4 (the hero floor) — a tight 3-item theme waits until a fourth hero-bearing fit emerges.
- The body is for the audience, not the user. Never write "pick one", "candidates", "today's pick" — those are internal terms.
- Never edit existing find files when building a bundle. The find body and metadata are stable; the bundle only references them.
- If a find that's referenced has been graduated to a review (its frontmatter has `promotedTo`), it can still appear in the bundle — the grid card simply links to the find page, which itself links forward to the review.