feat: warm-up deps — ts-reset, web-vitals, RHF devtool, query-broadcast
Four low-risk adds before the Zod 4 / drizzle-zod headliner: - @total-typescript/ts-reset: tightens TS stdlib types globally (JSON.parse → unknown, fetch().json() → unknown, .filter(Boolean) narrows, Set literals respect typed Set targets). Caught 179 latent type errors; fixed all production sites (8 files) and added `any` cast escape hatch in test files (ESLint exemption scoped to tests/). - web-vitals + /api/v1/internal/vitals endpoint + WebVitalsReporter client component: establishes Core Web Vitals baseline (LCP/INP/CLS/ FCP/TTFB) via navigator.sendBeacon. Required before optimisation work. - @hookform/devtools + FormDevtool wrapper: dev-only RHF state inspector, lazy-loaded via next/dynamic so the chunk is excluded from prod bundles entirely. - @tanstack/query-broadcast-client-experimental: cross-tab cache sync via BroadcastChannel — wired in query-provider.tsx, 1-liner. Audit doc updated with sections 35 + 36 (PDF stack overhaul + comprehensive second-pass package sweep) covering ~20 package adoption candidates and 4-5 deprecation candidates. Verified: tsc clean, vitest 1293/1293 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login, navigateTo, PORT_SLUG } from './helpers';
|
||||
import { login, navigateTo } from './helpers';
|
||||
|
||||
test.describe('AI Features', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -9,9 +9,12 @@ test.describe('AI Features', () => {
|
||||
// Test 39: AI features are hidden when flag is off
|
||||
test('AI score badge hidden when feature flag disabled', async ({ page }) => {
|
||||
// First, ensure the flag is off by checking via API
|
||||
const flagRes = await page.request.get('/api/v1/settings/feature-flag?key=ai_interest_scoring', {
|
||||
headers: { 'X-Port-Id': '' }, // Will use session port
|
||||
});
|
||||
const flagRes = await page.request.get(
|
||||
'/api/v1/settings/feature-flag?key=ai_interest_scoring',
|
||||
{
|
||||
headers: { 'X-Port-Id': '' }, // Will use session port
|
||||
},
|
||||
);
|
||||
|
||||
// Navigate to an interest
|
||||
await navigateTo(page, '/interests');
|
||||
@@ -27,11 +30,17 @@ test.describe('AI Features', () => {
|
||||
const hotBadge = page.getByText(/^(Hot|Warm|Cool|Cold)$/).first();
|
||||
|
||||
if (flagRes.ok()) {
|
||||
const flagData = await flagRes.json().catch(() => ({ enabled: false }));
|
||||
const flagData = (await flagRes.json().catch(() => ({ enabled: false }))) as {
|
||||
enabled?: boolean;
|
||||
};
|
||||
if (!flagData.enabled) {
|
||||
// Score badge should NOT be visible
|
||||
await expect(scoreBadge.first()).not.toBeVisible({ timeout: 3_000 }).catch(() => {});
|
||||
await expect(hotBadge).not.toBeVisible({ timeout: 2_000 }).catch(() => {});
|
||||
await expect(scoreBadge.first())
|
||||
.not.toBeVisible({ timeout: 3_000 })
|
||||
.catch(() => {});
|
||||
await expect(hotBadge)
|
||||
.not.toBeVisible({ timeout: 2_000 })
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +57,10 @@ test.describe('AI Features', () => {
|
||||
const aiToggle = page.getByText(/ai.*scoring|interest.*scoring/i).first();
|
||||
if (await aiToggle.isVisible({ timeout: 5_000 }).catch(() => false)) {
|
||||
// Find the associated switch/toggle
|
||||
const toggle = aiToggle.locator('..').locator('button[role="switch"], input[type="checkbox"]').first();
|
||||
const toggle = aiToggle
|
||||
.locator('..')
|
||||
.locator('button[role="switch"], input[type="checkbox"]')
|
||||
.first();
|
||||
if (await toggle.isVisible({ timeout: 2_000 }).catch(() => false)) {
|
||||
await toggle.click();
|
||||
await page.waitForTimeout(2_000);
|
||||
@@ -56,10 +68,12 @@ test.describe('AI Features', () => {
|
||||
} else {
|
||||
// If no UI for feature flags, try setting it via API
|
||||
// This is an acceptable approach for testing
|
||||
await page.request.put('/api/v1/settings/feature-flag', {
|
||||
data: { key: 'ai_interest_scoring', value: true },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).catch(() => {});
|
||||
await page.request
|
||||
.put('/api/v1/settings/feature-flag', {
|
||||
data: { key: 'ai_interest_scoring', value: true },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
@@ -78,10 +92,10 @@ test.describe('AI Features', () => {
|
||||
// Try calling the scoring API directly to verify it works
|
||||
const scoreRes = await page.request.get('/api/v1/ai/interest-score/bulk');
|
||||
if (scoreRes.ok()) {
|
||||
const data = await scoreRes.json().catch(() => null);
|
||||
const data = (await scoreRes.json().catch(() => null)) as any;
|
||||
if (data && Array.isArray(data) && data.length > 0) {
|
||||
// Verify scores are in 0-100 range
|
||||
const score = data[0].score?.totalScore ?? data[0].totalScore;
|
||||
const score = (data as any)[0].score?.totalScore ?? (data as any)[0].totalScore;
|
||||
if (score !== undefined) {
|
||||
expect(score).toBeGreaterThanOrEqual(0);
|
||||
expect(score).toBeLessThanOrEqual(100);
|
||||
@@ -97,7 +111,7 @@ test.describe('AI Features', () => {
|
||||
// Test via API to avoid UI dependencies
|
||||
const interests = await page.request.get(`/api/v1/interests?limit=1`).catch(() => null);
|
||||
if (interests?.ok()) {
|
||||
const data = await interests.json().catch(() => ({ data: [] }));
|
||||
const data = (await interests.json().catch(() => ({ data: [] }))) as any;
|
||||
const interest = data.data?.[0];
|
||||
|
||||
if (interest) {
|
||||
@@ -115,7 +129,7 @@ test.describe('AI Features', () => {
|
||||
expect([200, 202, 404].includes(draftRes.status())).toBeTruthy();
|
||||
|
||||
if (draftRes.status() === 202) {
|
||||
const result = await draftRes.json();
|
||||
const result = (await draftRes.json()) as any;
|
||||
expect(result.jobId).toBeTruthy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ async function signUpUser(email: string, password: string, name: string) {
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const data = (await res.json()) as any;
|
||||
return data.user?.id ?? data.id;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ async function signUpUser(email: string, password: string, name: string) {
|
||||
});
|
||||
|
||||
if (loginRes.ok) {
|
||||
const loginData = await loginRes.json();
|
||||
const loginData = (await loginRes.json()) as any;
|
||||
return loginData.user?.id ?? loginData.id;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user