Changelog

All the latest updates and improvements to Pulse.

New FeatureMinorFeb 21, 2026

Make Pulse operational for dogfooding

## Summary - **Production bootstrap**: Created "Pulse" project in Supabase, set admin role, registered GitHub App installation (ID: 111433380) - **GitHub backfill**: New `backfillFromGitHub` server action that imports the last 100 commits, 50 merged PRs, and 20 releases from a connected GitHub repo into changelog entries with idempotent content hashes - **Manual sync**: Replaced the no-op `triggerManualSync` with a real backfill that fetches from GitHub API - **Auto-backfill on link**: When a repo is linked to a project, historical data is automatically backfilled - **Admin sidebar link**: Conditional "Admin" link with Shield icon appears for admin users - **Widget embed card**: Project settings now shows a ready-to-copy `<script>` tag with configuration hints - **Better empty states**: Changelog, feedback, roadmap, and sync history tables show helpful guidance and action buttons when empty - **Project overview**: Setup status checklist (GitHub connected, changelog populated, roadmap items) and public page links - **Onboarding checklist**: Dashboard shows a getting-started guide that auto-hides once key steps are completed ## Test plan - [ ] `npm run check` passes (833 tests, 0 lint errors, 0 type errors) - [ ] Navigate to `/p/pulse/changelog` — project loads (no 404) - [ ] Go to `/projects/pulse/sync`, click "Trigger Sync" — changelog entries appear - [ ] Log in as admin, verify "Admin" link appears in sidebar - [ ] Go to `/projects/pulse/settings` — see widget embed code with copy button - [ ] Create empty project, verify improved empty states in all modules - [ ] Dashboard shows onboarding checklist, steps check off progressively

## Summary - **Production bootstrap**: Created "Pulse" project in Supabase, set admin role, registered GitHub App installation (ID: 111433380) - **GitHub backfill**: New `backfillFromGitHub` server action that imports the last 100 commits, 50 merged PRs, and 20 releases from a connected GitHub repo into changelog entries with idempotent content hashes - **Manual sync**: Replaced the no-op `triggerManualSync` with a real backfill that fetches from GitHub API - **Auto-backfill on link**: When a repo is linked to a project, historical data is automatically backfilled - **Admin sidebar link**: Conditional "Admin" link with Shield icon appears for admin users - **Widget embed card**: Project settings now shows a ready-to-copy `<script>` tag with configuration hints - **Better empty states**: Changelog, feedback, roadmap, and sync history tables show helpful guidance and action buttons when empty - **Project overview**: Setup status checklist (GitHub connected, changelog populated, roadmap items) and public page links - **Onboarding checklist**: Dashboard shows a getting-started guide that auto-hides once key steps are completed ## Test plan - [ ] `npm run check` passes (833 tests, 0 lint errors, 0 type errors) - [ ] Navigate to `/p/pulse/changelog` — project loads (no 404) - [ ] Go to `/projects/pulse/sync`, click "Trigger Sync" — changelog entries appear - [ ] Log in as admin, verify "Admin" link appears in sidebar - [ ] Go to `/projects/pulse/settings` — see widget embed code with copy button - [ ] Create empty project, verify improved empty states in all modules - [ ] Dashboard shows onboarding checklist, steps check off progressively
Bug FixPatchFeb 21, 2026

Targeted select, partial sync status, return sync record directly, onboarding comment

fix: targeted select, partial sync status, return sync record directly, onboarding comment
Bug FixPatchFeb 21, 2026

Address review feedback — batch upsert, release heuristic, env-aware URLs

fix: address review feedback — batch upsert, release heuristic, env-aware URLs - Replace N+1 SELECT+INSERT pattern in backfill with batch upsert (3 DB calls vs 340) - Add unique constraint migration on (project_id, content_hash) for upsert support - Fix release type heuristic: anchor regex to match only v?0.x tags, not v1.0.0 - Document fire-and-forget backfill tradeoff in serverless environments - Add TODO for unused _syncType parameter in triggerManualSync - Use NEXT_PUBLIC_APP_URL env var for widget embed code instead of hardcoded URL - Add console.warn for failed batch inserts in backfill
ImprovementPatchFeb 21, 2026

Add Claude Code GitHub Workflow

## 🤖 Installing Claude Code GitHub App This PR adds a GitHub Actions workflow that enables Claude Code integration in our repository. ### What is Claude Code? [Claude Code](https://claude.com/claude-code) is an AI coding agent that can help with: - Bug fixes and improvements - Documentation updates - Implementing new features - Code reviews and suggestions - Writing tests - And more! ### How it works Once this PR is merged, we'll be able to interact with Claude by mentioning @claude in a pull request or issue comment. Once the workflow is triggered, Claude will analyze the comment and surrounding context, and execute on the request in a GitHub action. ### Important Notes - **This workflow won't take effect until this PR is merged** - **@claude mentions won't work until after the merge is complete** - The workflow runs automatically whenever Claude is mentioned in PR or issue comments - Claude gets access to the entire PR or issue context including files, diffs, and previous comments ### Security - Our Anthropic API key is securely stored as a GitHub Actions secret - Only users with write access to the repository can trigger the workflow - All Claude runs are stored in the GitHub Actions run history - Claude's default tools are limited to reading/writing files and interacting with our repo by creating comments, branches, and commits. - We can add more allowed tools by adding them to the workflow file like: ``` allowed_tools: Bash(npm install),Bash(npm run build),Bash(npm run lint),Bash(npm run test) ``` There's more information in the [Claude Code action repo](https://github.com/anthropics/claude-code-action). After merging this PR, let's try mentioning @claude in a comment on any PR to get started!

## 🤖 Installing Claude Code GitHub App This PR adds a GitHub Actions workflow that enables Claude Code integration in our repository. ### What is Claude Code? [Claude Code](https://claude.com/claude-code) is an AI coding agent that can help with: - Bug fixes and improvements - Documentation updates - Implementing new features - Code reviews and suggestions - Writing tests - And more! ### How it works Once this PR is merged, we'll be able to interact with Claude by mentioning @claude in a pull request or issue comment. Once the workflow is triggered, Claude will analyze the comment and surrounding context, and execute on the request in a GitHub action. ### Important Notes - **This workflow won't take effect until this PR is merged** - **@claude mentions won't work until after the merge is complete** - The workflow runs automatically whenever Claude is mentioned in PR or issue comments - Claude gets access to the entire PR or issue context including files, diffs, and previous comments ### Security - Our Anthropic API key is securely stored as a GitHub Actions secret - Only users with write access to the repository can trigger the workflow - All Claude runs are stored in the GitHub Actions run history - Claude's default tools are limited to reading/writing files and interacting with our repo by creating comments, branches, and commits. - We can add more allowed tools by adding them to the workflow file like: ``` allowed_tools: Bash(npm install),Bash(npm run build),Bash(npm run lint),Bash(npm run test) ``` There's more information in the [Claude Code action repo](https://github.com/anthropics/claude-code-action). After merging this PR, let's try mentioning @claude in a comment on any PR to get started!
ImprovementPatchFeb 21, 2026

"Claude Code Review workflow"

"Claude Code Review workflow"
ImprovementPatchFeb 21, 2026

"Claude PR Assistant workflow"

"Claude PR Assistant workflow"
New FeatureMinorFeb 21, 2026

Add GitHub backfill, admin link, widget embed, empty states, onboarding checklist

feat: add GitHub backfill, admin link, widget embed, empty states, onboarding checklist - Extract shared categorizeMessage/cleanTitle utils from webhook route - Add backfillFromGitHub server action (fetches commits, PRs, releases) - Replace no-op triggerManualSync with real GitHub backfill - Auto-trigger backfill when linking a repo to a project - Add conditional admin link in dashboard sidebar for admin users - Add widget embed code card with copy button in project settings - Improve empty states with icons, descriptions, and action buttons - Add setup status checklist and public page links to project overview - Add getting-started onboarding checklist to dashboard - Add 44 new unit tests (changelog-utils + github-backfill)
Bug FixPatchFeb 21, 2026

Resolve E2E Clerk auth failures with ticket-based sign-in

