Skip to content

feat: Sprint 8 — E2E Tests, Documentation & Launch Prep#52

Merged
Work90210 merged 6 commits intomasterfrom
sprint-8/e2e-docs-launch-prep
Mar 22, 2026
Merged

feat: Sprint 8 — E2E Tests, Documentation & Launch Prep#52
Work90210 merged 6 commits intomasterfrom
sprint-8/e2e-docs-launch-prep

Conversation

@Work90210
Copy link
Copy Markdown
Owner

@Work90210 Work90210 commented Mar 22, 2026

Summary

  • E2E Test Suite: 15 Playwright spec files with 55+ tests covering auth, spec management, server configuration, MCP SSE connections, billing/Stripe checkout, code export, and landing page (CLS, navigation, responsiveness)
  • Documentation Finalization: Launch-ready README, comprehensive API reference (docs/API.md), 7 new Architecture Decision Records, expanded security policy with disclosure timeline, v0.1.0 changelog across all 8 sprints
  • Performance CI: Lighthouse assertions upgraded to error-level (fail < 90), bundle analysis workflow with PR comments, size-limit for transformer package (50KB gzip cap), slow query monitoring in runtime
  • Security Hardening: Pinned third-party action versions (Trivy, Snyk), added Dockerfile scanning, removed continue-on-error on audit jobs, fixed CORS * default in code generator (now requires explicit origin), added 30s fetch timeout in generated servers

Test plan

  • pnpm build passes (verified locally)
  • pnpm lint passes (verified locally)
  • pnpm typecheck passes (verified locally)
  • E2E tests run against local dev server (pnpm exec playwright test)
  • Security CI workflow runs on PR
  • Lighthouse CI runs on PR
  • Bundle analysis comments on PR
  • Transformer size check passes in CI
  • Documentation pages render correctly on docs site

Summary by CodeRabbit

  • New Features

    • Bundle analysis for web builds; Stripe billing (checkout & portal) and webhook handling; SSE-based MCP connectivity; server export/download UI.
  • Documentation

    • Full API reference, billing & auth docs, updated quick-start/self‑host guides, security disclosure timeline, new ADRs and refreshed changelog.
  • Tests

    • Large e2e suite added covering auth, billing, servers, console, specs import/export, MCP, and landing/UX.
  • Bug Fixes & Improvements

    • Stricter security scan failures, 30s upstream request timeout, DB query performance monitoring, and tighter Lighthouse enforcement.

…I, and security hardening

- Add comprehensive Playwright E2E test suite (15 spec files, 55+ tests)
  covering auth, specs, servers, console, MCP SSE, billing, export, and
  landing page flows with proper fixtures and helpers
- Finalize all documentation for launch: README rewrite, comprehensive
  API reference (docs/API.md), 7 new ADRs in ARCHITECTURE.md, expanded
  SECURITY.md with disclosure timeline, v0.1.0 changelog
- Add performance CI: Lighthouse assertions upgraded to error level,
  bundle analysis workflow, size-limit for transformer package, slow
  query monitoring in runtime
- Harden security CI: pin Trivy and Snyk action versions, add Dockerfile
  scanning, remove continue-on-error on audit jobs
- Fix CORS default in code generator to require explicit origin (was '*')
- Add fetch timeout (30s) to generated standalone servers
- Review and fix all 13 Fumadocs MDX pages for accuracy
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c1541076-f59c-440f-b880-96fd1bdd6acb

📥 Commits

Reviewing files that changed from the base of the PR and between 3c11092 and d9a901e.

📒 Files selected for processing (3)
  • .github/workflows/bundle-analysis.yml
  • .github/workflows/security.yml
  • .gitleaks.toml
✅ Files skipped from review due to trivial changes (2)
  • .gitleaks.toml
  • .github/workflows/bundle-analysis.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/security.yml

📝 Walkthrough

Walkthrough

Adds Playwright E2E infrastructure and many tests, introduces a DB query monitoring wrapper with logging, integrates Next.js bundle analysis and Lighthouse CI assertion changes, tightens CI/security workflows and transformer size enforcement, expands docs/ADRs/security, and updates web runtime/export behavior and CORS/runtime timeouts.

Changes

