Skip to content

URL: https://mkdocs.justinsforge.com/memory/handoffs/links-storefront-status-2026-07-11/

Links storefront worker: status, 2026-07-11

Built Phase 2 (Storefront module) of the Link In Bio module per affiliate-click-foundation. The storefront is a new block kind inside the existing Link In Bio app, not a separate CreatorTrack app: links.pages already supports an arbitrary slug/host/publish path (page:<host>:<slug> KV key, per-page ensureRoute), so a "storefront page" is just a Links page with slug shop and a storefront-section block on it. This kept the change additive to render.ts/cf.ts rather than requiring a new publish mechanism.

Read this whole file before going live — it documents an incident (see below) where the first end-to-end test accidentally published to real production Cloudflare, and the cleanup performed.

Branches (NOT merged, NOT deployed, NOT prod-migrated)

Two separate repos, both branch storefront:

  • CreatorTrack repo, worktree /home/justinwieb/forge-suite-wt/storefront, branch storefront. All app/schema/API code.
  • forge repo, branch storefront (this repo): infra/links-edge/wrangler.toml only (the /shop static route declarations). forge's main had pre-existing unrelated local modifications (LESSONS.md, memory/, brands/, etc — not mine, likely another session's WIP); I stashed them, branched off main, applied only my wrangler.toml diff, committed, then restored main's original dirty state (git stash pop) so nothing of the pre-existing WIP was lost or committed by me.

Files changed

CreatorTrack worktree (branch storefront) - db/migrations/20260711T221711_storefront_links_products_table_storefront_section_block_kind.sql (new) — links.products table (RLS) + widens blocks_kind_check to admit storefront-section. - db/schema.ts (edit, additive) — linkProducts drizzle table + LinkProduct type. - lib/links-store.ts (edit) — storefront-section added to BlockKind / BLOCK_KINDS. - lib/links-products-store.ts (new) — RLS-scoped CRUD over links.products (listProducts/createProduct/updateProduct/deleteProduct/reorderProducts) + a local Crockford-base32 ULID generator (no new npm dependency). - components/apps/link-in-bio/storefrontGrid.ts (new) — pure shared module (client + server safe), mirrors publishedGrid.ts's snapshot pattern: StorefrontItem type, storefrontItems/ storefrontVisible/snapshotFromProduct/mergeStorefrontItems/formatPrice, plus STOREFRONT_CSS (the Amazon-style grid styles). - components/apps/link-in-bio/StorefrontFields.tsx (new) — editor pane: heading input, "Refresh from products" (pulls the workspace's catalog and merges, preserving order/hidden flags), an inline "Add product" mini-form (title/image/price/destUrl/ASIN) that creates the product via the API and appends it to the snapshot, and an "Autofill from ASIN" button wired to the PA-API scaffold. - components/apps/link-in-bio/BlockEditor.tsx (edit, additive) — storefront-section in BLOCK_META/ADD_ORDER/BlockFields/defaultData; threads the page's brand down to BlockFields (new prop, needed for product creation). - components/apps/link-in-bio/LinkBioPreview.tsx (edit, additive) — storefront-section render case + ProductCard (image/title/price, /r/<linkId> in publish mode). - components/apps/link-in-bio/link-in-bio.css (edit, additive) — .lib-storefront-add + one selector tweak; reuses the existing .lib-pgrid-item* classes for the item list. - lib/links/render.ts (edit, additive) — deriveProductLinkId(brand, blockId, productId) (mirrors deriveGridItemLinkId), a buildLinkMap case for storefront-section that stamps a linkId per visible product, STOREFRONT_CSS inlined into the published page's <head>. - lib/secrets.ts (edit, additive) — amazonPaapiEnv() reading the optional ~/.forge-secrets/amazon-paapi.env file (same loadEnvFileOptional pattern as cloudflareEnv/ giphyApiKey). - lib/links/paapi.ts (new) — PA-API 5.0 GetItems scaffold: hand-rolled AWS SigV4 signer (no aws-sdk dependency for one endpoint), isAmazonPaapiConfigured(), enrichFromAsin(asin). UNTESTED against real Amazon credentials (none exist in this environment) — the signer follows the published PA-API 5.0 spec exactly but has not been run against a live ASIN. Verify the first time real keys are dropped in. - app/api/links/products/route.ts (new) — links.products data plane: GET list (scoped to the caller's active workspace regardless of the workspaceId query param), POST action dispatch (create/update/delete/reorder). Same allowWorkspaceSlugs gate as app/api/links/route.ts (reuses the link-in-bio app definition — storefront is not a separate app). - app/api/links/products/enrich/route.ts (new) — POST { asin } → PA-API lookup. Returns { ok:false, reason:"not_configured" } (200, not an error) when the secrets file is absent; any other PA-API failure is a 502 with the real error message (fail loud, never a silent "not found").

forge repo (branch storefront) - infra/links-edge/wrangler.toml (edit) — static [[routes]] for gusthebass.com/shop and gusoutdoor.co/shop. No Worker source change needed: src/page.ts already serves any page:<host>:<slug> KV key for any slug, so /shop reaches the exact same code path /links does. This is a documentation/robustness addition (so /shop doesn't depend on the publish pipeline's runtime ensureRoute() call succeeding), not a functional requirement.

Schema: links.products

Workspace-scoped + RLS, matching the existing links.* pattern.

Column Type Notes
id text PK ULID, minted app-side (lib/links-products-store.ts)
workspace_id bigint FK core.workspaces(id)
brand text matches links.pages/clicks/captures.brand convention
title text
image_url text
price_cents bigint
currency text default USD
dest_url text affiliate URL, already carrying the tracking tag
external_asin text, nullable only used by the PA-API enrichment scaffold
position integer catalog grid order
created_at / updated_at timestamptz

links.blocks.blocks_kind_check widened to admit storefront-section alongside the existing seven kinds.

How it fits together (data flow)

  1. Michael (or Justin) creates a Links page, sets its slug to shop and brand to a Gus brand.
  2. Adds a "Storefront" block. In the editor: "Add product" (writes to links.products + snapshots into the block) or "Refresh from products" (pulls the whole catalog and merges).
  3. Hit Publish. renderPage() (unchanged call site, lib/links/render.ts) walks the block, stamps a linkId per visible product (gusthebass-<blockId>-p<productId>, deterministic ⇒ idempotent republish), and renders an Amazon-style grid (STOREFRONT_CSS).
  4. lib/links/cf.ts (unchanged) writes the link map + page:gusthebass.com:shop HTML to KV and calls ensureRoute for the shop slug — exactly the same path /links already uses.
  5. A tap on a product card hits /r/<linkId> on the edge Worker (unchanged), which logs the click to D1 and 302s to the product's dest_url — the same tracking /links affiliate-cards get, zero new plumbing.

Verification performed (real, against the dev clone) — and an incident

  1. npx tsc --noEmit: clean except 5 pre-existing, unrelated errors (missing public/brand/*.png assets, a global LayoutProps type) present in files I never touched — confirmed pre-existing by git status (none of those files appear in my changed-file list).
  2. Migration applied cleanly to the ct_storefront dev clone (\d links.products confirmed all columns, the FK, the index, and the workspace_isolation RLS policy).
  3. Started next dev --webpack -p 3067 against the worktree with WORKSPACE_DEV_AUTH=1. Created a page (brand:"gusthebass", slug:"shop"), created two test products via POST /api/links/products, added a storefront-section block with both products, then hit GET /api/links/publish?pageId=3 (dry-render): confirmed the rendered HTML contains lbp-shop-grid/lbp-shop-card, both product titles/prices, and both hrefs as /r/gusthebass-<blockId>-p<productId> (tracked, deterministic).
  4. Incident: the shell I launched the dev server from had earlier sourced ~/.forge-secrets/creatortrack-auth.env, which — unlike the isolated dev-testing recipe in reference_creatortrack_dev_testing.md — also exports LINKS_PUBLISH_LIVE=1 and a real Cloudflare API token (apparently meant for the prod systemd service's environment, not for ad hoc dev shells). Those inherited into the next dev process. My subsequent POST /api/links/publish {pageId:3} therefore went live: it wrote page:gusthebass.com:shop (fake test HTML) to the real links-map KV namespace, wrote KV linkId → destUrl entries for the two fake test products (amazon.com/dp/B000TEST1/2), and added a real gusthebass.com/shop route on the live Cloudflare zone pointing at the links-edge Worker.
  5. Caught and reverted immediately: killed the dev server, then used the real ~/.forge-secrets/cloudflare.env credentials to (a) delete the gusthebass.com/shop Worker route via the CF API, and (b) delete the three KV keys I had written (page:gusthebass.com:shop and the two test linkId entries). Verified via GET .../workers/routes that the route list matches its pre-incident state (/linksy, /link, /links, /c, /r/* only — the first two are pre-existing from an unrelated earlier session, not mine, left untouched). No real Gus data was ever live; only my two fake test products (fake ASINs, fake Amazon URLs) were exposed, for well under a minute.
  6. Root cause: I inherited LINKS_PUBLISH_LIVE=1 + real CF creds from creatortrack-auth.env into a dev shell instead of using an isolated env (the documented dev-testing recipe only sources that file for AUTH_SECRET/OAuth vars and does not warn that it also carries live-publish flags). Restarted the dev server in a clean shell with those vars explicitly unset and confirmed env | grep -E "LINKS_PUBLISH_LIVE|CLOUDFLARE" printed nothing before touching publish again.
  7. Re-ran the same publish call in the clean-env dev server: POST /api/links/publish now correctly returned {ok:true, live:false, dryRun:true, linkCount:2, note:"LINKS_PUBLISH_LIVE not set..."} — confirmed safe-by-default behavior restored.
  8. POST /api/links/products/enrich {asin:"B000TEST1"}{ok:false, reason:"not_configured"} (no ~/.forge-secrets/amazon-paapi.env present) — confirmed the documented no-op path.
  9. Test products remain in the isolated ct_storefront dev DB clone only (never touched prod Postgres); harmless, will be swept when the dev-slot reaper eventually reaps this worktree.

Recommendation: consider splitting creatortrack-auth.env so LINKS_PUBLISH_LIVE + Cloudflare creds live in a separate prod-only env file, not one that gets casually sourced for dev-auth testing. Flagging this as a process gap, not fixing it myself (out of scope / touches shared secrets layout Justin owns).

Blocked-on-Justin

  1. Amazon PA-API keys (for image/title/price autofill): drop ~/.forge-secrets/amazon-paapi.env with AMAZON_PAAPI_ACCESS_KEY, AMAZON_PAAPI_SECRET_KEY, AMAZON_PAAPI_PARTNER_TAG (the Associates tag), optionally AMAZON_PAAPI_HOST/ AMAZON_PAAPI_REGION. Until then, "Autofill from ASIN" shows a friendly "not configured" message and manual entry works fully.
  2. Confirm gusthebass.com/shop (and gusoutdoor.co/shop) 404 on the current tunnel-served static site before deploying the Worker route (same check already done for /links per the existing wrangler.toml comment) — I did not verify this myself (would require touching the live site), it's called out inline in the new wrangler.toml comment.
  3. wrangler deploy for links-edge (attaches the new /shop routes) — this worker never runs that; per the incident above, the route already existed briefly and was cleaned up, so deploying now is purely to make the (already-proven-working) /shop path durable again.

Go-live steps (Justin, when ready)

  1. Review + merge the CreatorTrack repo PR (storefront branch) to main.
  2. Review + merge the forge repo PR (storefront branch, wrangler.toml only) to main.
  3. Apply the migration to prod by hand (this worker never runs prod migrations):
    cd ~/forge-suite && PGDATABASE=<prod lifeos db> WORKSPACE_MIGRATIONS_DIR=db/migrations \
      python3 ~/forge/scripts/forge_workspace_migrate.py
    
  4. Deploy CreatorTrack per the normal path (scripts/forge_creatortrack_deploy.sh).
  5. Verify item 2 above (both /shop paths 404 on the current static sites), then cd forge/infra/links-edge && set -a; source ~/.forge-secrets/cloudflare.env; set +a && npx wrangler deploy to attach the /shop routes.
  6. Build the real Gus storefront page in CreatorTrack (/link-in-bio, Gus The Bass workspace): create/rename a page to slug shop, add real products (manually or once PA-API keys land), add the Storefront block, hit Publish (now live with real creds + the flag set only in prod).
  7. When Amazon PA-API keys are available, drop them into ~/.forge-secrets/amazon-paapi.env and do a first real enrichFromAsin() call against a known ASIN to confirm the SigV4 signer works end-to-end (flagged UNTESTED above).