## Summary - Switch `clerk.signIn()` from password to emailAddress/ticket strategy to bypass Clerk Client Trust device verification in Playwright - Create `e2e/dev-server.mjs` wrapper to obtain `CLERK_TESTING_TOKEN` before starting Next.js dev server (Playwright starts webServer before globalSetup) - Remove `storageState` dependency — each test signs in fresh via `clerk.signIn()` in `beforeEach` - Add `waitForLoadState('networkidle')` after sign-in to prevent Firefox `NS_BINDING_ABORTED` errors - Fix `auth-flows.spec.ts` sign-up link selector to use substring match (Clerk appends redirect params) - Clean up temporary debug logging from `middleware.ts` and `auth.ts` ## Root causes resolved 1. **Client Trust / Device Verification** — password-based sign-in blocked by Clerk's unrecognized device check. Fixed with ticket strategy. 2. **60-second JWT expiry** — saved storageState expired before tests ran. Fixed with fresh sign-in per test. 3. **CLERK_TESTING_TOKEN propagation** — Playwright starts webServer before globalSetup. Fixed with dev-server.mjs wrapper. 4. **Clerk user ID mismatch** — profiles table had stale clerk_ids. Fixed by updating Supabase profiles to match actual Clerk API user IDs. 5. **Firefox navigation timing** — NS_BINDING_ABORTED during rapid navigation. Fixed with networkidle waits. ## Test plan - [x] 62 E2E tests passing (Chromium + Firefox) - [x] 789 unit tests passing - [x] `npm run check` passing (lint + format + typecheck + tests) - [ ] CI needs Clerk env vars to run E2E in pipeline

## Summary - Switch `clerk.signIn()` from password to emailAddress/ticket strategy to bypass Clerk Client Trust device verification in Playwright - Create `e2e/dev-server.mjs` wrapper to obtain `CLERK_TESTING_TOKEN` before starting Next.js dev server (Playwright starts webServer before globalSetup) - Remove `storageState` dependency — each test signs in fresh via `clerk.signIn()` in `beforeEach` - Add `waitForLoadState('networkidle')` after sign-in to prevent Firefox `NS_BINDING_ABORTED` errors - Fix `auth-flows.spec.ts` sign-up link selector to use substring match (Clerk appends redirect params) - Clean up temporary debug logging from `middleware.ts` and `auth.ts` ## Root causes resolved 1. **Client Trust / Device Verification** — password-based sign-in blocked by Clerk's unrecognized device check. Fixed with ticket strategy. 2. **60-second JWT expiry** — saved storageState expired before tests ran. Fixed with fresh sign-in per test. 3. **CLERK_TESTING_TOKEN propagation** — Playwright starts webServer before globalSetup. Fixed with dev-server.mjs wrapper. 4. **Clerk user ID mismatch** — profiles table had stale clerk_ids. Fixed by updating Supabase profiles to match actual Clerk API user IDs. 5. **Firefox navigation timing** — NS_BINDING_ABORTED during rapid navigation. Fixed with networkidle waits. ## Test plan - [x] 62 E2E tests passing (Chromium + Firefox) - [x] 789 unit tests passing - [x] `npm run check` passing (lint + format + typecheck + tests) - [ ] CI needs Clerk env vars to run E2E in pipeline
Bug FixPatchFeb 21, 2026

Update auth.setup.ts docs for ticket strategy, add spawn error handling

fix: update auth.setup.ts docs for ticket strategy, add spawn error handling Rewrite auth.setup.ts header to reflect ticket-based sign-in and per-test beforeEach auth pattern (storageState approach removed). Add child process error handler in dev-server.mjs for spawn failures.
Bug FixPatchFeb 21, 2026

Replace test.skip auth guards with assertions, decouple password validation

fix: replace test.skip auth guards with assertions, decouple password validation Replace conditional test.skip() on /sign-in redirect with expect assertions in admin, admin-blog, and feedback specs so auth regressions cause failures instead of silent skips. Separate email/password validation in auth.setup.ts since ticket-based sign-in only requires email — password is now optional with a warning for UI fallback path.
Bug FixPatchFeb 21, 2026

Exclude /projects/new from feedback spec project link selector

fix: exclude /projects/new from feedback spec project link selector The selector a[href*='/projects/'] matches the "New Project" button (/projects/new) before any real project links. Navigating to /projects/new/feedback causes CI E2E shard 2 to fail. Use :not() to exclude /projects/new from the match.
Bug FixPatchFeb 21, 2026

Resolve CI E2E failures from empty build artifact and server startup conflict

fix: resolve CI E2E failures from empty build artifact and server startup conflict Add include-hidden-files to build artifact upload (v4 default excludes dotfiles like .next/). Remove manual server startup from E2E job — let Playwright's webServer config handle it via dev-server.mjs which obtains CLERK_TESTING_TOKEN. Filter E2E to Chromium-only projects since Firefox isn't installed in CI.
Bug FixPatchFeb 21, 2026

Address Copilot review feedback on E2E auth setup

fix: address Copilot review feedback on E2E auth setup Add missing CLERK_SECRET_KEY warning in dev-server.mjs when env var is unset. Fix exit code default from 1 to 0 for successful child process termination. Document postAuthDelay rationale for Clerk session propagation timing. Clarify dual token-setting mechanism in playwright.config.ts auth strategy docs.
Bug FixPatchFeb 21, 2026

Resolve E2E Clerk auth failures with ticket-based sign-in strategy

fix: resolve E2E Clerk auth failures with ticket-based sign-in strategy Switch clerk.signIn() from password to emailAddress/ticket strategy to bypass Clerk Client Trust device verification in Playwright tests. Create dev-server.mjs wrapper to propagate CLERK_TESTING_TOKEN before Next.js starts. Remove storageState dependency in favor of fresh sign-in per test. Add networkidle waits after sign-in to prevent Firefox NS_BINDING_ABORTED errors. Clean up temporary debug logging. - 62 E2E tests passing (Chromium + Firefox) - 789 unit tests passing
ImprovementPatchFeb 21, 2026

Add .prettierignore and update .gitignore for debug artifacts

## Summary - Add `.prettierignore` to exclude non-source directories (`.claude/`, `.playwright-mcp/`, `supabase/.temp/`, `.next/`, `node_modules/`, `coverage/`, `playwright-report/`, `test-results/`, `build/`, `.vercel/`) from Prettier checks - Update `.gitignore` to ignore browser automation screenshots (`*.png`), Playwright MCP logs (`.playwright-mcp/`), Supabase temp files (`supabase/.temp/`), and Claude config (`.claude/`) - Commit pending `AI_PLANNING.md` updates from Session 9 (branch/PR status, test count 723→789, CSP + auth loop guard completion notes) and format to pass Prettier check ## Context Debug artifacts (12 PNG screenshots, Playwright MCP logs, Supabase temp files) accumulated in the repo from browser automation sessions. These should never be tracked. Additionally, `npm run check` was failing because Prettier was scanning `.claude/settings.local.json`. The `AI_PLANNING.md` had uncommitted session state updates from Session 9 that are included in this cleanup. ## Test plan - [x] `npm run check` passes (lint + format:check + typecheck + 789 tests) - [x] No untracked artifacts remain in `git status` - [x] All debug/temp directories are properly gitignored

## Summary - Add `.prettierignore` to exclude non-source directories (`.claude/`, `.playwright-mcp/`, `supabase/.temp/`, `.next/`, `node_modules/`, `coverage/`, `playwright-report/`, `test-results/`, `build/`, `.vercel/`) from Prettier checks - Update `.gitignore` to ignore browser automation screenshots (`*.png`), Playwright MCP logs (`.playwright-mcp/`), Supabase temp files (`supabase/.temp/`), and Claude config (`.claude/`) - Commit pending `AI_PLANNING.md` updates from Session 9 (branch/PR status, test count 723→789, CSP + auth loop guard completion notes) and format to pass Prettier check ## Context Debug artifacts (12 PNG screenshots, Playwright MCP logs, Supabase temp files) accumulated in the repo from browser automation sessions. These should never be tracked. Additionally, `npm run check` was failing because Prettier was scanning `.claude/settings.local.json`. The `AI_PLANNING.md` had uncommitted session state updates from Session 9 that are included in this cleanup. ## Test plan - [x] `npm run check` passes (lint + format:check + typecheck + 789 tests) - [x] No untracked artifacts remain in `git status` - [x] All debug/temp directories are properly gitignored
Bug FixPatchFeb 21, 2026

