Skip to content

Commit 15b1fab

Browse files
feat: filter HeadlessChrome submissions from feedback API (#1097)
Co-authored-by: Ona <no-reply@ona.com>
1 parent 959abf3 commit 15b1fab

3 files changed

Lines changed: 47 additions & 8 deletions

File tree

.agents/quality.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
2222
| Search | A | Full-text search via PostgreSQL tsvector + tsquery. API route with integration tests (14 tests) including transient network retry regression tests. Sidebar search component with unit tests (12 tests). Sentry error capture. E2E spec covers search flow (`e2e/search.spec.ts`, 5 tests). |
2323
| Import/Export | A | Markdown export (download .md) and import (parse .md, create page) via page menu and workspace home. Shared `useMarkdownImport` hook. Markdown utils with unit tests (8 tests). E2E specs: import-export flow plus edge cases (`e2e/import-export.spec.ts`, 7 tests), workspace home import (`e2e/workspace-home-import.spec.ts`, 3 tests) — 10 E2E tests total. |
2424
| Members | A | Member list with role badges, role change, remove. Invite form (email + role). Pending invite list with revoke. Invite accept page. Role select dropdown. Settings members page with server-side data fetching. Unit tests: invite-form (4 tests), member-list (14 tests), pending-invite-list (8 tests), role-select (8 tests). E2E coverage: invite, pending list, revoke, accept, role change, remove, member role restrictions (7 tests). |
25-
| Feedback | A | Feedback form with screenshot capture. API route with integration tests (15 tests). Feedback form design spec tests (5 tests). Screenshot hook unit tests (2 tests). E2E spec covers sheet open, type selection, character count, form submission, reset, and validation (6 tests). |
25+
| Feedback | A | Feedback form with screenshot capture. API route with integration tests (17 tests) including HeadlessChrome filter. Feedback form design spec tests (5 tests). Screenshot hook unit tests (2 tests). E2E spec covers sheet open, type selection, character count, form submission, reset, and validation (6 tests). |
2626
| App Shell | A | Collapsible sidebar (desktop: aside, mobile: Sheet), sidebar context with ⌘+\ shortcut, workspace switcher, page tree, user menu with sign-out, focus mode hint, theme toggle, skip-to-content accessibility link. Clean component decomposition. Sidebar context unit tests (21 tests): state management, keyboard shortcut registration, toggle behavior, focus mode, mobile close-on-navigate. Loading skeleton tests (25 tests): app (4), workspace (5), page (6), settings (5), settings members (5). Error boundary tests (22 tests): route-error component (6), workspace error (4), page error (4), settings error (4), settings members error (4). Focus mode hint design spec tests (3 tests). Workspace home design spec tests (5 tests). E2E specs: sidebar responsive (4 tests), mobile responsive (5 tests — page creation, table scroll, board cards, slash menu, floating toolbar at 375×667), theme toggle (4 tests), skip-to-content (2 tests), not-found pages (3 tests), public routes (18 tests), accessibility audit with axe-core (10 tests — sign-in, workspace home, page editor, workspace settings, members, database table view, database board view, database calendar view, database gallery view, database list view), keyboard shortcuts dialog (5 tests — open via ? key, category display incl. Page section, shortcut verification, close on Escape, open via user menu), workspace home new database (1 test), workspace home mobile header (1 test), workspace home interactions (6 tests — new page, filter, sort, page navigation, clear filter, recently visited), workspace home import (3 tests — button visible, import creates page, imported page in list). |
27-
| API Routes | A | Health endpoint (DB connectivity check, 9 tests), search endpoint (full-text search, 14 tests), account deletion endpoint (6 tests), trash purge cron endpoint (8 tests), page versions endpoints (28 tests), and feedback endpoint (17 tests). All routes use `captureApiError` for transient network error classification. Search route uses `retryOnNetworkError` for transient failure resilience. Rate limiting via `withRateLimit` on feedback (5/min), search (30/min), account delete (3/hour), page version create (20/min), page version restore (10/min). All have integration tests with mocked Supabase. Account deletion E2E spec (4 tests). |
27+
| API Routes | A | Health endpoint (DB connectivity check, 9 tests), search endpoint (full-text search, 14 tests), account deletion endpoint (6 tests), trash purge cron endpoint (8 tests), page versions endpoints (28 tests), and feedback endpoint (19 tests). All routes use `captureApiError` for transient network error classification. Search route uses `retryOnNetworkError` for transient failure resilience. Rate limiting via `withRateLimit` on feedback (5/min), search (30/min), account delete (3/hour), page version create (20/min), page version restore (10/min). All have integration tests with mocked Supabase. Account deletion E2E spec (4 tests). |
2828
| UI Components | A | 16 shadcn/ui components (base-nova style): alert-dialog, badge, button, card, checkbox, context-menu, dialog, dropdown-menu, input, label, select, separator, sheet, table, textarea, tooltip. Overlay opacity regression test (2 tests). Toast error duration regression test (1 test). Dialog design spec test (3 tests). Global design-spec compliance checks (9 tests): rounded corners (incl. bare `rounded`), hex/RGB/HSL colors, font-family, arbitrary spacing, bg-black/white opacity, text-white/text-black, large text outside editor. Reduced-motion accessibility test (6 tests): verifies prefers-reduced-motion media query in globals.css. Design tokens use oklch color space, --radius: 0 for sharp corners. Visual regression E2E spec (1 test). |
2929
| Database | A | Database CRUD, table/board/gallery/calendar/list views, inline database, row detail pages, property types (text, number, select, multi-select, date, checkbox, URL, person, formula, email, phone, files, status, computed, relation), filter engine, filter/sort toolbar, formula parser/evaluator, column drag-and-drop reorder, table keyboard navigation, calendar keyboard navigation, CSV export, database duplication, bulk row selection and delete, mobile-responsive layouts. 56 Vitest files (1034 tests): CRUD utilities (55), column helpers (65), database duplicate (11), filter engine (54), formula parser/evaluator (80), CSV export (38), property type picker (14), property type picker completeness (5), table view value format (33), table defaults (27), table cell (40), database page width (3), database view client (6), filter bar (20), filter value editor (34), sort menu (19), rename property dialog (14), row properties header (17), view tabs (24), 5 hooks (use-database-filters 18, use-database-properties 20, use-database-rows 23, use-database-views 24, use-row-selection 10), 14 property type tests (checkbox 8, computed 15, date 12, email 8, files 11, formula 6, multi-select 10, number 14, person 9, phone 10, relation 12, select 11, select-dropdown 32, status 14, text 10, url 10), 11 view tests (board-view-helpers 18, board-view 13, calendar-keyboard 25, calendar-view-helpers 35, calendar-view 15, database-empty-state 14, gallery-view 17, list-keyboard 14, list-view 22, row-count-announcer 7, row-count-status-bar 5), 6 design spec tests: formula (2), person skeleton (1), calendar view (1), gallery view (3), filter/sort toolbar (2). 30 E2E specs: database CRUD (10), database duplicate (2), database duplicate row (3), bulk select (7), add property types (10), board view (4), board keyboard (6), calendar view (4), calendar keyboard (7), column reorder (3), column resize (4), CSV export (3), error recovery (4), files (4), filter keyboard (6), filter types (4), formula (3), gallery view (4), gallery keyboard (5), inline database (4), list view (4), list keyboard (6), relation (3), row page (7), search (2), select options (6), table editor portal (4), table keyboard (6), view config (4), views (10) — 149 E2E tests total. |
3030
| Realtime | - | Deferred to post-MVP per architecture decision. |
@@ -33,9 +33,9 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
3333

3434
| Category | Files | Tests |
3535
|---|---|---|
36-
| Unit/Integration (Vitest) | 142 | 1909 |
36+
| Unit/Integration (Vitest) | 142 | 1911 |
3737
| E2E (Playwright) | 81 | 398 |
38-
| **Total** | **223** | **2307** |
38+
| **Total** | **223** | **2309** |
3939

4040
### Test files by domain
4141

@@ -48,8 +48,8 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
4848
- **Database**: `database.test.ts` (55 tests), `column-helpers.test.ts` (65 tests), `database-duplicate.test.ts` (11 tests), `database-filters.test.ts` (54 tests), `formula.test.ts` (80 tests), `csv-export.test.ts` (38 tests), `property-type-picker.test.ts` (14 tests), `property-type-picker-completeness.test.ts` (5 tests), `table-view-value-format.test.ts` (33 tests), `table-defaults.test.ts` (27 tests), `table-cell.test.tsx` (40 tests), `database-width.test.ts` (3 tests), `database-view-client.test.tsx` (6 tests), `filter-bar.test.tsx` (20 tests), `filter-value-editor.test.tsx` (34 tests), `sort-menu.test.tsx` (19 tests), `rename-property-dialog.test.tsx` (14 tests), `row-properties-header.test.tsx` (17 tests), `view-tabs.test.tsx` (24 tests), `use-database-filters.test.ts` (18 tests), `use-database-properties.test.ts` (20 tests), `use-database-rows.test.ts` (23 tests), `use-database-views.test.ts` (24 tests), `checkbox.test.tsx` (8 tests), `computed.test.tsx` (15 tests), `date.test.tsx` (12 tests), `email.test.tsx` (8 tests), `files.test.tsx` (11 tests), `formula.test.tsx` (6 tests), `multi-select.test.tsx` (10 tests), `number.test.tsx` (14 tests), `person.test.tsx` (9 tests), `phone.test.tsx` (10 tests), `relation.test.tsx` (12 tests), `select-dropdown.test.tsx` (32 tests), `select.test.tsx` (11 tests), `status.test.tsx` (14 tests), `text.test.tsx` (10 tests), `url.test.tsx` (10 tests), `board-view-helpers.test.ts` (18 tests), `board-view.test.tsx` (13 tests), `calendar-keyboard.test.ts` (25 tests), `calendar-view-helpers.test.ts` (35 tests), `calendar-view.test.tsx` (15 tests), `database-empty-state.test.tsx` (14 tests), `gallery-view.test.tsx` (17 tests), `list-keyboard.test.ts` (14 tests), `list-view.test.tsx` (22 tests), `row-count-announcer.test.tsx` (7 tests), `row-count-status-bar.test.tsx` (5 tests), `formula-design-spec.test.ts` (2 tests), `person-skeleton-design-spec.test.ts` (1 test), `calendar-view-design-spec.test.ts` (1 test), `gallery-view-design-spec.test.ts` (3 tests), `filter-sort-toolbar-design-spec.test.ts` (2 tests), `e2e/database-crud.spec.ts` (11), `e2e/database-duplicate.spec.ts` (2), `e2e/database-duplicate-row.spec.ts` (3), `e2e/database-add-property-types.spec.ts` (10), `e2e/database-board.spec.ts` (4), `e2e/database-board-keyboard.spec.ts` (6), `e2e/database-calendar.spec.ts` (4), `e2e/database-calendar-keyboard.spec.ts` (7), `e2e/database-column-reorder.spec.ts` (3), `e2e/database-column-resize.spec.ts` (4), `e2e/database-csv-export.spec.ts` (3), `e2e/database-error-recovery.spec.ts` (4), `e2e/database-files.spec.ts` (4), `e2e/database-filter-keyboard.spec.ts` (6), `e2e/database-filter-types.spec.ts` (4), `e2e/database-formula.spec.ts` (3), `e2e/database-gallery.spec.ts` (4), `e2e/database-gallery-keyboard.spec.ts` (5), `e2e/database-inline.spec.ts` (4), `e2e/database-list.spec.ts` (4), `e2e/database-list-keyboard.spec.ts` (6), `e2e/database-relation.spec.ts` (3), `e2e/database-row-page.spec.ts` (7), `e2e/database-search.spec.ts` (2), `e2e/database-select-options.spec.ts` (6), `e2e/database-table-editor-portal.spec.ts` (4), `e2e/database-table-keyboard.spec.ts` (6), `e2e/database-view-config.spec.ts` (4), `e2e/database-views.spec.ts` (10), `use-row-selection.test.ts` (10 tests), `e2e/database-bulk-select.spec.ts` (7)
4949
- **App Shell**: `sidebar-context.test.tsx` (21 tests), `(app)/loading.test.ts` (4 tests), `[workspaceSlug]/loading.test.ts` (5 tests), `[workspaceSlug]/[pageId]/loading.test.ts` (6 tests), `[workspaceSlug]/settings/loading.test.ts` (5 tests), `[workspaceSlug]/settings/members/loading.test.ts` (5 tests), `route-error.test.ts` (6 tests), `[workspaceSlug]/error.test.ts` (4 tests), `[workspaceSlug]/[pageId]/error.test.ts` (4 tests), `[workspaceSlug]/settings/error.test.ts` (4 tests), `[workspaceSlug]/settings/members/error.test.ts` (4 tests), `focus-mode-hint-design-spec.test.ts` (3 tests), `workspace-home-design-spec.test.ts` (5 tests), `e2e/sidebar-responsive.spec.ts` (4 tests), `e2e/mobile-responsive.spec.ts` (5 tests), `e2e/theme-toggle.spec.ts` (4 tests), `e2e/skip-to-content.spec.ts` (2 tests), `e2e/not-found.spec.ts` (3 tests), `e2e/public-routes.spec.ts` (18 tests), `e2e/demo-editor.spec.ts` (11 tests), `e2e/accessibility.spec.ts` (10 tests), `e2e/keyboard-shortcuts.spec.ts` (5 tests), `e2e/workspace-home-new-database.spec.ts` (1 test), `e2e/workspace-home-mobile-header.spec.ts` (1 test), `e2e/workspace-home.spec.ts` (6 tests)
5050
- **Members**: `invite-form.test.ts` (4 tests), `member-list.test.tsx` (14 tests), `pending-invite-list.test.tsx` (8 tests), `role-select.test.tsx` (8 tests), `e2e/members.spec.ts` (7 tests)
51-
- **Feedback**: `feedback/route.test.ts` (20 tests), `feedback-form-design-spec.test.ts` (5 tests), `use-screenshot.test.ts` (2 tests), `e2e/feedback.spec.ts` (6 tests)
52-
- **API**: `health/route.test.ts` (9 tests), `search/route.test.ts` (14 tests), `account/route.test.ts` (9 tests), `cron/purge-trash/route.test.ts` (10 tests), `feedback/route.test.ts` (20 tests), `pages/[pageId]/versions/route.test.ts` (17 tests), `pages/[pageId]/versions/route.rate-limit.test.ts` (3 tests), `pages/[pageId]/versions/[versionId]/route.test.ts` (14 tests), `pages/[pageId]/versions/[versionId]/route.rate-limit.test.ts` (3 tests), `e2e/account-deletion.spec.ts` (4 tests), `e2e/account-settings.spec.ts` (5 tests)
51+
- **Feedback**: `feedback/route.test.ts` (22 tests), `feedback-form-design-spec.test.ts` (5 tests), `use-screenshot.test.ts` (2 tests), `e2e/feedback.spec.ts` (6 tests)
52+
- **API**: `health/route.test.ts` (9 tests), `search/route.test.ts` (14 tests), `account/route.test.ts` (9 tests), `cron/purge-trash/route.test.ts` (10 tests), `feedback/route.test.ts` (22 tests), `pages/[pageId]/versions/route.test.ts` (17 tests), `pages/[pageId]/versions/route.rate-limit.test.ts` (3 tests), `pages/[pageId]/versions/[versionId]/route.test.ts` (14 tests), `pages/[pageId]/versions/[versionId]/route.rate-limit.test.ts` (3 tests), `e2e/account-deletion.spec.ts` (4 tests), `e2e/account-settings.spec.ts` (5 tests)
5353
- **UI**: `overlay-opacity.test.ts` (2 tests), `toast-error-duration.test.ts` (1 test), `dialog-design-spec.test.ts` (3 tests), `design-spec-compliance.test.ts` (9 tests), `relative-time.test.ts` (7 tests), `reduced-motion.test.ts` (6 tests), `e2e/visual-regression.spec.ts` (1 test)
5454
- **Lib**: `sentry.test.ts` (4 tests), `sentry.unit.test.ts` (171 tests), `retry.test.ts` (9 tests), `rate-limit.test.ts` (17 tests), `track-event-server.test.ts` (9 tests), `track-event.test.ts` (4 tests), `usage-tracking-guard.test.ts` (5 tests)
5555
- **Infrastructure**: `migrations.test.ts` (33 tests), `build-timeseries.test.mjs` (6 tests), `supabase/client.test.ts` (7 tests), `supabase/proxy.test.ts` (2 tests)
@@ -154,5 +154,6 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
154154
| 2026-05-14 | Add Import Markdown action to workspace home page (#1091). Extracted `useMarkdownImport` hook from page-menu.tsx to `src/lib/use-markdown-import.ts`. Added Import Markdown button to workspace home header and empty state. Added 1 new E2E spec: `e2e/workspace-home-import.spec.ts` (3 tests). Updated 5 visual regression baselines. Test totals: 142 Vitest files (1909 tests), 80 E2E specs (393 tests). |
155155
| 2026-05-14 | Fix date picker popover clipping at container boundary (#1098). Added `autoUpdate` from `@floating-ui/react` to `RegistryEditorCell` so portaled editors continuously reposition on scroll/resize. Added 1 E2E test to `database-table-editor-portal.spec.ts` (3→4) verifying date picker stays within viewport when opened near bottom edge. Updated `table-cell.test.tsx` mock to include `autoUpdate`. Test totals: 142 Vitest files (1909 tests), 80 E2E specs (394 tests). |
156156
| 2026-05-14 | Add E2E tests for database table column resize (#1100). Added 1 new E2E spec: `e2e/database-column-resize.spec.ts` (4 tests): drag to increase width, drag to decrease width, minimum width constraint, resizing one column does not affect others. Added `data-testid` to resize handle in `table-column-header.tsx`. Test totals: 142 Vitest files (1909 tests), 81 E2E specs (398 tests). |
157+
| 2026-05-14 | Filter HeadlessChrome submissions from feedback API (#1097). Updated `feedback/route.test.ts` (20→22): 2 tests for HeadlessChrome UA filter (silent discard without insert, normal UA inserts normally). Test totals: 142 Vitest files (1911 tests), 81 E2E specs (398 tests). |
157158

158159

src/app/api/feedback/route.test.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ import { trackEvent } from "@/lib/track-event-server";
5353
const mockedCreateClient = vi.mocked(createClient);
5454
const mockedTrackEvent = vi.mocked(trackEvent);
5555

56-
function makeRequest(body: unknown): NextRequest {
56+
function makeRequest(body: unknown, headers?: Record<string, string>): NextRequest {
5757
return new NextRequest("http://localhost:3000/api/feedback", {
5858
method: "POST",
59-
headers: { "Content-Type": "application/json" },
59+
headers: { "Content-Type": "application/json", ...headers },
6060
body: JSON.stringify(body),
6161
});
6262
}
@@ -120,6 +120,38 @@ describe("POST /api/feedback", () => {
120120
expect(body.error).toBe("Invalid JSON body");
121121
});
122122

123+
it("silently discards HeadlessChrome submissions without inserting", async () => {
124+
const { mockFrom } = mockAuthenticatedClient();
125+
126+
const response = await POST(
127+
makeRequest(validBody(), {
128+
"User-Agent":
129+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/125.0.0.0 Safari/537.36",
130+
}),
131+
);
132+
const body = await response.json();
133+
134+
expect(response.status).toBe(201);
135+
expect(body).toEqual({ success: true });
136+
expect(mockFrom).not.toHaveBeenCalled();
137+
});
138+
139+
it("inserts feedback normally for non-HeadlessChrome user agents", async () => {
140+
const { mockInsert } = mockAuthenticatedClient();
141+
142+
const response = await POST(
143+
makeRequest(validBody(), {
144+
"User-Agent":
145+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
146+
}),
147+
);
148+
const body = await response.json();
149+
150+
expect(response.status).toBe(201);
151+
expect(body).toEqual({ success: true });
152+
expect(mockInsert).toHaveBeenCalled();
153+
});
154+
123155
it("returns 401 if user is not authenticated", async () => {
124156
const mockGetUser = vi
125157
.fn()

src/app/api/feedback/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ async function handler(request: NextRequest) {
3030
);
3131
}
3232

33+
// Silently discard submissions from automated browser sessions (e.g. smoke tests)
34+
const userAgent = request.headers.get("user-agent") ?? "";
35+
if (userAgent.includes("HeadlessChrome")) {
36+
return NextResponse.json({ success: true }, { status: 201 });
37+
}
38+
3339
const { type, message, page_path, page_title, screenshot_url, metadata } =
3440
body as Record<string, unknown>;
3541

0 commit comments

Comments
 (0)