Docs SPA migration, portal theming, onboarding, nav toolbars, and backend hardening#23
Conversation
Point the PostHog browser SDK at the first-party managed reverse proxy (a.amend.sh) instead of us.i.posthog.com so events are not blocked by ad blockers. Set ui_host so the toolbar still resolves to us.posthog.com, and drop VITE_POSTHOG_HOST from the env examples since the proxy host is now the hardcoded default. Generated-By: PostHog Code Task-Id: 4a587277-acc5-4751-85c3-3ebdaf4355b2
Greptile SummaryThis is a large accumulation PR covering five workstreams: a full TanStack Start SPA migration for the docs app (removing Next.js App Router), portal theming with four presets and a sandboxed custom CSS paste mode, dashboard onboarding, a home page redesign, and—most critically—backend security hardening plus the PostHog managed reverse proxy.
Confidence Score: 5/5Safe to merge with the deployment prerequisite actioned first: provision GITHUB_WEBHOOK_SECRET before deploying to avoid webhooks being rejected. The security hardening changes are correct, well-tested, and well-structured. The trusted/public mutation split cleanly separates the two auth paths. Portal theming is sandboxed via an allowlist and React inline styles only. The two observations raised are both style-level: a missing useMemo in the settings panel and a silent no-op for var() references in custom CSS. No files require special attention beyond the deployment prerequisite (GITHUB_WEBHOOK_SECRET) called out in the PR description. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[HTTP Action\nrestGet / restPost] --> B{requiresGetApiToken\nor requiresApiToken?}
B -- "No (public resource)" --> C[Execute handler directly]
B -- "Yes (protected resource)" --> D[verifyApiToken]
D -- "AMEND_API_TOKEN absent\n+ non-local SITE_URL" --> E["401\nMissing token config"]
D -- "Token present\nbut wrong" --> F["401\nInvalid token"]
D -- "Token OK" --> G{Which action?}
G -- "source-events / changelog\n/ roadmap / deliveries..." --> H[ctx.runMutation\ninternal.amend.trustedXxx\ninternalMutation - no re-auth]
G -- "public mutation path" --> I[api.amend.xxx\nmutation - requires dashboard user]
J[GitHub Webhook] --> K[verifyGitHubSignature]
K -- "No secret\n+ production SITE_URL" --> L["401\nMissing webhook secret"]
K -- "No secret\n+ local SITE_URL" --> M[allowUnsigned = true\npass through]
K -- "Secret present" --> N[HMAC verify]
N -- "OK" --> H
Reviews (3): Last reviewed commit: "fix(fumadocs): declare tailwind-merge de..." | Re-trigger Greptile |
| posthog.capture("$pageview", { | ||
| $current_url: window.location.href, | ||
| path, | ||
| $current_url: analyticsPath(window.location.href), | ||
| path: safePath, | ||
| }); |
There was a problem hiding this comment.
$current_url now receives a bare pathname (e.g. /dashboard) instead of a full absolute URL. analyticsPath returns only url.pathname, stripping the origin. PostHog uses $current_url for URL-based insights, funnels, and session replay; a relative path breaks all URL-pattern filtering in the dashboard. The path custom property carries the stripped pathname already, so $current_url should still include the origin. Score: 4/5 confident this is a real regression.
| posthog.capture("$pageview", { | |
| $current_url: window.location.href, | |
| path, | |
| $current_url: analyticsPath(window.location.href), | |
| path: safePath, | |
| }); | |
| posthog.capture("$pageview", { | |
| $current_url: `${window.location.origin}${safePath}`, | |
| path: safePath, | |
| }); |
| api_host: optionalClientEnv("VITE_POSTHOG_HOST") ?? defaultPostHogHost, | ||
| ui_host: "https://us.posthog.com", |
There was a problem hiding this comment.
api_host still reads VITE_POSTHOG_HOST from env (allowing overrides), but ui_host is always hardcoded to https://us.posthog.com. If a developer or CI environment sets VITE_POSTHOG_HOST to an EU-region proxy, the toolbar and PostHog app links will still route to the US region, causing broken toolbar sessions or mismatched project lookups. Consider deriving ui_host from the region implied by VITE_POSTHOG_HOST or documenting that only US-region is supported. Score: 2/5 — unlikely to affect most users but a real inconsistency for EU-region overrides.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…updates Large feature batch landing on top of the managed reverse-proxy work. Docs (apps/fumadocs) - Migrate the docs site from the Next.js App Router to a TanStack Start SPA (Vite + router.tsx + file-based routes); drop the Next.js app/, postcss, and proxy.ts; add changelog/feedback/roadmap content. Web app (apps/web) - Onboarding: welcome/showcase panels, onboarding flow, and a dashboard onboarding checklist + model. - Navigation: move per-page sub-nav into top toolbars (dashboard, changelog, feedback, roadmap). - Icons: centralize on a Hugeicons wrapper (@/lib/icons) + brand icons. - Portal theming: shadcn-token portal themes with a settings picker; portal rail. - Motion: useDisclosureTransition hook + motion.css (t-* classes), MOTION.md. - Home: rebuild hero (static hero-loop asset) and sections; remove the particle hero and the memory/product sections; add a connect section. - Remove the proactivation surface; keep its design doc under docs/. Backend (packages/backend) - Update amend mutation handlers (content, custom domain, delivery, source ingest), workspace settings, REST endpoints, signatures, and schema tables. Tooling - Exclude generated route trees from oxfmt; refresh deps/lockfile, AGENTS.md, README, and env examples. bun run check passes: lint, format, types, repo hygiene, source size, 102 tests. Generated-By: PostHog Code Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
Greptile P2: the module-level DECLARATION_RE carries the /g flag, so exec() mutates lastIndex across calls. Iterate with String.prototype.matchAll so the parser holds no shared mutable state. Generated-By: PostHog Code Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
…m CSS Greptile pass 2: - P1: the portal settings sync effect depended on the whole `workspace` object, which gets a new reference on every Convex mutation. Any unrelated workspace write re-ran the effect and clobbered in-progress portal edits. Narrow the dep array to the specific portalSettings fields the effect reads. - P2: cap persisted `customThemeCss` at 50k chars in the update handler so a workspace owner can't store unbounded multi-megabyte documents. Generated-By: PostHog Code Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
…ations Greptile pass 3 (custom CSS parser): - Strip /* ... */ block comments before token matching so a commented-out declaration can't shadow the live one via iteration order. - Strip @media wrappers before extracting :root/.dark so a prefers-color-scheme-scoped :root can't be mistaken for the base block, regardless of whether it appears before or after it. @layer wrappers are left intact since shadcn exports nest the real tokens inside them. Adds scripts/portal-themes.test.ts covering the happy path, the allowlist, and both edge cases. Generated-By: PostHog Code Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
…ath fallback Greptile pass 4 (quality observations): - Parse custom portal CSS once in the settings panel and pass the result into resolvePortalTheme (new optional arg) instead of re-parsing on every keystroke. - Generalize the at-rule strip from @media to the conditional group rules (@media / @supports / @container) so a feature-query-scoped :root can't shadow the base block either; @layer stays intact. - analyticsPath: use `|| "/"` so an empty path (e.g. a bare "?query") actually falls back to "/"; the previous `?? "/"` was unreachable. Generated-By: PostHog Code Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
… open mount
Greptile pass 5:
- P1: the onboarding checklist read its dismissed flag from localStorage in a
useState initializer that only runs on mount, so switching projects (no
remount) carried the previous project's dismissed state and hid the new
project's checklist. Add key={activeProject.id} at the call site so the
per-project state resets on switch.
- P2: useDisclosureTransition reset to the pre-state in an effect even when it
mounted already-open, flashing through two frames. Track the previous `open`
and replay the entrance only on a real closed→open transition; open-on-mount
now stays in the open phase. Behaviour is unchanged for the current
(closed-on-mount) callers.
Generated-By: PostHog Code
Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
apps/fumadocs/src/lib/cn.ts imports tailwind-merge directly, but it was only declared in @amend/ui. It resolved locally via hoisting, so check-types passed, but a clean `bun install --frozen-lockfile` in CI nested it under @amend/ui only and fumadocs' tsc failed with TS2307. Declare it on fumadocs (same ^3.6.0 as @amend/ui so they dedupe) so the frozen install resolves it. Generated-By: PostHog Code Task-Id: db14a484-b04e-45e2-82a7-a88618b6dd30
Overview
Batches several independent workstreams on top of the original managed reverse-proxy change (this is the accumulated feature branch for production).
Docs site → TanStack Start SPA
apps/fumadocsfrom the Next.js App Router to a TanStack Start SPA (Vite + Nitro for server-only routes: search, sitemap, llms, schemas).app/, PostCSS, andproxy.ts; add file-based routes undersrc/routes/.Portal theming
APPLIED_TOKENSallowlist and applied via React inline style props only (nodangerouslySetInnerHTML); block comments and@media/@supports/@containerwrappers are stripped before matching:root/.dark.customThemeCsscapped at 50k chars server-side.Dashboard onboarding + navigation
@/lib/icons); shareduseDisclosureTransitionmotion hook.Home redesign
Backend security hardening
requiresApiTokenis now fail-secure in production even whenAMEND_API_TOKENis absent; newrequiresGetApiTokenguards sensitive GET endpoints.trusted*mutation variants (separating API-token auth from dashboard-session auth).verifyGitHubWebhookSignaturerejects unsigned requests in production. Newsecurity-regressions.test.tslocks these in.PostHog managed reverse proxy (original change)
https://a.amend.shto dodge ad-blocker drops;analyticsPath()strips query/hash from pageview URLs.verifyGitHubWebhookSignatureis now fail-secure: a production workspace with GitHub connected but noGITHUB_WEBHOOK_SECRETwill start rejecting inbound webhooks (401) after deploy. ProvisionGITHUB_WEBHOOK_SECRETin the production Convex environment before/with this deploy.Validation
bun run checkpasses locally: lint, format, types, repo hygiene, source size, 109 tests. Reviewed across 6 local Greptile passes; all actionable findings fixed.Created with PostHog Code