Rename misleading CI audit step label to reflect full dependency scan

fix: rename misleading CI audit step label to reflect full dependency scan
Bug FixPatchFeb 21, 2026

Add informational dev dependency audit step to CI

fix: add informational dev dependency audit step to CI Add a second non-blocking npm audit step that includes devDependencies so upstream vulnerabilities (e.g. minimatch in eslint chain) remain visible without failing CI on issues outside our control.
Bug FixPatchFeb 21, 2026

Scope CI security audit to production deps, fix npm audit vulnerabilities

fix: scope CI security audit to production deps, fix npm audit vulnerabilities Change npm audit to --omit=dev so dev-only minimatch/eslint chain vulnerabilities don't fail CI (eslint never ships to production). Run npm audit fix to resolve ajv, hono, and qs vulnerabilities in transitive production dependencies.
ImprovementPatchFeb 20, 2026

Add .prettierignore, update .gitignore for debug artifacts

chore: add .prettierignore, update .gitignore for debug artifacts Add .prettierignore to exclude non-source directories from Prettier checks (.claude/, .playwright-mcp/, supabase/.temp/, .next/, node_modules/, etc). Update .gitignore to ignore browser automation screenshots (*.png), Playwright MCP logs (.playwright-mcp/), Supabase temp files (supabase/.temp/), and Claude config (.claude/). Format AI_PLANNING.md to pass Prettier check.
Bug FixPatchFeb 9, 2026

Add auth redirect loop guard for stale Clerk sessions

## Summary - Adds a cookie-based loop guard to the Clerk middleware to prevent ERR_TOO_MANY_REDIRECTS when stale session cookies cause /sign-in ↔ /dashboard redirect loops - Sets a 10-second httpOnly cookie when redirecting from auth routes to /dashboard; if the user bounces back within that window, the sign-in page renders instead of redirecting again - Adds 4 new middleware tests covering the loop guard behavior ## Test plan - [ ] Verify authenticated users are still redirected from /sign-in to /dashboard on first visit - [ ] Verify the redirect loop is broken when stale cookies are present - [ ] Verify unauthenticated users can access /sign-in normally - [ ] All 789 tests pass

## Summary - Adds a cookie-based loop guard to the Clerk middleware to prevent ERR_TOO_MANY_REDIRECTS when stale session cookies cause /sign-in ↔ /dashboard redirect loops - Sets a 10-second httpOnly cookie when redirecting from auth routes to /dashboard; if the user bounces back within that window, the sign-in page renders instead of redirecting again - Adds 4 new middleware tests covering the loop guard behavior ## Test plan - [ ] Verify authenticated users are still redirected from /sign-in to /dashboard on first visit - [ ] Verify the redirect loop is broken when stale cookies are present - [ ] Verify unauthenticated users can access /sign-in normally - [ ] All 789 tests pass
Bug FixPatchFeb 9, 2026

Scope loop guard cookie path to /sign- prefix to reduce header exposure

fix: scope loop guard cookie path to /sign- prefix to reduce header exposure Narrow the __auth_loop_guard cookie path from "/" to "/sign-" so it only ships on auth routes (/sign-in, /sign-up) instead of every request. Use explicit set-to-empty with matching path for deletion since cookies.delete() defaults to path "/".
Bug FixPatchFeb 9, 2026

Make loop guard test assertions resilient to cookie format and NODE_ENV

fix: make loop guard test assertions resilient to cookie format and NODE_ENV Replace brittle hard-coded Expires timestamp with regex matching either Max-Age=0 or Expires= header. Explicitly set NODE_ENV before middleware call to avoid ambient environment dependency in Secure flag assertion.
Bug FixPatchFeb 9, 2026

Environment-aware secure flag on loop guard cookie, rename misleading test

fix: environment-aware secure flag on loop guard cookie, rename misleading test Make the __auth_loop_guard cookie's secure flag conditional on NODE_ENV so the loop guard works over HTTP in local development. Rename the sign-up loop guard test to accurately describe that it breaks the redirect loop. Update the cookie assertion to expect no Secure flag in non-production environments.
Bug FixPatchFeb 9, 2026

Add cookie-based loop guard to prevent auth redirect loops

fix: add cookie-based loop guard to prevent auth redirect loops When stale Clerk session cookies cause the middleware to detect a user as authenticated on /sign-in but the dashboard's requireAuth() fails (e.g. missing profile), a redirect loop occurs between /sign-in and /dashboard. The loop guard sets a 10-second httpOnly cookie on redirect from /sign-in to /dashboard. If the user lands back on /sign-in within that window, the middleware lets the sign-in page render instead of redirecting again.
Bug FixPatchFeb 9, 2026

Add Clerk production proxy domain to CSP allowlist

## Summary - Add `https://clerk.pulse.g8n.ai` to CSP script-src, connect-src, img-src, and frame-src directives The Clerk production proxy domain was being blocked by CSP, preventing the Clerk JS bundle from loading. This caused a blank sign-in page and broken auth on production. ## Test Plan - [x] Verified on production — sign-in page loads, zero CSP errors - [x] All 783 unit tests passing

## Summary - Add `https://clerk.pulse.g8n.ai` to CSP script-src, connect-src, img-src, and frame-src directives The Clerk production proxy domain was being blocked by CSP, preventing the Clerk JS bundle from loading. This caused a blank sign-in page and broken auth on production. ## Test Plan - [x] Verified on production — sign-in page loads, zero CSP errors - [x] All 783 unit tests passing
ImprovementPatchFeb 9, 2026

Add CSP regression assertions for Clerk production proxy domain

The middleware test suite verified CSP header existence but not its content. Without assertions on specific allowlisted domains, refactors could silently break production auth by removing required entries. ## Changes - Added test case validating `https://clerk.pulse.g8n.ai` presence in CSP directives (`script-src`, `connect-src`, `img-src`, `frame-src`) - Uses regex pattern matching to parse individual directives from the full CSP header string ```typescript it("includes Clerk production proxy domain in CSP directives", async () => { const response = await middlewareCallback(auth, request); const csp = response.headers.get("Content-Security-Policy"); // Verify it's in script-src const scriptSrcMatch = csp!.match(/script-src[^;]+/); expect(scriptSrcMatch![0]).toContain("https://clerk.pulse.g8n.ai"); // Verify it's in connect-src const connectSrcMatch = csp!.match(/connect-src[^;]+/); expect(connectSrcMatch![0]).toContain("https://clerk.pulse.g8n.ai"); // ... (img-src, frame-src) }); ``` This prevents future CSP refactors from inadvertently removing the production auth domain. <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

The middleware test suite verified CSP header existence but not its content. Without assertions on specific allowlisted domains, refactors could silently break production auth by removing required entries. ## Changes - Added test case validating `https://clerk.pulse.g8n.ai` presence in CSP directives (`script-src`, `connect-src`, `img-src`, `frame-src`) - Uses regex pattern matching to parse individual directives from the full CSP header string ```typescript it("includes Clerk production proxy domain in CSP directives", async () => { const response = await middlewareCallback(auth, request); const csp = response.headers.get("Content-Security-Policy"); // Verify it's in script-src const scriptSrcMatch = csp!.match(/script-src[^;]+/); expect(scriptSrcMatch![0]).toContain("https://clerk.pulse.g8n.ai"); // Verify it's in connect-src const connectSrcMatch = csp!.match(/connect-src[^;]+/); expect(connectSrcMatch![0]).toContain("https://clerk.pulse.g8n.ai"); // ... (img-src, frame-src) }); ``` This prevents future CSP refactors from inadvertently removing the production auth domain. <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
ImprovementPatchFeb 9, 2026

Format test file with prettier

style: format test file with prettier Co-authored-by: aamdani <3674123+aamdani@users.noreply.github.com>
ImprovementPatchFeb 9, 2026

Add CSP regression assertions for Clerk production proxy domain

test: add CSP regression assertions for Clerk production proxy domain Co-authored-by: aamdani <3674123+aamdani@users.noreply.github.com>
ImprovementPatchFeb 9, 2026

Initial plan

Initial plan
Bug FixPatchFeb 9, 2026

Add Clerk production proxy domain to CSP allowlist

fix: add Clerk production proxy domain to CSP allowlist clerk.pulse.g8n.ai was blocked by CSP script-src, preventing Clerk JS bundle from loading on production. Added to script-src, connect-src, img-src, and frame-src directives.
New FeatureMinorFeb 9, 2026

Migrate auth to Clerk, add GitHub App integration and legacy user reconciliation

## Summary - **Auth migration**: Replace Supabase Auth with Clerk for authentication, session management, and social logins (GitHub OAuth, passkeys, email+password) - **GitHub App integration**: Full repo connectivity — installation flow, repo linking, feedback-to-issue creation, webhook-driven changelog sync - **Legacy user reconciliation**: Seamless migration path for existing users via email-based profile matching and idempotent backfill - **Security hardening**: 34 Copilot review threads resolved across 7 review rounds — CSP tightening, CSRF state nonces, input validation, fail-closed middleware, server-only guards ## Auth Migration (Supabase Auth → Clerk) - Add `profiles` table (UUID PK + `clerk_id` TEXT) with idempotent backfill from `auth.users` - Migrate all FK constraints from `auth.users` to `profiles` with `IF EXISTS` for idempotency - Legacy profile reconciliation: `requireAuth()` and Clerk webhook match existing users by email, update `clerk_id` in-place to preserve all associated data - Rewrite middleware with `clerkMiddleware()`, route matchers, fail-closed error handling for protected routes, and CSP headers (dev + production Clerk domains) - Replace auth forms with Clerk `<SignIn>` / `<SignUp>` components (catch-all routes) - Clerk webhook endpoint for user sync (`user.created`/`updated`/`deleted`) with legacy reconciliation and null-email safety - Migrate 52+ auth call sites across 10 server action files to `requireAuth()` + service role `db()` - Admin system rewritten: Clerk Backend API for user management/suspend, PostgREST foreign-table joins for scalable plan filtering - Stripe routes updated with dual metadata keys (`pulse_user_id` + `supabase_user_id`) for backward compatibility - `import "server-only"` guards on Supabase service role and GitHub App modules ## GitHub App Integration - `octokit`-based GitHub App client (lazy singleton, base64 private key decoding) - Installation flow with CSRF state nonce (cookie + query param verificatio

## Summary - **Auth migration**: Replace Supabase Auth with Clerk for authentication, session management, and social logins (GitHub OAuth, passkeys, email+password) - **GitHub App integration**: Full repo connectivity — installation flow, repo linking, feedback-to-issue creation, webhook-driven changelog sync - **Legacy user reconciliation**: Seamless migration path for existing users via email-based profile matching and idempotent backfill - **Security hardening**: 34 Copilot review threads resolved across 7 review rounds — CSP tightening, CSRF state nonces, input validation, fail-closed middleware, server-only guards ## Auth Migration (Supabase Auth → Clerk) - Add `profiles` table (UUID PK + `clerk_id` TEXT) with idempotent backfill from `auth.users` - Migrate all FK constraints from `auth.users` to `profiles` with `IF EXISTS` for idempotency - Legacy profile reconciliation: `requireAuth()` and Clerk webhook match existing users by email, update `clerk_id` in-place to preserve all associated data - Rewrite middleware with `clerkMiddleware()`, route matchers, fail-closed error handling for protected routes, and CSP headers (dev + production Clerk domains) - Replace auth forms with Clerk `<SignIn>` / `<SignUp>` components (catch-all routes) - Clerk webhook endpoint for user sync (`user.created`/`updated`/`deleted`) with legacy reconciliation and null-email safety - Migrate 52+ auth call sites across 10 server action files to `requireAuth()` + service role `db()` - Admin system rewritten: Clerk Backend API for user management/suspend, PostgREST foreign-table joins for scalable plan filtering - Stripe routes updated with dual metadata keys (`pulse_user_id` + `supabase_user_id`) for backward compatibility - `import "server-only"` guards on Supabase service role and GitHub App modules ## GitHub App Integration - `octokit`-based GitHub App client (lazy singleton, base64 private key decoding) - Installation flow with CSRF state nonce (cookie + query param verification) - Project slug validation (alphanumeric + hyphens, 2-100 chars) in install/callback routes - Cookie deletion via `cookieStore.delete()` for cross-browser reliability - Server actions: repo listing, linking with GitHub API accessibility check, unlinking, issue creation from feedback with update error logging - `GitHubConnectionCard` component in project settings (4-state UI) - "Create GitHub Issue" button on feedback detail with duplicate prevention - Webhook handler: `installation` events (created/deleted/suspended) and `issues` events (closed → resolve feedback) ## Database Migrations - **013**: Create `profiles` table (clerk_id UNIQUE, email NOT NULL, role check constraint) - **014**: Drop FK constraints from `auth.users` (IF EXISTS), backfill profiles from `auth.users` with unique placeholder emails, re-add FKs to `profiles` - **015**: Disable RLS (documented: service role bypasses RLS, old policies used defunct `auth.uid()`, app-level auth enforced everywhere) ## Security (34 review threads resolved) - Fail-closed middleware for protected routes on auth errors - CSRF state nonce on GitHub App install/callback flow - PostgREST filter injection prevention (search param sanitization) - CSP: removed `unsafe-eval`, added production Clerk domains - Server-only guards on service role and GitHub App modules - Input validation: project slug format, UUID parameters, email null checks - Proper cookie deletion with matching attributes - Insert-only GitHub installations (never overwrite `user_id` on conflict) ## Test Coverage - 783 unit tests across 69 files (up from 642) - New test suites: middleware (17 tests), auth (15 tests), Clerk webhook (21 tests), GitHub callback (12 tests), GitHub install (7 tests) - E2E infrastructure rewritten for `@clerk/testing/playwright` with `requireEnv()` validation - All CI checks passing (lint, format, typecheck, unit tests, build, CodeQL, security audit) ## Setup Required After merging: 1. **Supabase**: Run migrations 013-015 on production 2. **Clerk**: Already configured (Pulse Dev app, webhook, test users created) 3. **Vercel**: Set `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `CLERK_SECRET_KEY`, `CLERK_WEBHOOK_SECRET` 4. **GitHub App**: Create on GitHub.com, set `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, `GITHUB_WEBHOOK_SECRET` 5. **Dogfood**: Install GitHub App on G8N-AI/pulse, link repo ## Test Plan - [x] `npm run check` passes (lint + format + typecheck + 783 tests) - [x] `npm run build` passes (all pages force-dynamic, no prerender failures) - [x] CI pipeline fully green (14/14 checks passing) - [ ] Run Supabase migrations on production - [ ] Verify Clerk sign-in (email+password, GitHub OAuth, passkeys) - [ ] Verify admin portal access and user management via Clerk API - [ ] Test GitHub App installation and repo linking - [ ] Test feedback → GitHub issue creation - [ ] Verify Stripe checkout and billing portal with new auth - [ ] Verify legacy user data preserved after Clerk sign-in
Bug FixPatchFeb 9, 2026

Add Clerk production CSP domains, proper cookie deletion, tighten email type to non-null

fix: add Clerk production CSP domains, proper cookie deletion, tighten email type to non-null - Add *.clerk.accounts.com to all CSP directives alongside dev domains for production compatibility - Use cookieStore.delete() instead of set-with-maxAge-0 for reliable cross-browser cookie clearing - Tighten ProfileRow.email from string|null to string to match TEXT NOT NULL schema constraint
Bug FixPatchFeb 9, 2026

Unique placeholder emails in backfill, slug validation, .maybeSingle() for legacy lookup

fix: unique placeholder emails in backfill, slug validation, .maybeSingle() for legacy lookup - Use per-user unique placeholder emails in migration backfill to prevent constraint violations - Remove hard-coded admin email from migration, defer to application-layer role assignment - Validate projectSlug format in GitHub install route before storing in cookie - Sanitize projectSlug cookie in GitHub callback route, fallback to /dashboard if invalid - Change legacy profile email lookup from .single() to .maybeSingle() for graceful null handling
ImprovementPatchFeb 9, 2026

Document why RLS is disabled in migration 015

docs: document why RLS is disabled in migration 015 Expand the header comment in 015_disable_rls.sql to explain the four reasons this is intentional and not a security regression: (1) service role key already bypasses RLS, (2) old policies referenced auth.uid() which is defunct after Clerk migration, (3) application-level auth is enforced in every code path, (4) the anon key is not exposed to clients. Notes future RLS restoration as a follow-up task.
Bug FixPatchFeb 9, 2026

Legacy profile reconciliation, GitHub callback CSRF, server-side plan joins, E2E env validation

fix: legacy profile reconciliation, GitHub callback CSRF, server-side plan joins, E2E env validation - Reconcile legacy profiles by email in requireAuth() and Clerk webhook before creating duplicates - Add state nonce to GitHub install/callback flow, prevent user_id ownership hijacking - Replace client-side ID filtering with PostgREST foreign-table joins for admin plan filter - Add requireEnv() helper in E2E fixtures for clear missing-credential errors
Bug FixPatchFeb 9, 2026

Reconcile legacy profiles by email before creating duplicates in requireAuth

fix: reconcile legacy profiles by email before creating duplicates in requireAuth When a profile is not found by clerk_id (PGRST116), requireAuth now checks for an existing profile by email before creating a new one. If found with a legacy_ clerk_id prefix, it updates the clerk_id to the real Clerk user ID, preserving all existing data. If found with a different real clerk_id, it throws a conflict error. Only creates a brand new profile if no email match. Adds 15 unit tests for requireAuth, requireAdmin, and getAuth covering all code paths including legacy reconciliation, conflict detection, and error handling.
Bug FixPatchFeb 9, 2026

Backfill profiles from auth.users before adding FK constraints in migration 014

fix: backfill profiles from auth.users before adding FK constraints in migration 014 The migration re-added FK constraints pointing to profiles(id) but never actually inserted profiles rows for existing auth.users. On a non-empty database this would cause the ADD CONSTRAINT statements to fail because owner_id/user_id values reference UUIDs not yet present in profiles. Replace the comment-only documentation with an actual idempotent INSERT from auth.users (ON CONFLICT DO NOTHING) that runs between the DROP and ADD CONSTRAINT sections.
Bug FixPatchFeb 9, 2026

Dead auth-route redirect, migration idempotency, free-plan filter perf, server-only guards

fix: dead auth-route redirect, migration idempotency, free-plan filter perf, server-only guards - Move auth-route check before public-route early return so sign-in/sign-up redirect works - Add IF EXISTS to all DROP CONSTRAINT statements in migration 014 - Replace N chained .neq() calls with single .not('id','in','(...)') for free-plan filter - Add server-only guard to GitHub App module to prevent client-side bundling
Bug FixPatchFeb 9, 2026

Address 9 review comments — security, correctness, and build fixes

fix: address 9 review comments — security, correctness, and build fixes - Move ClerkProvider inside body to satisfy Next.js root element requirement - Move admin users plan filter to DB query level before pagination, fix total count - Remove redundant unique index on profiles.clerk_id (already UNIQUE in column def) - Remove unsafe-eval from CSP script-src to tighten XSS protection - Add server-only guard to Supabase service role module - Throw error instead of empty-string fallback for missing email in auth - Validate repo accessibility via GitHub API before persisting link - Log warning when feedback row update fails after GitHub issue creation - Add force-dynamic to all pages to prevent Clerk prerender failures in CI
Bug FixPatchFeb 9, 2026

Resolve 6 review comments and CI build prerender failure

fix: resolve 6 review comments and CI build prerender failure - GitHub webhook: skip installation insert when no profile exists, update existing records only - Clerk webhook: skip profile insert/update when email is null (phone-only users) - Middleware: fail-closed for protected routes on auth errors, fail-open for public routes - Admin users search: sanitize PostgREST special characters from search parameter - E2E auth: remove redundant always-true negation check in retry logic - CI build: add force-dynamic to admin/dashboard/auth pages to prevent Clerk prerender failures - Add middleware test suite (14 tests), expand webhook and admin test coverage
Bug FixPatchFeb 9, 2026

Sanitize PostgREST special characters in admin user search filter

fix: sanitize PostgREST special characters in admin user search filter The admin users search endpoint interpolated the raw search param directly into a PostgREST .or() filter. Special characters (commas, parentheses, dots, asterisks, backslashes) could break or alter query semantics. Strip PostgREST-significant characters from the search term before interpolation. Add tests verifying sanitization and edge case handling when search contains only special characters.
New FeatureMinorFeb 8, 2026

Migrate auth from Supabase to Clerk, add GitHub App integration

feat: migrate auth from Supabase to Clerk, add GitHub App integration Auth migration: - Replace Supabase Auth with Clerk for sign-in, sign-up, and session management - Add profiles table (UUID PK + clerk_id) with FK migration from auth.users - Remove 59 RLS policies (authorization enforced at application level) - Rewrite middleware with clerkMiddleware() and route matchers - Replace custom auth forms with Clerk <SignIn>/<SignUp> components - Add Clerk webhook endpoint for user sync (user.created/updated/deleted) - Migrate 52+ auth call sites across 10 server action files - Rewrite admin system to use Clerk Backend API for user management - Update Stripe routes with new auth pattern and metadata keys - Update sidebar sign-out to use Clerk hooks GitHub App integration: - Add octokit-based GitHub App client (lazy singleton) - Add GitHub App installation flow (install + callback routes) - Add server actions for repo linking, listing, and issue creation - Add GitHubConnectionCard component to project settings - Add feedback-to-GitHub-issue creation button - Enhance webhook handler with installation and issues events Infrastructure: - Update 723 unit tests with new auth mocks - Rewrite E2E test infrastructure for @clerk/testing - Update all env vars, docs, and CI workflow
New FeatureMinorFeb 8, 2026

Full-stack SaaS platform with auth, billing, admin, and public API

## Summary Full-stack developer intelligence platform built with Next.js 16 (App Router), Supabase, TypeScript, Tailwind v4, shadcn/ui, and Stripe. ### Core Platform - **Authentication**: Email+password and GitHub OAuth via Supabase Auth with cookie-based sessions - **Multi-tenant Dashboard**: Projects CRUD with RLS-enforced data isolation - **Tier System**: Free / Pro ($100/month) with Stripe checkout, billing portal, and webhook handling ### Modules - **Changelog**: Versioned entries with categories, markdown sync, unseen tracking, RSS feeds, and AI summaries - **Roadmap**: Entries with milestone grouping, status tracking, and authenticated voting - **Feedback**: User submissions with categories, threaded messages, GitHub issue creation, and AI triage - **Notifications**: In-app system with read/unread tracking and email delivery (Pro) - **Announcements**: Scheduled banners with priority ordering, dismiss tracking, and WCAG contrast validation - **Content Generation**: Auto-generated marketing content across 5 template types ### Admin Portal - User management with search, plan filtering, suspension, and impersonation - Project and subscription overview with aggregated statistics - Blog management system with threaded comments and spam detection ### Public Interface - Landing page with hero, features, pricing, and CTA sections - Public project pages for changelog, roadmap, and feedback - Embeddable widget (Shadow DOM) with feedback, notifications, and announcements - REST API with API-key authentication and rate limiting ### Security Hardening - XSS sanitization (DOMPurify), open redirect prevention, UUID validation on all endpoints - CORS headers, field allowlisting, spoofable user ID prevention - Case-insensitive admin email gating, httpOnly impersonation cookies - Stripe error concealment, webhook signature verification, side-effect-free validation ordering - PGRST116-based 404 vs 500 distinction across all Supabase queries ### Infrastructure & Testing -

## Summary Full-stack developer intelligence platform built with Next.js 16 (App Router), Supabase, TypeScript, Tailwind v4, shadcn/ui, and Stripe. ### Core Platform - **Authentication**: Email+password and GitHub OAuth via Supabase Auth with cookie-based sessions - **Multi-tenant Dashboard**: Projects CRUD with RLS-enforced data isolation - **Tier System**: Free / Pro ($100/month) with Stripe checkout, billing portal, and webhook handling ### Modules - **Changelog**: Versioned entries with categories, markdown sync, unseen tracking, RSS feeds, and AI summaries - **Roadmap**: Entries with milestone grouping, status tracking, and authenticated voting - **Feedback**: User submissions with categories, threaded messages, GitHub issue creation, and AI triage - **Notifications**: In-app system with read/unread tracking and email delivery (Pro) - **Announcements**: Scheduled banners with priority ordering, dismiss tracking, and WCAG contrast validation - **Content Generation**: Auto-generated marketing content across 5 template types ### Admin Portal - User management with search, plan filtering, suspension, and impersonation - Project and subscription overview with aggregated statistics - Blog management system with threaded comments and spam detection ### Public Interface - Landing page with hero, features, pricing, and CTA sections - Public project pages for changelog, roadmap, and feedback - Embeddable widget (Shadow DOM) with feedback, notifications, and announcements - REST API with API-key authentication and rate limiting ### Security Hardening - XSS sanitization (DOMPurify), open redirect prevention, UUID validation on all endpoints - CORS headers, field allowlisting, spoofable user ID prevention - Case-insensitive admin email gating, httpOnly impersonation cookies - Stripe error concealment, webhook signature verification, side-effect-free validation ordering - PGRST116-based 404 vs 500 distinction across all Supabase queries ### Infrastructure & Testing - CI/CD pipeline with parallel jobs, E2E sharding, CodeQL analysis, and PR preview deploys - Comprehensive unit test suite (Vitest) and E2E coverage on Chromium + Firefox (Playwright) - Supabase migrations (18 tables with RLS), Vercel hosting - GitHub Actions self-sync workflow with merged-PR guard and safe payload quoting ## Test plan - [ ] All unit tests pass (`npm run check`) - [ ] E2E tests pass on Chromium and Firefox (`npm run test:e2e`) - [ ] Stripe checkout flow works end-to-end (free → pro upgrade) - [ ] Admin portal accessible only to configured admin emails (case-insensitive) - [ ] Public project pages render correctly for unauthenticated users - [ ] Widget embeds correctly in third-party pages via Shadow DOM - [ ] CI/CD pipeline passes all jobs including deploy preview
Bug FixPatchFeb 8, 2026

Use validated env var directly for Stripe price ID, remove redundant helper call

fix: use validated env var directly for Stripe price ID, remove redundant helper call
Bug FixPatchFeb 8, 2026

Validate project slug before Stripe customer creation to avoid side effects

fix: validate project slug before Stripe customer creation to avoid side effects
Bug FixPatchFeb 8, 2026

Ensure impersonate response has stable type, remove hardcoded test counts from ROADMAP

fix: ensure impersonate response has stable type, remove hardcoded test counts from ROADMAP
Bug FixPatchFeb 8, 2026

Case-insensitive admin email check, safe workflow payload quoting, decouple public E2E from auth setup

fix: case-insensitive admin email check, safe workflow payload quoting, decouple public E2E from auth setup
Bug FixPatchFeb 8, 2026

Warn instead of throw for missing E2E creds locally, fix misleading signIn comment

fix: warn instead of throw for missing E2E creds locally, fix misleading signIn comment
Bug FixPatchFeb 8, 2026

Add nodejs runtime to Stripe checkout route for SDK compatibility

fix: add nodejs runtime to Stripe checkout route for SDK compatibility
Bug FixPatchFeb 8, 2026

Fail-fast on missing E2E env vars and assert auth state instead of skipping

fix: fail-fast on missing E2E env vars and assert auth state instead of skipping
Bug FixPatchFeb 8, 2026

Guard pulse-sync workflow to skip unmerged PR close events

fix: guard pulse-sync workflow to skip unmerged PR close events
Bug FixPatchFeb 8, 2026

Distinguish PGRST116 from DB errors in blog/project GET, deduplicate suspend logic

fix: distinguish PGRST116 from DB errors in blog/project GET, deduplicate suspend logic - Blog post GET and admin project GET now return 500 for transient DB errors instead of masking them as 404 - Suspend/unsuspend handler consolidated from duplicated branches into single updateUserById call
Bug FixPatchFeb 8, 2026

Return 404 for blog post/comment DELETE on missing rows, add nodejs runtime to webhook

fix: return 404 for blog post/comment DELETE on missing rows, add nodejs runtime to webhook - Blog comment and post DELETE handlers now use .select().single() to detect missing rows and return 404 instead of silent success - Added explicit nodejs runtime annotation to Stripe webhook route for SDK compatibility
Bug FixPatchFeb 8, 2026

Lazy-load E2E credentials to allow partial test runs, add E2E vars to .env.example

fix: lazy-load E2E credentials to allow partial test runs, add E2E vars to .env.example - E2E fixtures now use getter-based lazy loading so missing admin credentials don't crash public-only test runs - Added E2E_USER_EMAIL, E2E_USER_PASSWORD, E2E_ADMIN_EMAIL, E2E_ADMIN_PASSWORD to .env.example for discoverability
Bug FixPatchFeb 8, 2026

Narrow admin projects query to only fetch columns used in response

fix: narrow admin projects query to only fetch columns used in response - Replace select("*") with explicit column list (id, name, slug, tier, owner_id, created_at, updated_at) to reduce data transfer from Supabase
Bug FixPatchFeb 8, 2026

Add explicit Node.js runtime to Stripe portal route, add aria-label to projects search

fix: add explicit Node.js runtime to Stripe portal route, add aria-label to projects search - Pin portal route to nodejs runtime to prevent accidental Edge runtime breakage with Stripe SDK - Add aria-label="Search projects" for screen reader accessibility
Bug FixPatchFeb 8, 2026

Add 300ms search debounce and AbortController to admin projects page

fix: add 300ms search debounce and AbortController to admin projects page - Prevents network fetch on every keystroke by debouncing search input 300ms - AbortController cancels stale requests to prevent race conditions - Matches the pattern already used in admin users page
Bug FixPatchFeb 8, 2026

Preserve actual Stripe status in webhook handler, remove redundant non-null assertions

fix: preserve actual Stripe status in webhook handler, remove redundant non-null assertions - Webhook subscription.updated handler now preserves actual Stripe status (incomplete, unpaid, paused) instead of collapsing all non-active statuses to "canceled" - Removed non-null assertion operators from e2e/setup-test-users.ts env vars since process.exit(1) guard already narrows types
Bug FixPatchFeb 8, 2026

Hide Stripe SDK error details from webhook response, log unhandled event types

fix: hide Stripe SDK error details from webhook response, log unhandled event types - Webhook signature verification errors now return a generic message instead of echoing internal Stripe SDK error details - Added default case to event type switch for observability of unexpected Stripe events
Bug FixPatchFeb 8, 2026

Handle DB errors in deleteProject, surface total_truncated flag in admin users API

fix: handle DB errors in deleteProject, surface total_truncated flag in admin users API - deleteProject now distinguishes PGRST116 (not found) from transient DB errors instead of always returning "Project not found" - Admin users API includes total_truncated flag when scan hits MAX_PAGES cap so UI knows total is an undercount
Bug FixPatchFeb 8, 2026

Require E2E email env vars instead of falling back to placeholder defaults

fix: require E2E email env vars instead of falling back to placeholder defaults
Bug FixPatchFeb 8, 2026

Accumulate totalUsers across all pages when API total field is missing

fix: accumulate totalUsers across all pages when API total field is missing
Bug FixPatchFeb 8, 2026

Surface auth admin API failure in stats response, fail fast on missing E2E passwords

fix: surface auth admin API failure in stats response, fail fast on missing E2E passwords - Add users_unavailable flag to admin stats response when auth admin API fails instead of silently returning zeros - Replace empty string fallback for E2E passwords with requireEnv that throws early with an actionable error message
Bug FixPatchFeb 8, 2026

Batch user ID enrichment queries to avoid PostgREST URL length limits

fix: batch user ID enrichment queries to avoid PostgREST URL length limits
Bug FixPatchFeb 8, 2026

Strip ILIKE wildcards from project search, guard null portal URL, fix key generation bias, stable FAQ keys

fix: strip ILIKE wildcards from project search, guard null portal URL, fix key generation bias, stable FAQ keys - Remove underscore from allowed search chars to prevent ILIKE wildcard injection and eliminate redundant escape step (resolves CodeQL alert) - Add null guard for Stripe billing portal session URL - Replace modulo-biased byte mapping with rejection sampling in API key generation for uniform character distribution - Use faq.question as stable React key instead of array index
ImprovementPatchFeb 8, 2026

Potential fix for code scanning alert no. 5: Incomplete string escaping or encoding

Potential fix for code scanning alert no. 5: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Bug FixPatchFeb 8, 2026

Guard null Stripe session URL, escape ILIKE wildcards in project search, abort stale user fetches

fix: guard null Stripe session URL, escape ILIKE wildcards in project search, abort stale user fetches - Return 500 when Stripe checkout session URL is null instead of passing null to the client - Escape _ and % in admin project search ILIKE patterns so they match literally instead of acting as SQL wildcards - Replace useCallback+useEffect fetch pattern with AbortController in admin users page to prevent stale search responses from overwriting newer results
Bug FixPatchFeb 8, 2026

Prevent checkout upsert from overwriting existing subscription tier and status

fix: prevent checkout upsert from overwriting existing subscription tier and status
Bug FixPatchFeb 8, 2026

Pre-compute Date boundaries in stats loop, add error handling to bootstrap project lookup

fix: pre-compute Date boundaries in stats loop, add error handling to bootstrap project lookup
Bug FixPatchFeb 8, 2026

Consistent owner lookup in admin project detail, validate API key scopes, expand README env vars

fix: consistent owner lookup in admin project detail, validate API key scopes, expand README env vars - Derive single ownerUserId from project_members owner role with fallback to projects.owner_id for consistent subscription and auth lookups in admin project detail endpoint - Add VALID_SCOPES allowlist and deduplication to createApiKey to prevent unknown or empty scopes from being persisted - Expand README env var table with missing variables and link to .env.example as authoritative source
Bug FixPatchFeb 8, 2026

Trim whitespace in ADMIN_EMAILS parsing to handle spaces after commas

fix: trim whitespace in ADMIN_EMAILS parsing to handle spaces after commas
Bug FixPatchFeb 8, 2026

Make impersonation email cookie httpOnly, centralize PRO_PRICE_USD constant

fix: make impersonation email cookie httpOnly, centralize PRO_PRICE_USD constant - Add httpOnly to pulse_impersonate_email cookie to prevent client-side PII exposure, pass email as server-side prop to ImpersonationBanner - Extract PRO_PRICE_USD=100 from 3 files into shared src/lib/stripe.ts constant to prevent pricing drift across admin stats, subscriptions, and dashboard
Bug FixPatchFeb 8, 2026

Add aria-label to admin users search input for screen reader accessibility

fix: add aria-label to admin users search input for screen reader accessibility
ImprovementPatchFeb 8, 2026

Extract shared clearImpersonationCookies helper, clarify bootstrap webhook secret naming

refactor: extract shared clearImpersonationCookies helper, clarify bootstrap webhook secret naming - Extract duplicated cookie-clearing logic from impersonate DELETE and exit-impersonation POST into shared clearImpersonationCookies helper - Clarify bootstrap script output to explain GITHUB_WEBHOOK_SECRET and PULSE_WEBHOOK_SECRET are the same value used on different sides
Bug FixPatchFeb 8, 2026

Prevent infinite loading on project fetch failure, handle null user email in Stripe checkout

fix: prevent infinite loading on project fetch failure, handle null user email in Stripe checkout Wrap dashboard page load functions in try/finally so setLoading(false) runs on all paths including getProject failure. Add separate loading and error states to announcements/new and settings pages. Conditionally pass email to Stripe customer creation to handle undefined user.email.
ImprovementPatchFeb 8, 2026

Derive Stripe return URLs from request origin, extract ban duration constant

refactor: derive Stripe return URLs from request origin, extract ban duration constant Replace NEXT_PUBLIC_APP_URL dependency in checkout and portal routes with request.nextUrl.origin so return URLs match the current deployment (production, preview, local dev) without env var alignment. Extract magic "876000h" ban duration to INDEFINITE_BAN_DURATION constant.
Bug FixPatchFeb 8, 2026

Add error checking to admin stats count queries

fix: add error checking to admin stats count queries Return 500 with the failing query label when any of the 10 Supabase count queries in the stats endpoint returns an error, instead of silently defaulting to 0.
ImprovementPatchFeb 8, 2026

Extract shared billing body parser, add signups_truncated flag to admin stats

refactor: extract shared billing body parser, add signups_truncated flag to admin stats Extract duplicated JSON body parsing and slug validation from Stripe checkout and portal routes into shared billing-helpers module. Add signups_truncated flag to admin stats response when signup counting is capped by MAX_PAGES pagination limit.
Bug FixPatchFeb 8, 2026

Compensate orphaned Stripe customers on upsert failure, use URL composition in checkout and portal routes

fix: compensate orphaned Stripe customers on upsert failure, use URL composition in checkout and portal routes
Bug FixPatchFeb 8, 2026

Correct README license reference, use left join for blog comments, add portal Stripe guard, fix DELETE test method

fix: correct README license reference, use left join for blog comments, add portal Stripe guard, fix DELETE test method
Bug FixPatchFeb 8, 2026

Batch N+1 project count queries, cap admin stats pagination, expand checkout and users test coverage

fix: batch N+1 project count queries, cap admin stats pagination, expand checkout and users test coverage
Bug FixPatchFeb 8, 2026

Verify subscription status before activation, hide raw Stripe errors, sanitize blog search, server-side counting

fix: verify subscription status before activation, hide raw Stripe errors, sanitize blog search, server-side counting - Retrieve Stripe subscription in checkout.session.completed to verify active/trialing before granting pro - Return generic error messages in checkout and portal routes instead of leaking raw Stripe errors - Remove early-exit ordering assumption in admin stats pagination loop - Trim and cap blog search input to 100 chars matching other admin routes - Replace in-memory row counting with server-side count queries in admin projects route
Bug FixPatchFeb 8, 2026

Always update subscription status in webhook, denormalize user_email on subscriptions

fix: always update subscription status in webhook, denormalize user_email on subscriptions - Move subscriptionItem guard to only protect period field access, not the entire status/tier update - Add user_email column to subscriptions table (migration 012) - Set user_email during Stripe checkout upsert and webhook checkout.session.completed - Replace N+1 getUserById loop in admin subscriptions route with direct user_email read - Update Subscription types with user_email field
ImprovementPatchFeb 8, 2026

Update all documentation and test counts, add admin projectId tests

docs: update all documentation and test counts, add admin projectId tests - Update unit test count from 363 to 628 across README, ROADMAP, AI_PLANNING, CLAUDE.md, and TESTING.md - Update review thread count to 256 resolved in ROADMAP - Add recent security fixes to CHANGELOG (Stripe type narrowing, Zod validation, webhook metadata, pagination) - Update TESTING.md file tree from 34 to 59 test files - Update AI_PLANNING session state with latest commit, test counts, and completed work - Add 14 unit tests for admin projects/[projectId] route (the last untested API route)
Bug FixPatchFeb 8, 2026

Narrow Stripe union types, add period field fallback, PGRST116 404 handling, project slug error check

fix: narrow Stripe union types, add period field fallback, PGRST116 404 handling, project slug error check - Narrow session.customer and session.subscription from union types before persisting in webhook handler - Try subscription object first for period dates, fall back to subscriptionItem for SDK v20.3.1 compat - Map PGRST116 error to 404 in announcements PUT handler instead of 500 - Handle .single() errors in createProject action, treat only PGRST116 as non-fatal
Bug FixPatchFeb 8, 2026

Guard webhook period fields and missing metadata, fix unread count logic, add admin page error handling

fix: guard webhook period fields and missing metadata, fix unread count logic, add admin page error handling - Add typeof guards for SubscriptionItem period fields to prevent NaN dates in webhook handler - Return 400 with console.error when supabase_user_id metadata missing in all 3 webhook event cases - Only decrement unreadCount when notification was previously unread (read_at == null) - Add try/catch/finally with res.ok checks to admin users and projects page fetch callbacks - Remove unused eslint-disable directives in admin pages - Update webhook tests to expect 400 status when userId metadata is missing
Bug FixPatchFeb 8, 2026

Handle listUsers error in stats, consistent PUT error format, honest N+1 comment, env-var test passwords

fix: handle listUsers error in stats, consistent PUT error format, honest N+1 comment, env-var test passwords - Throw listUsers error in admin stats route to trigger existing catch block instead of silently using empty data - Format PUT validation errors as "path: message" in announcements route, matching POST handler consistency - Update misleading "avoid N+1" comment in subscriptions route to describe actual concurrency-limited behavior - Move E2E test passwords from hardcoded values to E2E_USER_PASSWORD and E2E_ADMIN_PASSWORD env vars
Bug FixPatchFeb 8, 2026

Skip enrichment queries on empty IDs, NaN-safe blog pagination, Zod PUT validation, portal error handling

fix: skip enrichment queries on empty IDs, NaN-safe blog pagination, Zod PUT validation, portal error handling - Skip subscriptions/members queries when userIds is empty in admin users route (fixes __none__ UUID error) - Skip feedback/changelog queries when projectIds is empty in admin projects route - Add NaN-safe pagination to admin blog and blog comments routes - Replace manual PUT allowlist with Zod updateAnnouncementApiSchema validation in announcements route - Wrap Stripe portal session creation in try/catch with consistent error response
Bug FixPatchFeb 8, 2026

NaN-safe pagination in admin routes, Zod validation for announcements POST, update test count

fix: NaN-safe pagination in admin routes, Zod validation for announcements POST, update test count - Clamp NaN/negative page and limit params in admin subscriptions and projects routes - Replace unsafe as-casts with Zod schema validation in announcements POST handler - Add createAnnouncementApiSchema that reuses existing Zod schema minus project_id - Add 6 new announcements validation tests (type enum, boolean fields, priority, arrays, datetime) - Update CLAUDE.md test count to 620+
Bug FixPatchFeb 7, 2026

Validate UUIDs in admin PUT/DELETE, return 404 on empty DELETE, harden Stripe and pagination

fix: validate UUIDs in admin PUT/DELETE, return 404 on empty DELETE, harden Stripe and pagination - Add UUID regex validation to PUT and DELETE handlers in changelog, announcements, roadmap, and feedback admin routes - Return 404 instead of false success when DELETE matches no rows in all 4 admin routes - Handle upsert error when persisting Stripe customer ID in checkout route - Handle subscription query errors in admin projects/[projectId] route (PGRST116 for no-sub, 500 for real errors) - Clamp NaN/negative page and limit params to safe defaults in admin users route - Update CLAUDE.md test count to 610+
Bug FixPatchFeb 7, 2026

Eliminate open redirect in Stripe routes, handle query errors in admin routes, harden bootstrap

fix: eliminate open redirect in Stripe routes, handle query errors in admin routes, harden bootstrap - Require NEXT_PUBLIC_APP_URL in Stripe checkout and portal routes, removing Host-header-derived fallback - Handle Supabase query errors explicitly in admin users/[userId] route (PGRST116 for no-subscription, 500 for real errors) - Handle all enrichment query errors in admin projects/[projectId] route before constructing response - Add console.error logging to admin stats catch block for production observability - Use Postgres error code 23505 instead of brittle message substring for duplicate detection in bootstrap script
Bug FixPatchFeb 7, 2026

Handle Supabase query errors in Stripe routes, use PGRST116 error code, expand admin tests

fix: handle Supabase query errors in Stripe routes, use PGRST116 error code, expand admin tests - Handle Supabase query errors explicitly in Stripe portal and checkout routes (500 for real errors, PGRST116 for no rows) - Extract typed body.id variable in announcements and roadmap admin PUT handlers - Use error.code === "PGRST116" instead of fragile regex for PostgREST no-rows detection in blog routes - Expand admin feedback tests from 14 to 23 (auth, scope, allowlisted fields, empty updates) - Expand admin changelog tests from 12 to 24 (insert, update, delete, field allowlisting, 404) - Expand admin announcements tests from 17 to 29 (scope, empty updates, cross-tenant prevention)
Bug FixPatchFeb 7, 2026

Validate Stripe slug params, guard empty admin updates, fix blog 404s, import ReactNode

fix: validate Stripe slug params, guard empty admin updates, fix blog 404s, import ReactNode - Add slug regex validation to Stripe checkout and portal routes before URL interpolation - Tolerate empty request body in checkout route (projectSlug is optional) - Add try/catch for JSON parsing and 404 for missing resources in blog admin routes - Import type { ReactNode } from "react" in 4 layout files that used React.ReactNode - Guard against empty updates in all 4 admin PUT handlers (changelog, announcements, roadmap, feedback) - Update CLAUDE.md test counts to 570+ unit tests and 62 E2E tests
Bug FixPatchFeb 7, 2026

Persist Stripe customer ID on creation, handle trialing subscriptions, add banner a11y roles

fix: persist Stripe customer ID on creation, handle trialing subscriptions, add banner a11y roles - Persist new Stripe customer ID to subscriptions table to prevent duplicate customers - Treat trialing subscriptions as active/pro instead of incorrectly downgrading to free - Guard against missing subscription items in webhook handler - Add semantic role and aria-live attributes to announcement banner based on type - Mark decorative icon as aria-hidden in announcement banner - Tolerate empty request body in Stripe portal endpoint
ImprovementPatchFeb 7, 2026

Rewrite README, update CHANGELOG and ROADMAP, add architecture and API documentation

docs: rewrite README, update CHANGELOG and ROADMAP, add architecture and API documentation - Replace generic Create Next App README with comprehensive Pulse project documentation - Add CHANGELOG v0.0.2 covering security, accessibility, and bug fix work - Mark ROADMAP phases 1-3 as completed, add production readiness phase 3.5 - Create docs/ARCHITECTURE.md, docs/API.md, docs/DATABASE.md, docs/DEPLOYMENT.md, docs/TESTING.md - Add 211 unit tests across 25 new test files covering all previously untested API routes - Test coverage now at 574 tests across 59 files
Bug FixPatchFeb 7, 2026

Allowlist mutable fields in admin PUT handlers, fix WCAG contrast and PostgREST timestamp parsing

fix: allowlist mutable fields in admin PUT handlers, fix WCAG contrast and PostgREST timestamp parsing - Prevent cross-tenant mutation by allowlisting update fields in roadmap, changelog, announcements, and feedback admin PUT handlers - Fix PostgREST timestamp parsing by stripping milliseconds and quoting ISO values in announcement filter - Use proper sRGB linearization for WCAG-accurate contrast ratio calculation in banner component - Decouple announcement table filtering from display labels using stable status keys
Bug FixPatchFeb 7, 2026

Use read_at for notification unread counts, standardize UUID error messages, add rate limiting to feedback GET

fix: use read_at for notification unread counts, standardize UUID error messages, add rate limiting to feedback GET - Fix notification unread count query to use read_at IS NULL instead of is_read column - Add error handling for feedbackRes/changelogRes in admin projects route - Handle shorthand hex and non-hex colors in announcement banner contrast helper - Standardize all UUID validation errors to "x-pulse-user-id must be a valid UUID" - Add rate limiting to feedback GET endpoint to prevent enumeration - Add 14 unit tests for notifications API route
Bug FixPatchFeb 7, 2026

Add UUID validation to feedback POST, admin user enrichment error handling, WCAG banner contrast helper

fix: add UUID validation to feedback POST, admin user enrichment error handling, WCAG banner contrast helper
Bug FixPatchFeb 7, 2026

Add UUID validation to changelog and announcements, accurate notification unread counts, a11y for announcement table

fix: add UUID validation to changelog and announcements, accurate notification unread counts, a11y for announcement table
ImprovementPatchFeb 7, 2026

Add Stripe webhook and changelog unseen test suites, fix PostgREST in-filter quoting and comment a11y

test: add Stripe webhook and changelog unseen test suites, fix PostgREST in-filter quoting and comment a11y