product

Compare-cluster audit + registry

Created 2026-07-19. Which HiveJournal products should have a competitor "vs" SEO cluster (/compare/<product> + /compare/<product>/<competitor>, honesty-first), and the registry service + quarterly cron that keeps them fresh.

Prompted by QuickSites building the same pattern (their vs pages mirror ours). This doc is the analysis; services/compare-registry.ts is the machine-readable source of truth (one entry per product) that the cron reads.

The pattern (what a cluster is)

A lib/<product>-compare.ts file holds a competitor array (slug, name, oneLiner, pricing, freeTier, strengths, tradeoffs, pickThemIf, pickUsIf, sources) + a PRICES_VERIFIED date. Routes /compare/<product> (hub) + /compare/<product>/<slug> (per competitor) render it; sitemap.ts imports the slug array so a new competitor is auto-indexed. Honesty-first is the brand rule: every competitor gets a fair one-liner + real strengths, and we only claim the trade-off where it genuinely runs our way. Two clusters exist today (Family Wall, journaling).

Shared SEO enrichment (2026-07-19) — both clusters are set up identically via lib/compare-seo.tsx: a canonical tag on every hub + per-competitor page, BreadcrumbList JSON-LD (Home → Compare → cluster → competitor), and an auto-generated OG share card per page (opengraph-image.tsx, one design parameterized per cluster palette, pre-rendered statically per slug). Adding a new cluster → reuse the same three helpers so it launches at parity; a new competitor needs nothing extra (its OG image + breadcrumb + canonical all derive from the slug).

Audit findings (2026-07-19)

LIVE — keep fresh

ProductClusterCoversExpansion candidates
Family Wall/compare/family-wallSkylight, Hearth, CoziGoogle/Nest Hub, Echo Show, FamilyWall app, Jam
Journaling/compare/journaling (+ /best-journaling-apps-2026)Rosebud, Day One, StoicReflectly, Journey, Finch, Daylio

CANDIDATES — real named competitors + a public surface; build a cluster

ProductCompetitorsWhy / notes
EmberKiln / Graphene story studioNovelcrafter, Sudowrite, World Anvil, Campfire, Novarrium (+ LivingWriter, Scrivener, Plottr, Dabble…)Highest-value, lowest-effort gap. The research is already written + weekly-refreshed in COMPETITIVE_LANDSCAPE_STORY_SOFTWARE.md — scorecards, pricing, positioning. Angle: "one canon → five formats (audiobook/screenplay/video/print/reader)", a category no competitor spans. Build lib/story-compare.ts mirroring the two live libs.
Workout Window (accountability)StickK, Habitica, Focusmate, Strava clubs, 75 Hard appsBeta; coaching skin (John Rowley). Defensible.
write.cafe750 Words, NaNoWriMo, Reedsy Prompts, writing-sprint toolsDaily writing ritual + weekly contest. Viable.
JQ FormsJotform, Google Forms, Typeform, DocuSignNiche but viable — form creation + on-device autofill.

SKIP — deliberately NO public compare page

Kept in the registry (status skip) so the "why not" is documented + the audit stays exhaustive.

  • Sensitive / counsel-gated (never compare these): Family Photo Frame (minors, COPPA/BIPA/GDPR), Legacy Channel / eldercare voice (memorial voice cloning; ELVIS-Act/AB-1836/BIPA), Speech Mirror (clinical-adjacent, "a mirror, not a therapist"), Throughline / for-therapists (clinical/consent). Minor-facing education front doors stay informational, never "vs."
  • Category-of-one / niche (a vs-page would be forced): Odessa (journal → graphic novel), Downstream (procrastination-curve framing), Looking Glass (weak direct competition). DreamPro/Open-Energy citizen science competes with institutions (Zooniverse), not SaaS — no shopper intent.

The registry service + quarterly cron

services/compare-registry.ts holds COMPARE_REGISTRY (the table above, machine-readable) and two functions:

  • auditCompareRegistry(now?) (pure, unit-tested) → classifies every entry: a live cluster is stale when its PRICES_VERIFIED date is >90 days old (or unparseable — fail toward refreshing); a candidate with no cluster is a gap.
  • runCompareRegistryAuditTick() → the quarterly job. Files a product_tasks refresh task per stale cluster + a build task per gap (idempotent — one open task per product, category "Compare clusters" ⚖️). It does NOT rewrite competitor claims — pricing/features change and every claim is human-verified honesty-first, so it prompts a refresh rather than fabricating one (mirrors the auto-demo "generate into a review queue, never auto-publish" rule).

Cadence mechanics: the cron (index.ts compare_registry_audit) fires daily but only does work once ≥90 days have elapsed since the last audit (timestamp in site_settings.compare_registry_last_audit). A 90-day setInterval would never survive a process restart; the daily-fire + due-check does. OFF by default (kill switch at /dashboard/admin/crons, key compare_registry_audit; heartbeat compare_registry_audit_tick). Super-admin can view the live audit or force a run: GET /api/compare-registry/audit, POST /api/compare-registry/run.

How to add or refresh a cluster

  • Refresh (a "Refresh compare cluster: X" task fired): re-verify each competitor's pricing/features/free-tier, update lib/<x>-compare.ts, and bump PRICES_VERIFIED there AND the matching pricesVerified in compare-registry.ts. Mark the task done.
  • Build (a "Build compare cluster: X" task fired): create lib/<x>-compare.ts mirroring the two live libs, add the /compare/<x> + /compare/<x>/[competitor] routes, wire the slug array into sitemap.ts, then flip the registry entry from candidatelive with its clusterPath, libFile, and pricesVerified.
  • New product: add one CompareProduct entry (status candidate/skip with a rationale).
COMPARE CLUSTER AUDIT — Docs | HiveJournal