Cohort / File(s) Summary
CI / Workflows / Lighthouse
/.github/lighthouse/config.json, .github/workflows/ci.yml, .github/workflows/security.yml, .github/workflows/bundle-analysis.yml, lighthouserc.json
Tightened Lighthouse assertions (accessibility/best-practices/seo: warn→error), added bundle-analysis workflow, replaced inline transformer size check with package size script, pinned Trivy/Snyk actions, removed per-step continue-on-error, and added lighthouserc config.
Runtime query monitoring
apps/runtime/src/observability/query-monitor.ts, apps/runtime/__tests__/unit/query-monitor.test.ts, apps/runtime/src/index.ts
Added createMonitoredDb wrapper (times queries, truncates SQL, warns on slow queries, logs errors and rethrows) and integrated it into startWorker; included unit tests.
Playwright E2E infra & helpers
e2e/playwright.config.ts, e2e/global-setup.ts, e2e/fixtures/*, e2e/helpers/*
Added Playwright CI-aware config, global setup, fixtures (petstore, test-user, Stripe cards), and helpers for auth and API interactions.
Playwright E2E tests
e2e/tests/... (many files under e2e/tests/*)
Added ~15+ test suites covering auth (signin/signup), billing, plan limits/upgrade, specs import/delete, servers (configure/export/manage tools), MCP SSE/JSON-RPC, console tool-call, landing page, and export flows.
Web bundle analysis & Next config
apps/web/next.config.mjs, apps/web/package.json
Added @next/bundle-analyzer devDependency and composed analyzer into Next.js export controlled by ANALYZE env var.
Web export/runtime changes
apps/web/lib/export/generator.ts
Made CORS_ORIGIN required (no default; throw at startup) and added a 30s AbortSignal timeout to upstream fetch calls.
Docs, ADRs & site content
README.md, docs/API.md, docs/ARCHITECTURE.md, docs/SECURITY.md, docs/CONTRIBUTING.md, apps/web/content/docs/*, apps/web/content/docs/meta.json, apps/web/content/docs/changelog.mdx
Major documentation additions and rewrites: API reference, new ADRs, expanded security policy, rebranded references, billing/auth docs, added pages and changelog updates.
Transformer size & package
packages/transformer/.size-limit.json, packages/transformer/package.json, packages/transformer/README.md
Added size-limit config (50 KB gzip), scripts (size, size:check) and devDeps; README updated examples to include additional error types.
Lighthouse CI config file
lighthouserc.json
Added Lighthouse CI configuration executing 3 runs and asserting category minima (0.9) treating failures as errors; upload to temporary-public-storage.
Misc / repository config
.gitleaks.toml
Expanded gitleaks allowlist to include new workflow file path.
E2E API helpers
e2e/helpers/api.ts
Added central API helpers for tests (typed responses, apiCall, spec/server CRUD helpers, fixture loader, cleanup utilities).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant MonitoredDB as MonitoredDb (wrapper)
participant RawDB as Raw DbClient
participant Logger as Logger
Client->>MonitoredDB: query(sql, params)
MonitoredDB->>RawDB: query(sql, params)
RawDB-->>MonitoredDB: result / throws
alt success
MonitoredDB->>MonitoredDB: measure duration (performance.now)
alt duration > slowThreshold
MonitoredDB->>Logger: warn { duration, truncatedSql, paramCount }
end
MonitoredDB-->>Client: return result
else error
MonitoredDB->>MonitoredDB: measure duration
MonitoredDB->>Logger: error { duration, truncatedSql, params, error }
MonitoredDB-->>Client: rethrow error
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Sprint 8 — E2E Tests, Documentation & Launch Prep' clearly summarizes the main changes by highlighting the three primary focuses: E2E testing, documentation, and launch preparation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sprint-8/e2e-docs-launch-prep

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 22, 2026

Coverage Report for packages/transformer

Status Category Percentage Covered / Total
🔵 Lines 96.52% (🎯 80%) 584 / 605
🔵 Statements 96.52% (🎯 80%) 584 / 605
🔵 Functions 100% (🎯 80%) 35 / 35
🔵 Branches 94.67% (🎯 80%) 231 / 244
File CoverageNo changed files found.
Generated in workflow #136 for commit 90f0210 by the Vitest Coverage Report Action

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

109-109: ⚠️ Potential issue | 🟡 Minor

Update Next.js version reference in README.

Line 109 incorrectly states "Next.js 14 dashboard" when the project uses Next.js 15 (^15.5.14). Change to "Next.js 15 dashboard".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 109, Update the README entry for the Web App to reference
the correct Next.js version: change the phrase "Next.js 14 dashboard" to
"Next.js 15 dashboard" in the table row containing the Web App description (the
line with "| **Web App** | [`apps/web`](apps/web) | Next.js 14 dashboard. ...
|").
🟡 Minor comments (9)
apps/web/lib/export/generator.ts-59-59 (1)

59-59: ⚠️ Potential issue | 🟡 Minor

Documentation mismatch: CORS_ORIGIN vs CORS_ORIGINS

The generated standalone server requires CORS_ORIGIN (singular), but the relevant code snippet from .env.example:94-95 shows the project uses CORS_ORIGINS (plural). This inconsistency will cause confusion for users deploying generated servers—they'll lack clear guidance on the required variable and hit startup crashes.

Consider either:

  1. Aligning the generated server to read CORS_ORIGINS (and handle comma-separated values), or
  2. Adding CORS_ORIGIN to .env.example with documentation distinguishing it from CORS_ORIGINS

Also applies to: 250-254

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/lib/export/generator.ts` at line 59, Update the generated standalone
server to resolve the env var mismatch: either change the generator in
apps/web/lib/export/generator.ts so the server reads CORS_ORIGINS (plural) and
parses comma-separated values into the allowed origins list, or document and
support both by checking process.env.CORS_ORIGINS first then falling back to
process.env.CORS_ORIGIN; reference the CORS_ORIGINS/CORS_ORIGIN env names in the
generator logic and update the generated .env example accordingly so the
produced server and .env.example remain consistent.
apps/web/content/docs/self-hosting.mdx-23-24 (1)

23-24: ⚠️ Potential issue | 🟡 Minor

Repository URL points to a personal account.

The clone URL references Work90210/APIFold (a personal account). For launch-ready documentation, consider using an organization-owned repository URL (e.g., github.com/apifold/APIFold) to ensure long-term stability and discoverability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/content/docs/self-hosting.mdx` around lines 23 - 24, The
documentation uses a personal GitHub repo URL in the git clone command ("git
clone https://github.com/Work90210/APIFold.git" and "cd APIFold"); update this
to the organization-owned repository URL (for example "git clone
https://github.com/apifold/APIFold.git" and adjust the subsequent "cd" if
necessary) and ensure any other references in the same file to Work90210/APIFold
are replaced with the org-owned repo name for consistency.
e2e/tests/auth/signup.spec.ts-9-21 (1)

9-21: ⚠️ Potential issue | 🟡 Minor

Test intent and assertions are slightly mismatched.

The test name says it verifies both email and password fields, but it only asserts email + heading. Either add a password assertion or rename the test for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/auth/signup.spec.ts` around lines 9 - 21, The test "renders the
sign-up form with email and password fields" currently only asserts the email
input (emailInput) and a heading; either update the assertions to match the
title by locating the password field (e.g., use a locator like
'input[name="password"], input[type="password"]' and add await
expect(passwordInput).toBeVisible(...)) or change the test title string in the
test(...) call to reflect that it only checks the email input and heading;
adjust the test body accordingly (modify or add the locator referenced by
passwordInput) so the name and assertions are consistent.
e2e/tests/billing/upgrade.spec.ts-106-112 (1)

106-112: ⚠️ Potential issue | 🟡 Minor

Test may fail for users without Stripe customer ID.

Per apps/web/app/api/billing/portal/route.ts (lines 17-19), this endpoint returns 404 if the user has no stripeCustomerId. A fresh test user likely won't have one. Consider including 404 in expected statuses or documenting the precondition.

🛠️ Proposed fix
-    // Should either redirect (303) or return a URL
-    expect([200, 303, 302]).toContain(response.status());
+    // Should return a URL (200), redirect (302/303), or 404 if no billing account exists
+    expect([200, 302, 303, 404]).toContain(response.status());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/billing/upgrade.spec.ts` around lines 106 - 112, The test "billing
portal link is accessible" assumes the user has a Stripe customer ID but route
/api/billing/portal (route.ts) returns 404 when stripeCustomerId is missing;
update the assertion in the test (test("billing portal link is accessible", ...)
in e2e/tests/billing/upgrade.spec.ts) to include 404 in the allowed
response.status() values or alternatively add a setup step that ensures the test
user has a stripeCustomerId before calling
page.request.post("/api/billing/portal"); adjust the
expect([...]).toContain(...) accordingly so the test passes for fresh users
without a Stripe customer ID.
e2e/tests/landing/page-load.spec.ts-19-20 (1)

19-20: ⚠️ Potential issue | 🟡 Minor

Non-null assertion may cause false failures.

textContent() can return null even for visible elements (e.g., if the element contains only child elements). The non-null assertion headingText! will throw if this happens.

🛡️ Proposed fix
     const headingText = await heading.textContent();
-    expect(headingText!.length).toBeGreaterThan(0);
+    expect(headingText?.length ?? 0).toBeGreaterThan(0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/landing/page-load.spec.ts` around lines 19 - 20, The test uses a
non-null assertion on headingText from heading.textContent(), which can be null
and cause false failures; update the assertion to handle null safely by first
asserting headingText is not null (e.g., expect(headingText).not.toBeNull()) and
only then assert the length, or use a null-coalescing check like
expect(headingText?.length ?? 0).toBeGreaterThan(0); reference the
variables/methods heading, headingText, and textContent() when making the
change.
e2e/tests/servers/configure-server.spec.ts-62-65 (1)

62-65: ⚠️ Potential issue | 🟡 Minor

Assertion doesn't verify the update persisted correctly.

The assertion only checks that authMode property exists, but McpServer always has this field (per packages/types/src/server.ts). To verify the update persisted, assert the actual value.

🛠️ Proposed fix
     // Verify via API that the change persisted
     const server = await getServer(page, serverId);
-    expect(server).toHaveProperty("authMode");
+    expect(server.authMode).toBe("api_key");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/servers/configure-server.spec.ts` around lines 62 - 65, The test
currently only checks that the returned server object from getServer(page,
serverId) has an authMode property; instead assert that server.authMode equals
the specific value you updated it to earlier in the test (e.g., the variable or
literal used in the update request). Locate the getServer call and replace the
loose expect(server).toHaveProperty("authMode") with a strict equality assertion
against the expectedAuthMode (or the update payload variable) so the test
verifies the persisted value.
e2e/tests/mcp/sse-connection.spec.ts-100-102 (1)

100-102: ⚠️ Potential issue | 🟡 Minor

Assertion always passes — should require at least one event.

The test comment at line 100 states "Should have received at least one event", but the assertion expect(events.length).toBeGreaterThanOrEqual(0) always passes since array length is never negative. This defeats the purpose of the test.

🐛 Proposed fix
-    // Should have received at least one event (e.g., session init)
-    expect(events.length).toBeGreaterThanOrEqual(0);
+    // Should have received at least one event (e.g., session init)
+    expect(events.length).toBeGreaterThanOrEqual(1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/mcp/sse-connection.spec.ts` around lines 100 - 102, The assertion
in the test currently checks events.length >= 0 which always passes; update the
assertion in the sse-connection.spec.ts test so it requires at least one event
(e.g., change the expectation on events.length to beGreaterThanOrEqual(1) or
toBeGreaterThan(0)) and optionally add a clear failure message; locate the check
referencing the events array and the expect(...) call to modify it accordingly.
e2e/tests/specs/delete-spec.spec.ts-49-55 (1)

49-55: ⚠️ Potential issue | 🟡 Minor

Add visibility wait before clicking delete button for consistency.

The first test (line 27) waits for the delete button to be visible before clicking, but this test clicks immediately without the visibility check. This could cause flaky failures if the page hasn't fully rendered.

🐛 Proposed fix
     const deleteButton = page.getByRole("button", { name: /delete/i });
+    await expect(deleteButton).toBeVisible({ timeout: 10_000 });
     await deleteButton.click();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/specs/delete-spec.spec.ts` around lines 49 - 55, The test clicks
deleteButton immediately but should wait for it to be visible to match the first
test and avoid flakiness; update the block that uses page.getByRole("button", {
name: /delete/i }) (deleteButton) to wait for visibility before clicking (use
the locator's waitFor/visibility check) and then proceed to click and interact
with confirmButton (page.locator(...)) as before.
e2e/helpers/api.ts-57-63 (1)

57-63: ⚠️ Potential issue | 🟡 Minor

Handle non-JSON responses before calling response.json().

response.json() runs before any status check here, so an HTML/empty-body failure will surface as SyntaxError and hide the real HTTP status/body. That makes redirects, proxy failures, and no-content DELETEs much harder to diagnose.

Suggested hardening
   const response = await fetch(`${BASE_URL}${endpoint}`, options);
-  const json = (await response.json()) as ApiResponse<T>;
-
-  if (!response.ok || !json.success) {
+  const contentType = response.headers.get("content-type") ?? "";
+  const text = await response.text();
+  const json =
+    contentType.includes("application/json") && text
+      ? (JSON.parse(text) as ApiResponse<T>)
+      : undefined;
+
+  if (!response.ok) {
     throw new Error(
-      `API ${method} ${endpoint} failed: ${json.error ?? response.statusText}`,
+      `API ${method} ${endpoint} failed: ${json?.error ?? (text || response.statusText)}`,
     );
   }
+
+  if (!json?.success) {
+    throw new Error(
+      `API ${method} ${endpoint} failed: ${json?.error ?? "Unexpected response shape"}`,
+    );
+  }
 
   return json.data;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/api.ts` around lines 57 - 63, The code calls response.json()
unconditionally which can throw SyntaxError for non-JSON responses and hide the
real HTTP status/body; modify the fetch handling around the
fetch(`${BASE_URL}${endpoint}`, options) call so you first check response.ok and
the Content-Type (via response.headers.get('content-type')) and, for non-JSON or
empty bodies, call response.text() (or handle 204/empty) instead of
response.json(), then construct the thrown Error using method, endpoint,
response.status, response.statusText and the raw body text if available; adjust
references to response, json, ApiResponse<T>, method and endpoint when
reading/parsing so errors include the actual HTTP status and body.
🧹 Nitpick comments (17)
apps/runtime/__tests__/unit/query-monitor.test.ts (2)

51-68: Test timing is flaky-prone.

The test relies on a 50ms setTimeout exceeding a 10ms threshold. While there's margin, real-time delays in CI can be unpredictable. Consider mocking performance.now() to make the test deterministic.

♻️ Suggested deterministic approach
 it('warns for slow queries', async () => {
+  let callCount = 0;
+  vi.spyOn(performance, 'now').mockImplementation(() => {
+    // First call returns 0, second call returns 150 (simulating 150ms elapsed)
+    return callCount++ === 0 ? 0 : 150;
+  });
+
   const db = createMockDb(async () => {
-    // Simulate a slow query
-    await new Promise((resolve) => setTimeout(resolve, 50));
     return { rows: [] };
   });
   const monitored = createMonitoredDb({ logger, db, slowThresholdMs: 10 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/runtime/__tests__/unit/query-monitor.test.ts` around lines 51 - 68, The
test is flaky because it depends on real time delays; make it deterministic by
mocking performance.now() around the call so measured duration exceeds
slowThresholdMs without setTimeout. In the test for createMonitoredDb /
monitored.query, replace the 50ms setTimeout approach with a jest.spyOn or
similar on global.performance.now() that returns a start value (e.g., 1000)
before calling monitored.query and a later value (e.g., 1011) after to simulate
elapsed time > slowThresholdMs; ensure you restore the spy after the test and
keep assertions on logger.warn the same.

96-108: Same flakiness concern applies here.

This test also uses real setTimeout for slow query simulation. Same suggestion to mock performance.now() applies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/runtime/__tests__/unit/query-monitor.test.ts` around lines 96 - 108, The
test uses a real setTimeout inside createMockDb which causes flakiness; instead
simulate the slow query by mocking time—replace the sleep-based callback with a
deterministic mock of performance.now so that createMockDb's query handler
returns immediately but performance.now reports a start and end that exceed
slowThresholdMs; update the test to stub performance.now (used by
createMonitoredDb/monitored.query) to return two values (start and end) that
produce the desired elapsed time and remove reliance on setTimeout.
lighthouserc.json (1)

1-27: Duplicate Lighthouse configuration may cause confusion.

This lighthouserc.json duplicates .github/lighthouse/config.json (with the only addition being upload.target). The GitHub Actions workflow explicitly uses configPath: .github/lighthouse/config.json and already sets temporaryPublicStorage: true, making this root config redundant.

Consider either:

  1. Removing this file if it's not used by local lhci runs
  2. Consolidating to a single config and updating the workflow reference
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lighthouserc.json` around lines 1 - 27, The lighthouserc.json duplicates the
GitHub Actions Lighthouse config and adds only "upload.target"; either remove
this root lighthouserc.json to avoid confusion or consolidate into one config
and update the workflow to reference it — e.g., remove the duplicate
lighthouserc.json (which contains the "ci.collect", "ci.assert", and
"ci.upload.target" entries) if local lhci runs are unnecessary, or merge its
"upload.target": "temporary-public-storage" into the single canonical config
used by the workflow and ensure the workflow's configPath (currently set to
.github/lighthouse/config.json) and temporaryPublicStorage flag reflect the
consolidated file.
docs/CONTRIBUTING.md (1)

17-18: Consider using an organization-owned repository URL.

The clone URL points to a personal GitHub account (Work90210/APIFold). If this project may transfer ownership or use an organization in the future, consider whether this URL will remain stable. Otherwise, the capitalization change to APIFold is consistent with other documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/CONTRIBUTING.md` around lines 17 - 18, Update the repository URL in the
CONTRIBUTING.md clone instruction so it points to the organization-owned
repository (or a stable canonical URL) instead of the personal account;
specifically replace the "git clone https://github.com/Work90210/APIFold.git"
string with the org's repo URL (or a generic placeholder like "git clone
https://github.com/ORG_NAME/APIFold.git") to ensure future ownership changes
won't break the docs while preserving the existing "cd APIFold" line.
docs/ARCHITECTURE.md (1)

150-163: ADR-010 duplicates ADR-002.

ADR-002 ("Dual Licensing (AGPL-3 + MIT)") already documents this exact decision. Consider either removing ADR-010 or consolidating it into ADR-002 to avoid redundancy and potential drift between the two records.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/ARCHITECTURE.md` around lines 150 - 163, ADR-010 duplicates the decision
already recorded in ADR-002; remove ADR-010 or merge its content into ADR-002 to
avoid redundancy. Update ADR-002 to include any unique wording from ADR-010
(including the explicit "packages/transformer is MIT" boundary and consequences)
and delete ADR-010; also search for and update any cross-references or index
entries that point to ADR-010 so they now reference ADR-002.
e2e/tests/servers/manage-tools.spec.ts (2)

69-84: Fragile state detection and hardcoded wait.

Two concerns:

  1. Silent error swallowing: The .catch() pattern masks all errors, not just "element is a switch." If isChecked() fails for other reasons, the test silently falls back to incorrect logic.

  2. Hardcoded timeout: waitForTimeout(1_000) is flaky. Prefer waiting for a specific condition (e.g., expect(toggle).toBeChecked() or toHaveAttribute('aria-checked', ...)).

♻️ Suggested improvement
-    // Get initial state
-    const wasChecked = await toggle.isChecked().catch(() => {
-      // role="switch" uses aria-checked
-      return toggle.getAttribute("aria-checked").then((v) => v === "true");
-    });
-
-    // Toggle it
-    await toggle.click();
-
-    // Wait for the state to update
-    await page.waitForTimeout(1_000);
-
-    // Verify state changed
-    const isChecked = await toggle.isChecked().catch(() => {
-      return toggle.getAttribute("aria-checked").then((v) => v === "true");
-    });
-    expect(isChecked).not.toBe(wasChecked);
+    // Determine element type and get initial state
+    const role = await toggle.getAttribute("role");
+    const isSwitch = role === "switch";
+
+    const getCheckedState = async () =>
+      isSwitch
+        ? (await toggle.getAttribute("aria-checked")) === "true"
+        : await toggle.isChecked();
+
+    const wasChecked = await getCheckedState();
+
+    await toggle.click();
+
+    // Wait for state change using assertion retry
+    await expect(async () => {
+      const nowChecked = await getCheckedState();
+      expect(nowChecked).not.toBe(wasChecked);
+    }).toPass({ timeout: 5_000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/servers/manage-tools.spec.ts` around lines 69 - 84, The test
currently swallows all errors when calling toggle.isChecked() and uses a
hardcoded waitForTimeout(1_000), making it flaky; change the logic in the block
that computes wasChecked/isChecked to first try toggle.isChecked() but only
fallback if the element truly lacks isChecked support (e.g., detect presence of
aria-checked via await toggle.getAttribute('aria-checked') !== null) rather than
catching all errors, then replace page.waitForTimeout(1_000) with an explicit
wait for the toggle state to change such as await
expect(toggle).toHaveAttribute('aria-checked', expectedValue) or use
page.waitForFunction to poll the attribute/value until it differs from the
previous value; update references to toggle, wasChecked, isChecked,
toggle.isChecked, toggle.getAttribute and page.waitForTimeout accordingly.

21-27: Best-effort cleanup with empty catch may hide test setup issues.

Silently catching all errors in afterEach can mask failures in test infrastructure (e.g., authentication issues, API changes). Consider logging the error for debugging:

♻️ Suggested improvement
   test.afterEach(async ({ page }) => {
     try {
       await deleteSpec(page, specId);
-    } catch {
-      // Best-effort cleanup
+    } catch (error) {
+      console.warn(`Cleanup failed for spec ${specId}:`, error);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/servers/manage-tools.spec.ts` around lines 21 - 27, The afterEach
currently swallows all errors which can hide infra failures; update the
test.afterEach cleanup to catch the error from deleteSpec(page, specId) as a
named variable (e) and log it (e.g., console.error or test.info().log) with
context including specId and page URL/session info, rather than leaving the
catch empty—keep the best-effort intent but surface failures for debugging by
logging the exception from deleteSpec in the test.afterEach block.
e2e/global-setup.ts (1)

39-42: Warning message implies skipping, but setup doesn’t skip tests.

Line 41 says auth tests “will be skipped,” but this file only logs a warning. Either wire an actual skip mechanism in auth specs or reword to “may fail”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/global-setup.ts` around lines 39 - 42, The warning in e2e/global-setup.ts
currently logs that auth tests "will be skipped" but does not implement any
skipping; update the behavior to match reality by either (A) changing the
console.warn message in the block that references the missing array (the
console.warn call using `${missing.join(", ")}`) to say the auth tests "may
fail" or "will likely fail", or (B) implement an actual skip mechanism by
exporting a flag (e.g., export const SKIP_AUTH_TESTS = true/false) from this
setup and make auth specs read that flag to conditionally call test.skip or set
Jest/Mocha to skip those suites; choose one approach and apply it consistently
(modify the console.warn text if you pick rewording, or add the exported flag
and update auth spec files to reference SKIP_AUTH_TESTS if you add skipping).
e2e/tests/billing/upgrade.spec.ts (2)

66-78: Same issue: move test.skip() to the beginning.

As noted above, the skip check should precede navigation and UI interactions to avoid wasted effort.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/billing/upgrade.spec.ts` around lines 66 - 78, Move the test.skip
invocation to the top of the "can fill in Stripe test card details" test so the
skip check runs before any navigation or UI interactions; specifically, add the
test.skip(!process.env["STRIPE_SECRET_KEY"], "STRIPE_SECRET_KEY not configured")
at the start of the test body for the test named "can fill in Stripe test card
details" (the async ({ page }) => { ... } block) and then proceed with page.goto
and the upgradeButton clicks only if the test is not skipped.

39-53: Move test.skip() to the beginning of the test.

The test.skip() check runs after navigating and clicking the upgrade button (lines 42-47). This wastes resources and could cause confusing failures if the button click triggers errors before the skip is evaluated.

♻️ Proposed fix
   test("Stripe checkout page renders with test mode badge", async ({
     page,
   }) => {
+    test.skip(
+      !process.env["STRIPE_SECRET_KEY"],
+      "STRIPE_SECRET_KEY not configured",
+    );
+
     await page.goto("/dashboard/settings");

     const upgradeButton = page.getByRole("button", {
       name: /upgrade|go pro|subscribe/i,
     }).or(page.getByRole("link", { name: /upgrade|go pro|subscribe/i }));
     await upgradeButton.first().click();

-    // Wait to land on Stripe checkout
-    test.skip(
-      !process.env["STRIPE_SECRET_KEY"],
-      "STRIPE_SECRET_KEY not configured",
-    );
-
     await page.waitForURL(/checkout\.stripe\.com/, { timeout: 30_000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/billing/upgrade.spec.ts` around lines 39 - 53, The test's
conditional skip (test.skip(!process.env["STRIPE_SECRET_KEY"], ...)) should be
moved to the very start of the test block so the test is skipped before any page
navigation or interaction occurs; update the test in the "test(...)" function so
the test.skip call runs before page.goto("/dashboard/settings") and before using
upgradeButton (page.getByRole(...).or(...).first().click()) to avoid performing
unnecessary actions when STRIPE_SECRET_KEY is not set.
docs/API.md (1)

9-11: Add language specifiers to HTTP endpoint code blocks.

Static analysis flagged 25 code blocks without language specifiers. For HTTP endpoint definitions, use http or text to satisfy linters and improve syntax highlighting.

♻️ Example fix for one block
-```
-Authorization: Bearer <session-token>
-```
+```http
+Authorization: Bearer <session-token>
+```

Apply similar changes to endpoint definition blocks throughout the document (lines 55, 82, 100, 110, 125, 139, 149, 167, 177, 193, 207, 217, 232, 266, 285, 299, 332, 359, 387, 412, 435, 467, 491, 505).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/API.md` around lines 9 - 11, Several HTTP endpoint code blocks (for
example the block containing "Authorization: Bearer <session-token>") lack
language specifiers; update each triple-backtick fence that contains HTTP
endpoint definitions or header lines to use a language tag (prefer `http`, or
`text` where appropriate). Locate each code block that shows request/response
headers or endpoint examples (e.g., the block with "Authorization: Bearer
<session-token>") and change the opening fence from ``` to ```http and keep the
closing fence as ```, applying the same transformation to all similar endpoint
definition blocks throughout the document.
e2e/tests/specs/import-from-file.spec.ts (1)

4-5: Combine imports from the same module.

Both imports are from ../../helpers/api. They can be consolidated.

♻️ Proposed fix
-import { cleanupTestSpecs } from "../../helpers/api";
-import { getPetstoreFixturePath } from "../../helpers/api";
+import { cleanupTestSpecs, getPetstoreFixturePath } from "../../helpers/api";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/specs/import-from-file.spec.ts` around lines 4 - 5, Combine the two
separate imports from the same module into a single import statement: replace
the separate imports of cleanupTestSpecs and getPetstoreFixturePath (from
"../../helpers/api") with one consolidated import that imports both symbols
together; update the import line that currently references cleanupTestSpecs and
getPetstoreFixturePath so both named exports are imported in a single import
declaration.
e2e/tests/servers/configure-server.spec.ts (1)

67-99: Consider adding persistence verification for other fields.

The baseUrl and rateLimit tests only verify the success message but don't confirm persistence via API like the authMode test attempts. For consistency and thoroughness, consider adding getServer calls to verify the values persisted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/servers/configure-server.spec.ts` around lines 67 - 99, Update the
"updates the base URL" and "updates the rate limit" tests to assert persistence
by calling the same API helper used in the authMode test (e.g., getServer) after
the UI save completes; locate the tests by the test names and the locators
baseUrlInput and rateLimitInput, then after awaiting the save confirmation call
getServer(serverId) and assert the returned object's baseUrl equals
"https://api.example.com/v2" in the first test and rateLimitPerMinute equals 120
(or "120" normalized to a number) in the second test so the tests verify
server-side persistence not just the success message.
e2e/tests/landing/navigation.spec.ts (1)

50-68: Conditional tests silently pass when links are missing.

If the docs or changelog links are accidentally removed from the landing page, these tests will silently pass (the if block is skipped). This could mask regressions. Consider either:

  1. Making visibility a hard requirement if these links should always exist
  2. Using test.skip() with a clear message when the link is absent
  3. Adding a test annotation or console log to indicate the test was skipped
♻️ Option: Fail if link unexpectedly missing
   test("docs link navigates to documentation", async ({ page }) => {
     const docsLink = page.getByRole("link", { name: /docs|documentation/i });
-
-    if (await docsLink.first().isVisible().catch(() => false)) {
-      await docsLink.first().click();
-      await page.waitForURL(/docs/, { timeout: 15_000 });
-      expect(page.url()).toContain("docs");
-    }
+    await expect(docsLink.first()).toBeVisible({ timeout: 10_000 });
+    await docsLink.first().click();
+    await page.waitForURL(/docs/, { timeout: 15_000 });
+    expect(page.url()).toContain("docs");
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/landing/navigation.spec.ts` around lines 50 - 68, The tests
currently use an "if (await ...isVisible().catch(() => false))" guard so they
silently pass when links are missing; update both tests ("docs link navigates to
documentation" and "changelog link navigates to changelog page") to assert
visibility as a hard requirement: replace the conditional guards around docsLink
and changelogLink with explicit Playwright assertions (e.g., await
expect(docsLink.first()).toBeVisible() and await
expect(changelogLink.first()).toBeVisible()) before attempting .click(), then
proceed to click and waitForURL as before so the test fails immediately if the
links are missing.
e2e/tests/specs/delete-spec.spec.ts (1)

41-42: Static analysis false positive — IDs are backend-generated UUIDs.

The specId and serverId values come from createTestSpec, which returns backend-generated UUIDs containing only hex characters and hyphens — no special regex characters that could cause ReDoS. The static analysis warning is a false positive in this context.

That said, you could simplify by using exact text matching:

♻️ Suggested simplification
-    await expect(page.getByText(new RegExp(specId))).not.toBeVisible({
+    await expect(page.getByText(specId)).not.toBeVisible({
       timeout: 5_000,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/specs/delete-spec.spec.ts` around lines 41 - 42, The test uses a
regex locator getByText(new RegExp(specId)) which triggered a static-analysis
ReDoS false positive; replace the regex-based lookup with exact text matching
(use getByText(specId) or the equivalent exact locator) for both specId and
serverId checks so they match the backend-generated UUIDs directly; locate the
usages created from createTestSpec (references to specId and serverId) and
update the assertions that call getByText(new RegExp(...)) to use the
exact-string form and keep the same timeout behavior.
e2e/fixtures/test-user.ts (1)

42-45: Consider using a far-future expiry date.

The test card expiry 12/30 will become invalid after December 2030. While this gives several years of runway, using a further date like 12/99 would prevent future test failures.

♻️ Suggested change
 export const STRIPE_TEST_CARD_DETAILS = {
-  expiry: "12/30",
+  expiry: "12/99",
   cvc: "123",
   zip: "10001",
 } as const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/fixtures/test-user.ts` around lines 42 - 45, Update the test card expiry
in the STRIPE_TEST_CARD_DETAILS object to a far-future date (e.g., change expiry
"12/30" to "12/99") so the fixture doesn't expire; keep the same string format
and leave cvc/zip unchanged when modifying the STRIPE_TEST_CARD_DETAILS
constant.
e2e/tests/mcp/sse-connection.spec.ts (1)

110-129: Consider adding a timeout to the JSON-RPC fetch call for consistency.

The SSE reachability test (lines 39-46) uses an AbortController with a 10-second timeout, but this JSON-RPC call has no equivalent. While Playwright's test timeout provides a backstop, adding an explicit timeout would make the failure mode clearer and keep the tests consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/mcp/sse-connection.spec.ts` around lines 110 - 129, The JSON-RPC
fetch inside the page.evaluate (the POST to messageUrl) needs an explicit
timeout like the earlier SSE test: create an AbortController with a 10s timeout,
pass controller.signal into the fetch options, and clear the timer on success;
ensure the catch branch handles AbortError similarly (returning status 0 and the
error string) so the test fails fast and consistently with the SSE reachability
check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/content/docs/api-reference.mdx`:
- Around line 305-314: Add an explicit exception paragraph under the "Stripe
Webhook" section clarifying that the /api/webhooks/stripe route does not use the
global Bearer token auth or the standard API response envelope; instead it
authenticates via the stripe-signature header and returns raw JSON ({
"received": true }) on success to allow raw webhook payload verification.
Reference the route (/api/webhooks/stripe) and the stripe-signature header in
that note so integrators know to bypass the usual Bearer auth and envelope for
this webhook endpoint.

In `@e2e/global-setup.ts`:
- Around line 13-14: The readiness polling uses fetch(HEALTH_URL) with no
timeout, so a hung request blocks the retry loop; wrap each attempt with an
AbortController and a per-attempt timer (e.g., create AbortController, pass
controller.signal to fetch(HEALTH_URL, { signal }), set a setTimeout to call
controller.abort() after your desired timeout, and clear the timeout when the
request completes) and handle the abort/error so the loop continues to the next
retry instead of hanging; update the block that checks response.ok to use this
abortable fetch and ensure any errors from abort are caught and treated as a
failed attempt.

In `@e2e/helpers/api.ts`:
- Around line 84-88: createTestSpec currently allows arbitrary names while
cleanupTestSpecs only deletes specs with the "E2E Test" prefix, which can leak
or accidentally delete other runs; modify createTestSpec and related functions
(e.g., cleanupTestSpecs) to tie creation and teardown to a run-specific
identifier: generate a single runId at module init (or accept it via env) and
prefix all created spec names with `${runId}-E2E Test ...`, or alternatively
record and return the created spec IDs from createTestSpec in a module-level
array (e.g., createdSpecIds) and update cleanupTestSpecs to delete only those
recorded IDs; ensure both name construction (in createTestSpec) and deletion
logic (in cleanupTestSpecs and any other cleanup helper mentioned around the
149-156 region) reference the same runId or createdSpecIds so created fixtures
are reliably cleaned up.
- Around line 72-78: loadPetstoreSpec currently returns only { _raw: content }
which forces createTestSpec to hit the public Petstore; change loadPetstoreSpec
to either parse the checked-in YAML into a JS object (using the existing YAML
parser used elsewhere) and return that parsed spec (so callers can send it as
rawSpec when creating specs) or else change its contract to return the raw YAML
string/path explicitly; update any call sites (notably createTestSpec and the
usage around the helper at the other occurrence mentioned) to use the parsed
object as rawSpec when calling the /api/specs creation flow (or use the returned
string for upload tests), referencing loadPetstoreSpec and createTestSpec to
locate where to adjust behavior.

In `@e2e/helpers/auth.ts`:
- Line 51: The URL matcher passed to page.waitForURL is too strict and rejects
valid post-sign-out redirects with query params; update the matcher used in
page.waitForURL (in e2e/helpers/auth.ts) to allow optional query strings — for
example replace /\/(sign-in)?$/ with a regex that permits trailing query params
like /^\/(sign-in)?(\?.*)?$/ or use a starts-with/glob pattern that matches "/"
or any "/sign-in" plus queries so sign-out waits don't flake.

In `@e2e/tests/auth/signin.spec.ts`:
- Around line 24-42: Replace the hardcoded email string with the known test
account so the failure is due to a wrong password rather than a missing user:
use getTestUser().email when filling the emailInput in the invalid-password
scenario (keep the intentionally bad password), ensure the test imports or
accesses getTestUser() and that the rest of the flow still targets the same
selectors (emailInput, continueButton, passwordInput, signInButton,
errorMessage) so the assertion checks the “invalid credentials” error path.

In `@e2e/tests/billing/plan-limits.spec.ts`:
- Around line 50-56: The test named "health endpoint is accessible without auth"
is running in an authenticated suite and asserting the wrong response shape;
update it to use an unauthenticated request context instead of page.request
(e.g., use the global request fixture or create a new unauthenticated request
context) and change assertions to match the real response shape returned by
/api/health: assert response.status() is 200 and that the parsed JSON has the
keys/status values like "status" (string or boolean as appropriate), "db"
(object or status), and "timestamp" (present and a valid value) rather than
expecting a "success" property; keep the test title and use the existing call
site (page.request.get) as the reference to replace with the unauthenticated
request call.

In `@e2e/tests/billing/upgrade.spec.ts`:
- Around line 23-36: The test currently swallows waitForURL timeouts and treats
remaining on settings as success; update the logic in upgrade.spec.ts so
waitForURL errors are not silently ignored and the assertion requires a real
navigation: remove the empty .catch or replace it with capturing the error and
failing the test (or logging then rethrowing), and change the final expectation
to assert that either isOnStripe or isOnCheckoutApi is true (i.e. disallow
isStillOnSettings as a pass); reference the waitForURL call, the page.url()
usage, and the isStillOnSettings/isOnStripe/isOnCheckoutApi variables when
making this change.

In `@e2e/tests/console/test-tool-call.spec.ts`:
- Around line 98-106: The test currently only asserts behavior when
executeButton is enabled, so it can silently pass if the button is disabled;
update the test around the executeButton check (the executeButton variable and
the error locator created via page.locator('[data-testid="error-message"],
[role="alert"], .text-destructive')) to assert the disabled path as well—i.e.,
add an explicit expectation when executeButton.isEnabled() is false (for example
use expect(executeButton).toBeDisabled() or an equivalent assertion) so both
enabled and disabled branches are covered.
- Around line 7-24: The afterEach cleanup currently calls deleteSpec(specId)
even when specId/serverId may be undefined from a failed beforeEach; make specId
and serverId nullable (e.g., string | undefined) and guard the deletions in
test.afterEach by checking for truthiness before calling deleteSpec and any
server deletion function; update references to specId/serverId in the
surrounding test.beforeEach/test.afterEach blocks to use the nullable types and
only invoke deleteSpec/deleteServer when the IDs are defined to avoid deleting
the wrong resource or calling endpoints with "undefined".

In `@e2e/tests/specs/import-from-url.spec.ts`:
- Around line 61-63: The success assertion in import-from-url.spec.ts uses
page.getByText(/imported successfully|spec/i) which is too broad; update the
test to assert a specific success indicator instead (e.g., a unique text string
or data-testid) by replacing the regex locator used in page.getByText with a
precise locator such as page.getByText('Import completed successfully') or
page.getByTestId('import-success') (or the actual testid used in the app), and
keep the same timeout option; ensure you change any references to the loose
regex in this test to the new specific locator so the assertion cannot
accidentally match generic "spec" text.
- Around line 14-16: The teardown misses specs created by this import flow
because cleanupTestSpecs only deletes names starting with "E2E Test"; update the
test in import-from-url.spec.ts (the test using test.afterEach) so imported
Petstore specs are created with a deterministic prefix that matches
cleanupTestSpecs (e.g., prepend "E2E Test" to the imported spec name) or capture
the created spec IDs during the import and pass them to cleanupTestSpecs;
reference the test's import call and the cleanupTestSpecs function to either
modify the created spec name or return/store IDs for removal so CI artifacts are
reliably cleaned up.

---

Outside diff comments:
In `@README.md`:
- Line 109: Update the README entry for the Web App to reference the correct
Next.js version: change the phrase "Next.js 14 dashboard" to "Next.js 15
dashboard" in the table row containing the Web App description (the line with "|
**Web App** | [`apps/web`](apps/web) | Next.js 14 dashboard. ... |").

---

Minor comments:
In `@apps/web/content/docs/self-hosting.mdx`:
- Around line 23-24: The documentation uses a personal GitHub repo URL in the
git clone command ("git clone https://github.com/Work90210/APIFold.git" and "cd
APIFold"); update this to the organization-owned repository URL (for example
"git clone https://github.com/apifold/APIFold.git" and adjust the subsequent
"cd" if necessary) and ensure any other references in the same file to
Work90210/APIFold are replaced with the org-owned repo name for consistency.

In `@apps/web/lib/export/generator.ts`:
- Line 59: Update the generated standalone server to resolve the env var
mismatch: either change the generator in apps/web/lib/export/generator.ts so the
server reads CORS_ORIGINS (plural) and parses comma-separated values into the
allowed origins list, or document and support both by checking
process.env.CORS_ORIGINS first then falling back to process.env.CORS_ORIGIN;
reference the CORS_ORIGINS/CORS_ORIGIN env names in the generator logic and
update the generated .env example accordingly so the produced server and
.env.example remain consistent.

In `@e2e/helpers/api.ts`:
- Around line 57-63: The code calls response.json() unconditionally which can
throw SyntaxError for non-JSON responses and hide the real HTTP status/body;
modify the fetch handling around the fetch(`${BASE_URL}${endpoint}`, options)
call so you first check response.ok and the Content-Type (via
response.headers.get('content-type')) and, for non-JSON or empty bodies, call
response.text() (or handle 204/empty) instead of response.json(), then construct
the thrown Error using method, endpoint, response.status, response.statusText
and the raw body text if available; adjust references to response, json,
ApiResponse<T>, method and endpoint when reading/parsing so errors include the
actual HTTP status and body.

In `@e2e/tests/auth/signup.spec.ts`:
- Around line 9-21: The test "renders the sign-up form with email and password
fields" currently only asserts the email input (emailInput) and a heading;
either update the assertions to match the title by locating the password field
(e.g., use a locator like 'input[name="password"], input[type="password"]' and
add await expect(passwordInput).toBeVisible(...)) or change the test title
string in the test(...) call to reflect that it only checks the email input and
heading; adjust the test body accordingly (modify or add the locator referenced
by passwordInput) so the name and assertions are consistent.

In `@e2e/tests/billing/upgrade.spec.ts`:
- Around line 106-112: The test "billing portal link is accessible" assumes the
user has a Stripe customer ID but route /api/billing/portal (route.ts) returns
404 when stripeCustomerId is missing; update the assertion in the test
(test("billing portal link is accessible", ...) in
e2e/tests/billing/upgrade.spec.ts) to include 404 in the allowed
response.status() values or alternatively add a setup step that ensures the test
user has a stripeCustomerId before calling
page.request.post("/api/billing/portal"); adjust the
expect([...]).toContain(...) accordingly so the test passes for fresh users
without a Stripe customer ID.

In `@e2e/tests/landing/page-load.spec.ts`:
- Around line 19-20: The test uses a non-null assertion on headingText from
heading.textContent(), which can be null and cause false failures; update the
assertion to handle null safely by first asserting headingText is not null
(e.g., expect(headingText).not.toBeNull()) and only then assert the length, or
use a null-coalescing check like expect(headingText?.length ??
0).toBeGreaterThan(0); reference the variables/methods heading, headingText, and
textContent() when making the change.

In `@e2e/tests/mcp/sse-connection.spec.ts`:
- Around line 100-102: The assertion in the test currently checks events.length
>= 0 which always passes; update the assertion in the sse-connection.spec.ts
test so it requires at least one event (e.g., change the expectation on
events.length to beGreaterThanOrEqual(1) or toBeGreaterThan(0)) and optionally
add a clear failure message; locate the check referencing the events array and
the expect(...) call to modify it accordingly.

In `@e2e/tests/servers/configure-server.spec.ts`:
- Around line 62-65: The test currently only checks that the returned server
object from getServer(page, serverId) has an authMode property; instead assert
that server.authMode equals the specific value you updated it to earlier in the
test (e.g., the variable or literal used in the update request). Locate the
getServer call and replace the loose expect(server).toHaveProperty("authMode")
with a strict equality assertion against the expectedAuthMode (or the update
payload variable) so the test verifies the persisted value.

In `@e2e/tests/specs/delete-spec.spec.ts`:
- Around line 49-55: The test clicks deleteButton immediately but should wait
for it to be visible to match the first test and avoid flakiness; update the
block that uses page.getByRole("button", { name: /delete/i }) (deleteButton) to
wait for visibility before clicking (use the locator's waitFor/visibility check)
and then proceed to click and interact with confirmButton (page.locator(...)) as
before.

---

Nitpick comments:
In `@apps/runtime/__tests__/unit/query-monitor.test.ts`:
- Around line 51-68: The test is flaky because it depends on real time delays;
make it deterministic by mocking performance.now() around the call so measured
duration exceeds slowThresholdMs without setTimeout. In the test for
createMonitoredDb / monitored.query, replace the 50ms setTimeout approach with a
jest.spyOn or similar on global.performance.now() that returns a start value
(e.g., 1000) before calling monitored.query and a later value (e.g., 1011) after
to simulate elapsed time > slowThresholdMs; ensure you restore the spy after the
test and keep assertions on logger.warn the same.
- Around line 96-108: The test uses a real setTimeout inside createMockDb which
causes flakiness; instead simulate the slow query by mocking time—replace the
sleep-based callback with a deterministic mock of performance.now so that
createMockDb's query handler returns immediately but performance.now reports a
start and end that exceed slowThresholdMs; update the test to stub
performance.now (used by createMonitoredDb/monitored.query) to return two values
(start and end) that produce the desired elapsed time and remove reliance on
setTimeout.

In `@docs/API.md`:
- Around line 9-11: Several HTTP endpoint code blocks (for example the block
containing "Authorization: Bearer <session-token>") lack language specifiers;
update each triple-backtick fence that contains HTTP endpoint definitions or
header lines to use a language tag (prefer `http`, or `text` where appropriate).
Locate each code block that shows request/response headers or endpoint examples
(e.g., the block with "Authorization: Bearer <session-token>") and change the
opening fence from ``` to ```http and keep the closing fence as ```, applying
the same transformation to all similar endpoint definition blocks throughout the
document.

In `@docs/ARCHITECTURE.md`:
- Around line 150-163: ADR-010 duplicates the decision already recorded in
ADR-002; remove ADR-010 or merge its content into ADR-002 to avoid redundancy.
Update ADR-002 to include any unique wording from ADR-010 (including the
explicit "packages/transformer is MIT" boundary and consequences) and delete
ADR-010; also search for and update any cross-references or index entries that
point to ADR-010 so they now reference ADR-002.

In `@docs/CONTRIBUTING.md`:
- Around line 17-18: Update the repository URL in the CONTRIBUTING.md clone
instruction so it points to the organization-owned repository (or a stable
canonical URL) instead of the personal account; specifically replace the "git
clone https://github.com/Work90210/APIFold.git" string with the org's repo URL
(or a generic placeholder like "git clone
https://github.com/ORG_NAME/APIFold.git") to ensure future ownership changes
won't break the docs while preserving the existing "cd APIFold" line.

In `@e2e/fixtures/test-user.ts`:
- Around line 42-45: Update the test card expiry in the STRIPE_TEST_CARD_DETAILS
object to a far-future date (e.g., change expiry "12/30" to "12/99") so the
fixture doesn't expire; keep the same string format and leave cvc/zip unchanged
when modifying the STRIPE_TEST_CARD_DETAILS constant.

In `@e2e/global-setup.ts`:
- Around line 39-42: The warning in e2e/global-setup.ts currently logs that auth
tests "will be skipped" but does not implement any skipping; update the behavior
to match reality by either (A) changing the console.warn message in the block
that references the missing array (the console.warn call using
`${missing.join(", ")}`) to say the auth tests "may fail" or "will likely fail",
or (B) implement an actual skip mechanism by exporting a flag (e.g., export
const SKIP_AUTH_TESTS = true/false) from this setup and make auth specs read
that flag to conditionally call test.skip or set Jest/Mocha to skip those
suites; choose one approach and apply it consistently (modify the console.warn
text if you pick rewording, or add the exported flag and update auth spec files
to reference SKIP_AUTH_TESTS if you add skipping).

In `@e2e/tests/billing/upgrade.spec.ts`:
- Around line 66-78: Move the test.skip invocation to the top of the "can fill
in Stripe test card details" test so the skip check runs before any navigation
or UI interactions; specifically, add the
test.skip(!process.env["STRIPE_SECRET_KEY"], "STRIPE_SECRET_KEY not configured")
at the start of the test body for the test named "can fill in Stripe test card
details" (the async ({ page }) => { ... } block) and then proceed with page.goto
and the upgradeButton clicks only if the test is not skipped.
- Around line 39-53: The test's conditional skip
(test.skip(!process.env["STRIPE_SECRET_KEY"], ...)) should be moved to the very
start of the test block so the test is skipped before any page navigation or
interaction occurs; update the test in the "test(...)" function so the test.skip
call runs before page.goto("/dashboard/settings") and before using upgradeButton
(page.getByRole(...).or(...).first().click()) to avoid performing unnecessary
actions when STRIPE_SECRET_KEY is not set.

In `@e2e/tests/landing/navigation.spec.ts`:
- Around line 50-68: The tests currently use an "if (await
...isVisible().catch(() => false))" guard so they silently pass when links are
missing; update both tests ("docs link navigates to documentation" and
"changelog link navigates to changelog page") to assert visibility as a hard
requirement: replace the conditional guards around docsLink and changelogLink
with explicit Playwright assertions (e.g., await
expect(docsLink.first()).toBeVisible() and await
expect(changelogLink.first()).toBeVisible()) before attempting .click(), then
proceed to click and waitForURL as before so the test fails immediately if the
links are missing.

In `@e2e/tests/mcp/sse-connection.spec.ts`:
- Around line 110-129: The JSON-RPC fetch inside the page.evaluate (the POST to
messageUrl) needs an explicit timeout like the earlier SSE test: create an
AbortController with a 10s timeout, pass controller.signal into the fetch
options, and clear the timer on success; ensure the catch branch handles
AbortError similarly (returning status 0 and the error string) so the test fails
fast and consistently with the SSE reachability check.

In `@e2e/tests/servers/configure-server.spec.ts`:
- Around line 67-99: Update the "updates the base URL" and "updates the rate
limit" tests to assert persistence by calling the same API helper used in the
authMode test (e.g., getServer) after the UI save completes; locate the tests by
the test names and the locators baseUrlInput and rateLimitInput, then after
awaiting the save confirmation call getServer(serverId) and assert the returned
object's baseUrl equals "https://api.example.com/v2" in the first test and
rateLimitPerMinute equals 120 (or "120" normalized to a number) in the second
test so the tests verify server-side persistence not just the success message.

In `@e2e/tests/servers/manage-tools.spec.ts`:
- Around line 69-84: The test currently swallows all errors when calling
toggle.isChecked() and uses a hardcoded waitForTimeout(1_000), making it flaky;
change the logic in the block that computes wasChecked/isChecked to first try
toggle.isChecked() but only fallback if the element truly lacks isChecked
support (e.g., detect presence of aria-checked via await
toggle.getAttribute('aria-checked') !== null) rather than catching all errors,
then replace page.waitForTimeout(1_000) with an explicit wait for the toggle
state to change such as await expect(toggle).toHaveAttribute('aria-checked',
expectedValue) or use page.waitForFunction to poll the attribute/value until it
differs from the previous value; update references to toggle, wasChecked,
isChecked, toggle.isChecked, toggle.getAttribute and page.waitForTimeout
accordingly.
- Around line 21-27: The afterEach currently swallows all errors which can hide
infra failures; update the test.afterEach cleanup to catch the error from
deleteSpec(page, specId) as a named variable (e) and log it (e.g., console.error
or test.info().log) with context including specId and page URL/session info,
rather than leaving the catch empty—keep the best-effort intent but surface
failures for debugging by logging the exception from deleteSpec in the
test.afterEach block.

In `@e2e/tests/specs/delete-spec.spec.ts`:
- Around line 41-42: The test uses a regex locator getByText(new RegExp(specId))
which triggered a static-analysis ReDoS false positive; replace the regex-based
lookup with exact text matching (use getByText(specId) or the equivalent exact
locator) for both specId and serverId checks so they match the backend-generated
UUIDs directly; locate the usages created from createTestSpec (references to
specId and serverId) and update the assertions that call getByText(new
RegExp(...)) to use the exact-string form and keep the same timeout behavior.

In `@e2e/tests/specs/import-from-file.spec.ts`:
- Around line 4-5: Combine the two separate imports from the same module into a
single import statement: replace the separate imports of cleanupTestSpecs and
getPetstoreFixturePath (from "../../helpers/api") with one consolidated import
that imports both symbols together; update the import line that currently
references cleanupTestSpecs and getPetstoreFixturePath so both named exports are
imported in a single import declaration.

In `@lighthouserc.json`:
- Around line 1-27: The lighthouserc.json duplicates the GitHub Actions
Lighthouse config and adds only "upload.target"; either remove this root
lighthouserc.json to avoid confusion or consolidate into one config and update
the workflow to reference it — e.g., remove the duplicate lighthouserc.json
(which contains the "ci.collect", "ci.assert", and "ci.upload.target" entries)
if local lhci runs are unnecessary, or merge its "upload.target":
"temporary-public-storage" into the single canonical config used by the workflow
and ensure the workflow's configPath (currently set to
.github/lighthouse/config.json) and temporaryPublicStorage flag reflect the
consolidated file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c7858a63-7285-4d31-8310-e3c6bed493f9

📥 Commits

Reviewing files that changed from the base of the PR and between f12d831 and c4dc7fe.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (45)
  • .github/lighthouse/config.json
  • .github/workflows/bundle-analysis.yml
  • .github/workflows/ci.yml
  • .github/workflows/security.yml
  • README.md
  • apps/runtime/__tests__/unit/query-monitor.test.ts
  • apps/runtime/src/index.ts
  • apps/runtime/src/observability/query-monitor.ts
  • apps/web/content/docs/api-reference.mdx
  • apps/web/content/docs/architecture.mdx
  • apps/web/content/docs/changelog.mdx
  • apps/web/content/docs/meta.json
  • apps/web/content/docs/self-hosting.mdx
  • apps/web/lib/export/generator.ts
  • apps/web/next.config.mjs
  • apps/web/package.json
  • docs/API.md
  • docs/ARCHITECTURE.md
  • docs/CONTRIBUTING.md
  • docs/SECURITY.md
  • e2e/fixtures/petstore.yaml
  • e2e/fixtures/test-user.ts
  • e2e/global-setup.ts
  • e2e/helpers/api.ts
  • e2e/helpers/auth.ts
  • e2e/playwright.config.ts
  • e2e/tests/auth/signin.spec.ts
  • e2e/tests/auth/signup.spec.ts
  • e2e/tests/billing/plan-limits.spec.ts
  • e2e/tests/billing/upgrade.spec.ts
  • e2e/tests/console/test-tool-call.spec.ts
  • e2e/tests/export/download-server.spec.ts
  • e2e/tests/landing/navigation.spec.ts
  • e2e/tests/landing/page-load.spec.ts
  • e2e/tests/mcp/sse-connection.spec.ts
  • e2e/tests/servers/configure-server.spec.ts
  • e2e/tests/servers/connection-snippets.spec.ts
  • e2e/tests/servers/manage-tools.spec.ts
  • e2e/tests/specs/delete-spec.spec.ts
  • e2e/tests/specs/import-from-file.spec.ts
  • e2e/tests/specs/import-from-url.spec.ts
  • lighthouserc.json
  • packages/transformer/.size-limit.json
  • packages/transformer/README.md
  • packages/transformer/package.json

Comment thread apps/web/content/docs/api-reference.mdx
Comment thread e2e/global-setup.ts Outdated
Comment thread e2e/helpers/api.ts
Comment on lines +72 to +78
function loadPetstoreSpec(): Record<string, unknown> {
const yamlPath = path.resolve(__dirname, "../fixtures/petstore.yaml");
const content = readFileSync(yamlPath, "utf-8");

// Simple YAML-to-JSON: import the spec via the API using sourceUrl instead
// For file upload tests, we return the raw string
return { _raw: content } as unknown as Record<string, unknown>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the local Petstore fixture actually usable for spec creation.

loadPetstoreSpec() is documented as a parsed spec, but it only returns { _raw: content } via a double cast. Because of that, createTestSpec() still depends on the public Petstore endpoint for every setup. That adds third-party network flakiness even though /api/specs already accepts rawSpec (apps/web/lib/validation/spec.schema.ts, Lines 3-11; apps/web/app/api/specs/route.ts, Lines 120-138). Parse the checked-in fixture or store a JSON version and send it via rawSpec; if this helper is only for upload flows, make its contract a string/path instead.

Also applies to: 90-94

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/api.ts` around lines 72 - 78, loadPetstoreSpec currently returns
only { _raw: content } which forces createTestSpec to hit the public Petstore;
change loadPetstoreSpec to either parse the checked-in YAML into a JS object
(using the existing YAML parser used elsewhere) and return that parsed spec (so
callers can send it as rawSpec when creating specs) or else change its contract
to return the raw YAML string/path explicitly; update any call sites (notably
createTestSpec and the usage around the helper at the other occurrence
mentioned) to use the parsed object as rawSpec when calling the /api/specs
creation flow (or use the returned string for upload tests), referencing
loadPetstoreSpec and createTestSpec to locate where to adjust behavior.

Comment thread e2e/helpers/api.ts
Comment on lines +84 to +88
export async function createTestSpec(
page: Page,
options?: { readonly name?: string },
): Promise<CreateSpecResult> {
const name = options?.name ?? `E2E Test Spec ${Date.now()}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tie creation and cleanup to the same identifier.

createTestSpec() can create arbitrary names via options.name, but cleanupTestSpecs() only deletes specs whose names start with E2E Test. That means custom-named fixtures can leak, and the shared prefix can also wipe another concurrent run's data if the same account is reused. Always stamp created resources with a run-specific prefix, or track created IDs and delete only those.

Also applies to: 149-156

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/api.ts` around lines 84 - 88, createTestSpec currently allows
arbitrary names while cleanupTestSpecs only deletes specs with the "E2E Test"
prefix, which can leak or accidentally delete other runs; modify createTestSpec
and related functions (e.g., cleanupTestSpecs) to tie creation and teardown to a
run-specific identifier: generate a single runId at module init (or accept it
via env) and prefix all created spec names with `${runId}-E2E Test ...`, or
alternatively record and return the created spec IDs from createTestSpec in a
module-level array (e.g., createdSpecIds) and update cleanupTestSpecs to delete
only those recorded IDs; ensure both name construction (in createTestSpec) and
deletion logic (in cleanupTestSpecs and any other cleanup helper mentioned
around the 149-156 region) reference the same runId or createdSpecIds so created
fixtures are reliably cleaned up.

Comment thread e2e/helpers/auth.ts Outdated
Comment thread e2e/tests/billing/upgrade.spec.ts Outdated
Comment thread e2e/tests/console/test-tool-call.spec.ts Outdated
Comment thread e2e/tests/console/test-tool-call.spec.ts
Comment thread e2e/tests/specs/import-from-url.spec.ts
Comment thread e2e/tests/specs/import-from-url.spec.ts Outdated
KyleFuehri added 5 commits March 22, 2026 07:23
- Add webhook auth exception note in API reference docs
- Add fetch timeout (5s) to global-setup health polling
- Fix sign-out URL matcher to allow query params
- Use test user email for invalid-password test scenario
- Fix health endpoint test to use unauthenticated context
- Replace permissive upgrade test with modal fallback assertion
- Guard console test cleanup against null IDs
- Add disabled-button assertion for missing params test
- Track created spec IDs for reliable URL import cleanup
- Narrow success assertion to match specific text
…dcoded key

- Fix trivy-action version: 0.28.0 -> v0.28.0 (needs v prefix)
- Fix snyk/actions/node version: 0.4.0 -> v0.4.0 (needs v prefix)
- Fix hashicorp/nextjs-bundle-analysis: v0.7.2 -> v0.5.0 (latest valid)
- Move Clerk publishable key to secrets to avoid gitleaks false positive
- Use trivy-action@v0.35.0 (latest stable, v0.28.0 had broken dep)
- Use snyk/actions/node@v1 (v0.4.0 tag format incompatible)
- Remove broken hashicorp/nextjs-bundle-analysis action (no action.yml)
- Fix gitleaks: rename sk_test_ placeholder, allowlist workflow files
…uild

The Next.js build attempts static page generation for dashboard routes
which requires a running PostgreSQL instance. Bundle analysis cannot
run in CI without database infrastructure.
@Work90210 Work90210 merged commit ea98978 into master Mar 22, 2026
7 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant