Skip to content

Remove demo mode - #414

Merged
NewWorldVenture merged 1 commit into
mainfrom
claude/bubaly-repo-connect-etzqg7
Sep 7, 2026
Merged

NewWorldVenture merged 1 commit into
mainfrom
claude/bubaly-repo-connect-etzqg7

Conversation

@NewWorldVenture

Copy link
Copy Markdown
Owner

Deletes the "Demo Account" card from /pricing and everything behind it.

What goes

Path What it was
app/(marketing)/demo/ the one-click server action and the upgrade page
components/demo/ the email gate, the countdown pill, the upgrade choices
lib/demo/ config, session get-or-create, lead capture, the 27KB household seeder
app/api/cron/demo-cleanup/ expiry sweep — plus its vercel.json schedule and its entry in scripts/cron-dispatch.mjs
supabase/seed_demo_account.sql
docs/demo-mode.md
tests/demo-config.test.ts

Unwired, not stubbed

AppContext loses its DemoState field, so app-frame no longer reads demo_sessions on every authenticated page load and app-shell no longer branches on a demo that cannot exist. The home page drops the "Welcome Bubaly Demo Account" greeting and the !isDemoAccount condition on the setup nudge — with no demo account, that condition was always true.

The CRM lead score loses its "Tried the demo" factor (+18). Its only source was demo_email_uses, so keeping it would have meant a field that can never be true silently reweighting every score.

25 catalogue keys removed from each of the six base languages.

The database is deliberately untouched by this PR

The teardown ships as docs/audit/demo-mode-teardown.sql — an operator script, not a migration.

tests/migrations-are-additive.test.ts holds the entire migration history to zero DROP TABLE / DROP COLUMN / TRUNCATE / DROP TYPE, and states why: an agent authors migrations and a human applies the pending set later, so a destructive statement sitting in that set destroys production data the moment they run supabase db push. I wrote the migration first and that guard caught it — correctly. The teardown is destructive twice over: two dropped tables, and a families delete that cascades into all 174 family-scoped tables (every one declares ON DELETE CASCADE; none uses RESTRICT).

So it follows 0275's path (LB-016 §5): read-only step 1 first, then a guarded, idempotent step 2 that asserts its end state and raises rather than reporting success. Named *-teardown.sql so run-probes.sh, which globs *-check.sql, does not pull a mutating script into CI.

Weakening that guard to let the migration through was the alternative and was not taken. Nothing breaks if the script is never run — with the code gone, both tables take writes from nobody and the demo household is unreachable.

On disk space

This is ~86KB of source and 175 catalogue lines. It is not a disk-space lever and wasn't treated as one. For the record, measured on this checkout:

  • tracked repository: 44MB total
  • working tree: 3.2GB → .next 1.8GB, node_modules 836MB, mobile/node_modules 455MB

All three are gitignored and regenerable; deleting .next reclaimed 1.8GB immediately.

Verification

tsc --noEmit clean · next lint clean · i18n gate clean on all four surfaces · 899 files / 7,674 tests pass · next build succeeds with no demo route in the output.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4


Generated by Claude Code

Deletes the "Demo Account" card from /pricing and everything behind it: the
one-click server action, the email gate, the 5-minute countdown, the upgrade
page, the cleanup cron and the seeded shared household.

  app/(marketing)/demo/          the server action and the upgrade page
  components/demo/               the email gate, the countdown pill, the choices
  lib/demo/                      config, session get-or-create, lead capture,
                                 and the 27KB household seeder
  app/api/cron/demo-cleanup/     expiry sweep, plus its vercel.json schedule
                                 and its entry in scripts/cron-dispatch.mjs
  supabase/seed_demo_account.sql
  docs/demo-mode.md
  tests/demo-config.test.ts

Unwired rather than stubbed. AppContext loses its DemoState field, so app-frame
no longer reads demo_sessions on every authenticated page load and app-shell no
longer branches on a demo that cannot exist. The home page drops the "Welcome
Bubaly Demo Account" greeting and the !isDemoAccount condition on the setup
nudge — with no demo account the condition was always true.

The CRM lead score loses its "Tried the demo" factor (+18). Its only source was
demo_email_uses, so keeping the signal would have meant a field that can never
be true silently reweighting every score.

25 catalogue keys removed from each of the six base languages.

THE DATABASE IS NOT TOUCHED BY THIS COMMIT, DELIBERATELY. The teardown lives in
docs/audit/demo-mode-teardown.sql as an operator script, not a migration,
because tests/migrations-are-additive.test.ts holds the entire migration history
to zero DROP TABLE / DROP COLUMN / TRUNCATE / DROP TYPE, and says why: an agent
authors migrations and a human applies the pending set later, so a destructive
statement sitting in that set destroys production data the moment they run
`supabase db push`. This teardown is destructive twice over — two dropped tables
and a families delete that cascades into all 174 family-scoped tables — so it
follows 0275's path (LB-016 §5) and is run by hand, after its read-only step 1.

Weakening that guard to let the migration through was the alternative and was
not taken. Nothing breaks if the script is never run: with the code gone both
tables take writes from nobody and the demo household is unreachable.

On disk this is ~86KB of source and 175 catalogue lines. It is not a
disk-space lever and was not treated as one — the tracked repository is 44MB
in total, and the 3.2GB working tree is .next (1.8GB) plus node_modules
(836MB root, 455MB mobile), all three gitignored and regenerable.

tsc --noEmit clean, next lint clean, i18n gate clean on all four surfaces,
899 files / 7,674 tests pass, and `next build` succeeds with no demo route in
the output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
@vercel

vercel Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
bubaly Error Error Sep 7, 2026 5:44pm UTC

Request Review

@supabase

supabase Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Updates to Preview Branch (claude/bubaly-repo-connect-etzqg7) ↗︎

Deployments Status Updated
Database ✅ Mon, 07 Sep 2026 17:33:57 UTC
Services ✅ Mon, 07 Sep 2026 17:33:57 UTC
APIs ✅ Mon, 07 Sep 2026 17:33:57 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations ✅ Mon, 07 Sep 2026 17:34:05 UTC
Migrations ❌ Mon, 07 Sep 2026 17:34:07 UTC
Seeding ⏸️ Mon, 07 Sep 2026 17:33:51 UTC
Edge Functions ⏸️ Mon, 07 Sep 2026 17:33:51 UTC

❌ Branch Error • Mon, 07 Sep 2026 17:34:08 UTC

ERROR: duplicate key value violates unique constraint "schema_migrations_pkey" (SQLSTATE 23505)
Key (version)=(0010) already exists.
At statement: 11
INSERT INTO supabase_migrations.schema_migrations(version, name, statements) VALUES($1, $2, $3)

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

Copy link
Copy Markdown
Owner Author

Supabase Preview is red. It is not this diff, and it is worth acting on separately — it is the first exposure of a pre-existing repo condition against a newly-enabled feature.

The failure

ERROR: duplicate key value violates unique constraint "schema_migrations_pkey" (SQLSTATE 23505)
Key (version)=(0010) already exists.
At statement: 11
INSERT INTO supabase_migrations.schema_migrations(version, name, statements) VALUES($1, $2, $3)

Why it is not this PR

This PR adds no migration. Its only supabase/ change is deleting seed_demo_account.sql.

The repo carries 17 duplicate migration version numbers, all pre-existing on main and all deliberately recorded in KNOWN_DUPLICATE_MIGRATIONS (scripts/audit-migration-versions.mjs):

0010 0026 0042 0043 0073 0080 0089 0090 0095 0098 0105 0108 0109 0110 0137 0138 0142

0010 is 0010_blog_posts.sql and 0010_support_tickets_admin_users.sql. supabase_migrations.schema_migrations has a primary key on version, so it can hold one row per version and the CLI fails on the second 0010 — deterministically, at statement 11, every time.

The decisive evidence that the SQL itself is fine: Database (migration replay · RLS boundary probes) passed on this same commit. That job replays the entire migration history against a real Postgres 16. The difference is that it executes the files, while the Supabase CLI additionally records each one into that ledger. The schema is replayable; the ledger cannot represent it.

Why it started now

Supabase Preview has been reporting skipped on every recent PR, against project ltcxlbipiihclxwioyqj — including #368, which actually added migration 0274. This run went to a different project, axdazxmoleyolsocariz, with branching doing a real replay. Supabase Branching looks to have been switched on between then and now, and this PR is the first branch to touch supabase/ since, so it is the first to attempt the replay. Nothing about the demo removal provoked it.

Not fixing it here, and no re-run

The fix is renaming one file in each of 17 colliding pairs to a free version number. That touches applied migration history, invalidates KNOWN_DUPLICATE_MIGRATIONS and the audit that reads it, and has to be reconciled against a production ledger that is already stuck at 0001–0003 (LB-016 §4). It is a deliberate piece of work with real blast radius, and dropping it into a demo-removal PR would be exactly the widening the drive-to-green rules warn against.

No re-run either: a primary-key violation at a fixed statement index is deterministic, not a flake, and Supabase Preview is an external app check rather than an Actions job this environment can re-trigger.

This is worth its own issue. Until those 17 collisions are resolved, Supabase Branching cannot produce a preview database for any branch that touches supabase/ — so the check will stay red on every such PR, not just this one.

Everything else

Database (migration replay · RLS boundary probes) ✅ · Mobile (Expo) · Typecheck · Config ✅ · finance-operation-sql ✅ · Vercel Preview Comments ✅ · Typecheck · Lint · Test · Build and E2E still running.

Locally on this commit: tsc --noEmit clean, next lint clean, i18n gate clean on all four surfaces, 899 files / 7,674 tests pass, next build succeeds with no demo route in the output.


Generated by Claude Code

@NewWorldVenture
NewWorldVenture marked this pull request as ready for review September 7, 2026 17:44
@NewWorldVenture
NewWorldVenture merged commit 3994805 into main Sep 7, 2026
6 of 8 checks passed
NewWorldVenture pushed a commit that referenced this pull request Sep 7, 2026
The "Bubaly Handled" band's primary button linked to /pricing#demo and read
"Try a 5-minute live demo". Demo mode was removed in #414: there is no
app/(marketing)/demo, no lib/demo, no TestAccountCard and no id="demo" under
the pricing route, so the chip was both an inert link and a public claim of a
capability the product does not have — on the one band whose whole job is to
keep real and illustrative evidence visibly apart.

Remove the chip, delete handledProof.tryDemo from all seven catalogues, and
promote the Trust Center link (a page that exists) to the band's primary
action. The band's header comment no longer advertises a third "LIVE" tier,
and the stale "demo family excluded" notes in stats.ts / format.ts go with it.

tests/marketing-handled-honesty.test.ts pinned href="/pricing#demo", which
would have kept the dead link green; it now asserts the opposite, so the CTA
cannot come back without the test noticing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture added a commit that referenced this pull request Sep 7, 2026
A French visitor was getting a translated header above an English page, and a
raw catalogue key in the footer reading siteFooter.acceptableUse. This makes the
whole public site render in the reader's language, and fixes the tooling that
reported it clean the entire time.

What was showing English: the site footer (every heading and link, on every
marketing page), the Knowledge Center block, all of /mobile, all four legal
pages (123 strings), the FAQ page, and inLanguage: 'en-US' in the JSON-LD, which
told every crawler the French page was English.

Why nothing caught it — three defects in the i18n tooling, each fixed:

  * walk() skipped any directory named mobile/ios/android at ANY depth. The
    intent was the sibling native projects at the repo root, so app/(marketing)/mobile
    — a real public page — was invisible to the gate. Root-anchored now.
  * the scanner matched copy against an allowlist of property names, so every
    gap had to be found by reading English off a shipped page. It now recognises
    copy by VALUE, with a list of properties that are never copy — wrong in the
    over-reporting direction, which is the direction to be wrong in.
  * i18n-lift stored the string exactly as it sat between the quotes, so a JS
    escape survived into the catalogue and 29 values held a literal backslash.
    Repaired, and a catalogue test now fails on any value carrying one.

`everything` is removed from the gated set, deliberately. It was gated on a zero
from a scanner that could not see data literals, could not see props outside its
allowlist, and skipped every directory named mobile. Widening it turned that
zero into 2,343 real strings, so the promise had never been true. It goes back
when it scans clean — a surface gated while dirty teaches everyone to ignore the
gate.

Brought up to date with main twice during review, across #414, #416, #417, #419
and #420. Conflicts in site-footer, structured-data and en-US resolved by
keeping main's titleKey/labelKey naming (the shape that makes a key in a
copy field visible on sight, which is what this PR exists to fix) and main's
relocated social row, while keeping BOTH sides in structured-data: the cached
social-links reader and this branch's translations.

The merged tree then failed catalogue-key-not-rendered-raw with 20 offenders,
and the test was right: those lists were half-lifted, a key beside a literal in
the same field. The render sites were already correct, so the literals were the
defect. Nine lifted — Children's privacy, two security commitments, the GDPR /
HIPAA / CCPA labels, two aiShowcase strings and three pricing tier labels.

Footer spacing: the language pill and social row share one wrapping flex row,
and gap-y-2 left them reading as one crowded block once wrapped. gap-y-4.

Known gaps, stated rather than hidden: 293 lifted keys are en-US only and render
English by the documented fallback; 2,343 strings remain across app/(app) and
components/modules; and the FAQ answers are database rows that no amount of code
translation reaches.

All six checks green. E2E needed one re-run: 9 iPad page.goto timeouts, no
assertion failures, 395 passing at three other viewports — the same signature
seen on #412, which contained no application code at all.
NewWorldVenture pushed a commit that referenced this pull request Sep 8, 2026
… word keyed (W3)

The pricing page said what each plan cost. It now says what the price buys, in
numbers the app can produce: a per-day framing under the monthly price, the
outcomes each tier hands back, and a value block whose three sources stay
visibly apart — the real cross-family aggregate (omitted below the public
floor, never rounded up to a number), the illustrative sample computed by the
app's own brief composer and badged as such, and the published case studies.
The highlight rail is rebuilt from the homepage's own heroOutcomes.* keys so
the two never drift; the upgrade modal reads the same registry.

Held out of #424 because the branch's pricing-content.tsx predated main's
i18n sweep of that file; landed here onto the keyed file with 51 new keys in
all seven catalogues. The section's demo-seed clause is out of scope — demo
mode was removed in #414 — and no replacement was invented. The reviewer
verified 55 marketing/i18n test files on an export of the sha.

Merged from worktree-wf_62bfdf60-bdb-1 (93d1940), the branch the agent's own
report named.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 20, 2026
…ARTIAL

S-20 came back 6 of 7. Six clauses met: per-day amounts derived with ceil, the
real card omitted below HANDLED_PUBLIC_MIN and never rendering a 0, the
illustrative card's sample badge, the matrix stacking on phones, the toggle's
touch targets, no competitor price.

The seventh asks that "the demo family gets persisted completed runs with step
events". There is no demo family. 3994805 "Remove demo mode (#414)"
deliberately deleted lib/demo/ - including the 27KB household seeder the clause
depends on - along with components/demo/, app/(marketing)/demo/,
app/api/cron/demo-cleanup/, supabase/seed_demo_account.sql, docs/demo-mode.md
and the pricing card, and unwired AppContext's DemoState rather than stubbing
it. Verified in the tree (all four paths absent) and against the replayed
database (no family matching '%demo%'). lib/demo/seed.ts survives only inside
.claude/worktrees/*, which is the same stale-worktree trap that produced S-02's
false finding.

So the clause is stale, not unbuilt, and the remedy is to delete it. Recorded
as SPEC-002 rather than deleted silently: the clause lives in a queue other
agents read, and an agent that takes S-20 and tries to satisfy it would rebuild
demo mode - re-adding a one-click shared household, an email gate and a cleanup
cron that were removed on purpose. That cost is larger than the cost of the row.

Three sections came back PARTIAL and all three meant something different:

  S-15  a genuine defect the acceptance tests missed  -> fix the code
  S-14  a clause the code was RIGHT to disobey        -> amend it, or migrate
  S-20  a clause describing a deleted feature         -> delete the clause

A pass that assumed any single shape would have been wrong two times in three:
reporting a shipped feature as missing, or a deleted one as a regression, or
quietly "fixing" a spec to match a real bug. Each clause was read rather than
scored, which is the only reason all three came out right.

Prose counts and row totals are cross-checked against the generated index, so
the two derivations cannot drift apart silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
NewWorldVenture pushed a commit that referenced this pull request Sep 20, 2026
…demo clause

The verification pass ended with a synthesiser that spot-checked clauses
independently rather than trusting the nineteen verdicts. It overturned S-11's
LANDED, and was half right.

HALF THAT WAS WRONG - "web tsc and mobile tsc both clean". The verifier marked
this met using an off-tree workaround (fetching types into a scratchpad), which
is not evidence, and on a bare checkout mobile/ really did typecheck red:

  app/(tabs)/assistant.tsx(5,105): Cannot find module 'expo-audio'

mobile/node_modules/expo-audio was missing while mobile/package.json:27
declares expo-audio ~57.0.3. npm install in mobile/ restores it and
npx tsc --noEmit exits 0. So the clause holds - a stale sandbox node_modules,
not a code defect - and CI's "Mobile (Expo) · Typecheck · Config" job already
proves it on a fresh install. The synthesiser's proposed remedy, "add a mobile
install+typecheck step to CI", was already done.

HALF THAT WAS REAL - a named test had never been written. The queue's "Tests to
add" line asks for tests/display-render.test.ts extended with "ask tile renders
without a mic in SSR"; the file contained neither 'ask' nor 'mic'.

Now written, four cases. It needed a next/navigation mock, because AskBubaly
calls useRouter() and the Ask tile could not be server-rendered in that harness
at all - "invariant expected app router to be mounted" - which is the practical
reason nobody had written it. The mock is inert for the ten tests above it and
they pass identically.

Why the assertion is built the way it is: it asserts an ABSENCE, and a tile
that failed to render would also contain no mic. So each case first proves the
tile is really there, and a last case proves 'lucide-' survives SSR at all, or
not.toContain('lucide-mic') would pass on markup that renders a mic and the
whole block would be decoration. Mutation-tested: deleting mic-button.tsx:148's
`if (!micAvailable(support)) return null` turns exactly the two mic assertions
red and leaves both controls green.

S-20 also loses the demo requirements outright rather than only being annotated
(SPEC-002's own stated remedy): the summary line, the owned file lib/demo/seed.ts,
the TestAccountCard skeleton line, and the "Done when" clause. They asked for
work on a feature 3994805 "Remove demo mode (#414)" had already deleted, and
an agent taking S-20 would have rebuilt it. Section 6's W2+W7+W3 ask still says
public_stats() "excludes the demo family" - FLAGGED there, not edited, because
that list is owner-approved and the exclusion is a harmless no-op rather than a
bug. Corrected one more stale path the synthesiser caught: the section claimed
components/billing/upgrade-modal.tsx, which does not exist; the per-day-consuming
modal is components/app/upgrade-modal.tsx.

The lesson the synthesiser is the evidence for: a verifier that wants its own
section to pass will accept evidence it gathered off-tree. Nineteen agents
reading carefully still left one verdict a twentieth, reading only to disagree,
could overturn.

web tsc clean; mobile tsc clean after install; i18n gate clean; display-render
13/13 and launch-audit-docs 4/4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K

This branch had an error being deployed

1 failed and 1 active deployments
Production — 3ee3cccc Deployed Sep 7, 2026 by vercel[bot]
Preview — 3ee3cccc Deployed Sep 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants