Lossless Changelog
product

/promote — gated investment-opportunity surface, scroll-deck prototype, memo rendering

A private surface for sharing investment opportunities with a small circle of co-investors — passcode-gated in middleware so a locked deck never assembles, with the opportunity content itself gitignored out of this public repo.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 4.7

New surface: /promote

  • Added a multi-tenant opportunity-promotion surface for sharing investment opportunities and syndicates with a small circle of co-investors.

  • Three-surface architecture: public index at /promote, gated opportunity hub at /promote/[slug], gated materials at /promote/[slug]/{type}/{format?}/{version?}.

  • URL grammar: decks carry both format (scroll | static | interactive) and version dimensions; memos carry only version. Computed by lib/promote/urls.ts so YAML never has to declare hrefs.

  • Public index renders OG + Twitter card meta so iMessage / WhatsApp DMs unfurl with a brand image and headline.

Hard gate

  • New Astro middleware at src/middleware.ts reads a signed promote_session cookie and gates every path matching ^/promote/[^/]+/.+$. Locked requests are rewritten to the hub's locked-state render — deck and memo content never assemble for unauthenticated callers.

  • Server-validated passcode flow at src/pages/api/promote/unlock.ts. Codes are scrypt-hashed against an environment-configured master hash; an optional PROMOTE_OVERRIDE_CODES_JSON map enables per-opportunity codes without code changes.

  • Cookie shape: HMAC-SHA256-signed payload { unlocked, exp, scope }, HttpOnly, SameSite=Lax, Secure in production, scoped to /promote. TTL: 7 days, sliding renewal with a polished "signed in for 7 days on this device" toast on first unlock.

  • PROMOTE_DEV_BYPASS=1 accepts any code locally for development.

  • Env reads use process.env first with import.meta.env fallback — Astro 6 doesn't reliably surface non-PUBLIC_* vars via import.meta.env at runtime.

Materials chooser

  • MaterialsChooser component conditionally renders by material count: 0 → 404, 1 → auto-redirect (hub becomes a router), 2-3 → vertical card list, 4+ → 2-column grid.

  • Each Material declares type, optional format, title, description, primary, status, default_version. Drafts hidden in production.

  • Sibling formats of the same type (e.g. deck.scroll + deck.static) render as separate chooser cards.

Scroll-deck rendering

  • Copied PageAsDeckWrapper.astro pattern from sites/calmstorm-decks and adapted to mpstaton-site's two-tier semantic tokens. Provides scroll-snap container, keyboard nav (↑ ↓ Home End PgUp PgDn), double-click upper/lower half navigation, fixed {n}/{N} counter bottom-right, and IntersectionObserver reveal-on-intersect for .reveal-item children.

  • New PromotionDeckLayout.astro wraps the deck wrapper with minimal chrome (back-link to hub + session toast).

  • Section components live under src/layouts/sections/promote/{slug}/deck/{format}/v{N}/ so a single opportunity can host multiple formats and multiple versions side-by-side.

  • Loader at lib/promote/sections.ts uses import.meta.glob to discover sections at build time per (slug, format, version) triple.

Memo route

  • Memos render through @lossless-group/lfm (parseMarkdown → MDAST → existing AstroMarkdown.astro + Sources.astro). Reuses the citations pipeline already wired for context-v document rendering.

  • Versions resolve from memo/v{N}.md (or legacy memo.md for v1) — lib/promote/memos.ts handles fallback.

Content model

  • Each opportunity is a directory at src/content/promote/{slug}/:

    • opportunity.yaml — metadata: company name, status (active | closing-soon | closed | paused), logo, accent color, materials list, gate config.

    • variants.yaml — registry of (format, version) deck pairs and per-format default version.

    • data.yaml — shared structured data the deck composes against.

    • narratives/{NN}-{slug}.md — one markdown per slide with frontmatter + prose ("what this slide is", "why it's here", "visual hierarchy").

    • memo/v{N}.md — versioned memo content.

  • Loader at lib/promote/opportunities.ts reads via fs, parses YAML with the existing yaml dep, caches in-process.

Privacy convention (this is a public repo)

  • New .gitignore patterns at sites/mpstaton-site/.gitignore: opportunity content under src/content/promote/, src/layouts/sections/promote/, and public/promote/ is local-only, except for directories prefixed with _ which are committed.

  • Pattern: _demo, _template, _scaffold etc. become committable scaffolding seeds that future opportunities copy from. Real opportunity slugs (anything not starting with _) stay on-disk only.

  • Verified with git check-ignore: real opportunity content is invisible to git add; _demo remains stageable.

Index card system

  • OpportunityCard.astro renders a card per active opportunity. Anchored on the symbolic mark (appIcon / wordmark fragment), not the full logo — full logo is reserved for the OG share image.

  • StatusPill.astro shows Active (default), Closing soon (emphasised), Closed (kept on the index below active opportunities so prospects can see what they missed), or Paused.

  • Index sorts: Closing soon first, then Active, then Paused, with Closed rendered separately at the bottom.

Variant iteration loop (the speed motion)

  • Workflow targeted at 3-5 minute cycles to ship a new deck variant once narratives + data are sharp.

  • Recipe lifted from calmstorm-decks: pre-sharpened narrative copy → shared YAML data → established theme tokens → single rendering primitive (PageAsDeckWrapper).

  • Net effect: brand-new opportunities take an hour or two (narrative is the slow step). Additional variants of an already-modelled opportunity approach single-session economics.

Demo opportunity

  • _demo opportunity scaffold at src/content/promote/_demo/ with three placeholder section components, a placeholder memo, and listed_in_index: false. Hidden from the public index but reachable at /promote/_demo for end-to-end smoke testing of the gate, chooser, deck, and memo flow.

Blueprint

  • New blueprint at astro-knots/context-v/blueprints/Build-a-Promotion-Surface-for-Investment-Opportunities.md. Frames the architecture, hard-gate posture, materials chooser, variant iteration loop, content model, and decisions audit trail. Sibling to Build-a-Fundraise-Deck-Workspace.md and Confidential-Content-Access-Control-Blueprint.md.

Environment additions

  • .env.example extended with PROMOTE_SESSION_SECRET (HMAC signing key, hex 32 bytes), PROMOTE_MASTER_CODE_HASH (scrypt of master code), PROMOTE_OVERRIDE_CODES_JSON (per-slug overrides), PROMOTE_DEV_BYPASS (local development convenience).

  • src/env.d.ts declares App.Locals.promote typing for the middleware-set unlock flag.