Link In Bio: image/hero block kind¶
Branch: links-hero-image (worktree ~/forge-suite-wt/links-hero-image, dev DB clone ct_links_hero_image). NOT merged, NOT deployed.
What shipped¶
A new Link In Bio block kind, "image": a full-width hero/cover image for teaser/landing
pages (e.g. the merch-teaser signup page). Data shape: { url: string; alt?: string }.
Files changed¶
lib/links-store.ts—BlockKindunion +BLOCK_KINDSarray: added"image".components/apps/link-in-bio/BlockEditor.tsx—BLOCK_META["image"](label "Image", iconimage),ADD_ORDERincludes it,BlockFieldscase (URL input + optional alt-text input),defaultData("image")returns{ url: "", alt: "" }.components/apps/link-in-bio/LinkBioPreview.tsx— render case for"image": a single<img className="lbp-hero" src={data.url} alt={data.alt}>(renders nothing if no url set).lib/links/bio-css.ts—.lbp-hero { display:block; width:100%; height:auto; border-radius:12px; object-fit:cover; margin:0; }.db/schema.ts— updated thekindcolumn comment to list"image"(no schema shape change;datastays jsonb).db/migrations/20260711T203954_links_hero_image_links_blocks_kind_check_widen_image.sql— new migration, was NOT anticipated in the task brief.links.blockshas ablocks_kind_checkCHECK constraint (added20260710T052433to admitpublished-grid); it hard-rejects any kind not in its literal list, so"image"needed the same DROP-IF-EXISTS + ADD widening. Applied to the dev clone; will apply cleanly on merge via the normal migration runner. This is the one thing that deviates from "no DB migration needed" in the task brief — the brief was right thatblocks.data(jsonb) needs no migration, but missed thekindCHECK constraint.lib/links/render.ts— confirmed untouched, as specified.buildLinkMaponly special-caseslink/affiliate-card/published-grid; every other kind (includingimage) passes through unmodified, andrenderPagereusesLinkBioPreviewinmode="publish", so the hero renders identically in the deployed HTML with zero render.ts changes.
How it was verified (end-to-end, not hand-waved)¶
- Bootstrapped the slot per the setup script, but port 3064 was already occupied by a stale,
already-merged
feat/account-deletionworktree's dev server (unrelated to this task). Used port 3065 instead for this slot's.env.localandnext dev --webpack -p 3065. No other process was touched. creatortrack_dev_baseclone was missing ~40 migrations (schema drift from the base snapshot). Ranforge_workspace_migrate.pyagainstct_links_hero_imagebefore the app would boot without 500s — this is expected slot hygiene, not part of the feature change.- Drove the real API with
curlunder dev auth (WORKSPACE_DEV_AUTH=1), workspace resolved viaReferer: http://127.0.0.1:3065/justin/link-in-bio: createPage→ page id 3, brandgus-the-bass, slugmerch-teaser-hero-test.addBlockkindimage(urlhttps://picsum.photos/seed/hero/800/450, alt "Merch teaser hero") → 201, block persisted (this is what proved the CHECK-constraint migration was necessary and correct: it failed with a Postgres constraint violation before the migration was applied, succeeded after).addBlockkindtextandemail-capturealongside it → both 201.- Fetched the editor preview HTML (
GET /link-in-bio/3): confirmed<img class="lbp-hero" src="https://picsum.photos/seed/hero/800/450" alt="Merch teaser hero"/>present, full-width in the.lbp-blocksflow above the text and email-capture form. - Fetched the published-page dry-run render (
GET /api/links/publish?pageId=3,LINKS_PUBLISH_LIVEunset in dev so this is render-only, nothing written to Cloudflare): responseok:true dryRun:true linkCount:0; the returnedhtmlcontains the identical<img class="lbp-hero" ...>tag plus the.lbp-heroCSS rule inlined in<style>, proving the deployed static page will render the hero the same way as the editor.linkCount:0is correct — image/text/email-capture are not trackable link kinds, sobuildLinkMapneeded noimagehandling, as specified. npx tsc --noEmit— clean (exit 0) after the worktree's dev server had run once (Next.js generates.next/typeson first boot; running it before that produces unrelated pre-existing-asset TS2307 errors that are an environment artifact, not this change).
Raw captures saved during verification (scratch, not committed): editor HTML at
/tmp/ct-editor.html, published dry-run HTML at /tmp/ct-published.html, publish JSON at
/tmp/ct-publish.json.
Exact steps to go live¶
git checkout main && git merge links-hero-image(or open a PR) — review the migration file above along with the UI diff.- Deploy prod (normal CreatorTrack deploy pipeline). Prod deploy does not run migrations
automatically — the account-deletion precedent in
reference_creatortrack_account_deletion
applies here too. After deploy, run the prod migration explicitly:
PGDATABASE=<prod> WORKSPACE_MIGRATIONS_DIR=db/migrations python3 ~/forge/scripts/forge_workspace_migrate.pyagainst prod, applying20260711T203954_links_hero_image_links_blocks_kind_check_widen_image.sql. - In the Link In Bio editor, open (or create) the merch-teaser page, click "Image" in the Add block bar, paste the hero image URL (+ optional alt text).
- Click Publish. On prod with
LINKS_PUBLISH_LIVEset, this pushes the rendered HTML (with the hero) to Cloudflare Pages/KV for real; no further code change needed.
No other migration is needed — blocks.data is jsonb and was never schema-constrained.