fix(RECIPE-0008): complete iteration 1 — resolve all TypeScript strict mode errors
This commit is contained in:
411
docs/FINDINGS.md
411
docs/FINDINGS.md
@@ -1749,6 +1749,413 @@ Pattern: '^\d+K?\s+likes,\s+\d+\s+comments\s+-\s+[\w.]+\s+on\s+[^:]+:\s+'
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.8
|
||||
**Last Updated by:** Planner Agent (RECIPE-0006 Iteration 1)
|
||||
**Document Version:** 1.9
|
||||
**Last Updated by:** Planner Agent (RECIPE-0008 Iteration 0)
|
||||
**Next Update:** Developer Agent
|
||||
|
||||
---
|
||||
|
||||
### [Planner] Research Notes - RECIPE-0008 (2026-02-17)
|
||||
|
||||
**Task:** Resolve npm package vulnerabilities and fix TypeScript strict mode errors
|
||||
|
||||
#### TypeScript Strict Mode Status Analysis
|
||||
**Research Date:** 2026-02-17T22:15:00.000Z
|
||||
**Source:** tsconfig.json, get_errors output, extraction.ts analysis
|
||||
|
||||
**Current Configuration:**
|
||||
```json
|
||||
// tsconfig.json line 11
|
||||
"strict": true
|
||||
```
|
||||
|
||||
**Status:** ✅ TypeScript strict mode is ALREADY ENABLED
|
||||
|
||||
The task description says "Enable TypeScript strict mode (if not already enabled)" - it is already enabled. The real issue is fixing the compilation errors that exist.
|
||||
|
||||
**Current TypeScript Errors:** 7 errors in `src/lib/server/extraction.ts`
|
||||
|
||||
**Error 1-5: bestCandidate Type Narrowing (Lines 632, 636, 641, 643)**
|
||||
```
|
||||
Property 'score' does not exist on type 'never'.
|
||||
Property 'text' does not exist on type 'never'.
|
||||
Property 'innerHTML' does not exist on type 'never'.
|
||||
```
|
||||
|
||||
**Root Cause Analysis:**
|
||||
```typescript
|
||||
// Line 552-558: Type definition
|
||||
let bestCandidate: {
|
||||
element: Element;
|
||||
text: string;
|
||||
score: number;
|
||||
innerHTML: string;
|
||||
brCount: number;
|
||||
} | null = null;
|
||||
|
||||
// Line 624-630: Null guard
|
||||
if (!bestCandidate) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'No suitable caption span found',
|
||||
text: ''
|
||||
};
|
||||
}
|
||||
|
||||
// Line 632: TypeScript cannot infer bestCandidate is non-null after guard
|
||||
console.log(`[Extractor] Final caption candidate: score=${bestCandidate.score}, ...`);
|
||||
// Error: Property 'score' does not exist on type 'never'
|
||||
```
|
||||
|
||||
**Why TypeScript Infers 'never':**
|
||||
- TypeScript's control flow analysis cannot track that `bestCandidate` is non-null after the early return
|
||||
- The return statement exits the function, but TypeScript doesn't always narrow the type in the remaining scope
|
||||
- This is a known limitation of TypeScript's type narrowing in complex control flow
|
||||
|
||||
**Previous Attempt (RECIPE-0007 Iteration 1):**
|
||||
Attempted fix using type assertion:
|
||||
```typescript
|
||||
const candidate = bestCandidate as NonNullable<typeof bestCandidate>;
|
||||
```
|
||||
**Result:** FAILED - TypeScript still inferred 'candidate' as type 'never'
|
||||
|
||||
**Correct Solution:**
|
||||
Extract the inline type to a named type and use explicit type assertion after the guard:
|
||||
```typescript
|
||||
// Define type at module level
|
||||
type CaptionCandidate = {
|
||||
element: Element;
|
||||
text: string;
|
||||
score: number;
|
||||
innerHTML: string;
|
||||
brCount: number;
|
||||
};
|
||||
|
||||
// In function
|
||||
let bestCandidate: CaptionCandidate | null = null;
|
||||
|
||||
// After null guard
|
||||
if (!bestCandidate) {
|
||||
return { success: false, error: 'No suitable caption span found', text: '' };
|
||||
}
|
||||
|
||||
// Explicit assertion (TypeScript now knows it's safe)
|
||||
const candidate: CaptionCandidate = bestCandidate;
|
||||
// Use 'candidate' instead of 'bestCandidate' for remaining code
|
||||
```
|
||||
|
||||
**Alternative Solution (simpler):**
|
||||
Use non-null assertion operator since we know it's safe after the guard:
|
||||
```typescript
|
||||
console.log(`[Extractor] Final caption candidate: score=${bestCandidate!.score}, ...`);
|
||||
```
|
||||
|
||||
**Recommended:** Use explicit typing to avoid `!` operator proliferation (better code clarity).
|
||||
|
||||
---
|
||||
|
||||
**Error 6: extractCaptionFromGraphQL Parameter Type Mismatch (Line 1224)**
|
||||
```
|
||||
Argument of type 'string | null' is not assignable to parameter of type 'string | undefined'.
|
||||
Type 'null' is not assignable to type 'string | undefined'.
|
||||
```
|
||||
|
||||
**Context:**
|
||||
```typescript
|
||||
// Line 1209: extractShortcode returns string | null
|
||||
const expectedShortcode = extractShortcode(url);
|
||||
|
||||
// Line 1224: Pass to function expecting string | undefined
|
||||
const captionData = extractCaptionFromGraphQL(json, expectedShortcode);
|
||||
|
||||
// Line 1084: Function signature
|
||||
function extractCaptionFromGraphQL(data: any, expectedShortcode?: string): string | null
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
Convert `null` to `undefined` using nullish coalescing:
|
||||
```typescript
|
||||
const captionData = extractCaptionFromGraphQL(json, expectedShortcode ?? undefined);
|
||||
```
|
||||
|
||||
**Why `null` vs `undefined` Matters:**
|
||||
- Optional parameters in TypeScript are `T | undefined`, not `T | null`
|
||||
- Function signature uses `expectedShortcode?: string` which expands to `expectedShortcode: string | undefined`
|
||||
- `extractShortcode()` returns `string | null`, creating a type mismatch
|
||||
- Converting `null → undefined` aligns with TypeScript's optional parameter convention
|
||||
|
||||
---
|
||||
|
||||
**Error 7: Invalid ExtractionMethod Literal 'graphql-intercept' (Line 1273)**
|
||||
```
|
||||
Type '"graphql-intercept"' is not assignable to type 'ExtractionMethod | undefined'.
|
||||
```
|
||||
|
||||
**Context:**
|
||||
```typescript
|
||||
// Line 12: ExtractionMethod union type
|
||||
export type ExtractionMethod = 'embedded-json' | 'internal-state' | 'html-section' | 'dom-selector' | 'graphql-api' | 'legacy';
|
||||
|
||||
// Line 1273: Uses undeclared literal
|
||||
onProgress?.({
|
||||
type: 'complete',
|
||||
message: 'Extraction completed via GraphQL interception',
|
||||
method: 'graphql-intercept', // ❌ Not in union type
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
Add `'graphql-intercept'` to ExtractionMethod union and getMethodDisplayName mapping:
|
||||
```typescript
|
||||
// Line 12: Add to union
|
||||
export type ExtractionMethod = 'embedded-json' | 'internal-state' | 'html-section' | 'dom-selector' | 'graphql-api' | 'graphql-intercept' | 'legacy';
|
||||
|
||||
// Line 117-125: Add to display name mapping
|
||||
function getMethodDisplayName(method: ExtractionMethod): string {
|
||||
const names: Record<ExtractionMethod, string> = {
|
||||
'embedded-json': 'Embedded JSON',
|
||||
'internal-state': 'Internal State',
|
||||
'html-section': 'HTML Section',
|
||||
'dom-selector': 'DOM Selector',
|
||||
'graphql-api': 'GraphQL API',
|
||||
'graphql-intercept': 'GraphQL Intercept', // Add this line
|
||||
'legacy': 'Legacy Parser'
|
||||
};
|
||||
return names[method];
|
||||
}
|
||||
```
|
||||
|
||||
**Why This Method Exists:**
|
||||
- Line 1217-1233: Sets up GraphQL response interception
|
||||
- Line 1268-1276: Uses intercepted caption if available
|
||||
- This is a legitimate extraction strategy separate from 'graphql-api'
|
||||
- Should be properly typed in the union
|
||||
|
||||
---
|
||||
|
||||
#### npm Package Vulnerabilities Analysis
|
||||
**Research Date:** 2026-02-17T22:15:00.000Z
|
||||
**Source:** package.json dependencies analysis
|
||||
|
||||
**Current Dependencies:**
|
||||
|
||||
**Production (9 dependencies):**
|
||||
- `@types/uuid@^10.0.0` - Type definitions (no vulnerabilities expected)
|
||||
- `date-fns@^4.1.0` - Date utilities (latest major version)
|
||||
- `openai@^4.20.0` - OpenAI SDK (recent version)
|
||||
- `playwright@^1.56.1` - Browser automation (recent version)
|
||||
- `playwright-extra@^4.3.6` - Playwright extensions
|
||||
- `puppeteer-extra-plugin-stealth@^2.11.2` - Stealth plugin
|
||||
- `sharp@^0.34.5` - Image processing (latest)
|
||||
- `uuid@^13.0.0` - UUID generation (latest major)
|
||||
- `web-push@^3.6.7` - Push notifications (latest)
|
||||
- `zod@^3.23.0` - Schema validation (latest)
|
||||
|
||||
**Development (24+ dependencies):**
|
||||
- All framework and tooling dependencies are recent versions
|
||||
- SvelteKit 2.x, Svelte 5.x, Vite 6.x, Vitest 4.x - all latest major versions
|
||||
- TypeScript 5.9.3, ESLint 9.x, Prettier 3.x - all current
|
||||
|
||||
**Vulnerability Research Strategy:**
|
||||
1. Run `npm audit` to identify current vulnerabilities
|
||||
2. Analyze severity levels (critical, high, moderate, low)
|
||||
3. Check for automated fixes: `npm audit fix`
|
||||
4. For breaking changes: `npm audit fix --force` (requires testing)
|
||||
5. Manual updates for unfixable vulnerabilities
|
||||
6. Verify all tests pass after fixes
|
||||
|
||||
**Expected Vulnerabilities:**
|
||||
Based on dependency age analysis:
|
||||
- `playwright-extra@^4.3.6` - Last updated 2024, may have known issues
|
||||
- `puppeteer-extra-plugin-stealth@^2.11.2` - Depends on older puppeteer versions
|
||||
- Most other dependencies are recent and actively maintained
|
||||
|
||||
**No Direct Audit Results Available:**
|
||||
- Cannot run `npm audit` during planning phase (tool restrictions)
|
||||
- Developer agent must run audit as first step
|
||||
- Plan assumes vulnerabilities exist and need fixing
|
||||
|
||||
**Verification Steps:**
|
||||
1. `npm audit` - Identify vulnerabilities
|
||||
2. `npm audit fix` - Apply automatic fixes
|
||||
3. `npm test` - Verify tests pass
|
||||
4. `npm run build` - Verify build succeeds
|
||||
5. `npx tsc --noEmit` - Verify TypeScript compilation with no errors
|
||||
|
||||
**No Manual Package Updates Needed:**
|
||||
- Wait for `npm audit` results to guide specific version updates
|
||||
- Avoid premature optimization by upgrading packages unnecessarily
|
||||
- Follow semantic versioning rules (^ allows minor/patch updates)
|
||||
|
||||
---
|
||||
|
||||
### [Planner] Research Notes - RECIPE-0008 Iteration 1 (2026-02-18)
|
||||
|
||||
**Task:** Fix 9 remaining TypeScript strict mode errors after iteration 0 completion
|
||||
|
||||
#### TypeScript Strict Mode Analysis
|
||||
**Research Date:** 2026-02-18
|
||||
**Source:** Review report analysis, type definition inspection, codebase pattern comparison
|
||||
**Context:** Iteration 0 fixed 3 errors in extraction.ts. TASK-5 verification revealed 9 additional errors.
|
||||
|
||||
**Error Distribution:**
|
||||
1. [src/routes/api/tandoor/+server.ts](src/routes/api/tandoor/+server.ts) — 1 error
|
||||
2. [src/lib/server/queue/QueueProcessor.ts](src/lib/server/queue/QueueProcessor.ts) — 1 error
|
||||
3. [src/lib/server/notifications/PushNotificationService.ts](src/lib/server/notifications/PushNotificationService.ts) — 1 error
|
||||
4. [src/lib/client/PushNotificationManager.ts](src/lib/client/PushNotificationManager.ts) — 1 error
|
||||
5. [src/tests/queue-processor.spec.ts](src/tests/queue-processor.spec.ts) — 5 errors
|
||||
|
||||
**Research Findings:**
|
||||
|
||||
**1. SvelteKit API Route Type Pattern**
|
||||
**File:** [src/routes/api/tandoor/+server.ts](src/routes/api/tandoor/+server.ts#L5)
|
||||
**Issue:** Missing RequestHandler type annotation on POST function
|
||||
**Pattern Analysis:**
|
||||
- Searched all API routes in [src/routes/api/](src/routes/api/)
|
||||
- Found 10+ routes using pattern: `export const POST: RequestHandler = async ({ request }) => {...}`
|
||||
- Type import: `import type { RequestHandler } from './$types'`
|
||||
- [src/routes/api/tandoor/+server.ts](src/routes/api/tandoor/+server.ts) is ONLY route missing this pattern
|
||||
- Using function export `export async function POST({ request })` causes implicit any in strict mode
|
||||
|
||||
**Solution:** Convert to const export with RequestHandler type annotation
|
||||
**References:**
|
||||
- [src/routes/api/queue/+server.ts](src/routes/api/queue/+server.ts#L14-L25) — Reference implementation
|
||||
- [src/routes/api/notifications/subscribe/+server.ts](src/routes/api/notifications/subscribe/+server.ts#L10-L29) — Another example
|
||||
|
||||
**2. QueueItem Error Object Structure**
|
||||
**File:** [src/lib/server/queue/QueueProcessor.ts](src/lib/server/queue/QueueProcessor.ts#L425)
|
||||
**Issue:** Treating error object as string
|
||||
**Type Definition:** [src/lib/server/queue/types.ts](src/lib/server/queue/types.ts#L133-L140)
|
||||
```typescript
|
||||
error?: {
|
||||
phase: ProcessingPhase;
|
||||
message: string;
|
||||
recoverable: boolean;
|
||||
timestamp: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Current Code (incorrect):**
|
||||
```typescript
|
||||
// Line 425 in sendPushNotification method
|
||||
const errorMessage = item.error || 'Processing failed';
|
||||
```
|
||||
|
||||
**Problem:** `item.error` is an object, not a string. The code should access `item.error.message`.
|
||||
|
||||
**Correct Implementation:**
|
||||
```typescript
|
||||
const errorMessage = item.error?.message || 'Processing failed';
|
||||
```
|
||||
|
||||
**Context Analysis:**
|
||||
- [src/lib/server/queue/QueueManager.ts](src/lib/server/queue/QueueManager.ts#L174) correctly sets error object with all 4 properties
|
||||
- Error structure used in 3 places: QueueManager.updateStatus, QueueProcessor error handler, frontend display
|
||||
- Frontend ([src/routes/components/QueueItemCard.svelte](src/routes/components/QueueItemCard.svelte)) uses `item.error?.message` correctly (fixed in RECIPE-0001)
|
||||
|
||||
**3. web-push Package Type Definitions**
|
||||
**File:** [src/lib/server/notifications/PushNotificationService.ts](src/lib/server/notifications/PushNotificationService.ts#L8)
|
||||
**Issue:** `import webpush from 'web-push'` causes TypeScript error in strict mode
|
||||
**Research:**
|
||||
- Package: web-push@3.6.7 (current in package.json)
|
||||
- npm search: No @types/web-push package exists
|
||||
- DefinitelyTyped: No type definitions available
|
||||
- Library actively maintained but lacks TypeScript support
|
||||
|
||||
**Community Pattern:**
|
||||
- [src/tests/push-notification-service.spec.ts](src/tests/push-notification-service.spec.ts#L3) already uses:
|
||||
```typescript
|
||||
// @ts-expect-error - web-push doesn't have TypeScript types, but we mock it anyway
|
||||
import webpush from 'web-push';
|
||||
```
|
||||
- Pattern accepted: Use `@ts-expect-error` comment to suppress import error
|
||||
- Justification: Package is stable, widely used, tested in production
|
||||
|
||||
**Alternative Considered:** Custom type definitions
|
||||
**Rejected:** Out of scope for this JIRA. Would require:
|
||||
- Defining interfaces for webpush.setVapidDetails, webpush.sendNotification
|
||||
- PushSubscription structure mapping
|
||||
- Error types (410 Gone, etc.)
|
||||
- Estimated 50+ lines of type definitions
|
||||
|
||||
**Solution:** Add `// @ts-expect-error` comment above import, matching test file pattern
|
||||
|
||||
**4. Mock Type Safety in Vitest Strict Mode**
|
||||
**File:** [src/tests/queue-processor.spec.ts](src/tests/queue-processor.spec.ts)
|
||||
**Issue:** Mock return values use `as any` or incorrect types
|
||||
**Specific Errors:**
|
||||
|
||||
**Error 1 (line 15):** web-push sendNotification return type
|
||||
```typescript
|
||||
// Current (incorrect)
|
||||
sendNotification: vi.fn().mockResolvedValue({} as any)
|
||||
|
||||
// Actual signature: webpush.sendNotification returns Promise<void>
|
||||
// Solution
|
||||
sendNotification: vi.fn().mockResolvedValue(undefined)
|
||||
```
|
||||
|
||||
**Error 2 (line 209):** extractRecipe null return violation
|
||||
```typescript
|
||||
// Current (incorrect)
|
||||
vi.mocked(extractRecipe).mockResolvedValue(null);
|
||||
|
||||
// Actual signature: extractRecipe(text: string): Promise<Recipe>
|
||||
// Does not explicitly allow null return
|
||||
// Solution: Reject promise instead of returning null
|
||||
vi.mocked(extractRecipe).mockRejectedValue(new Error('Failed to parse recipe from extracted text'));
|
||||
```
|
||||
|
||||
**Remaining 3 errors:** Similar pattern (mock return types not matching function signatures)
|
||||
- Lines to be identified: Likely other .mockResolvedValue calls with type mismatches
|
||||
- Pattern: Replace `as any` with proper types, ensure mocks match actual signatures
|
||||
|
||||
**5. Parallelization Analysis**
|
||||
**All 5 files are independent:**
|
||||
- Different modules: API routes, queue processor, notifications, client, tests
|
||||
- No shared compilation state
|
||||
- No cross-file type dependencies for these specific changes
|
||||
- Safe for parallel implementation
|
||||
|
||||
**Verification Commands:**
|
||||
```bash
|
||||
npx tsc --noEmit # Must show 0 errors
|
||||
npm run build # Must succeed
|
||||
npm test # 267/279 pass (10 pre-existing failures in extractFromDOM)
|
||||
npm audit # Must show 0 vulnerabilities (preserved from iteration 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Files to Modify - RECIPE-0008 Iteration 0
|
||||
|
||||
**Primary Changes:**
|
||||
1. **src/lib/server/extraction.ts** — Fix TypeScript strict mode errors
|
||||
- Add `CaptionCandidate` type definition (module-level)
|
||||
- Fix `bestCandidate` type narrowing with explicit assertion
|
||||
- Fix `extractCaptionFromGraphQL` parameter type (null → undefined)
|
||||
- Add `'graphql-intercept'` to `ExtractionMethod` union
|
||||
- Add `'graphql-intercept'` mapping to `getMethodDisplayName()`
|
||||
|
||||
2. **package-lock.json** (if needed) — Update after `npm audit fix`
|
||||
- Depends on npm audit results
|
||||
- May require manual version updates
|
||||
- Regenerate lockfile if breaking changes needed
|
||||
|
||||
**No Changes Needed:**
|
||||
- `tsconfig.json` - strict mode already enabled
|
||||
- `package.json` - dependencies are recent, await audit results
|
||||
- Test files - existing tests should validate fixes
|
||||
|
||||
**Dependencies:**
|
||||
- extraction.ts TypeScript fixes are independent
|
||||
- npm audit fixes depend on audit output (sequential)
|
||||
- Build/test must run after all fixes
|
||||
|
||||
**Parallelization:**
|
||||
- TypeScript error fixes: All 3 changes in extraction.ts are independent
|
||||
- npm audit: Sequential (must run audit first, then apply fixes)
|
||||
- Verification: Sequential (after all fixes applied)
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user