From c6bccc8ba241159cb707fd7534d6972557e52af1 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 13 Jun 2026 14:12:03 -0400 Subject: [PATCH 1/8] feat(web): route PostHog through managed reverse proxy 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 --- .env.production.example | 1 - apps/web/.env.example | 1 - apps/web/src/lib/posthog.ts | 18 +++++++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.env.production.example b/.env.production.example index d868d42..5957638 100644 --- a/.env.production.example +++ b/.env.production.example @@ -3,7 +3,6 @@ VITE_CONVEX_URL=https://your-production-convex.convex.cloud VITE_CONVEX_SITE_URL=https://your-production-convex.convex.site VITE_DOCS_URL=https://amend.sh/docs VITE_POSTHOG_TOKEN=phc_BCb25jVTo59jtEMPysgGUvgt85bUYGwN8XBNA2oMNLY7 -VITE_POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_PROJECT_ID=441195 POSTHOG_CLI_API_KEY=replace-with-error-tracking-write-api-key POSTHOG_CLI_HOST=https://us.posthog.com diff --git a/apps/web/.env.example b/apps/web/.env.example index 796f385..6996ed9 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -2,5 +2,4 @@ VITE_CONVEX_URL=http://127.0.0.1:3210 VITE_CONVEX_SITE_URL=http://127.0.0.1:3211 VITE_DOCS_URL=http://docs.amend.localhost:1355/docs VITE_POSTHOG_TOKEN=phc_BCb25jVTo59jtEMPysgGUvgt85bUYGwN8XBNA2oMNLY7 -VITE_POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_PROJECT_ID=441195 diff --git a/apps/web/src/lib/posthog.ts b/apps/web/src/lib/posthog.ts index 710b000..2905abd 100644 --- a/apps/web/src/lib/posthog.ts +++ b/apps/web/src/lib/posthog.ts @@ -3,7 +3,7 @@ import type { PostHogConfig } from "posthog-js"; import { optionalClientEnv } from "@/lib/client-env"; -const defaultPostHogHost = "https://us.i.posthog.com"; +const defaultPostHogHost = "https://a.amend.sh"; const defaultPostHogProjectId = "441195"; const defaultPostHogToken = "phc_BCb25jVTo59jtEMPysgGUvgt85bUYGwN8XBNA2oMNLY7"; @@ -53,6 +53,7 @@ export function getPostHogToken() { export function getPostHogOptions(): Partial { return { api_host: optionalClientEnv("VITE_POSTHOG_HOST") ?? defaultPostHogHost, + ui_host: "https://us.posthog.com", autocapture: false, capture_exceptions: true, capture_pageleave: false, @@ -94,10 +95,11 @@ export async function capturePostHogPageview(path: string) { } const posthog = await loadPostHog(); + const safePath = analyticsPath(path); posthog.capture("$pageview", { - $current_url: window.location.href, - path, + $current_url: analyticsPath(window.location.href), + path: safePath, }); } @@ -135,6 +137,16 @@ export async function identifyAndCapturePostHogEvent({ posthog.capture(postHogEventNames[event], cleanProperties(properties)); } +export function analyticsPath(value: string) { + try { + const baseUrl = typeof window === "undefined" ? "https://amend.sh" : window.location.origin; + const url = new URL(value, baseUrl); + return url.pathname; + } catch { + return value.split(/[?#]/, 1)[0] ?? "/"; + } +} + function cleanProperties(properties: PostHogEventProperties) { return Object.fromEntries( Object.entries(properties).filter( From f1e4ff3d825b340b486ed3950f087f67c23f62c1 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 13 Jun 2026 21:51:42 -0400 Subject: [PATCH 2/8] feat: docs SPA, onboarding, toolbar nav, portal theming, and backend 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 --- .env.production.example | 1 + .oxfmtrc.json | 6 +- AGENTS.md | 10 + README.md | 12 +- apps/fumadocs/.gitignore | 14 +- apps/fumadocs/AGENTS.md | 67 ++ .../content/docs/changelog/ai-drafting.mdx | 46 + .../fumadocs/content/docs/changelog/index.mdx | 32 + .../fumadocs/content/docs/changelog/meta.json | 4 + .../content/docs/changelog/writing.mdx | 67 ++ .../content/docs/feedback/collecting.mdx | 102 ++ apps/fumadocs/content/docs/feedback/index.mdx | 38 + apps/fumadocs/content/docs/feedback/meta.json | 4 + .../content/docs/feedback/triaging.mdx | 55 + apps/fumadocs/content/docs/index.mdx | 21 +- apps/fumadocs/content/docs/meta.json | 7 + apps/fumadocs/content/docs/roadmap/index.mdx | 42 + .../content/docs/roadmap/managing.mdx | 67 ++ apps/fumadocs/content/docs/roadmap/meta.json | 4 + apps/fumadocs/content/docs/roadmap/public.mdx | 39 + apps/fumadocs/next.config.mjs | 19 - apps/fumadocs/package.json | 53 +- apps/fumadocs/postcss.config.mjs | 7 - apps/fumadocs/proxy.ts | 29 - apps/fumadocs/src/app/(home)/layout.tsx | 6 - apps/fumadocs/src/app/api/chat/route.ts | 114 --- apps/fumadocs/src/app/api/search/route.ts | 7 - .../src/app/docs/[[...slug]]/page.tsx | 101 -- apps/fumadocs/src/app/docs/layout.tsx | 31 - apps/fumadocs/src/app/layout.tsx | 54 - apps/fumadocs/src/app/llms-full.txt/route.ts | 14 - .../app/llms.mdx/docs/[[...slug]]/route.ts | 23 - .../src/app/og/docs/[...slug]/route.tsx | 29 - apps/fumadocs/src/app/robots.txt/route.ts | 11 - .../route.ts | 11 - .../route.ts | 11 - .../route.ts | 11 - .../route.ts | 11 - apps/fumadocs/src/app/sitemap.xml/route.ts | 76 -- .../src/components/ai/search-context.tsx | 63 -- .../src/components/ai/search-controls.tsx | 191 ---- .../src/components/ai/search-messages.tsx | 144 --- .../src/components/ai/search-panel.tsx | 94 -- apps/fumadocs/src/components/ai/search.tsx | 6 - apps/fumadocs/src/components/markdown.tsx | 116 --- apps/fumadocs/src/components/mdx.tsx | 2 + apps/fumadocs/src/components/screenshot.tsx | 57 ++ apps/fumadocs/src/components/ui/button.tsx | 29 - ...-ready-completion-audit-report.schema.json | 0 .../agent-ready-live-report.schema.json | 0 .../agent-ready-production-report.schema.json | 0 .../agent-ready-status-report.schema.json | 0 apps/fumadocs/src/lib/shared.ts | 3 +- apps/fumadocs/src/lib/source.ts | 21 +- apps/fumadocs/src/routeTree.gen.ts | 240 +++++ apps/fumadocs/src/router.tsx | 20 + apps/fumadocs/src/routes/__root.tsx | 45 + apps/fumadocs/src/routes/api/search.ts | 17 + apps/fumadocs/src/routes/docs/$.tsx | 107 ++ .../{app/(home)/page.tsx => routes/index.tsx} | 46 +- apps/fumadocs/src/routes/llms-full[.]txt.ts | 17 + apps/fumadocs/src/routes/llms[.]mdx/docs/$.ts | 19 + .../route.ts => routes/llms[.]txt.ts} | 22 +- apps/fumadocs/src/routes/robots[.]txt.ts | 14 + apps/fumadocs/src/routes/schemas/$.ts | 30 + apps/fumadocs/src/routes/sitemap[.]xml.ts | 74 ++ .../src/{app/global.css => styles.css} | 12 +- apps/fumadocs/tsconfig.json | 42 +- apps/fumadocs/vercel.json | 2 +- apps/fumadocs/vite.config.ts | 32 + apps/web/MOTION.md | 49 + apps/web/package.json | 42 +- apps/web/public/images/hero-loop.webp | Bin 0 -> 44302 bytes .../components/amend-dashboard-constants.ts | 10 +- .../amend-dashboard-content-types.ts | 10 +- .../components/amend-dashboard-content.tsx | 6 +- .../components/amend-dashboard-core-types.ts | 12 +- .../src/components/amend-dashboard-data.tsx | 2 +- .../amend-dashboard-main-workspace.tsx | 130 ++- .../src/components/amend-dashboard-shared.tsx | 6 +- .../amend-dashboard-status-utils.ts | 3 - .../src/components/amend-dashboard-status.tsx | 2 +- apps/web/src/components/amend-dashboard.tsx | 15 +- apps/web/src/components/amend-logo.tsx | 13 +- apps/web/src/components/brand-mark.tsx | 32 +- .../web/src/components/brand-menu-popover.tsx | 2 +- .../components/changelog-editor-header.tsx | 284 ++++-- .../src/components/changelog-editor-main.tsx | 370 +++++-- .../components/changelog-editor-sidebar.tsx | 233 ++--- .../src/components/changelog-editor-types.ts | 27 +- .../components/changelog-editor-workspace.tsx | 176 ++-- apps/web/src/components/changelog-toolbar.tsx | 78 ++ .../src/components/changelog-workspace.tsx | 6 +- .../components/dashboard-detail-shared.tsx | 2 +- .../components/dashboard-header-filters.tsx | 137 +-- apps/web/src/components/dashboard-header.tsx | 60 +- .../dashboard-module-sidebar-admin.tsx | 115 +-- .../dashboard-module-sidebar-primitives.tsx | 3 - .../dashboard-module-sidebar-roadmap.tsx | 2 +- .../components/dashboard-module-sidebar.tsx | 71 +- .../dashboard-onboarding-checklist.tsx | 210 ++++ .../components/dashboard-onboarding-model.ts | 112 +++ .../components/dashboard-sidebar-chrome.tsx | 31 +- apps/web/src/components/dashboard-toolbar.tsx | 60 ++ .../src/components/dashboard-user-menu.tsx | 141 ++- .../web/src/components/dashboard-view-nav.tsx | 29 +- .../dashboard-workspace-surface.tsx | 7 +- .../dashboard-workspace-switcher.tsx | 16 +- .../components/dev-demo-sign-in-button.tsx | 121 ++- .../src/components/dev-demo-sign-in-model.ts | 32 + .../feedback-detail-comments-panel.tsx | 10 +- .../src/components/feedback-detail-header.tsx | 6 +- .../components/feedback-detail-workspace.tsx | 10 +- apps/web/src/components/feedback-toolbar.tsx | 37 + .../animated-hero-mark-particle-factory.ts | 58 -- .../animated-hero-mark-particle-physics.ts | 51 - .../animated-hero-mark-particle-renderer.ts | 38 - .../animated-hero-mark-particle-shapes.ts | 112 --- .../home/animated-hero-mark-particle-types.ts | 22 - .../home/animated-hero-mark-particle-utils.ts | 5 - .../home/animated-hero-mark-particles.ts | 4 - .../components/home/animated-hero-mark.tsx | 127 --- apps/web/src/components/home/brand-icons.tsx | 47 + .../components/home/home-connect-section.tsx | 64 ++ apps/web/src/components/home/home-content.ts | 261 ++--- .../components/home/home-features-section.tsx | 56 +- apps/web/src/components/home/home-footer.tsx | 191 ++-- apps/web/src/components/home/home-header.tsx | 29 +- apps/web/src/components/home/home-hero.tsx | 92 +- .../components/home/home-memory-section.tsx | 28 - .../components/home/home-pricing-section.tsx | 175 ++-- .../components/home/home-product-sections.tsx | 4 - .../web/src/components/home/home-sections.tsx | 156 +-- .../components/home/home-workflow-section.tsx | 144 ++- .../src/components/home/use-landing-motion.ts | 49 +- apps/web/src/components/onboarding-flow.tsx | 66 ++ .../components/onboarding-showcase-panel.tsx | 55 + .../components/onboarding-welcome-panel.tsx | 42 + .../src/components/portal-account-actions.tsx | 12 +- .../portal-feedback-submission-panel.tsx | 13 +- .../src/components/portal-list-elements.tsx | 59 +- .../post-composer-board-popover.tsx | 12 +- .../web/src/components/post-composer-demo.tsx | 2 +- .../post-composer-discard-dialog.tsx | 6 +- .../components/post-composer-editor-body.tsx | 4 +- .../post-composer-editor-tool-panel.tsx | 20 +- .../post-composer-footer-controls.tsx | 8 +- .../src/components/post-composer-footer.tsx | 12 +- .../src/components/post-composer-header.tsx | 18 +- .../src/components/post-composer-modal.tsx | 4 +- .../post-composer-people-date-popovers.tsx | 18 +- .../post-composer-popover-primitives.tsx | 4 +- .../post-composer-selection-toolbar.tsx | 4 +- .../post-composer-status-tag-popovers.tsx | 16 +- .../post-composer-toolbar-button.tsx | 2 +- apps/web/src/components/posts-workspace.tsx | 59 +- .../proactivation-activity-feed.tsx | 83 -- .../proactivation-agent-metrics.tsx | 34 - .../proactivation-analytics-panel.tsx | 127 --- .../components/proactivation-channel-list.tsx | 155 --- .../proactivation-inspector-block.tsx | 39 - ...proactivation-inspector-control-panels.tsx | 172 ---- ...roactivation-inspector-evidence-panels.tsx | 82 -- .../components/proactivation-inspector.tsx | 77 -- .../components/proactivation-main-panel.tsx | 100 -- .../components/proactivation-workspace.tsx | 65 -- apps/web/src/components/project-logo.tsx | 2 +- ...project-setup-github-repository-picker.tsx | 2 +- ...roject-setup-repository-directory-list.tsx | 2 +- .../src/components/project-setup-shell.tsx | 28 +- .../components/project-setup-step-status.tsx | 2 +- .../public-portal-feedback-section.tsx | 130 +-- .../src/components/public-portal-header.tsx | 83 +- .../web/src/components/public-portal-hero.tsx | 27 +- .../web/src/components/public-portal-rail.tsx | 103 ++ .../public-portal-roadmap-updates.tsx | 125 +-- .../web/src/components/public-portal-types.ts | 12 + .../web/src/components/public-portal-view.tsx | 58 +- apps/web/src/components/roadmap-card.tsx | 2 +- apps/web/src/components/roadmap-column.tsx | 2 +- .../components/roadmap-detail-workspace.tsx | 8 +- apps/web/src/components/roadmap-toolbar.tsx | 69 ++ .../settings-workspace-accounts-panel.tsx | 2 +- .../settings-workspace-automation-panel.tsx | 2 +- .../settings-workspace-controller-state.ts | 33 +- .../settings-workspace-general-panel.tsx | 34 +- .../components/settings-workspace-header.tsx | 2 +- .../settings-workspace-panel-primitives.tsx | 2 +- .../components/settings-workspace-panels.tsx | 19 + .../settings-workspace-portal-panel.tsx | 199 +++- .../settings-workspace-services-panel.tsx | 6 +- .../components/settings-workspace-sidebar.tsx | 2 +- .../web/src/components/settings-workspace.tsx | 104 +- apps/web/src/components/sign-in-form.tsx | 21 +- .../use-amend-dashboard-controller.ts | 9 +- .../components/use-amend-dashboard-model.ts | 16 +- .../components/use-disclosure-transition.ts | 81 ++ .../components/use-proactivation-actions.ts | 194 ---- .../use-proactivation-controller.ts | 72 -- .../use-settings-workspace-controller.ts | 6 + .../use-settings-workspace-save-actions.ts | 3 + apps/web/src/components/user-menu.tsx | 2 +- apps/web/src/index.css | 304 +++++- apps/web/src/lib/icons.tsx | 169 ++++ apps/web/src/lib/portal-themes.ts | 288 ++++++ apps/web/src/lib/toast.ts | 4 +- apps/web/src/routes/embed-demo.tsx | 2 +- apps/web/src/routes/index.tsx | 23 +- apps/web/vite.config.ts | 33 + bun.lock | 950 ++++++------------ docs/proactive-agent-design.md | 340 +++++++ package.json | 18 +- packages/api-spec/package.json | 2 +- packages/backend/convex/amend.ts | 23 +- .../convex/amendContentMutationHandlers.ts | 16 + .../amendCustomDomainMutationHandlers.ts | 17 + .../convex/amendDeliveryMutationHandlers.ts | 17 + .../amendMutationFunctionDefinitions.ts | 43 +- packages/backend/convex/amendSourceIngest.ts | 10 +- packages/backend/convex/amendTypes.ts | 3 + .../convex/amendWorkspaceFunctionArgs.ts | 3 + .../amendWorkspaceSettingsMutationHandlers.ts | 14 +- packages/backend/convex/httpRestGet.ts | 9 + .../backend/convex/httpRestPostAutomation.ts | 6 +- .../backend/convex/httpRestPostSignals.ts | 6 +- .../backend/convex/httpRestPostWorkspace.ts | 8 +- packages/backend/convex/httpRuntimeAuth.ts | 33 +- .../convex/schemaWorkspaceCoreTables.ts | 3 + packages/backend/convex/signatures.ts | 6 +- packages/backend/package.json | 4 +- packages/cli/package.json | 2 +- packages/ui/package.json | 12 +- packages/ui/src/styles/amend.css | 21 + packages/ui/src/styles/motion.css | 4 +- packages/ui/src/styles/theme.css | 11 +- scripts/agent-ready-built-web.ts | 1 - scripts/agent-ready-docs-surface-tests.ts | 75 +- scripts/docs-schema-copies.test.ts | 2 +- scripts/github-signatures.test.ts | 11 +- scripts/security-regressions.test.ts | 78 ++ scripts/setup-agentic-convex.ts | 43 +- 241 files changed, 7028 insertions(+), 5574 deletions(-) create mode 100644 apps/fumadocs/AGENTS.md create mode 100644 apps/fumadocs/content/docs/changelog/ai-drafting.mdx create mode 100644 apps/fumadocs/content/docs/changelog/index.mdx create mode 100644 apps/fumadocs/content/docs/changelog/meta.json create mode 100644 apps/fumadocs/content/docs/changelog/writing.mdx create mode 100644 apps/fumadocs/content/docs/feedback/collecting.mdx create mode 100644 apps/fumadocs/content/docs/feedback/index.mdx create mode 100644 apps/fumadocs/content/docs/feedback/meta.json create mode 100644 apps/fumadocs/content/docs/feedback/triaging.mdx create mode 100644 apps/fumadocs/content/docs/roadmap/index.mdx create mode 100644 apps/fumadocs/content/docs/roadmap/managing.mdx create mode 100644 apps/fumadocs/content/docs/roadmap/meta.json create mode 100644 apps/fumadocs/content/docs/roadmap/public.mdx delete mode 100644 apps/fumadocs/next.config.mjs delete mode 100644 apps/fumadocs/postcss.config.mjs delete mode 100644 apps/fumadocs/proxy.ts delete mode 100644 apps/fumadocs/src/app/(home)/layout.tsx delete mode 100644 apps/fumadocs/src/app/api/chat/route.ts delete mode 100644 apps/fumadocs/src/app/api/search/route.ts delete mode 100644 apps/fumadocs/src/app/docs/[[...slug]]/page.tsx delete mode 100644 apps/fumadocs/src/app/docs/layout.tsx delete mode 100644 apps/fumadocs/src/app/layout.tsx delete mode 100644 apps/fumadocs/src/app/llms-full.txt/route.ts delete mode 100644 apps/fumadocs/src/app/llms.mdx/docs/[[...slug]]/route.ts delete mode 100644 apps/fumadocs/src/app/og/docs/[...slug]/route.tsx delete mode 100644 apps/fumadocs/src/app/robots.txt/route.ts delete mode 100644 apps/fumadocs/src/app/schemas/agent-ready-completion-audit-report.schema.json/route.ts delete mode 100644 apps/fumadocs/src/app/schemas/agent-ready-live-report.schema.json/route.ts delete mode 100644 apps/fumadocs/src/app/schemas/agent-ready-production-report.schema.json/route.ts delete mode 100644 apps/fumadocs/src/app/schemas/agent-ready-status-report.schema.json/route.ts delete mode 100644 apps/fumadocs/src/app/sitemap.xml/route.ts delete mode 100644 apps/fumadocs/src/components/ai/search-context.tsx delete mode 100644 apps/fumadocs/src/components/ai/search-controls.tsx delete mode 100644 apps/fumadocs/src/components/ai/search-messages.tsx delete mode 100644 apps/fumadocs/src/components/ai/search-panel.tsx delete mode 100644 apps/fumadocs/src/components/ai/search.tsx delete mode 100644 apps/fumadocs/src/components/markdown.tsx create mode 100644 apps/fumadocs/src/components/screenshot.tsx delete mode 100644 apps/fumadocs/src/components/ui/button.tsx rename apps/fumadocs/src/{app/schemas/_data => data/schemas}/agent-ready-completion-audit-report.schema.json (100%) rename apps/fumadocs/src/{app/schemas/_data => data/schemas}/agent-ready-live-report.schema.json (100%) rename apps/fumadocs/src/{app/schemas/_data => data/schemas}/agent-ready-production-report.schema.json (100%) rename apps/fumadocs/src/{app/schemas/_data => data/schemas}/agent-ready-status-report.schema.json (100%) create mode 100644 apps/fumadocs/src/routeTree.gen.ts create mode 100644 apps/fumadocs/src/router.tsx create mode 100644 apps/fumadocs/src/routes/__root.tsx create mode 100644 apps/fumadocs/src/routes/api/search.ts create mode 100644 apps/fumadocs/src/routes/docs/$.tsx rename apps/fumadocs/src/{app/(home)/page.tsx => routes/index.tsx} (67%) create mode 100644 apps/fumadocs/src/routes/llms-full[.]txt.ts create mode 100644 apps/fumadocs/src/routes/llms[.]mdx/docs/$.ts rename apps/fumadocs/src/{app/llms.txt/route.ts => routes/llms[.]txt.ts} (59%) create mode 100644 apps/fumadocs/src/routes/robots[.]txt.ts create mode 100644 apps/fumadocs/src/routes/schemas/$.ts create mode 100644 apps/fumadocs/src/routes/sitemap[.]xml.ts rename apps/fumadocs/src/{app/global.css => styles.css} (77%) create mode 100644 apps/fumadocs/vite.config.ts create mode 100644 apps/web/MOTION.md create mode 100644 apps/web/public/images/hero-loop.webp create mode 100644 apps/web/src/components/changelog-toolbar.tsx create mode 100644 apps/web/src/components/dashboard-onboarding-checklist.tsx create mode 100644 apps/web/src/components/dashboard-onboarding-model.ts create mode 100644 apps/web/src/components/dashboard-toolbar.tsx create mode 100644 apps/web/src/components/feedback-toolbar.tsx delete mode 100644 apps/web/src/components/home/animated-hero-mark-particle-factory.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark-particle-physics.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark-particle-renderer.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark-particle-shapes.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark-particle-types.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark-particle-utils.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark-particles.ts delete mode 100644 apps/web/src/components/home/animated-hero-mark.tsx create mode 100644 apps/web/src/components/home/brand-icons.tsx create mode 100644 apps/web/src/components/home/home-connect-section.tsx delete mode 100644 apps/web/src/components/home/home-memory-section.tsx delete mode 100644 apps/web/src/components/home/home-product-sections.tsx create mode 100644 apps/web/src/components/onboarding-flow.tsx create mode 100644 apps/web/src/components/onboarding-showcase-panel.tsx create mode 100644 apps/web/src/components/onboarding-welcome-panel.tsx delete mode 100644 apps/web/src/components/proactivation-activity-feed.tsx delete mode 100644 apps/web/src/components/proactivation-agent-metrics.tsx delete mode 100644 apps/web/src/components/proactivation-analytics-panel.tsx delete mode 100644 apps/web/src/components/proactivation-channel-list.tsx delete mode 100644 apps/web/src/components/proactivation-inspector-block.tsx delete mode 100644 apps/web/src/components/proactivation-inspector-control-panels.tsx delete mode 100644 apps/web/src/components/proactivation-inspector-evidence-panels.tsx delete mode 100644 apps/web/src/components/proactivation-inspector.tsx delete mode 100644 apps/web/src/components/proactivation-main-panel.tsx delete mode 100644 apps/web/src/components/proactivation-workspace.tsx create mode 100644 apps/web/src/components/public-portal-rail.tsx create mode 100644 apps/web/src/components/roadmap-toolbar.tsx create mode 100644 apps/web/src/components/use-disclosure-transition.ts delete mode 100644 apps/web/src/components/use-proactivation-actions.ts delete mode 100644 apps/web/src/components/use-proactivation-controller.ts create mode 100644 apps/web/src/lib/icons.tsx create mode 100644 apps/web/src/lib/portal-themes.ts create mode 100644 docs/proactive-agent-design.md create mode 100644 scripts/security-regressions.test.ts diff --git a/.env.production.example b/.env.production.example index 5957638..d868d42 100644 --- a/.env.production.example +++ b/.env.production.example @@ -3,6 +3,7 @@ VITE_CONVEX_URL=https://your-production-convex.convex.cloud VITE_CONVEX_SITE_URL=https://your-production-convex.convex.site VITE_DOCS_URL=https://amend.sh/docs VITE_POSTHOG_TOKEN=phc_BCb25jVTo59jtEMPysgGUvgt85bUYGwN8XBNA2oMNLY7 +VITE_POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_PROJECT_ID=441195 POSTHOG_CLI_API_KEY=replace-with-error-tracking-write-api-key POSTHOG_CLI_HOST=https://us.posthog.com diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 9218321..74416b1 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,3 +1,7 @@ { - "ignorePatterns": ["apps/web/src/routeTree.gen.ts", "packages/backend/convex/_generated/**"] + "ignorePatterns": [ + "apps/web/src/routeTree.gen.ts", + "apps/fumadocs/src/routeTree.gen.ts", + "packages/backend/convex/_generated/**" + ] } diff --git a/AGENTS.md b/AGENTS.md index 540be44..2d5ec33 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,16 @@ Do not install `recharts` — it has CJS/ESM interop issues with this Vite + TanStack Start setup (causes `require_isUnsafeProperty is not a function` at runtime). Use pure CSS/SVG bar charts instead (see `proactivation-analytics-panel.tsx` for the pattern: flex columns with `height: pct%` + `bg-foreground`). +## Documentation Sync + +**When you change a feature, update its docs in the same change.** The public docs live in `apps/fumadocs/content/docs/`. If your work changes what a user or integrating agent can observe — a customer-facing surface, an API route or SDK method, a CLI command, an automation rule, a config/env var, an auth scope, a setup or deploy step, or a workflow's behavior — the matching MDX page must be updated before the work is considered done. + +This applies to **content only**. Do not touch the Fumadocs framework, routing, or build config as part of feature work. + +- A pure internal refactor with no observable change does **not** require a docs edit. +- If a feature has **no** owning doc page yet, add a page (and register it in `content/docs/meta.json`) or flag the gap in your summary — do not silently leave it undocumented. +- The feature → doc-page map and detailed triggers live in `apps/fumadocs/AGENTS.md`. Read it before editing docs. + ## SirPaul PR Workflow SirPaul story work must go through a pull request. Do not treat "ship it" or similar language as permission to bypass a PR for SirPaul work unless the user explicitly says not to make a PR in the current turn. diff --git a/README.md b/README.md index 892963e..b90a7cc 100644 --- a/README.md +++ b/README.md @@ -186,9 +186,15 @@ bun run dev:setup -- --expiration "in 2 days" bun run dev:setup -- --worktree-name my-feature ``` -Then it runs `convex dev --once --tail-logs disable`, seeds safe default Convex env vars for the -worktree-local URL, and writes `apps/web/.env` from the generated `packages/backend/.env.local` -`CONVEX_URL` and `CONVEX_SITE_URL`. +Then it runs `bun install`, `convex dev --once --tail-logs disable`, seeds safe default Convex env +vars for the worktree-local URL, and writes `apps/web/.env` from the generated +`packages/backend/.env.local` `CONVEX_URL` and `CONVEX_SITE_URL`. + +If dependencies are already installed and you only want to refresh the Convex selection/env, use: + +```bash +bun run dev:setup -- --skip-install +``` For local anonymous deployments instead of cloud dev deployments, run: diff --git a/apps/fumadocs/.gitignore b/apps/fumadocs/.gitignore index 9e429e4..031a756 100644 --- a/apps/fumadocs/.gitignore +++ b/apps/fumadocs/.gitignore @@ -4,11 +4,14 @@ # generated content .source -# test & build -/coverage -/.next/ -/out/ +# tanstack start / vite / nitro build output +/.output +/.nitro +/.tanstack +/dist +/out /build +/coverage *.tsbuildinfo # misc @@ -22,5 +25,4 @@ yarn-error.log* # others .env*.local -.vercel -next-env.d.ts \ No newline at end of file +.vercel \ No newline at end of file diff --git a/apps/fumadocs/AGENTS.md b/apps/fumadocs/AGENTS.md new file mode 100644 index 0000000..6d2c38b --- /dev/null +++ b/apps/fumadocs/AGENTS.md @@ -0,0 +1,67 @@ +# Agent Guidelines — Docs (`apps/fumadocs`) + +This is the canonical public documentation for Amend. When a feature changes, the docs change with it — see **Documentation Sync** in the repo-root `AGENTS.md` for the core rule. This file tells you _which page_ to update and _when_. + +## Scope + +- Edit **content** only: `content/docs/*.mdx` and `content/docs/meta.json`. +- Do **not** change the Fumadocs framework, `source.config.ts`, app routing, layouts, search, or build config as part of feature work. A docs migration is a separate, explicit task. +- New pages must be registered in `content/docs/meta.json` (the `pages` array is the sidebar order). + +## When a docs edit is required + +Update docs in the same change when your work alters anything a user or integrating agent can **observe**: + +- A customer-facing surface (portal, feedback board, voting/comments/reactions, update feeds, embed panel) +- A REST route (`/api/v1/*`) or SDK method (`packages/sdk`, the `Amend` class) +- A CLI command or flag (`packages/cli`) +- An automation rule, AI drafting behavior, proactive-agent behavior, or delivery/safety gate +- A config value, env var, provider key, auth scope/token tier, or default +- A setup, onboarding, deploy, domain, or launch step +- The observable behavior or wording of any documented workflow + +You do **not** need a docs edit for a pure internal refactor, rename, or perf change with no observable difference. + +## Feature → doc-page map + +The docs are organized by **job in the loop** (intake → evidence → review → publish), not by UI screen. Find the surface you touched and update the page(s) that own it. + +| Feature / code area | Owning page(s) | +| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| Feedback inbox, posts board, post composer, voting, comments, reactions | `feedback/triaging.mdx`, `feedback/collecting.mdx` (+ `api-reference.mdx`) | +| Feedback intake — portal form + `feedbackMode`, SDK `submitRequest`, imports | `feedback/collecting.mdx` (+ `customer-surfaces.mdx`) | +| Embed panel — `createAmendPanel`, `packages/sdk` `embed.ts` | `customer-surfaces.mdx` | +| Roadmap board, drag/drop, priorities, item detail; status values + API mapping | `roadmap/managing.mdx`, `roadmap/index.mdx` (+ `source-trace.mdx`) | +| Public roadmap visibility (`roadmapVisibility`), voting, `GET /roadmap` | `roadmap/public.mdx` | +| Changelog editor, statuses, categories, versions, publish + email toggles | `changelog/writing.mdx` | +| AI changelog drafting (`draftChangelog`, `POST /drafts`, CLI `changelog draft`) | `changelog/ai-drafting.mdx` (+ `automation.mdx` for provider keys) | +| Source events — GitHub / Slack / Discord / Linear / CSV imports, event kinds, idempotency | `source-events.mdx` | +| Automation rules, proactive agent, build briefs, decisions, delivery safety | `automation.mdx` | +| SDK surface (`Amend` class methods) | `api-reference.mdx` (+ `customer-surfaces.mdx` for client read/write methods) | +| REST HTTP actions (Convex `/api/v1`), webhooks, auth tiers | `api-reference.mdx`, `integration.mdx` | +| CLI commands (`packages/cli`) | `source-events.mdx` (imports) — no dedicated CLI page yet; see Gaps | +| Settings UI (general, services, portal, automation, accounts) | `integration.mdx` (portal/services), `self-hosting.mdx` (env) | +| Onboarding / project-setup wizard | `quickstart.mdx` | +| Env vars, provider keys, model policy, deployment pieces | `self-hosting.mdx` | +| Domains, Vercel proxy, `amend.sh/docs` routing, `llms.txt`, schemas | `production-routing.mdx` | +| Auth scopes / token tiers (public / webhook / owner) | `integration.mdx`, `api-reference.mdx` | +| Pre-launch gates (typecheck, smoke, DNS, billing, email) | `launch.mdx` | +| Local dev loop (`bun run dev:setup`, prove one full loop) | `quickstart.mdx` | + +## Gaps — built but undocumented + +These features ship in the app but have no owning page. If you touch one, add a page (and register it in `meta.json`) rather than wedging it into an unrelated page: + +- **Analytics workspace** (`analytics-workspace*.tsx`) — no public page +- **Full CLI reference** — every `packages/cli` subcommand; only import-related usage is covered today +- **Full SDK method reference** — the `Amend` class has ~25+ methods; only a subset is documented +- **Settings UI walkthrough** — services, portal settings, automation rules as configured in the dashboard + +Internal-only surfaces (brand guidelines page, brand menu, admin sidebar) are **not** public docs — do not document them here. + +## Style + +- Match the existing voice: terse, source-linked, written for a maintainer or coding agent verifying what actually runs. +- Keep the loop framing (intake / evidence / review / publish) — don't reorganize docs around UI screens. +- Use relative links between pages (e.g. `[Source events](source-events.mdx)`), Fumadocs ``/`` for hubs, and tables for surface/route/auth maps, as existing pages do. +- Don't hardcode the docs origin in product code — that's `VITE_DOCS_URL`. diff --git a/apps/fumadocs/content/docs/changelog/ai-drafting.mdx b/apps/fumadocs/content/docs/changelog/ai-drafting.mdx new file mode 100644 index 0000000..a4ae361 --- /dev/null +++ b/apps/fumadocs/content/docs/changelog/ai-drafting.mdx @@ -0,0 +1,46 @@ +--- +title: AI drafting +description: Turn source evidence into a first-draft changelog entry from the SDK, REST, or CLI. +--- + +Writing a changelog entry from scratch is the part people skip, which is how shipped work goes unannounced. Drafting closes that gap: hand Amend the evidence for a change and get back a draft entry you can edit and publish, instead of a blank editor. + +## What you need first + +Drafting calls a language model, so it needs a provider key configured for the workspace. That is set up alongside the rest of your [automation](/docs/automation) settings. Without a key, the draft endpoints have nothing to call. + +## Draft from code + +The SDK turns input into a draft entry: + +```ts +const draft = await amend.draftChangelog({ + title: "Bulk CSV export", // required + body: "Rough notes or context for the model to expand on.", + kind: "added", + sourceLinks: [{ url: "https://github.com/acme/app/pull/482" }], + dryRun: true, // preview without saving a draft +}); +``` + +Only `title` is required. `body`, `kind`, and `sourceLinks` give the model more to work with, and `dryRun` returns the draft without persisting it. + +The matching REST route is `POST /api/v1/your-workspace/drafts`, which takes an owner token. Both return a draft you then review, adjust, and save through the normal [writing and publishing](/docs/changelog/writing) flow. Nothing is published automatically. + +## Draft from the CLI + +The CLI is handy when you want a draft as part of a release script: + +```bash +amend changelog draft --title "Bulk CSV export" +``` + +It prints the draft as JSON and supports a dry run, so you can see what you would get before anything is written. + +## The editor's assistant panel + +The changelog editor has an assistant rail down the right side with quick actions like improve writing, add a summary, and suggest tags. The panel is in place, but it needs a model provider wired up before it returns suggestions. Until then, use the drafting calls above, which run through the same provider configuration. + +## Where drafts fit + +Drafting does not replace the loop, it speeds up the last step of it. The strongest drafts come from entries that already have source evidence attached, since the model is summarizing real work rather than guessing. Connect the roadmap item to its PRs and issues, and the draft has something concrete to describe. diff --git a/apps/fumadocs/content/docs/changelog/index.mdx b/apps/fumadocs/content/docs/changelog/index.mdx new file mode 100644 index 0000000..1545c11 --- /dev/null +++ b/apps/fumadocs/content/docs/changelog/index.mdx @@ -0,0 +1,32 @@ +--- +title: Changelog +description: Write and publish shipped-work announcements that trace back to the feedback and roadmap behind them. +--- + +The changelog is the end of the loop: the public record of what you shipped. An entry is the announcement a customer reads, but because it carries the same source evidence as the roadmap item it came from, it is also the proof that a request actually got answered. Someone who asked for a thing can be told, by name, that it landed. + + + +## What an entry holds + +Each entry has: + +- A `title` and `body`, plus a `summary` for the list view. The editor fills the summary from the first line of the body so you do not write it twice; over the API you pass `summary` yourself. +- A category: `added` ("New"), `changed` ("Improved"), `fixed` ("Fixed"), or `removed` ("Removed"). +- A status that moves through `draft`, `in_review`, `scheduled`, and `published`. +- An optional `version` like "2.4.0", plus free-form tags and the `sourceLinks` that tie it to the work. + +## How an entry travels + +An entry is a draft until you decide otherwise. You write it, set a category and maybe a version, then move it along: `in_review` when it needs a second set of eyes, `scheduled` if it should go out later, `published` when it is live. Publishing with the portal toggle on is what puts it in front of customers, and you can have publishing email your subscribers at the same time. + +## Where to go next + + + + The editor, categories and versions, the draft-to-published path, and notifying subscribers. + + + Turn source evidence into a first-draft entry from the SDK, REST, or CLI. + + diff --git a/apps/fumadocs/content/docs/changelog/meta.json b/apps/fumadocs/content/docs/changelog/meta.json new file mode 100644 index 0000000..534637e --- /dev/null +++ b/apps/fumadocs/content/docs/changelog/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Changelog", + "pages": ["index", "writing", "ai-drafting"] +} diff --git a/apps/fumadocs/content/docs/changelog/writing.mdx b/apps/fumadocs/content/docs/changelog/writing.mdx new file mode 100644 index 0000000..4c62bd7 --- /dev/null +++ b/apps/fumadocs/content/docs/changelog/writing.mdx @@ -0,0 +1,67 @@ +--- +title: Writing and publishing +description: Use the changelog editor, set category and version, move an entry from draft to published, and notify subscribers. +--- + +The editor is a focused writing surface with the controls you need and not much else. You write in the middle, and the things that decide where the entry goes live in a settings popover. + +## Write the entry + +Start a new entry from "Write changelog" on the empty state or "New changelog" in the header. You get a blank draft, category `added`, status `draft`, ready to type into. + +The body is a rich-text editor with a toolbar. As you write, the summary is taken from the first line, and the editor shows word count, character count, and an estimated read time so you can keep entries tight. + + + +Saving is explicit. Cmd/Ctrl+S or the Save button writes the entry, and a small indicator tells you the state: an amber dot for unsaved changes, a green check once saved. If you try to close with unsaved edits, you get a "Discard unsaved changes?" prompt rather than losing the work. + +## Category and version + +Two fields shape how an entry reads in the list: + +- Category sorts the entry into New, Improved, Fixed, or Removed. Pick the one that matches the change. +- Version is optional. Set something like "2.4.0" when an entry maps to a release; leave it blank when it does not. + +## Draft to published + +An entry's status is how you control its life: + +| Status | Use it when | +| ----------- | -------------------------- | +| `draft` | Still writing | +| `in_review` | Ready for someone to check | +| `scheduled` | Approved, going out later | +| `published` | Live | + +You can set the status in the settings popover, or flip the quick toggle in the header between draft and published. Two checkboxes in that popover decide what publishing actually does: + +- Show on public portal — whether customers see the entry. A published entry with this on appears in the portal's changelog feed. +- Email subscribers on publish — whether publishing also notifies the people subscribed to updates. + +Leave both off and a published entry is effectively an internal record. Turn them on and publishing becomes the customer-facing announcement. + + + +## Save from code + +If you generate entries elsewhere, upsert them directly. The call creates or updates on `stableKey`: + +```ts +await amend.upsertChangelog({ + title: "Bulk CSV export is live", + body: "You can now export a month of reports in one download.", + summary: "Export a month of reports in one download.", + category: "added", + status: "published", + version: "2.4.0", + stableKey: "changelog-bulk-export", +}); +``` + +The editor derives `summary` from your first line for you, but over the API you set it yourself, as shown above. The matching route is `POST /api/v1/your-workspace/changelog`. To draft an entry from source evidence instead of writing it cold, see [AI drafting](/docs/changelog/ai-drafting). diff --git a/apps/fumadocs/content/docs/feedback/collecting.mdx b/apps/fumadocs/content/docs/feedback/collecting.mdx new file mode 100644 index 0000000..8735ae5 --- /dev/null +++ b/apps/fumadocs/content/docs/feedback/collecting.mdx @@ -0,0 +1,102 @@ +--- +title: Collecting feedback +description: The five ways a customer request gets into Amend, with the code and settings for each. +--- + +A request can reach the inbox five ways. They all create the same kind of post, so you can mix them without ending up with two formats of the same thing. + +## The portal form + +The fastest path is the hosted portal. Customers see a submission panel with a title, email, and body, and what they can do is set by `feedbackMode` in your portal settings: + +- `open` — anyone can submit. +- `authenticated` — they have to sign in first, otherwise they see a sign-in prompt. +- `closed` — the form is hidden and reads "Feedback is closed." + +After a successful submit the customer sees "Request sent for triage." Nothing else is exposed to them at that point. + + + +## The SDK + +Use the SDK when you want to capture feedback from inside your own product, like a "Send feedback" widget. No token is required for customer-side writes. + +```ts +import { Amend } from "@amend/sdk"; + +const amend = new Amend({ project: "your-workspace", apiBaseUrl: "/api/v1" }); + +await amend.submitRequest({ + title: "Bulk export for reports", + body: "We need to pull a month of reports as CSV in one go.", + authorEmail: "casey@acme.com", + authorName: "Casey", + labels: ["reporting"], + sourceUrl: "https://app.acme.com/reports", +}); +``` + +The same client handles the interactions customers take on existing requests: + +```ts +await amend.vote("request-key", userId); +await amend.comment("request-key", "We'd use this weekly.", userId); +await amend.react("request-key", "+1", userId); +``` + +If you want votes and updates attributed to a real person, identify them first: + +```ts +await amend.identify({ + externalUserId: "user_123", + email: "casey@acme.com", + name: "Casey", +}); +``` + +## The REST API + +If you are not on JavaScript, post directly. The routes live under `/api/v1/:workspace`. Like the SDK customer writes, these do not need an auth token. + +```http +POST /api/v1/your-workspace/feedback +Content-Type: application/json + +{ + "title": "Bulk export for reports", + "body": "We need to pull a month of reports as CSV in one go.", + "authorEmail": "casey@acme.com" +} +``` + +Votes, comments, and reactions go through one interactions route: + +```http +POST /api/v1/your-workspace/interactions +Content-Type: application/json + +{ "kind": "vote", "requestKey": "request-key", "userId": "user_123" } +``` + +See the [API reference](/docs/api-reference) for the full route and auth table. + +## Imports + +Moving off another tool, or sitting on a backlog in a spreadsheet? Import in bulk instead of one call per row. The SDK takes an array: + +```ts +await amend.importFeedback([ + { title: "Dark mode", body: "Please.", authorEmail: "a@acme.com" }, + { title: "SSO", body: "Okta, ideally.", authorEmail: "b@acme.com" }, +]); +``` + +Imports are idempotent on the request key, so re-running a file does not duplicate posts. + +## The composer + +For the request that came in over a call or in Slack, log it by hand. The dashboard composer captures the title and body, plus the board, status, a priority tag, an assignee, and a due date, so a request you type in starts triaged instead of raw. + + + +Once a request is in, it goes to the board. That is [triaging](/docs/feedback/triaging). diff --git a/apps/fumadocs/content/docs/feedback/index.mdx b/apps/fumadocs/content/docs/feedback/index.mdx new file mode 100644 index 0000000..f2e3058 --- /dev/null +++ b/apps/fumadocs/content/docs/feedback/index.mdx @@ -0,0 +1,38 @@ +--- +title: Feedback +description: Collect customer requests, triage them on boards, and trace each one to the work that answers it. +--- + +Feedback is where customer asks land before they become anything else. A request comes in from the portal, your app, an import, or someone on the team typing it in. From there it sits in a structured inbox you can sort, triage, and link to the roadmap and changelog, so the same ask never gets logged twice and nobody loses track of who wanted what. + + + +## What a post holds + +Every request is a single post. It carries: + +- A `title` and `body` (the ask itself). +- A board: `feature`, `bug`, `changelog`, or `feedback`. Boards are fixed, so a post is always one of those four. +- A status that matches the roadmap: `backlog` ("Under Review"), `next` ("Planned"), `progress` ("In Progress"), or `done` ("Done"). +- A `source` string saying where it came from, and `sourceLinks` for the hard evidence (a GitHub PR, an issue, an email). +- A vote count, free-form labels, and an author. + +Two counters tie a post to the rest of the loop: `linkedRoadmapCount` and `linkedChangelogCount`. When a post is on the roadmap or shipped in a changelog entry, you see it on the post without leaving the inbox. + +## The two sides + +Customers and your team see different things. + +- Customers use the portal. They submit a request, and depending on your settings they can vote and comment on existing ones. After submitting they get a plain "Request sent for triage." +- Your team uses the dashboard. You get the full list with vote counts, board and status filters, a composer for logging requests by hand, and a detail view with internal notes and the source evidence behind each ask. + +## Where to go next + + + + Every way a request gets in: the portal form, the SDK, the REST API, imports, and the composer. + + + Boards, statuses, internal notes, and linking a request to the roadmap. + + diff --git a/apps/fumadocs/content/docs/feedback/meta.json b/apps/fumadocs/content/docs/feedback/meta.json new file mode 100644 index 0000000..5355160 --- /dev/null +++ b/apps/fumadocs/content/docs/feedback/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Feedback", + "pages": ["index", "collecting", "triaging"] +} diff --git a/apps/fumadocs/content/docs/feedback/triaging.mdx b/apps/fumadocs/content/docs/feedback/triaging.mdx new file mode 100644 index 0000000..f7367e1 --- /dev/null +++ b/apps/fumadocs/content/docs/feedback/triaging.mdx @@ -0,0 +1,55 @@ +--- +title: Triaging the inbox +description: Sort requests by board and status, read the evidence, and promote an ask to the roadmap without duplicating it. +--- + +Triage is the daily work of feedback: deciding what each request is, how far along it is, and whether it earns a spot on the roadmap. The inbox is built for that, not for storage. + +## Sort by board, then status + +The list groups posts by board, so feature requests, bug reports, and general feedback stay separate. Within that, filter by status to see where things stand: + +| Status | Shown as | Means | +| ---------- | ------------ | ------------------------- | +| `backlog` | Under Review | New, not yet committed to | +| `next` | Planned | Decided, not started | +| `progress` | In Progress | Being built | +| `done` | Done | Shipped | + +The status pills sit in the sidebar and the toolbar. These are the same display columns the roadmap uses, so a post and its roadmap item line up visually. The roadmap's API uses its own status names underneath, which map onto these columns; see [managing the board](/docs/roadmap/managing) if you set roadmap status over the API. + + + +## Open a request + +Clicking a post opens its detail workspace. Three tabs: + +- Comments — internal notes and replies. This is where the team talks through a request. +- Details — the post's fields and its link counts. +- Sources — the evidence chain: the GitHub PRs, issues, and other links tied to this ask. + +The sidebar shows how many roadmap and changelog items this request is connected to, plus the same source evidence, so you can judge a request without digging through other tools. + + + +## Promote to the roadmap + +When a request is worth committing to, link it to a roadmap item instead of retyping it. The roadmap item keeps a reference back to the original post (its key is prefixed `roadmap-feedback-`), so the two stay joined. + +On the feedback side you will see `linkedRoadmapCount` go up, and the sidebar notes that the request "is available to the roadmap view without creating a duplicate post." That is the point: one ask, one record, visible from both places. From the roadmap item you can jump straight back to the feedback that started it. + +This is the first link in the chain that ends at a published changelog entry. The full model is in [source trace](/docs/source-trace); the next stop is the [roadmap](/docs/roadmap). + +## From the command line + +For quick checks or scripts, the CLI reads the same inbox: + +```bash +amend feedback list +amend requests search --query "export" +``` + +Both print JSON, so you can pipe them into whatever you already use. diff --git a/apps/fumadocs/content/docs/index.mdx b/apps/fumadocs/content/docs/index.mdx index 5dcd4f9..b880a75 100644 --- a/apps/fumadocs/content/docs/index.mdx +++ b/apps/fumadocs/content/docs/index.mdx @@ -7,7 +7,23 @@ Amend connects four things teams usually keep apart: customer asks, source evide and shipped updates. These docs are for the person wiring that loop into a real product, with enough detail for a maintainer or coding agent to verify what is actually running. -## Start Here +## The three products + +Most of what you do in Amend happens in one of three places. They are the same loop seen from three angles: what people asked for, what you committed to, and what you shipped. + + + + Collect customer requests, triage them on boards, and link each to the work that answers it. + + + A board of what you are building, tied back to the feedback that asked for it. + + + Publish shipped-work announcements that trace back to the roadmap and feedback behind them. + + + +## Set up and integrate @@ -25,9 +41,6 @@ detail for a maintainer or coding agent to verify what is actually running. Bring your own Convex deployment, provider keys, domains, and model policy. - - Serve docs from `docs.amend.sh` and make `amend.sh/docs` work through Vercel. - Check provider credentials, DNS, Convex, email, billing, and final gates. diff --git a/apps/fumadocs/content/docs/meta.json b/apps/fumadocs/content/docs/meta.json index a8d8873..2ffe1e4 100644 --- a/apps/fumadocs/content/docs/meta.json +++ b/apps/fumadocs/content/docs/meta.json @@ -3,12 +3,19 @@ "pages": [ "index", "quickstart", + "---Products---", + "feedback", + "roadmap", + "changelog", + "---Concepts---", "source-trace", + "---Integrate---", "integration", "customer-surfaces", "source-events", "automation", "api-reference", + "---Operate---", "self-hosting", "production-routing", "launch" diff --git a/apps/fumadocs/content/docs/roadmap/index.mdx b/apps/fumadocs/content/docs/roadmap/index.mdx new file mode 100644 index 0000000..e5821ff --- /dev/null +++ b/apps/fumadocs/content/docs/roadmap/index.mdx @@ -0,0 +1,42 @@ +--- +title: Roadmap +description: A board of what you have committed to building, tied back to the feedback that asked for it. +--- + +The roadmap is the middle of the loop. Feedback tells you what people want; the changelog tells them what shipped; the roadmap is the part in between, where you say what you are actually going to build and how far along it is. It is a board, so the state of a thing is just which column it sits in. + + + +## Four columns + +Items move left to right through the same four statuses you see on feedback: + +| Column | Column id | Means | +| ------------ | ---------- | --------------------------- | +| Under Review | `backlog` | On the table, not committed | +| Planned | `next` | Committed, not started | +| In Progress | `progress` | Being built now | +| Done | `done` | Shipped | + +The column ids are what the board and its URL filters use. When you create items over the API you pass a different set of status values, which map onto these columns; see [managing the board](/docs/roadmap/managing) for that table. + +A card shows the title, a priority badge, a target if one is set, its vote count, and the first piece of source evidence. Behind each card, an item also carries a `priority` from P0 to P3, an `impact` note, a changelog link count, and the `sourceLinks` that connect it to real work. + +## Tied to feedback, not floating + +A roadmap item is not a fresh idea typed into a column. Most start as a feedback request that earned a commitment, and the item keeps a reference back to that post. Open an item and the sidebar either offers an "Open feedback" button to jump to the request that started it, or notes that it was created directly on the roadmap. Either way you can always answer "who asked for this?" + +## Public or private + +You decide whether customers see the roadmap at all. The `roadmapVisibility` setting is `private` or `public`. When it is public, customers can view item statuses and upvote the things they care about, which feeds the vote counts you triage by. + +## Where to go next + + + + Add items, drag them between columns, set priority and targets, and read an item's evidence. + + + What customers see, how voting works, and how to read it over the API. + + diff --git a/apps/fumadocs/content/docs/roadmap/managing.mdx b/apps/fumadocs/content/docs/roadmap/managing.mdx new file mode 100644 index 0000000..e0e5a1a --- /dev/null +++ b/apps/fumadocs/content/docs/roadmap/managing.mdx @@ -0,0 +1,67 @@ +--- +title: Managing the board +description: Create roadmap items, move them through statuses, set priority and targets, and read their evidence. +--- + +The board is where you run the roadmap day to day. Everything here also has an API equivalent, so you can drive it by hand or from automation. + +## Add an item + +Three ways an item shows up on the board: + +- A team member clicks the "+" on a column, which drops a new card into that status. +- A feedback request gets promoted, carrying its link back to the original post. +- The automation engine creates one from source evidence, like a GitHub PR that clearly maps to planned work. + +To create one from code, upsert it. The same call creates and updates, keyed on `stableKey`: + +```ts +await amend.upsertRoadmapItem({ + title: "Bulk CSV export", + description: "Export a month of reports in one download.", + status: "planned", + priority: "P1", + impact: "Top ask from reporting-heavy accounts.", + target: "Q3", + stableKey: "roadmap-bulk-export", +}); +``` + +A note on status values. The board columns and the API use different names. Pass one of the API `status` values below to `upsertRoadmapItem`, and the board places the item in the matching column: + +| API `status` | Board column | +| --------------------------------------- | ------------ | +| `under_review`, `considering`, `paused` | Under Review | +| `planned` | Planned | +| `in_progress` | In Progress | +| `shipped`, `closed` | Done | + +The example above passes `planned`, so the item lands in the Planned column. + +## Move it across + +Dragging a card to another column changes its status. That is the normal way work progresses: Under Review when you are deciding, Planned once you commit, In Progress while building, Done when it ships. Each move is the same status change you would make through `upsertRoadmapItem`, so the board and the API never disagree. + + + +## Priority and targets + +Two fields help you sort within a column: + +- Priority runs P0 to P3. P0 is critical, P1 high, P2 normal, P3 low. +- Target is a free-form milestone or date, like "Q3" or "v2.4". It shows on the card so you can see commitments at a glance. + +## Read an item + +Open a card for the full picture. The detail view shows the description and impact, the vote count with an upvote button, and a stats row: status, priority, votes, changelog links, target, and last updated. Below that is the source evidence, the links to the PRs and issues this item is built on. + + + +The sidebar is where the loop shows itself. "Linked feedback" takes you back to the request that started the item, and the changelog link count tells you whether the work has shipped yet. + +## Multiple views + +You can keep more than one named roadmap view, each with its own set of items. The default is "Main roadmap," which holds every item Amend knows about, tied back to feedback, GitHub source, and changelog evidence. Views show up in the sidebar with their item counts. diff --git a/apps/fumadocs/content/docs/roadmap/meta.json b/apps/fumadocs/content/docs/roadmap/meta.json new file mode 100644 index 0000000..7786eeb --- /dev/null +++ b/apps/fumadocs/content/docs/roadmap/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Roadmap", + "pages": ["index", "managing", "public"] +} diff --git a/apps/fumadocs/content/docs/roadmap/public.mdx b/apps/fumadocs/content/docs/roadmap/public.mdx new file mode 100644 index 0000000..ccf1c49 --- /dev/null +++ b/apps/fumadocs/content/docs/roadmap/public.mdx @@ -0,0 +1,39 @@ +--- +title: The public roadmap +description: Show customers what you are building, let them vote, and read the roadmap over the API. +--- + +A public roadmap does two jobs at once. It tells customers you heard them and you are working on it, and it turns "which of these matters most?" into vote counts you can actually triage by. + +## Turn it on + +Visibility is one setting: `roadmapVisibility`, either `private` or `public`. While it is private, the roadmap lives only in your dashboard. Set it to public and the roadmap becomes readable through the portal and the API. + +```ts +await amend.updatePortalSettings({ roadmapVisibility: "public" }); +``` + +## What customers can do + +On a public roadmap, customers see the columns and the items in each, with their statuses. They can upvote the items they want, which is the signal you read back on the board: every vote bumps the count you sort by. They do not see internal fields like impact notes or your priority labels. + + + +## Read it over the API + +A public roadmap is available through the SDK and REST. Both can filter by status. + +```ts +const items = await amend.roadmap(); // everything visible +const planned = await amend.roadmap("planned"); // one column +``` + +```http +GET /api/v1/your-workspace/roadmap?status=planned +``` + +The `status` filter takes an API status value like `planned`, `in_progress`, or `shipped`, not a board column id. The full set and how each maps to a column is in [managing the board](/docs/roadmap/managing). + +This is what you would call to render the roadmap inside your own product instead of the hosted portal, or to pull the current state into a status page. Reads of the public roadmap are tracked, so you can see how much attention it gets. + +For the embeddable panel that drops the roadmap and changelog into your own UI, see [customer surfaces](/docs/customer-surfaces). diff --git a/apps/fumadocs/next.config.mjs b/apps/fumadocs/next.config.mjs deleted file mode 100644 index 2dd087d..0000000 --- a/apps/fumadocs/next.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import { createMDX } from "fumadocs-mdx/next"; -import { fileURLToPath } from "node:url"; - -const withMDX = createMDX(); -const docsOrigin = process.env.AMEND_DOCS_ORIGIN ?? "https://docs.amend.sh"; -const isProductionBuild = process.env.NODE_ENV === "production"; - -/** @type {import('next').NextConfig} */ -const config = { - allowedDevOrigins: ["docs.amend.localhost", "*.localhost"], - assetPrefix: isProductionBuild ? docsOrigin : undefined, - serverExternalPackages: ["@takumi-rs/image-response"], - reactStrictMode: true, - turbopack: { - root: fileURLToPath(new URL("../..", import.meta.url)), - }, -}; - -export default withMDX(config); diff --git a/apps/fumadocs/package.json b/apps/fumadocs/package.json index 5785f73..2200fe8 100644 --- a/apps/fumadocs/package.json +++ b/apps/fumadocs/package.json @@ -2,49 +2,38 @@ "name": "fumadocs", "version": "0.0.0", "private": true, + "type": "module", "scripts": { - "build": "next build", - "check-types": "fumadocs-mdx && next typegen && tsc --noEmit", - "dev": "portless docs.${WORKTREE_NAME:-amend} next dev", + "build": "vite build", + "check-types": "fumadocs-mdx && tsc --noEmit", + "dev": "portless docs.${WORKTREE_NAME:-amend} vite dev", "format": "oxfmt .", "format:check": "oxfmt --check .", "lint": "oxlint .", - "start": "next start", + "start": "node .output/server/index.mjs", "types:check": "bun run check-types", "postinstall": "fumadocs-mdx" }, "dependencies": { - "@ai-sdk/react": "^3.0.179", - "@openrouter/ai-sdk-provider": "^2.9.0", - "@radix-ui/react-presence": "^1.1.5", - "@takumi-rs/image-response": "^1.1.2", - "ai": "^6.0.177", - "class-variance-authority": "^0.7.1", - "flexsearch": "^0.8.212", - "fumadocs-core": "16.8.10", - "fumadocs-mdx": "15.0.3", - "fumadocs-ui": "16.8.10", - "hast-util-to-jsx-runtime": "^2.3.6", - "lucide-react": "^1.14.0", - "next": "16.2.6", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "remark": "^15.0.1", - "remark-gfm": "^4.0.1", - "remark-rehype": "^11.1.2", - "tailwind-merge": "^3.5.0", - "unist-util-visit": "^5.1.0", + "@tailwindcss/vite": "^4.3.1", + "@tanstack/react-router": "^1.170.15", + "@tanstack/react-start": "^1.168.25", + "fumadocs-core": "^16.10.2", + "fumadocs-mdx": "^15.0.12", + "fumadocs-ui": "^16.10.2", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "tailwindcss": "^4.3.1", "zod": "^4.4.3" }, "devDependencies": { - "@tailwindcss/postcss": "^4.3.0", - "@types/hast": "^3.0.4", - "@types/mdx": "^2.0.13", - "@types/node": "^25.6.2", - "@types/react": "^19.2.14", + "@types/mdx": "^2.0.14", + "@types/node": "^25.9.3", + "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "postcss": "^8.5.14", - "tailwindcss": "^4.3.0", - "typescript": "^6.0.3" + "@vitejs/plugin-react": "^6.0.2", + "nitro": "^3.0.260522-beta", + "typescript": "^6.0.3", + "vite": "^8.0.16" } } diff --git a/apps/fumadocs/postcss.config.mjs b/apps/fumadocs/postcss.config.mjs deleted file mode 100644 index 61e3684..0000000 --- a/apps/fumadocs/postcss.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const config = { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; - -export default config; diff --git a/apps/fumadocs/proxy.ts b/apps/fumadocs/proxy.ts deleted file mode 100644 index 4955629..0000000 --- a/apps/fumadocs/proxy.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation"; -import { docsContentRoute, docsRoute } from "@/lib/shared"; - -const { rewrite: rewriteDocs } = rewritePath( - `${docsRoute}{/*path}`, - `${docsContentRoute}{/*path}/content.md`, -); -const { rewrite: rewriteSuffix } = rewritePath( - `${docsRoute}{/*path}.md`, - `${docsContentRoute}{/*path}/content.md`, -); - -export default function proxy(request: NextRequest) { - const result = rewriteSuffix(request.nextUrl.pathname); - if (result) { - return NextResponse.rewrite(new URL(result, request.nextUrl)); - } - - if (isMarkdownPreferred(request)) { - const result = rewriteDocs(request.nextUrl.pathname); - - if (result) { - return NextResponse.rewrite(new URL(result, request.nextUrl)); - } - } - - return NextResponse.next(); -} diff --git a/apps/fumadocs/src/app/(home)/layout.tsx b/apps/fumadocs/src/app/(home)/layout.tsx deleted file mode 100644 index c16b056..0000000 --- a/apps/fumadocs/src/app/(home)/layout.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { HomeLayout } from "fumadocs-ui/layouts/home"; -import { baseOptions } from "@/lib/layout.shared"; - -export default function Layout({ children }: LayoutProps<"/">) { - return {children}; -} diff --git a/apps/fumadocs/src/app/api/chat/route.ts b/apps/fumadocs/src/app/api/chat/route.ts deleted file mode 100644 index 48d4f5d..0000000 --- a/apps/fumadocs/src/app/api/chat/route.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { createOpenRouter } from "@openrouter/ai-sdk-provider"; -import { convertToModelMessages, stepCountIs, streamText, tool, type UIMessage } from "ai"; -import { z } from "zod"; -import { source } from "@/lib/source"; -import { Document, type DocumentData } from "flexsearch"; - -interface CustomDocument extends DocumentData { - url: string; - title: string; - description: string; - content: string; -} - -export type ChatUIMessage = UIMessage< - never, - { - client: { - location: string; - }; - } ->; - -const searchServer = createSearchServer(); - -async function createSearchServer() { - const search = new Document({ - document: { - id: "url", - index: ["title", "description", "content"], - store: true, - }, - }); - - const docs = await chunkedAll( - source.getPages().map(async (page) => { - if (!("getText" in page.data)) return null; - - return { - title: page.data.title, - description: page.data.description, - url: page.url, - content: await page.data.getText("processed"), - } as CustomDocument; - }), - ); - - for (const doc of docs) { - if (doc) search.add(doc); - } - - return search; -} - -async function chunkedAll(promises: Promise[]): Promise { - const SIZE = 50; - const out: O[] = []; - for (let i = 0; i < promises.length; i += SIZE) { - out.push(...(await Promise.all(promises.slice(i, i + SIZE)))); - } - return out; -} - -const openrouter = createOpenRouter({ - apiKey: process.env.OPENROUTER_API_KEY, -}); - -/** System prompt, you can update it to provide more specific information */ -const systemPrompt = [ - "You are an AI assistant for a documentation site.", - "Use the `search` tool to retrieve relevant docs context before answering when needed.", - "The `search` tool returns raw JSON results from documentation. Use those results to ground your answer and cite sources as markdown links using the document `url` field when available.", - "If you cannot find the answer in search results, say you do not know and suggest a better search query.", -].join("\n"); - -export async function POST(req: Request) { - const reqJson = await req.json(); - - const result = streamText({ - model: openrouter.chat(process.env.OPENROUTER_MODEL ?? "anthropic/claude-3.5-sonnet"), - stopWhen: stepCountIs(5), - tools: { - search: searchTool, - }, - messages: [ - { role: "system", content: systemPrompt }, - ...(await convertToModelMessages(reqJson.messages ?? [], { - convertDataPart(part) { - if (part.type === "data-client") - return { - type: "text", - text: `[Client Context: ${JSON.stringify(part.data)}]`, - }; - }, - })), - ], - toolChoice: "auto", - }); - - return result.toUIMessageStreamResponse(); -} - -export type SearchTool = typeof searchTool; - -const searchTool = tool({ - description: "Search the docs content and return raw JSON results.", - inputSchema: z.object({ - query: z.string(), - limit: z.number().int().min(1).max(100).default(10), - }), - async execute({ query, limit }) { - const search = await searchServer; - return await search.searchAsync(query, { limit, merge: true, enrich: true }); - }, -}); diff --git a/apps/fumadocs/src/app/api/search/route.ts b/apps/fumadocs/src/app/api/search/route.ts deleted file mode 100644 index aa9d5cd..0000000 --- a/apps/fumadocs/src/app/api/search/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { source } from "@/lib/source"; -import { createFromSource } from "fumadocs-core/search/server"; - -export const { GET } = createFromSource(source, { - // https://docs.orama.com/docs/orama-js/supported-languages - language: "english", -}); diff --git a/apps/fumadocs/src/app/docs/[[...slug]]/page.tsx b/apps/fumadocs/src/app/docs/[[...slug]]/page.tsx deleted file mode 100644 index f9c2349..0000000 --- a/apps/fumadocs/src/app/docs/[[...slug]]/page.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { getPageImage, getPageMarkdownUrl, source } from "@/lib/source"; -import { - DocsBody, - DocsDescription, - DocsPage, - DocsTitle, - MarkdownCopyButton, - ViewOptionsPopover, -} from "fumadocs-ui/layouts/docs/page"; -import { notFound } from "next/navigation"; -import { getMDXComponents } from "@/components/mdx"; -import type { Metadata } from "next"; -import { createRelativeLink } from "fumadocs-ui/mdx"; -import { gitConfig } from "@/lib/shared"; - -const docsUrl = "https://docs.amend.sh"; - -function absoluteDocsUrl(path: string) { - return `${docsUrl}${path === "/" ? "" : path}`; -} - -export default async function Page(props: PageProps<"/docs/[[...slug]]">) { - const params = await props.params; - const page = source.getPage(params.slug); - if (!page) notFound(); - - const MDX = page.data.body; - const markdownUrl = getPageMarkdownUrl(page).url; - const pageUrl = absoluteDocsUrl(page.url); - const structuredData = { - "@context": "https://schema.org", - "@type": "TechArticle", - description: page.data.description, - headline: page.data.title, - isPartOf: { - "@type": "WebSite", - name: "Amend.sh Docs", - url: docsUrl, - }, - mainEntityOfPage: pageUrl, - publisher: { - "@type": "Organization", - name: "Amend.sh", - url: "https://amend.sh", - }, - url: pageUrl, - }; - - return ( - <> -