Lossless Changelog
product

The deck operating system grew a real auth surface tonight — chroma installed end-to-end from scratch; the shell + the convention now extend to every future client deck

DidiDecks has shipped two client engagements so far (calmstorm-decks, chroma-decks) and a third is in flight. Both production sites carry sensitive fundraise materials: portfolio company graphs, named advisors, deal flow that's not for the open web. Calmstorm had an owned-auth gate from day one. Chroma did not — `output: 'static'`, no DB, no middleware, no /access. Tonight that changed: chroma got the full calmstorm pattern installed greenfield (with four exploration-named additions baked in), the shell picked up two latent prerender-bypass fixes that benefit every consumer, and the deck-level organization-naming convention got locked into the parent ai-labs exploration. The pattern is now ready to install into every future client engagement with one well-shaped plan file.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 4.7 (1M context)

Why Care?

Every DidiDecks client engagement is a fundraise — a closed audience of investors looking at deals, portfolios, and team graphs that are deliberately not for the open web. Anyone can read the deck if they have the URL is a posture that costs deals: it leaks competitive signal, signals carelessness, and (in the case of an advisor's name and headshot rendered into an audit page that the search engines find) damages relationships outside the team. The deck operating system needs a real gate.

DidiDecks already had one — calmstorm-decks ships an owned-auth surface (middleware, magic-link redemption, passcode tier-3 fallback, libSQL-backed sessions, append-only audit log) that the Calmstorm-Auth-Inventory Session-1 deliverable catalogued earlier this month. Chroma-decks had nothing. Tonight that asymmetry resolved: the calmstorm pattern installed greenfield into chroma in six phases over ~3 hours, with the four "missing pieces" the inventory named (OAuth via arctic, Organization + Membership + FirmProfile, opaque UUIDv7 lossless_id, app_slug on AuthEvent) baked into the initial schema rather than added as deltas.

The practical upshot for the deck operating system:

  • A second consumer empirically validated the pattern. Calmstorm was the reference; chroma is now the second site running it. Two consumers means future package extraction has a real test bed for "would this API survive a third deck."

  • The shell got two prerender-bypass fixes that every deck inherits. Both data-assets/* audit routes used to ship as prerendered HTML and bypass the consumer's auth middleware in prod — a latent leak across both decks. Patched at the shell tier.

  • The org-naming convention is now deck-wide policy. Organization.id = canonical email domain (lossless.group, trychroma.com, calmstormvc.com). Every future client deck seeds two orgs on first boot: lossless.group (Lossless team) + {client-domain} (the client). Locked in the parent ai-labs Shared-Auth exploration; pointer in dididecks-ai/CLAUDE.md.

What's New?

At the deck-operating-system tier:

  • @dididecks/shell data-assets routes ship SSR'd, not prerendered. Both /data-assets/companies and /data-assets/people were prerender = true since their introduction, which on Vercel meant CDN-direct serving and middleware bypass — anyone with a URL would have leaked the consumer's people + portfolio graph past whatever gate the consumer installed. Now they SSR through the consumer's middleware properly. Audit pages are reviewer chrome, low traffic; per-request SSR cost is irrelevant. (Commit 7cb5cc7.)

  • @dididecks/shell data-assets sticky column headers actually work now. The <th> had position: sticky; top: 53px; from the start, but .table-wrap { overflow-x: auto } was trapping the sticky-context vertically. One-line fix (drop the overflow on the wrap); on narrow viewports the table now pushes the page horizontally instead. (Commit f4d12c7.)

  • dididecks-ai/CLAUDE.md grew an Auth Surface Conventions section so future agents working in this tree find (a) that the pattern exists, (b) where the per-client install plans live (client-sites/<client>/context-v/plans/Install-Auth-Surface-from-Calmstorm-Pattern.md), and (c) the org-naming-convention pointer to the parent exploration. (Commit d2b4bec.)

Inside chroma-decks (the substantive install — full detail in the chroma-decks changelog entry):

  • 6 commits across 6 phases — infra, schema, helpers, middleware, /access UI, GitHub OAuth via arctic

  • 10 tables in astro over libSQL; local file in dev, Turso (ddd-chroma-mpstaton) in prod

  • ~1,800 LOC of new code, all owned, no Auth0/Clerk dependency

  • output: "static" → "server" with the deck pages SSR'd so the gate actually runs

  • Chroma-themed /access UI per DESIGN.md brand tokens (lowercase wordmark, IBM Plex Mono eyebrow, orange accent)

  • Org-naming convention applied at the consumer tier: Organization.id = "trychroma.com", slug = "chroma", seeded alongside "lossless.group"

Inside calmstorm-decks:

  • Parity commit for app_slug on AuthEvent. Small standalone change (~15 LOC across 5 files) so calmstorm's audit events stamp the same cross-app rollup dimension chroma does. When the future auth_events_outbox ingester pulls rows from both decks, each row carries its source unambiguously. (Commit df06779, pushed to origin.)

The deck operating system, two-consumer view

The two client engagements now look like this from an auth perspective:

                        @dididecks/shell
                              │
              ┌───────────────┴───────────────┐
              │                               │
        calmstorm-decks                  chroma-decks
              │                               │
   ┌──────────┴──────────┐         ┌──────────┴──────────┐
   │  cookie: cs_session │         │  cookie: cd_session │
   │  app_slug: calmstorm-decks    │  app_slug: chroma-decks
   │  org: calm-storm-ventures     │  org: trychroma.com
   │  passcode: staycalm           │  passcode: chromaadmin / chromaviewer
   │  OAuth: (none yet)            │  OAuth: GitHub via arctic
   │  Tables: 6 (no Org/Mem yet)   │  Tables: 10 (Org/Mem/OAuthAccount in)
   └─────────────────────────────────────────────────────┘
                              │
              shared identity via lossless_id (UUIDv7)
              shared audit dimension via app_slug
              shared org-naming convention (domain-as-id)

The two decks share the same shape; they don't share state. Cross-app rollup later joins by lossless_id (one human, one stable UUID across every Lossless app) and groups by app_slug (which deck this event came from).

What the shell shipped

Two patches at the shell tier. Both small. Both consequential.

Data-assets audit routes — SSR'd, not prerendered

The two audit routes (/data-assets/companies, /data-assets/people) were built as reviewer chrome — "are the headshots loading, do the logos render." They've earned their keep. They were also prerender = true. Under output: "server" on Vercel, prerendered pages are served from the CDN directly, and the consumer's middleware never runs for them in production. Anyone who guessed https://<deck-url>/data-assets/people would have seen the consumer's people + portfolio graph past the gate.

The fix: prerender = false. The audit pages now SSR through the consumer's middleware. The same pattern calmstorm landed at the deck-page tier earlier today (commit 677417f on calmstorm-decks); we extended the discipline to shell-injected reviewer routes.

Data-assets sticky column headers

Bonus while we were under the shell's hood: the audit-table column headers (LOGO / FAVICON / NAME / HOMEPAGE / SECTOR / DESCRIPTION / OG / STRATEGY / STATUS for companies; PHOTO / NAME / TITLE/ORG / LINKS / AFFILIATION / BIO / CONFIDENCE / STATUS for people) had position: sticky from the start but never engaged. The cause: .table-wrap { overflow-x: auto } was establishing a scrolling-ancestor that trapped the sticky vertically. Drop the overflow; the table pushes the page horizontally on narrow viewports instead. Now scrolling down through the Lead's 58 portfolio companies keeps the column header in view.

The convention: domain-as-id

Late in tonight's session: "the organizations needed to generate at first, and we should put this in CLAUDE.md or something in dididecks, is lossless-group and {client-name} or we should just use the url/email domain so lossless.group and trychroma.com"

Domain wins:

  • OAuth-natural. A Google Workspace OAuth callback gives user@trychroma.com. The org id is email.split('@')[1]. Zero translation table.

  • Globally unique. Two firms can both want the slug "studio"; nobody else gets your domain.

  • Self-documenting. organization_id = "trychroma.com" requires no mental mapping for whoever's reading the data later.

  • Cross-app rollup keys naturally. When future Lossless apps stamp memberships against the same domain ids, the rollup ingester joins without coordination.

Organization.id = canonical email domain (lossless.group, trychroma.com). Organization.slug = human handle (lossless-group, chroma). Every deck seeds two orgs on first boot: lossless.group (the operating-team org — Lossless members will eventually attach here with superuser role) plus the {client-domain} for the client this deck represents.

Personal-email signups bucket as id = "personal". Multi-domain firms get an aliases column when first encountered (not v1). Subdomains strip to apex (memos.acmevc.com → acmevc.com).

Full rationale and edge-case handling lives one tier up: in the parent ai-labs Shared-Auth-for-Applied-AI-Labs exploration under the new "Organization naming convention — domain-as-id" section. Pointer in dididecks-ai/CLAUDE.md so future agents in this tree find it.

What this enables for the deck pattern

  • Third client deck, when it comes, installs the same surface from a single plan file. The Install-Auth-Surface-from-Calmstorm-Pattern.md plan in chroma-decks' context-v/plans/ is the template. A new client engagement copies the plan, fills in three values (cookie name, APP_SLUG, client domain), and runs the 6 phases. Calendar estimate: a focused half-day per deck, plus the brand-themed /access UI per deck's DESIGN.md.

  • Cross-deck "who's reading what" reporting becomes a 5-table JOIN on lossless_id once we build the rollup ingester. Today an investor at the Lead Capital signing into both calmstorm and chroma has two lossless_ids (one per app); they get stitched lazily on email match. Phase 7 (someday) is the ingester that does that stitching at write-time across all decks.

  • The eventual lossless-auth-core package extraction has two empirical consumers (calmstorm + chroma) when it happens. Three is the rule for premature abstraction; we have two, so the extraction is closer but not forced. The extraction trigger will likely be: a third deck arrives, and the cookie-name + role-enum drift between calmstorm and chroma starts being painful at the deck-template tier.

  • The @lossless.group → superuser fast-path is named as a follow-up in chroma's plan: when an OAuth callback yields an email matching lossless.group, auto-attach superuser Membership to the lossless.group org. Small change in the OAuth callback; saves manual admin work for the operating team. Lands when a Lossless teammate other than Mike first signs into a deck.