Skip to content

feat(seed): realistic seed content for Pages, plus BlogPosts/BlogTopics seed generators - #43

Merged
danielheene merged 15 commits into
developfrom
worktree-blog-and-pages-seed-realism
Aug 21, 2026
Merged

feat(seed): realistic seed content for Pages, plus BlogPosts/BlogTopics seed generators#43
danielheene merged 15 commits into
developfrom
worktree-blog-and-pages-seed-realism

Conversation

@danielheene

Copy link
Copy Markdown
Owner

Summary

Extends the fixture-seeding infrastructure (previously Pages-only) to cover BlogPosts and BlogTopics, and upgrades Pages' seeded content from generic placeholder text to realistic, varied content.

  • Fixed a real pre-existing bug: BlogTopics' delete-access check queried a nonexistent tags field instead of topics, silently granting delete access to referenced topics.
  • Generalized SeedTaskProgress's success payload from Pages-specific (deletedPages) to collection-agnostic (created/deleted/deletedMedia/skipped).
  • Extracted a shared Lexical rich-text builder module (src/lib/seed/lexical.ts) from the old scripts/seed-blog.ts, fixing a real bug in the ported link() builder along the way — its field shape didn't match this project's actual LinkField config and would deterministically fail validation on every use.
  • Upgraded Pages' seeded content: realistic titles from a pool, varied block composition (One-Column, Two-Column, Code, Link Group).
  • Added seedTopics/cleanTopics for BlogTopics and seedPosts/cleanPosts for BlogPosts (ports the old article generator onto the shared Lexical builders, adds hero-image download, topic resolution, cascade-delete).
  • Wired both into the jobs-queue task, CLI (pnpm seed:topics/seed:posts + :clean variants), and admin-panel Seed…/Clean actions.
  • Retired the now-superseded scripts/seed-blog.ts and its old pnpm seed/seed:blog/seed:blog:clean commands.

Process

Built via Subagent-Driven Development: 11 planned tasks, each implemented and reviewed independently (spec compliance + code quality gates), followed by a final whole-branch review. One real bug was caught and fixed mid-plan (a link() field-shape mismatch that would fail validation on every use); the final whole-branch review caught and fixed a topic-resolution performance/realism issue in seedPosts (redundant per-post queries, insufficient topic variety across the seeded corpus).

Verification

  • pnpm typecheck — clean
  • pnpm vitest run — 75 files, 654 tests passing
  • pnpm build — succeeds, all pages generate
  • Manual end-to-end seed/clean cycle against a real dev database for all three collections — confirmed 0 leftover seeded documents after cleanup

Non-goals (per the design spec)

No changes to the Resume collections, no BlogTopics hero image, no changes to Pages' cascade-delete safety property, no internal-relationship links in seeded LinkGroupBlock entries, no global cleanAll command.

🤖 Generated with Claude Code

danielheene and others added 15 commits August 20, 2026 18:07
The access.delete function queried BlogPosts for a 'tags' field that
doesn't exist (only 'topics' does) — the query threw, was silently
caught, and delete access was always granted regardless of whether
the topic was still referenced. Corrected the field name.
deletedPages only made sense for Pages. Renamed to deleted and added
skipped, so the same channel type and admin-panel toast summary work
for cleanPosts/cleanTopics (added in later tasks) without inventing a
new field name per collection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Ported verbatim (same node shapes, same deterministic-random helpers)
into src/lib/seed/lexical.ts so Pages, BlogPosts, and BlogTopics'
seed generators can share one implementation instead of each
carrying its own copy. Added test coverage the original script
never had.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Flagged by Task 3's review — imported but never referenced. No
italic-specific test coverage was planned; IS_BOLD already exercises
the format bitmask structurally.
lexical.ts's link() was ported from scripts/seed-blog.ts, whose claimed
field shape (nested under a 'link' key with a 'type' discriminator) no
longer matches this project's actual LinkField (flat fields, no 'type'
field at all — removed in an earlier refactor). Every link insertion
into a rich-text field threw a deterministic ValidationError. Verified
the fix against the real Payload validator, not just the unit test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Replaced the generic 'Seeded Dummy Page N' title and single
boilerplate paragraph with a realistic title pool and 2-4 blocks per
page (One-Column, Two-Column, Code, Link Group), each with real
prose generated via the shared src/lib/seed/lexical builders.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Follows the seedPages/cleanPages shape exactly. cleanTopics' one
new behavior: it never deletes a seeded topic a non-seeded (real)
post still references, relying on the corrected topics.contains
query from the BlogTopics access-check fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Ports scripts/seed-blog.ts's lexicalArticle generator onto the
shared src/lib/seed/lexical builders. seedPosts resolves 1-2 topic
ids per post, creating its own seeded topics via seedTopics when too
few exist rather than requiring seedTopics to run first. cleanPosts
cascade-deletes hero images with the same live-flag-recheck safety
property cleanPages already has, and does not touch BlogTopics.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
seedTopics is idempotent-by-slug, not "create N more" — it skips pool
slots that already exist. A naive shortfall calculation could silently
under-fulfill the requested count. resolveTopicIds now loops until it
actually has `count` ids (bounded, since topicFor's cycling suffixes
make running out of the pool practically impossible for realistic
seed sizes).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Extends the single 'pages'-only branch to also handle 'posts' and
'topics', each calling the matching seedX/cleanX pair from Tasks 5-6.
Same task, same channel, same mode-parameterized shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Registers the existing, unmodified SeedActions component on both
collections, matching Pages' registration exactly — no component
changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
Superseded by seed:posts and seed:topics (src/lib/seed/posts.ts,
src/lib/seed/topics.ts), which cover the same content generation
via the shared src/lib/seed/lexical module, plus the seeded-dummy
tagging and admin-panel action seed-blog.ts never had.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
…-returns

resolveTopicIds was being called fresh for every seeded post, causing
redundant queries and giving every post the same leading 1-2 topics
instead of a spread across the pool. Hoisted resolution to run once
per seedPosts call, rotating which topics each post gets by index.
Also logs when the bounded retry loop can't reach the requested count,
since a silent short-return now affects every post in a run instead
of just one.

Also: dropped the vestigial type:'custom' field from pages.ts's
linkGroupBlock (inconsistent with lexical.ts's corrected link() shape),
replaced two inline text-node literals with the shared text() helper,
and restored the spec's videoIds parity branch in lexicalArticle
(dead but present, matching the design doc's explicit instruction).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjuWYPbCQ16C3Fkc1k8grT
The final-review fix hoisted resolveTopicIds out of seedPosts' loop but
requested only MAX_TOPICS_PER_POST (2), so the whole corpus rotated
between just 2 topics regardless of how many were seeded — the review's
re-verification flagged this as a residual realism gap versus the
retired script's full-pool rotation. Pool size is now
min(count, MAX_TOPIC_POOL_SIZE=6), floored at 2, so a larger seed run
spreads posts across more topics. Updated two tests whose mocks assumed
a fixed 2-topic pool request to match the wider, count-scaled request.
@danielheene
danielheene merged commit bd1d7e5 into develop Aug 21, 2026
5 checks passed
@danielheene
danielheene deleted the worktree-blog-and-pages-seed-realism branch August 21, 2026 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant