Conversation
Replace raw Tailwind utilities (bg-yellow-50, bg-red-50, border-red-300, text-red-*) on /hipaa and /crisis 911-emergency callouts with design-system status tokens. Extends the existing @theme inline aliases in globals.css with warning/error/critical background twins and a critical foreground alias. Resolves audit UX-08 (.audit-report.md). The /crisis 988 section already used bg-crisis-bg (calm blue, intentional) and is unchanged. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a visually-hidden skip link as the first focusable element on the main
layout fragment, revealed on focus. Annotates <main> with id="main" and
tabIndex={-1} so focus lands inside content after activation.
Resolves audit A11Y-09 (.audit-report.md) — the /accessibility page advertises
this feature on line 90 but it was never wired up. Restores WCAG 2.1 AA
conformance (2.4.1 Bypass Blocks).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds vitest as a devDependency with test/test:watch scripts and a vitest.config.ts that aliases @/ to repo root and runs in the node environment (Cloudflare Workers' nodejs_compat flag matches). Covers the waitlist POST handler across all 8 branches: - missing/invalid email (400) - missing NOTION_TOKEN or NOTION_WAITLIST_DB_ID (500) - no A/B variant (Notion payload without Variant, no trackConversion) - variant 'A' assigned (Variant select + trackConversion called) - Notion API non-ok response (500) - malformed request body (500 via outer catch) Resolves audit TEST-08 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ic (#5) Covers lib/ab-testing.ts: - assignVariant: default 50/50, weighted (0.9/0.1), edge weights [1,0]/[0,1], fallback when weights sum < 1, distribution sanity check over 2000 draws. - getVariant: cookie A/B/absent/invalid via mocked next/headers cookies(). - getVariantFromRequest: equivalent cases via NextRequest cookie header. - trackConversion: logs in development, silent in production. Branched off chore/test-vitest-waitlist so the Vitest scaffold is available locally; rebase onto preview after PR 3 lands. Resolves audit TEST-09 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
middleware.ts: A/B cookie's `secure` flag was gated on `process.env.NODE_ENV === 'production'`, which is not reliably 'production' on Cloudflare Workers. Set unconditionally to `true` — the cookie is non- essential and there's no scenario where we want it shipped over HTTP. next.config.ts: add `headers()` returning HSTS, X-Frame-Options DENY, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and CSP for all routes. CSP allows the Notion API (waitlist) and Cloudflare Turnstile (used in the follow-up SEC-08 PR). Resolves audit SEC-09 + SEC-11 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tsconfig.json: add noUncheckedIndexedAccess, exactOptionalPropertyTypes,
noImplicitOverride, noFallthroughCasesInSwitch, noImplicitReturns.
lib/ab-testing.ts: refactor ExperimentConfig from {variants[], weights[]}
to entries: {variant, weight}[] so the two halves can no longer drift in
length. Add non-null assertions to crypto.getRandomValues result and the
fallback-variant access (both are provably safe at runtime).
Test fixes surfaced by the new strict mode:
- waitlist route.test.ts: fetchSpy.mock.calls[0] is T|undefined under
noUncheckedIndexedAccess; add ! assertion where the prior expectation
already proved the call happened.
- ab-testing.test.ts: stubRandom's mockImplementation signature was a
too-loose ArrayBufferView; cast through typeof crypto.getRandomValues
to satisfy the typed-array overloads.
Resolves audit TS-12 + TS-13 (.audit-report.md).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/philosophy was a 554-line 'use client' page hydrating an entire React component tree just to support 4 accordion buttons. Most of the page is static prose that benefits from SSR. Extract the 'Deepen Your Practice' accordion section into a new components/philosophy/PracticeAccordions.tsx client subcomponent that owns the useState + toggle logic. Convert app/(main)/philosophy/page.tsx to a server component (removed 'use client', useState import, toggleSection handler) and reference <PracticeAccordions /> in place. Initial HTML now carries all the static prose (hero, principles, comparisons, CTA) — only the accordion section hydrates. One-open-at-a-time behavior preserved. While extracting, also wrap the decorative emoji and +/- indicators in aria-hidden spans and add aria-expanded / aria-controls on each accordion button so the section is properly announced to screen readers. Resolves audit PERF-W-01 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MobileBottomTabs: expand 'Phil'/'Feat' to full words for screen readers (icons preserve the visual abbreviation); wrap emoji in aria-hidden; add aria-current='page' on the active tab; aria-label on the nav. - MobileHeader: aria-label='988 Crisis Support' on the compact 988 pill. - home page: wrap the three '→' CTA arrows in aria-hidden spans so they aren't announced as 'right arrow'. - (standalone) splash: replace the placeholder-as-sole-label anti-pattern with a visually-hidden <label htmlFor>; drop the redundant aria-label. Resolves audit A11Y-10, A11Y-11, A11Y-12, A11Y-14 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The splash page (a 'use client' module) was calling redirect() from next/navigation at top level — a server-context function invoked from a client module doesn't redirect cleanly and bloats the client bundle with a dead-code-shaped branch. Move the NEXT_PUBLIC_SHOW_FULL_SITE check into (standalone)/layout.tsx, which is a server component by default. The redirect now runs before the client splash bundle ships; the page itself stays client-only for the form state. NEXT_PUBLIC_* is still build-time-inlined; replacing with a runtime env var would be a follow-up. This PR only fixes the redirect-context bug flagged in PERF-W-02 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace `email.includes('@')` with a zod schema (`z.string().email()`).
Catches malformed bodies, restricts to the email shape we expect, and
gives the API a clean validation boundary at the entrypoint.
Also drops the unused `Variant` type import (pre-existing lint warning).
Tests unchanged — the schema rejects the same cases the manual check did
(missing email, invalid email) with the same 400 'Valid email required'
response.
Resolves audit TS-14. Companion Turnstile / rate-limit work (SEC-08)
deferred until the waitlist starts seeing real traffic — current volume
doesn't justify the operational overhead.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six versions of catch-up. Notable changes the website can now adopt:
- 1.5.0 — therapeutic.* color palette (breathing/breathingLight/
breathingBackground) for flow-agnostic components
- 1.6.0 — published artifact alignment (was previously installing 1.5.0
against a 1.0.1 caret range)
- 1.7.0 — typography CSS variables (--text-{style}-{size,weight,
tracking,leading}) under /css output, plus font-weight scale
(--font-weight-{light,regular,medium,semibold,bold,black}). Enables
removing hardcoded typography values from globals.css .legal-content
- 1.7.0 — web.ts adapters rebuilt as literal types: spacing[4] now
types as '0.25rem', not string
- 1.8.0 — new /a11y subpath: getContrastRatio, meetsWCAGAA,
touchTargets (WCAG 2.5.5/2.5.8)
- 1.8.0 — new `pairings` token (12 curated FG/BG pairs with WCAG
levels) re-exported from package root
No code changes required by this bump; new exports are additive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore(deps): bump @mp2ez/being-design-system from ^1.0.1 to ^1.8.0
Adds 15 render smoke tests covering every server-component page (`tests/pages.smoke.test.tsx`). Each test imports the page module and asserts `renderToString` produces non-trivial markup. Catches build-time failures (broken imports, missing exports, runtime exceptions in JSX) that `next build` is the only safety net for today. The (standalone) splash is excluded — its 'use client' + stateful hooks don't survive renderToString cleanly. Worth a separate @testing-library/react render in a follow-up. vitest.config.ts changes: - Adds @vitejs/plugin-react for JSX/TSX transforms. - Adds a tiny inline `md-raw-loader` plugin so .md imports work in Vitest (next.config.ts's raw-loader rule is Turbopack-only; Vitest uses Vite). - Keeps `environment: 'node'` — renderToString is server-side and happy-dom's polyfilled Request/Headers break NextRequest cookie parsing in lib/ab-testing.test.ts. Tests needing a real DOM can opt in via per-file directive `// @vitest-environment happy-dom`. New .github/workflows/ci.yml runs on every PR against main/preview: fetches the legal content from mp2ez/being (mirroring deploy.yml's step), installs deps, runs lint, runs tests. Previously zero PR-time gating existed — only push-to-main/preview triggered the deploy workflow. Resolves audit TEST-11 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ns (#16) DS 1.8 finally ships typography tokens (--text-headline1-size, --text-title-size, --text-bodySmall-size, etc.), unblocking the .legal-content migration that the audit flagged as blocked. Refactor all hardcoded values in the .legal-content block to reference DS tokens: - Font sizes / weights / tracking → --text-* tokens - Margins / padding → --spacing-* tokens (4px-stepped) - Border radii → --radius-medium Two intentional overrides documented inline: - h2 keeps font-weight 700 (DS headline4 is 600) — legal H2s need stronger separation between sections. - p keeps line-height 1.75 (DS bodyRegular is 1.5) — legal pages benefit from extra airiness for dense reading. The h1 size shrinks 2px (2.25rem → 2.125rem via --text-headline1-size) to align with the DS scale; visual impact is negligible. Resolves audit UX-09 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sweep across app/ and components/. The @theme inline aliases in globals.css already map rounded-sm/md/lg/2xl → rounded-small/medium/ large/xxl, so this PR is a pure rename — zero visual diff. Substitutions (with \b boundaries, ordered to avoid 2xl/xl collision): - rounded-2xl → rounded-xxl (24px) - rounded-xl → rounded-large (12px; Tailwind's xl default = DS large, so this collapses two semantic names into one) - rounded-lg → rounded-large (12px) - rounded-md → rounded-medium (8px) - rounded-sm → rounded-small (4px) 76 occurrences migrated across 14 files. rounded-full and rounded-none are left as-is (no DS equivalent). Aliases in globals.css are intentionally NOT removed — third-party / DS package CSS may still emit those class names, and removing the aliases would silently shift to Tailwind v4 defaults (e.g., rounded-md would drop from 8px to 6px). The aliases stay as a safety net; this PR is purely about JSX semantic correctness so CLAUDE.md's rule ("Radius: small/medium/large NOT sm/md/lg") is followed. Resolves audit UX-10 (.audit-report.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `wrangler-smoke` job that runs in parallel with the existing
`test` job. Builds the worker with OpenNext, boots `wrangler dev` in
the background, polls /. once it responds posts `{}` to /api/waitlist
and asserts 400 'Valid email required'.
This exercises the route handler in the real Workers runtime —
catching CF-specific divergences in `next/headers` cookies(), the
marked() import, or any future runtime breakage that unit tests
running in node wouldn't see.
NOTION_TOKEN / NOTION_WAITLIST_DB_ID are dummy values; the smoke
test exits at the zod 400 before any Notion call, so no real-write
risk and no CI secrets needed for this job.
Stacked on #PR-A (chore/ci-render-smoke-tests) — after that lands,
this PR's diff is just the wrangler-smoke job addition.
Resolves audit TEST-10 (.audit-report.md).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three CI improvements following the test audit: 1. New `npm run typecheck` script (`tsc --noEmit`) gates the 5 strict TS flags that PR #9 enabled. Without this, the flags can erode silently — ESLint doesn't enforce TS compiler errors. Step added to the `test` job between lint and tests. 2. tests/pages.smoke.test.tsx now asserts specific content tokens on the regulated/safety-critical pages. Previously the `length > 100` + `contains '<'` checks were tautological — a regression that swapped the crisis page body for placeholder text would still pass. Now /crisis must contain '988' + 'Crisis Lifeline', /hipaa must contain 'NOT a HIPAA Covered Entity', /privacy/california must contain 'CCPA', etc. Non-regulated pages keep the length-only check. 3. The wrangler-smoke CI job now GETs /crisis after the waitlist 400 check and asserts: HTTP 200, '988' in body, and the three highest- value security headers (HSTS, CSP, X-Frame-Options: DENY). This validates that page rendering survives OpenNext bundling and that next.config.ts headers() actually ships them — neither of which the unit tests can verify. Resolves audit findings TEST-12, TEST-15, TEST-19, TEST-22. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the JSON parse lived inside the outer try/catch, so a malformed body would fall through to the generic 500 'Failed to join waitlist' error — conflating "garbage from client" with "Notion is down" and degrading production debuggability. Move `await request.json()` into its own try/catch returning 400 'Invalid JSON'. The 500 codepath now means actual upstream issues only. Test updated: the malformed-body case now asserts 400 + 'Invalid JSON' instead of 500 + 'Failed to join waitlist'. Tiny contract change — the splash form constructs valid JSON and no known external client depends on the 500 here. Resolves audit TEST-16. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
middleware.ts had zero direct coverage. The underlying assignVariant / getVariantFromRequest functions in lib/ab-testing.ts are well-tested, but the composition — "if no cookie, call assignVariant, then set cookies with secure/sameSite/httpOnly/maxAge/path" — was not. Three new tests: - Sets Set-Cookie with full attribute set (secure: true, sameSite: lax, httpOnly: false, maxAge: 2592000, path: /) when no existing variant. - Assigns the variant returned by assignVariant (B vs A). - Does NOT emit Set-Cookie when an existing variant is present (also asserts assignVariant is not called — early-return preserved). Underlying ab-testing functions are mocked; this isolates the middleware composition without retesting their internals. The cookie attributes are precisely what changed in the recent SEC-11 fix (secure: true unconditionally). Without this test, that fix could silently revert. Resolves audit TEST-14. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Button anchor branch previously ignored the `disabled` prop silently
— anchors don't have a native disabled attribute, so passing it did
nothing (audit A11Y-15).
Refactor ButtonProps into a discriminated union:
- AsButton: CommonProps + ButtonHTMLAttributes + `href?: never`
- AsAnchor: CommonProps + AnchorHTMLAttributes + `href: string` + `disabled?: never`
TypeScript now rejects `<Button href="/x" disabled>Bad</Button>` at the
call site. Existing usages (~30 across the codebase) all use exactly one
of href OR disabled, so no consumer breakage.
Runtime safety net: if `disabled` ever leaks onto the anchor branch via
untyped JS (cast, spread from Record<string, unknown>, etc.), the anchor
renders as inert: `aria-disabled="true"`, `tabIndex={-1}`, `onClick`
preventDefaults. Screen readers announce it as disabled, keyboard skips
it, clicks are blocked.
New tests/button.test.tsx covers:
- anchor render when href provided
- button render when href absent
- disabled forwarding on the button branch
- inert anchor render when disabled is cast-bypassed onto the anchor
branch (uses createEvent + fireEvent to inspect defaultPrevented
reliably across React/native event phases)
Resolves audit A11Y-15 and TEST-17.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new test files, all using the per-file directive \`// @vitest-environment happy-dom\` so the global vitest env stays 'node' (preserves NextRequest cookie parsing for ab-testing tests). - tests/standalone.test.tsx (TEST-13): renders the Coming Soon splash via @testing-library/react, fills the email input, mocks global.fetch, asserts the success branch renders + the right POST body. Second test covers the error branch when fetch returns non-OK. - tests/accordions.test.tsx (TEST-18): clicks the four PracticeAccordions buttons in sequence and asserts aria-expanded transitions correctly. Validates the "one open at a time" reducer that's the entire reason the file is a client component. - tests/layout.test.tsx (TEST-20): renders the home page through MainLayout (mocking next/navigation's usePathname) and asserts the skip-to-content link, the mobile nav landmark, and page content all surface. This is the first test to exercise DesktopNav, MobileHeader, MobileBottomTabs, and Footer composition. Resolves audit TEST-13, TEST-18, TEST-20, and (by consuming the deps) TEST-21 — happy-dom and @testing-library/react are no longer unused. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ims (#27) Three FTC §5 cleanups on consumer-facing legal pages: 1. Replace [DATE - TO BE DETERMINED] placeholders on /cookies and /accessibility with the actual last-updated date (2026-05-23). A live placeholder is worse than a wrong date — it signals the document is incomplete. 2. Strip the /accessibility "Testing & Compliance" section which asserted specific ongoing operational practices not verifiable in the repo: automated aXe + Lighthouse testing, manual VoiceOver/TalkBack/NVDA testing, and "user testing with people who rely on assistive technologies." If these are not active programs, the claims are deceptive under FTC §5. Replace with hedged statement: "We design to WCAG 2.1 Level AA standards. Report accessibility issues to accessibility@being.fyi." 3. Remove "Section 508" from /accessibility "Standards We Follow." Section 508 applies to federal agencies and their contractors — not consumer wellness apps. Claiming compliance creates phantom regulatory obligation. WCAG 2.1 AA, iOS Accessibility, and Android Accessibility entries are preserved — those are accurate voluntary design-standard references. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gnment (#28) Apple StoreKit introductory offers only support these durations: 3 days, 1 week, 2 weeks, 1 month, 2 months, 3 months, 6 months, 1 year. "28 days" is not a configurable duration. The underlying App Store offer will be configured as P1M (1 month). Marketing copy must match — a mismatch between website copy and App Store offer is both an App Store Review Guideline 2.3.7 violation (accurate metadata) and FTC §5 exposure (misleading offer claim). 6 occurrences updated across home, features, download, and the coming-soon splash: - 4× "28-day free trial" → "1 month free trial" - 1× "28 days" → "1 month" (download page big-display) - 1× "28-day free trial included" → "1 month free trial included" No copy that just says "free trial" (without a duration) needs to change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
) * fix(copy): remove "HIPAA-level" + "clinical" terminology from marketing Same FTC §5 + regulatory-applicability.md pattern as the /hipaa page removal: claiming "HIPAA-level" encryption/privacy when Being is not a HIPAA-covered entity creates deceptive-practices exposure even when technically accurate (AES-256 is fine encryption — the issue is invoking the HIPAA brand to imply equivalence). Per regulatory-applicability.md terminology table: - "HIPAA-compliant encryption" → "AES-256 encryption" - "Clinical assessment" → "Wellness self-assessment" Three replacements: 1. home page Privacy callout: "HIPAA-level encryption" → "AES-256 encryption" 2. features page hero subhead: "clinical assessments...with HIPAA- level privacy" → "wellness self-assessments...with privacy- first design and AES-256 encryption" 3. features page Privacy & Security section: "HIPAA-level encryption" → "AES-256 encryption" Also updates the JSDoc comment in features/page.tsx to use the correct terminology. These references surfaced during the legal-cleanup PR series (see #24-#28). Same legal exposure pattern, missed by the original handoff because they were inline marketing copy, not whole pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(copy): also fix "Clinical tools" → "Wellness self-monitoring tools" Found while doing a final HIPAA/clinical sweep — same regulatory- applicability.md terminology pattern as the other fixes in this PR. The home page's Science pillar copy referred to PHQ-9 + GAD-7 as "clinical tools." Per the terminology table: - "Clinical assessment" / "Clinical tool" → "Wellness screening" or "self-monitoring tool" Also softened "evidence-based care" → "evidence-based practice" since "care" implies clinical/treatment relationship that Being explicitly disclaims. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /hipaa page used "HIPAA Notice" terminology (term-of-art) while disclaiming HIPAA applicability, and made voluntary commitments mirroring HIPAA §164.524 and 60-day breach notification. The page also carried a publicly visible "Draft document - Attorney review required" banner and [TO BE DETERMINED] placeholders. Per the regulatory-applicability source-of-truth doc, Being must not use HIPAA framing in user-facing materials. The 60-day breach notification commitment is preserved in a new privacy-policy section under the FTC Health Breach Notification Rule (16 CFR Part 318) — see companion PR in the being repo. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /privacy-practices route was reachable by direct URL (not in footer) and used "Notice of Privacy Practices" as the page title — a term of art defined in 45 CFR §164.520 (HIPAA Privacy Rule). Using that title while disclaiming HIPAA applicability creates FTC §5 exposure via the same legal-exposure pattern as /hipaa. The substantive content was already redundant with the privacy policy. Per regulatory-applicability.md: "Do not claim HIPAA compliance in code comments, documentation, or user-facing materials." The page title violated this regardless of disclaimer text. Companion PRs in mp2ez/being delete the source markdown (docs/legal/notice-of-privacy-practices.md) and update the regulatory-applicability.md Related Documents table. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /do-not-sell route was reachable by direct URL (not in footer) and duplicated content that now lives in the privacy policy. CCPA §1798.135(b)(1) allows businesses that do not sell or share personal information to publish a statement to that effect in their privacy policy in lieu of maintaining a separate opt-out link. Being genuinely does not sell — folding the disclosure into the privacy policy is compliant and reduces page sprawl. The new "No Sale or Sharing of Personal Information" section in the privacy policy (companion PRs in mp2ez/being) preserves the load- bearing GPC honoring statement that this page contained — needed for TDPSA and CPA universal-opt-out compliance. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /disclaimers route was deployed but not linked from anywhere in the website (footer link was removed earlier; TOS Section 3 link to the singular /disclaimer was repaired by deleting the link entirely, since TOS Section 3 carries the load-bearing medical-disclaimer language already). The medical disclaimer remains accessible via: - TOS Section 3 (https://being.fyi/terms) - The in-app Legal Documents screen (mobile app bundles docs/legal/medical-disclaimer.md via Metro) Removes the route file only. The content file docs/legal/medical-disclaimer.md stays — the React Native app still requires it via legalDocuments.ts. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Publishes the website-side companion to INFRA-151. Adds the /privacy/multi-state route (CCPA + TDPSA + CPA + CTDPA + VCDPA survey) that the rewritten §5.3 of privacy-policy.md now promises, and surfaces both California Privacy Rights and Multi-State Privacy Rights from the footer Legal column to satisfy FTC §5 reasonable-accessibility for the newly-promised disclosures. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…te (#32) Closes the website-side technical half of INFRA-151. Middleware detects Sec-GPC: 1, sets being_gpc cookie + X-GPC-Honored response header, and clears the cookie when the header is absent (per-request semantics, not persisted preferences). Client-side GpcNotice component renders an acknowledgement on /privacy, /privacy/multi-state, /privacy/california, /cookies, and /support — matching the scope of the public commitment in privacy-policy.md §5.3. Discovery confirmed the site ships zero third-party trackers, so the "suppress trackers on GPC=1" half of the original spec collapses; the implementation is detect-and-acknowledge, not detect-and-gate. /cookies page rewritten to honestly disclose the two first-party functional cookies (being_ab_variant from INFRA-93, being_gpc from this change) — the prior "we don't use cookies" claim was inaccurate even before this change. Vary: Sec-GPC declared in next.config.ts headers() as intent, but Next.js's RSC layer overwrites it in both dev and prod. Filed as a follow-up requiring OpenNext Worker middleware to append after the framework writes. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…02 (#33) LegalPage was calling marked() inside the component body on every render. OpenNext on Cloudflare Workers re-executes server components per request even for force-static pages, so the markdown parse ran on every request to /privacy, /terms, /support, /privacy/california, /privacy/multi-state, and /cookies — adding ~50ms (P50) to ~500ms (P99) of Worker CPU per page load. Workers analytics for being-website-preview showed 6 exceededResources (Error 1102) failures in a 2-hour window post-INFRA-151 — the privacy-policy.md §5.3 rewrite added ~15 lines, which pushed borderline requests over the per-isolate CPU budget. User-reported failure had Ray ID a017a0ac992cd43b. Fix: memoize marked() output in a module-scope Map keyed on the content string. Content imports are module-level (identity-stable), so the cache is bounded by the number of legal docs (currently 6). First request on a fresh isolate parses; all subsequent requests on the same isolate hit the cache (<1ms). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ax (#34) Two related Tailwind v4 migration gaps that combined to break the footer on the deployed preview. # Problem 1: brand-* / crisis-* / accent-* utilities weren't generated Tailwind v4 only generates utility classes for color tokens declared inside @theme. The design system package exposes its tokens as :root variables, which makes them usable via raw var(--color-X) but does not trigger Tailwind utility generation. The local @theme inline block had aliases for sage-*, status-*, and crisis-bg, but was missing: - brand-midnight, brand-sage, brand-blue-gray, brand-off-white - crisis-text, crisis-border - accent-50/100/500/600/700 Result: every className like `bg-brand-midnight`, `text-brand-sage`, `border-crisis-border` silently became a no-op. Most visibly: the footer's `bg-brand-midnight` did nothing, so the footer rendered with no background — and all its dark-bg-styled text colors (text-white, text-gray-300/400) became invisible or barely visible on the page's white background. Also broken (now fixed by the same change): the "Download Being" CTA button on /home (uses bg-brand-sage), the sage-green section links on /home, the GpcNotice's sage-green border + midnight strong text, and every text-brand-midnight heading on /crisis. The brand-blue-gray and brand-off-white aliases bridge a casing gap: the design system stores those as camelCase variables (--color-brand-blueGray, --color-brand-offWhite) but the codebase uses kebab-case utility classes. # Problem 2: bg-opacity-* / border-opacity-* are Tailwind v3 syntax Tailwind v4 removed these in favor of slash syntax (bg-color/N, border-color/N) that keeps the color and alpha coupled. The old classes silently produce no CSS in v4. Affected: - Footer.tsx: `bg-crisis-bg bg-opacity-10` → `bg-crisis-bg/10` - Footer.tsx: `border-crisis-border border-opacity-20` → `border-crisis-border/20` - DesktopNav.tsx: `hover:bg-opacity-80` → `hover:bg-crisis-bg/80` - QRCodePlaceholder.tsx: `bg-opacity-90` → `bg-brand-midnight/90` # Verification - CSS bundle before: 35115 bytes, zero `.bg-brand-*` rules - CSS bundle after: 37983 bytes, includes `.bg-brand-midnight`, `.bg-brand-sage`, `.text-brand-midnight`, `.bg-accent-*`, etc. - Computed styles on /home footer after build: background-color rgb(27, 41, 81) = #1B2951 (brand-midnight) — confirmed via Chrome DevTools against `npm run start`. - Crisis disclaimer box now renders as 10% / 20% translucent overlay on the dark footer (was 100% pale-blue floating on white before). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(layout): move standalone routes into (main) + suppress mobile tabs on /crisis Five pages (/accessibility, /crisis, /support, /privacy/california, /privacy/multi-state) lived in app/<route>/ rather than app/(main)/, so they inherited only the root layout — no DesktopNav, no Footer, no MobileBottomTabs. A user landing on any of them from a footer link, search result, or shared URL had no way to navigate back to the rest of the site except the browser back button. The 2026-05-25 visual audit (see docs/audits/) flagged this as the highest-impact finding; PR #31's new footer links to /privacy/california and /privacy/multi-state made the orphaning more visible. URLs are unchanged — route groups in parens don't affect paths. External links, redirects, sitemaps, SEO are all unaffected. The only mechanical update needed was four imports in tests/pages.smoke.test.tsx; this also adds /privacy/multi-state to the smoke list (was missing). Side effect: MobileBottomTabs now renders on /crisis. Per compliance + ux agent review, suppressed on /crisis specifically — the bottom tabs (Home / Philosophy / Features / Crisis) compete spatially with the in-page 988 Call/Text buttons for the same thumb-zone attention. Per AFSP / Zero Suicide digital safety guidance, the primary crisis action shouldn't compete with marketing-navigation affordances at the moment a user is in distress. Other moved pages (incl. /support) keep the tabs since they're exploratory/help-center contexts. Also removes /crisis's in-page "About Being" section, which became redundant once the global Footer is rendered on the page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(audit): add 2026-05-25 screen-by-screen visual audit Output of a Chrome DevTools MCP sweep across all 12 visible routes on the preview deployment at desktop (1440×900) and mobile (~500) — run after the #31–#34 wave to catch any remaining silent display bugs of the same v3→v4 / token-registration class as #34, and to surface UX-subjective improvements for design review. Findings: 0 critical, 1 display bug (standalone routes orphaned, fixed in the previous commit), 8 UX-judgment, 4 nits. Baseline health is good: zero console errors, broken images, network failures, or horizontal overflow on any page. The #34 token fix really did fix everything. Audit doc was reviewed by the ux agent for a second-opinion pass on the UX-judgment items; that pass added 4 new findings (missing CTA on /features, undersized CTA on /philosophy, /download pricing visual hierarchy, possible borderline contrast on /home cards). Those are filed but not addressed yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rod (#36) Removes the last blockers to flipping the splash gate so production (main branch) serves the full site, which is needed for TestFlight + App Store review. The app isn't downloadable yet — but the previous /download page (placeholder app-store badges marked '(graphic needed)') would be embarrassing for users and confidence-breaking for App Store reviewers. Three coordinated changes: 1. Extract the splash page's waitlist signup form into components/shared/WaitlistSignupForm.tsx so both the splash and the new /download can use it. Splash refactored to consume it; visual no-change to the splash itself. 2. Rewrite /download as the pre-launch waitlist experience: - Hero: 'Coming soon to iOS and Android' (was 'Download Being') - Primary CTA: waitlist signup form - REMOVED: placeholder iOS/Android badges + QR codes - REMOVED: duplicate bottom 'Download for iOS / Android' CTA - REFRAMED: pricing + system requirements as 'What to expect when we launch' - FAQ retained with one new entry ('When will Being launch?') Every existing CTA across the site (5 of them on home/features/ philosophy) still routes to /download — they now land on a coherent waitlist signup instead of broken download buttons. 3. DesktopNav 'Download' link text → 'Get Early Access'. URL unchanged. 4. NEXT_PUBLIC_SHOW_FULL_SITE flipped to unconditional 'true' in deploy.yml. The splash component + redirect logic remain in the codebase as emergency-fallback, but the splash is now unreachable in deployed envs — / redirects to /home on both preview and main. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MP2EZ
added a commit
that referenced
this pull request
May 26, 2026
…ge (#39) Closes three deployment-confidence gaps that the recent prod cutover (PR #37) made visible. # 1. Build with NEXT_PUBLIC_SHOW_FULL_SITE='true' in wrangler-smoke CI previously built without the env var, so the / → /home redirect codepath (production behavior) was never exercised in CI. Now matches the deploy.yml build mode. Readiness probe switched from / (now 307) to /home (always 200) so wrangler-dev startup detection still works. # 2. Extend wrangler-smoke route coverage Old coverage: POST /api/waitlist + GET /crisis (2 routes). New coverage adds 4 routes: - GET / → 307 with location: /home - GET /home → 200 + 'Mindfulness with meaning' body marker - GET /download → 200 + 'Coming soon' marker, NOT 'graphic needed' (catches if the page regresses to the pre-#36 placeholder badges) - GET /privacy/multi-state → 200 + 'Multi-State' body marker Each follows the existing /crisis assertion pattern. # 3. Post-deploy smoke against live URL (deploy.yml) Catches the failure mode CI cannot reach: DNS misconfig, custom-domain unbound, SSL broken, deploy succeeded-but-routed-wrong. After the wrangler-action deploy command succeeds: - Branch-aware URL (main → being.fyi, preview → workers.dev) - 15s sleep for Cloudflare edge propagation - Cache-busted curl on /home (200), /download (Coming soon marker), /privacy (Strict-Transport-Security header) - FAIL exits the workflow loudly so an alert fires Note on the 'Coming soon' assertion: temporary marker tied to pre-launch state. When the app launches and /download swaps to real app-store badges, update this assertion or replace with a durable marker like the H1 text. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Production cutover: splash → full marketing site
This is the launch PR. `main` has been at the "Coming Soon" splash since December 2025; `preview` has accumulated 33 PRs of compliance/audit/launch-readiness work. Merging this exposes the full marketing site at `being.fyi` so the App Store / TestFlight review can proceed.
Pre-merge gate (already verified)
What ships (grouped)
Launch-readiness — this session (PRs #31–#36)
Compliance + legal sweep — May 2026 (PRs #24–#29)
A11Y + design system (PRs #1, #2, #6, #7, #16, #17, #23, design-system 1.8 bump)
Test infrastructure (PRs #3, #5, #8, #9, #11, #12, #14, #18, #19, #20, #21, #22)
Net diff: 75 files changed, +11566 / -7983 (largely `package-lock.json` churn from DS 1.8 + Next 16.0.10)
CI is good for code correctness but doesn't smoke-test the production domain after deploy. After this PR merges and `.github/workflows/deploy.yml` runs:
```bash
Step 1: confirm being.fyi serves the new site
curl -sI https://being.fyi/ # expect 307 → /home (or 200 if Cloudflare follows redirects)
curl -sI https://being.fyi/home # expect 200
curl -s https://being.fyi/download | grep -q 'Coming soon' # expect match (NOT 'Download Being')
curl -sI https://being.fyi/privacy # expect 200 + Strict-Transport-Security header
```
Then:
Out of scope (separate concerns)
What "ready to submit to TestFlight" looks like after this merge
🤖 Generated with Claude Code