Repository navigation
Connect calendars during onboarding with private preview and safe imports - #450
Merged
NewWorldVenture merged 0 commit intoSep 10, 2026
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
NewWorldVenture
marked this pull request as ready for review
September 9, 2026 22:29
NewWorldVenture
force-pushed
the
claude/roadmap-implementation-ld8bon
branch
3 times, most recently
from
September 10, 2026 16:31
11aa925 to
677e44c
Compare
NewWorldVenture
merged commit Sep 10, 2026
35befc1
into
claude/roadmap-implementation-ld8bon
7 checks passed
NewWorldVenture
force-pushed
the
claude/roadmap-implementation-ld8bon
branch
from
September 10, 2026 20:53
56d8dd0 to
404d5d5
Compare
NewWorldVenture
pushed a commit
that referenced
this pull request
Sep 13, 2026
Found by crawling the internal links on the live site. The homepage and /pricing both render a section headed "Family stories", introducing what follows as quotes "In the family's own words". What followed were three database seed rows: Seed Case Studies #450 Seed Summary value 450 Seed Result Metric value 450 Seed Case Studies #180 Seed Summary value 180 Seed Result Metric value 180 Seed Case Studies #90 Seed Summary value 90 Seed Result Metric value 90 Each linked to a detail page answering 200 with <h1>Seed Case Studies #450</h1> at /customers/seed-case_studies-450. Same class as the sitemap defect on this branch — seeded rows reaching a public surface — but worse in kind. That one published dead links; this publishes invented customers under a heading that states they are real families. is_published could never have caught it: the seeder sets it true, which is what made them public. The rows are identifiable only by the seeder's slug convention, `seed-<table>-<n>`. isSyntheticSeedSlug() therefore joins publishedOnly() in the shared reputation helpers, so both pages' list reader excludes them, and loadPublishedCaseStudy() refuses one BEFORE touching the database — a link surviving in a cache or a search index must not still render one. The list and the detail page agreeing is the entire point: the sitemap defect existed precisely because two readers of the same content did not agree. Deliberately narrow, and both halves pinned: - testimonials have no `slug` column, so they pass through untouched. The predicate takes null/undefined and answers false. - the marker must be at the START of the slug, so a genuine story called `seeds-of-change` or `seed-starting-with-kids` is not swept up. Verified non-vacuous: without the filter, `keeps seeded rows out of the public list` and `404s a seeded story on its detail page without querying` both fail. The existing marketing-reputation and customer-story suites pass unchanged. The rows themselves remain in the database. The site no longer renders them, which is the visible outcome, but deleting them is an admin action this session has no credentials for; finalaudit.md records that. tsc clean, lint 0 errors, 1149 files / 13098 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
NewWorldVenture
added a commit
that referenced
this pull request
Sep 13, 2026
* Stop the sitemap advertising 435 URLs that 404, and name every gated surface in robots An audit of the live site found the sitemap carrying 1,508 URLs of which 435 — 29% — answered 404. All of them were `/blog/Seed <uuid>`, a slug shape that also carries a literal space and so is not a valid <loc> either. Sampling was unambiguous: 8 of 8 seed URLs returned 404, 8 of 8 real ones 200. The cause is two halves of one system disagreeing about what exists. Blog entries come from getAllPosts(), which filters through publicRows -> isSyntheticBlogSeedSlug; that filter is correct and catches 435 of the 435 live slugs. But the sitemap ALSO emits rows from `marketing_pages`, which stores a path directly and had no seed filter of any kind, so any published row went out verbatim. The renderer meanwhile refuses them outright — getPost() opens with `if (isSyntheticBlogSeedSlug(slug)) return undefined` and the page calls notFound(). So one table advertised what the other would not render. The file's own comment already stated the rule it was breaking: "a sitemap must never point at a 404", written for the benchmarks entry directly above. isAdvertisablePlatformPath() now applies the SAME predicate the renderer uses, so the two cannot drift apart again, and rejects a non-rooted path as well. canonicalSitemapUrl() collapses a stored root of `/` onto the static spelling `''` — the dedupe keys on the URL string, so the homepage was published twice, as `…com` and `…com/`. WHY THIS SHIPPED UNDER A GREEN TEST. tests/marketing-sitemap-closed-loop.test.ts — the file named for this exact property — never ran the sitemap. All three cases read app/sitemap.ts as TEXT and asserted substrings appeared in it. A grep cannot observe what a function emits. Demonstrated rather than argued: against the unfixed code all three source cases still pass while the new behavioural ones fail. They are kept (they do pin real wiring) and five cases added that execute sitemap() against a stubbed database and assert on the URLs returned. The stub is a Proxy rather than a fixed method list, so adding a .limit() or .range() to either query cannot quietly turn the suite into a false pass. Separately, robots.ts carried the comment "Authenticated app surfaces should not be indexed" above a list of four. The authenticated route group holds 22 segments, and production confirmed 20 gated surfaces missing from that list — /admin and its 80 pages among them — each answering 307 to /login. That is NOT a data leak and is not claimed as one: every one of them redirects while signed out. What was wrong is that the stated policy and the implementation disagreed, crawl budget was spent discovering them, and a future route answering 200 anonymously would have inherited no protection at all. A new test derives the segment list from app/(app) on disk so it cannot fall behind, and asserts the public marketing routes stay allowed — /family-display is public while /family and /display are not, so a prefix rule would have taken it down with them. Also: page titles doubled the brand. Live: `Security & Privacy — Bubaly · Bubaly` and `Contact Bubaly · Bubaly`. The titles come from the admin SEO store, not the catalogue (0 catalogue strings end that way), so correcting the rows would only last until someone typed another one. The layout template owns the brand, so a self-branded title now opts out of it via Next's `absolute`. Only a TRAILING brand counts — "Bubaly — The AI Family Operating System" still wants the suffix — and the separator boundary keeps "Meet the Bubalys" untouched. finalaudit.md records all of this, including what was checked and found healthy, one finding I withdrew after re-checking (a missing-metadata reading that was my own extraction bug), and the measured i18n payload problem left open for a decision rather than fixed blind. tsc clean, lint 0 errors, i18n gate clean, 1148 files / 13091 tests, build exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Stop presenting seeded rows as real customer stories Found by crawling the internal links on the live site. The homepage and /pricing both render a section headed "Family stories", introducing what follows as quotes "In the family's own words". What followed were three database seed rows: Seed Case Studies #450 Seed Summary value 450 Seed Result Metric value 450 Seed Case Studies #180 Seed Summary value 180 Seed Result Metric value 180 Seed Case Studies #90 Seed Summary value 90 Seed Result Metric value 90 Each linked to a detail page answering 200 with <h1>Seed Case Studies #450</h1> at /customers/seed-case_studies-450. Same class as the sitemap defect on this branch — seeded rows reaching a public surface — but worse in kind. That one published dead links; this publishes invented customers under a heading that states they are real families. is_published could never have caught it: the seeder sets it true, which is what made them public. The rows are identifiable only by the seeder's slug convention, `seed-<table>-<n>`. isSyntheticSeedSlug() therefore joins publishedOnly() in the shared reputation helpers, so both pages' list reader excludes them, and loadPublishedCaseStudy() refuses one BEFORE touching the database — a link surviving in a cache or a search index must not still render one. The list and the detail page agreeing is the entire point: the sitemap defect existed precisely because two readers of the same content did not agree. Deliberately narrow, and both halves pinned: - testimonials have no `slug` column, so they pass through untouched. The predicate takes null/undefined and answers false. - the marker must be at the START of the slug, so a genuine story called `seeds-of-change` or `seed-starting-with-kids` is not swept up. Verified non-vacuous: without the filter, `keeps seeded rows out of the public list` and `404s a seeded story on its detail page without querying` both fail. The existing marketing-reputation and customer-story suites pass unchanged. The rows themselves remain in the database. The site no longer renders them, which is the visible outcome, but deleting them is an admin action this session has no credentials for; finalaudit.md records that. tsc clean, lint 0 errors, 1149 files / 13098 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Give five public pages the h1 they never had Verified against production: /faq, /contact, /mobile and /family-display each returned ZERO <h1> elements and began their document outline at <h2>. All four used SectionHeading for their PAGE title, and that component hard-coded h2. Pages with a hand-rolled hero (/, /pricing, /features) render exactly one h1 and were fine — which is why this only ever hit the pages that reused the shared component. A missing h1 costs screen-reader users their primary way of orienting on a page, and drops the strongest on-page relevance signal for search. SectionHeading now takes an optional `as="h1"`, defaulting to h2 so every existing section heading is untouched. The class list is identical at either level and a test asserts that: this is a document-outline change and those pages must look exactly as they did. THE FIFTH PAGE. The structural test caught /resources/benchmarks, which probing the live site never could — it answers 404 while its admin publication flag is off. Same defect, same fix, found only because the check reads the tree rather than the deployment. A VACUOUS TEST, CAUGHT. The first version of the page sweep passed with the fix reverted. The JSDoc I had just written on SectionHeading contains the literal `as="h1"` as an instruction to callers, so every page importing that module matched on the COMMENT rather than on its own markup. Found by reverting /faq and watching the test stay green — the same failure as the sitemap closed-loop test earlier on this branch, which is sharp enough twice to be worth writing down. The sweep strips comments before scanning now, and reverting /faq correctly fails and names it. The sweep also resolves each page's local imports one level deep. Without that it reported /pricing, /privacy, /terms and others as missing an h1 when their hero simply lives in a sibling component — all three were confirmed live to render exactly one. tsc clean, lint 0 errors, i18n gate clean, 1150 files / 13102 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Close the audit: finalaudit.md records every finding and its owner Thirteen findings. Seven fixed and shipped on this branch, one disproved and withdrawn, five closed as decisions or assigned to someone with access this session does not have. Every remaining item has an owner and a next step. The substantial work in this commit is the investigation behind F9 — the whole 848 KB catalogue serialized into every page — which is now closed as a decision rather than left as an open question. Measured what the public site actually needs, as a transitive import closure per route group: marketing 81 client files 324 keys 21 KB 2.6% of the catalogue auth 62 client files 89 keys 5 KB 0.6% signed-in 1122 client files 8282 keys 442 KB 55.7% So the marketing site ships 793 KB to deliver 21 KB of value. Then established that pruning marketing and auth is SAFE rather than assuming it: 9 dynamic `t(variable)` call sites across both groups, every one reading from a static module inside the closure — MARKETING_NAV, VALUE_ROWS, the consent categories, the local LINKS map in legal-consent — none from the database. 36 of 36 keys reachable through them are in the computed set; zero missing. And disproved the tempting shortcut. A single global client-only subset needs no path detection and saves 40%, and it is WRONG: 123 catalogue keys are defined as literals in server components and handed to client components as props — admins.tab.users, supportTickets.tab.open, runHistory.filterActive, displaySetup.stepInstallTitle and 119 more. Every one would have rendered as a raw key in front of a person. Found by scanning for it rather than by shipping it. What is left is the delivery mechanism, and all of it turns on verification I cannot do here. Scoping by request path needs a header from middleware, whose own comment documents that mishandling its request/response pair makes Supabase treat a reused refresh token as stolen and revoke the entire session family. Per-group root layouts would be clean but mean deleting app/layout.tsx and moving eight ungrouped routes. Both risk surfaces are client-side hydration, and no browser runs in this environment — Chromium dies in the proxy relay for every host including google.com, which the document states plainly so nothing in it is over-read. The scaffolding written while proving this out — a scope generator, a scope module and a generated key manifest — is deliberately NOT committed. It is not wired to anything, and unused machinery in a repo is a claim that something is handled when it is not. Two process notes are recorded rather than quietly corrected: a finding I withdrew after re-checking (a missing-metadata reading that was my own extraction bug), and a vacuous test I caught in the fix for F11 — it passed with the fix reverted because the JSDoc under test contains the literal it grepped for, which is the same failure as F4. tsc clean, 1150 files / 13102 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Record the sitemap finding this audit's method could not see A parallel session raised it on #526; verified independently before recording: GET /blog?category=Parenting -> 200 <link rel="canonical" href="https://www.bubaly.com/blog"/> app/sitemap.ts emits one entry per blog category, nine of them. Each answers 200 and then names /blog as its canonical, so the sitemap asks Google to index URLs the pages themselves declare are not the canonical version. The entries are dropped and the crawl budget is spent anyway. Same family as F1 — the sitemap claiming something the site does not support — and squarely in this audit's scope. I missed it because I checked whether every sitemap URL RESOLVED, which they do, and never checked whether it was INDEXABLE. That is a gap in the method, not only in the sitemap, so it is written down as one. Not fixed here on purpose. #526 rewrites the same file with a broader canonicalUrl() — trailing-slash normalisation, per-segment percent-encoding, and refusal of query strings, fragments, non-relative paths and anything robots.txt disallows, reading that disallow list from robots.ts so the two cannot drift. That is a better fix than a second patch of mine, and two PRs rewriting app/sitemap.ts in different directions helps nobody. The overlap is stated plainly in the document rather than left for whoever merges second to discover: #526 also found F1 and F3 independently. Its URL hygiene is broader; the one property worth carrying across is that isAdvertisablePlatformPath() applies the SAME predicate the renderer uses, so the sitemap and the blog page cannot drift apart again — which is the root cause F1 actually had. Nothing else on this branch touches #526's files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Sharpen two findings on re-examination, and close the dependency question F7 was understated. I had recorded "a Free family is given an address it cannot see". Tracing the whole path found the gate exists ONLY on the screen: Contact Center screen requirePlanLevel(2) — Family+ provisioning at onboarding none — every tier inbound webhook none lib/contact-center/* none The webhook resolves the family from the recipient local-part and proceeds. So a Free family has a working @bubaly.com address that receives mail, files it, runs the AI concierge over it and auto-replies AS the family, while the product surface says Family+. The only thing they cannot do is look at the inbox. That cuts both ways, which is why it stays a decision rather than getting a default: either Free families are consuming AI inference and sending mail under the product's domain ungated, or the screen's requirePlanLevel(2) is the thing that is wrong. Gating the route would also start dropping mail for any Free address already in circulation, so the ordering matters and is written down. Dependency security is closed rather than open, and checked rather than assumed. npm audit reports 8 advisories; every package was resolved against the production tree and NONE of them appear in it — js-yaml, tar, brace-expansion, browserslist, @xmldom/xmldom, vitest, @vitest/mocker, baseline-browser-mapping are all build and test tooling. `npm audit --omit=dev --audit-level=moderate`, the exact command CI runs, reports 0. There is no site finding here, so it is recorded as closed with the evidence instead of sitting on a list. F9 gains a third mechanism that is better than both I had, and removes the objection I raised against them. A nested provider needs no middleware change and no app-router restructure: the root layout keeps LocaleProvider with only the keys routes outside app/(app) can reach, and app/(app)/layout.tsx — which already exists — renders a nested one carrying the full catalogue, whose context wins for signed-in pages. Measured: the root set is 702 keys, 44 KB of 793 KB. Public pages drop ~94%; signed-in pages carry 44 KB extra, behind a login. Completeness leaves exactly three uncovered keys, all marketingDisplay.* in family-display/page.tsx, defined in the server page and passed to a client component — the same pattern that makes a global subset unsafe, but three keys in one file rather than 123 across the admin console. Still not shipped, and the reason is unchanged and specific: the blast radius is every public page and this environment cannot render one. What is different is that the remaining risk is now "is a key missing", which a regeneration test settles exhaustively without a browser — so whoever picks it up has a mechanism, a number, and three named gaps rather than a research task. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 * Put the audit document in reading order F14 was appended after "Verified healthy" because it was recorded last, which put a finding after the section that says what had no findings. It now sits with F1-F13 where a reader looks for it. Three headings disagreed with their own status rows after the items were reassigned this round — F5 and F6 to operator ownership, F12 to closed. The heading and the summary table now say the same thing about each. No content changed; this is the deliverable and it should read as one document. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4 --------- Co-authored-by: Claude <noreply@anthropic.com>
This branch was successfully deployed
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.
Onboarding previously offered paste/demo calendar imports and sent connected-calendar users out to the dashboard. This implements the W8 gap from
MARKET_DOMINATION_AUDIT.mdand the coordination claim in #421: configured Google/Microsoft buttons now return to the owned wizard, show a real private calendar preview and first brief, and import the reviewed appointments at Finish.manualdirection. Every generic and legacy sync entry re-reads persisted policy before token, provider or job work. Finish enables calendar imports; it never enables calendar/task exports or task imports for these accounts.calendar_eventsmaterializer and stable provider identities. Replayed or changed previews preserve one managed member/invite and one event copy. Local edits/deletions are preserved. Only explicit cancellation tombstones delete untouched rows; an event absent from a bounded read is retained. Orphaned mappings cannot establish a deletion baseline.Validation: the full native-Windows suite passed 1,003 files / 9,623 tests, with the same five pre-existing Windows path failures across household-trail, i18n-server-boundary (two), supabase-query-audit and untrusted-fence-ratchet. Subsequent provider/date and cancellation-recovery regressions passed 93 targeted tests; the final status change passed another 37 targeted tests. Typecheck, changed-file ESLint, i18n gate and diff check passed.
Provider exchange/read behavior is tested through real routes, actions, services and engines with mocked provider/DB boundaries. No live provider account was exercised. Buttons require existing provider credentials and
SYNC_TOKEN_KEY; Apple remains on its existing CalDAV path. The existing legacy membership editors are recorded in the service-layer ratchet now that onboarding owner provisioning has a service writer.