initial: four-collection content pipeline for unique.rzen.dev
A small Astro + MDX blog for delightful, daily-use finds. The architecture separates capture from publication and supports reuse: - find/ captured items, hidden from indexes - finds/ editorial superposts referencing find slugs in a grid - reviews/ full-length writeups; can graduate from a find via fromFind - posts/ regular essays Three project-local skills under .claude/skills/ drive the pipeline: daily-finds (capture-only sweep across sources.json), build-finds (draft a thematic superpost), build-review (graduate a find). sources.json carries 47 curated feeds with per-source fetch strategy (webfetch / curl / skip) so Cloudflare-blocked sites and Reddit JSON endpoints are handled deterministically. /sources renders a 3-column card stream with a "Recent finds" cross-reference per source. Seeds: 17 reviews and one superpost referencing 5 finds.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: build-finds
|
||||
description: Draft a 'finds' superpost (editorial roundup) for unique.rzen.dev. Selects find items from src/content/find/, groups them under a theme, and writes a new file at src/content/finds/. Use when the user runs /build-finds, asks to "draft today's finds post", "build a roundup", or "make a catch".
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# build-finds
|
||||
|
||||
Author one editorial superpost (collection: `finds`) that references existing find items. Each superpost has a theme, a short blurb, and an `items` array listing find slugs. The grid layout is rendered by the page template — this skill only writes the MDX.
|
||||
|
||||
Invoke from inside the `unique.rzen.dev` repo.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `src/content/find/*.mdx` — the available pool of finds.
|
||||
- `src/content/finds/*.mdx` — existing superposts (used to detect already-referenced finds).
|
||||
- Optional argument: a theme phrase (e.g. "single-virtue tools", "things-disguised-as-other-things").
|
||||
- Optional argument: an explicit list of find slugs the user wants in the post.
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Determine candidate pool
|
||||
|
||||
- If the user provided explicit slugs: use those.
|
||||
- If the user provided a theme phrase only: scan all finds, surface the 8–12 that best fit the theme.
|
||||
- If neither: scan recent (last ~14 days) finds that are **not yet referenced** by any existing superpost (read every `items[]` array; subtract). From the unreferenced pool, propose 1–3 thematic clusters of 5–10 finds each, with a candidate theme phrase per cluster.
|
||||
|
||||
Re-use is allowed — a find can appear in multiple superposts under different themes. But by default, prefer surfacing **unreferenced** finds first; only reach for re-uses when the theme genuinely calls for them.
|
||||
|
||||
### 2. Confirm the cluster
|
||||
|
||||
If proposing clusters, show the user:
|
||||
|
||||
```
|
||||
Cluster A — "<theme phrase>"
|
||||
- <slug>: <name>
|
||||
- <slug>: <name>
|
||||
- ...
|
||||
|
||||
Cluster B — "<theme phrase>"
|
||||
...
|
||||
```
|
||||
|
||||
Wait for the user to pick a cluster (or refine). Don't write anything until confirmed.
|
||||
|
||||
### 3. Write the superpost
|
||||
|
||||
File: `src/content/finds/<YYYY-MM-DD>-<theme-slug>.mdx` (theme-slug = kebab-case of the theme phrase, lowercase, ~30 chars max).
|
||||
|
||||
Frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "<Theme phrased as a noun phrase, max ~60 chars>"
|
||||
date: YYYY-MM-DD
|
||||
tags: [daily-finds, <topic-tag-1>, <topic-tag-2>]
|
||||
description: "<One-line description for meta tags. Reuse the title if nothing better.>"
|
||||
blurb: "<One short editorial sentence; appears as the post's secondary line in the home feed.>"
|
||||
items:
|
||||
- <slug>
|
||||
- <slug>
|
||||
- ...
|
||||
---
|
||||
```
|
||||
|
||||
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[]`.
|
||||
|
||||
Example body:
|
||||
|
||||
```mdx
|
||||
> Today's catch from a morning sweep across the daily feeds. Five small things, each one built around exactly one virtue — a single sound, a single diagnostic, a single disguise, a single object made, a single quiet joke.
|
||||
```
|
||||
|
||||
### 4. Verify
|
||||
|
||||
- Confirm every slug in `items[]` exists at `src/content/find/<slug>.mdx`. If any is missing, abort and report — never reference a non-existent find.
|
||||
- Run `npm run build` to confirm the new superpost compiles.
|
||||
|
||||
### 5. Update the log
|
||||
|
||||
Append to `daily-finds.log.md`:
|
||||
|
||||
```
|
||||
## YYYY-MM-DD — superpost: <theme>
|
||||
|
||||
Slug: <date>-<theme-slug>
|
||||
Items referenced:
|
||||
- <slug>
|
||||
- <slug>
|
||||
...
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Default to 5 finds per superpost. Use 3 for tighter themes, up to 8 if the theme really supports it. Beyond 8 the grid stops being scannable.
|
||||
- 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 superpost. The find body and metadata are stable; the superpost 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 superpost — the grid card simply links to the find page, which itself links forward to the review.
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: build-review
|
||||
description: Graduate a find item to a full review for unique.rzen.dev. Reads src/content/find/<slug>.mdx, expands the body into a full review, writes src/content/reviews/<slug>.mdx, and links the two via fromFind / promotedTo. Use when the user runs /build-review <slug>, says "review this find", or "graduate <slug>".
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# build-review
|
||||
|
||||
Convert one captured `find` into a full `review`. Both records persist — the find stays put as the historical capture, the review becomes the canonical long-form treatment, and the two link to each other.
|
||||
|
||||
Invoke from inside the `unique.rzen.dev` repo with a find slug as the argument.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `src/content/find/<slug>.mdx` — the source find (must exist; abort otherwise).
|
||||
- `src/content/reviews/<slug>.mdx` — must NOT exist (abort if a review with the same slug is already there; ask the user to disambiguate).
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Read the find
|
||||
|
||||
Parse frontmatter. Note: `name`, `subtitle`, `link`, `linkText`, `source`, `topics`, `tags`, plus the body.
|
||||
|
||||
If `promotedTo` is already set, abort: "This find has already been promoted to <slug>."
|
||||
|
||||
### 2. Suggest a hero asset
|
||||
|
||||
Look at the external link and propose a hero asset — a screenshot, demo gif, or product photo from the maker's site.
|
||||
|
||||
- Mac apps: an App Store screenshot or hero image from the developer's site.
|
||||
- Physical products: the maker's product photo.
|
||||
- Travel destinations: a Wikipedia or Wikimedia Commons photo (free-to-use only).
|
||||
|
||||
Tell the user what you'd download and where to place it (`src/assets/reviews/<slug>/hero.<ext>` or similar). Pause for approval before downloading.
|
||||
|
||||
### 3. Expand the body
|
||||
|
||||
Take the find's 2-3 sentence body and expand it to a full review:
|
||||
|
||||
- Lead with a `>` blockquote summary (1-2 sentences) — this renders as the accent-bordered lead per the existing review style.
|
||||
- 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.
|
||||
- Reference the hero asset inline if downloaded (``).
|
||||
- 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.
|
||||
|
||||
### 4. Write the review
|
||||
|
||||
File: `src/content/reviews/<slug>.mdx`.
|
||||
|
||||
Frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: "<from the find>"
|
||||
subtitle: "<from the find — required for reviews>"
|
||||
date: YYYY-MM-DD # today (the review date), not the find's date
|
||||
category: <pick from existing categories or propose a new one>
|
||||
tags: <merge the find's tags with any review-specific additions>
|
||||
link: "<from the find>"
|
||||
linkText: "<from the find, if set>"
|
||||
description: "<short meta description>"
|
||||
source: "<from the find>"
|
||||
fromFind: "<find slug>"
|
||||
---
|
||||
```
|
||||
|
||||
If the find had no `subtitle`, generate one — it's required on reviews.
|
||||
|
||||
### 5. Update the find
|
||||
|
||||
Edit `src/content/find/<slug>.mdx` frontmatter: add `promotedTo: <review-slug>`.
|
||||
|
||||
(Slug usually matches the find's slug, so `promotedTo` and the find's filename match.)
|
||||
|
||||
### 6. Verify
|
||||
|
||||
- Run `npm run build`. Confirm:
|
||||
- `/reviews/<slug>/` exists, shows the source badge and "← First surfaced…" backlink to the find page.
|
||||
- `/find/<slug>/` shows the "→ Read the full review" callout pointing to the review.
|
||||
- Any superpost that references the find still renders fine; the find card on the grid remains.
|
||||
|
||||
### 7. Update the log
|
||||
|
||||
Append to `daily-finds.log.md`:
|
||||
|
||||
```
|
||||
## YYYY-MM-DD — review: <slug>
|
||||
|
||||
Graduated from find captured on <find-date>, source: <source>.
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The find's slug and the review's slug should match. Same slug = same item across the pipeline; cross-references stay simple.
|
||||
- Don't move or delete the find. Both records persist. The find is the historical capture; the review is the long-form treatment.
|
||||
- Never auto-write a review without showing the user the proposed body first. This is editorial work; the skill's job is to draft, not to publish unilaterally.
|
||||
- If multiple finds describe the same underlying thing, the user should consolidate them manually before running this skill.
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: daily-finds
|
||||
description: Capture-only daily sweep for unique.rzen.dev. Scans every source in sources.json, writes one find file per qualifying item to src/content/find/, and nominates 1-2 new sources to consider. Does NOT pick a winner, write a superpost, or address the user/audience. Picking happens via /build-finds and /build-review. Use when the user asks for "today's finds", "morning sweep", "capture", or runs /daily-finds.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# daily-finds
|
||||
|
||||
Pure capture: every item that passes the bar becomes a `find` file. No daily cap, no theme detection, no audience-facing post. Whether any of these finds appears in a `finds` superpost or graduates to a review is a separate editorial decision.
|
||||
|
||||
Invoke from inside the `unique.rzen.dev` repo.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `sources.json` at the project root — curated source list with per-source `fetch` strategy (`webfetch` / `curl` / `skip`) and optional `fetchUrl`.
|
||||
- `daily-finds.log.md` — running log of every find slug ever written (used to skip duplicates).
|
||||
- `src/content/find/*.mdx` — already-captured finds (every existing slug is a "do not re-suggest" item).
|
||||
- `src/content/reviews/*.mdx` — already-reviewed items (do not re-suggest).
|
||||
- `candidate-sources.md` — newly nominated sources awaiting user vetting.
|
||||
|
||||
## The bar
|
||||
|
||||
A find qualifies if and only if it is **(a)** delightful, unusual, or distinctive in a way the audience would care about *and* **(b)** usable daily (relax to "memorable for a visitor" for travel). Drop generic mass-market items, ad-copy listicles, speculative / not-yet-shipping products, self-promo, and anything not traceable to a real currently-buyable / currently-visitable thing.
|
||||
|
||||
Crucially: do **not** filter by "is it the best of the day." Capture broadly. Editorial selection happens later.
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Scan every source
|
||||
|
||||
For each source in `sources.json`:
|
||||
|
||||
- **Default (`fetch: "webfetch"` or no field)**: WebFetch against `fetchUrl` if present, else `url`. Extract recently-featured items (name, link, one-line description).
|
||||
- **`fetch: "curl"`**: shell out to `curl -sL -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" --max-time 25 "<fetchUrl|url>"` and parse the HTML body. For Reddit JSON endpoints, pipe through `python3 -c '…'` to extract titles, links, and selftext. Treat HTTP 403 with a non-empty HTML body as a content response (Atlas Obscura quirk) — only treat empty bodies or Cloudflare challenge markers as blocked.
|
||||
- **`fetch: "skip"`**: log the source as skipped (with `skipReason`) and move on. Do not retry.
|
||||
|
||||
Parallelize WebFetch in batches of ~8 concurrent requests; finish all sources before proceeding.
|
||||
|
||||
For each source record: items extracted, or `empty` / `blocked` / `skipped`.
|
||||
|
||||
### 2. Filter
|
||||
|
||||
For each extracted item, drop:
|
||||
|
||||
- Anything whose slug already exists in `src/content/find/`.
|
||||
- Anything whose name appears anywhere in `daily-finds.log.md`.
|
||||
- Anything reviewed in `src/content/reviews/` (compare by `name` field).
|
||||
- Items that fail the bar above.
|
||||
|
||||
### 3. Write find files
|
||||
|
||||
For every survivor, write one file at `src/content/find/<slug>.mdx`:
|
||||
|
||||
- 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` (copied from the source's `topics`), `tags` (free editorial tags inferred from the item — keep to 2-4), `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".
|
||||
- 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.
|
||||
|
||||
### 4. Nominate 1–2 new sources
|
||||
|
||||
Spend a small slice of the run on source discovery:
|
||||
|
||||
- During scans, when another publication / blog / shop / newsletter is referenced in a way that suggests it'd fit the brief, capture it.
|
||||
- Or run one targeted `WebSearch` for `"best <topic> newsletter"` or `"<topic> curator blog"`.
|
||||
- Append nominees to `candidate-sources.md` (create if missing) under today's date heading. For each: name, URL, a one-line case for adding, and which existing source surfaced it (or "WebSearch").
|
||||
- Don't auto-add to `sources.json` — that's a user decision.
|
||||
|
||||
### 5. Update the log
|
||||
|
||||
Append to `daily-finds.log.md`:
|
||||
|
||||
```
|
||||
## YYYY-MM-DD — capture run
|
||||
|
||||
Sources scanned: <N> total — <hits> hits, <empty> empty, <blocked> blocked, <skipped> skipped.
|
||||
Productive sources: <comma-separated list of sources that produced at least one written find>.
|
||||
|
||||
Captured finds:
|
||||
- <slug> — <name> — source: <source>
|
||||
- <slug> — <name> — source: <source>
|
||||
- ...
|
||||
|
||||
New source nominees: <count> (see candidate-sources.md).
|
||||
```
|
||||
|
||||
### 6. Report to the user
|
||||
|
||||
Short summary in chat:
|
||||
|
||||
```
|
||||
Captured <N> finds across <M> productive sources today.
|
||||
Blocked: <K> sources. New source nominees: <count>.
|
||||
Run /build-finds to draft a superpost from the recent pool, or /build-review <slug> to graduate one to a review.
|
||||
```
|
||||
|
||||
That's it. No theme, no candidates list, no "pick a winner."
|
||||
|
||||
## Notes
|
||||
|
||||
- "Why it fits" / find descriptions should name a *specific* virtue. Bad: "great design". Good: "magnetic connector means it never strains the cable when a foot catches it".
|
||||
- Travel destinations relax the daily-use criterion — a place qualifies if a visitor would remember it years later.
|
||||
- If a source's homepage is too noisy, prefer adding a `fetchUrl` to `sources.json` rather than working around it inside the skill.
|
||||
- Never invent slugs that collide with existing finds or reviews. If a name would produce a colliding slug, append a year or distinguishing word.
|
||||
- Source attribution must be exact — the `source` field in each find file must match a `name` in `sources.json` so the `/sources` page cross-reference works.
|
||||
Reference in New Issue
Block a user