diff --git a/cspell.json b/cspell.json index 8b261336..593cc049 100644 --- a/cspell.json +++ b/cspell.json @@ -3,7 +3,16 @@ "ignorePaths": [], "dictionaryDefinitions": [], "dictionaries": [], - "words": ["daterangepicker", "Greptile", "intraday", "prcs", "Qodo", "Qube", "upserts"], + "words": [ + "daterangepicker", + "Greptile", + "intraday", + "prcs", + "Qodo", + "Qube", + "upserts", + "wakatime" + ], "ignoreWords": [], "import": [] } diff --git a/docs/02-delivery/phase-01/ticket-04-axios-to-fetch.md b/docs/02-delivery/phase-01/ticket-04-axios-to-fetch.md index 891ecb06..ae5381c7 100644 --- a/docs/02-delivery/phase-01/ticket-04-axios-to-fetch.md +++ b/docs/02-delivery/phase-01/ticket-04-axios-to-fetch.md @@ -45,9 +45,7 @@ Size: 2 points ## Rationale -> Append here during implementation. - -Red first: -Why this path: -Alternative considered: -Deferred: +Red first: `pnpm check` passed before implementation with the existing warning baseline. +Why this path: Replaced each axios call with native `fetch(...).then((response) => response.json())`, keeping the same direct JSON assignment shape the ticket called for and removing the axios-only component test mock. +Alternative considered: A shared fetch helper would reduce repetition, but this ticket intentionally avoids adding new error-handling behavior or broadening the abstraction surface. +Deferred: HTTP status handling remains unchanged in spirit and is not added here; Codex preflight is disabled per operator instruction because the current environment cannot run the Claude-code-only preflight agent. diff --git a/orchestrator.config.json b/orchestrator.config.json index 87b4e640..26f053a9 100644 --- a/orchestrator.config.json +++ b/orchestrator.config.json @@ -6,7 +6,7 @@ "ticketBoundaryMode": "gated", "reviewPolicy": { "selfAudit": "skip_doc_only", - "codexPreflight": "skip_doc_only", + "codexPreflight": "disabled", "externalReview": "disabled" } } diff --git a/package.json b/package.json index 6c70c4fb..b169c7fb 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "@vitest/coverage-v8": "4.0.16", "@vitest/ui": "4.0.16", "autoprefixer": "10.4.23", - "axios": "1.13.2", "daisyui": "4.12.24", "dayjs": "1.11.19", "echarts": "6.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbd90574..272bbf2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,9 +87,6 @@ importers: autoprefixer: specifier: 10.4.23 version: 10.4.23(postcss@8.5.6) - axios: - specifier: 1.13.2 - version: 1.13.2 daisyui: specifier: 4.12.24 version: 4.12.24(postcss@8.5.6) @@ -2404,9 +2401,6 @@ packages: resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} - axios@1.13.2: - resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} - axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -3130,15 +3124,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -8136,7 +8121,8 @@ snapshots: async@3.2.6: optional: true - asynckit@0.4.0: {} + asynckit@0.4.0: + optional: true autoprefixer@10.4.23(postcss@8.5.6): dependencies: @@ -8150,14 +8136,6 @@ snapshots: axe-core@4.11.0: optional: true - axios@1.13.2: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.5 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axobject-query@4.1.0: {} balanced-match@1.0.2: {} @@ -8284,6 +8262,7 @@ snapshots: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 + optional: true callsites@3.1.0: {} @@ -8391,6 +8370,7 @@ snapshots: combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 + optional: true commander@2.20.3: {} @@ -8556,7 +8536,8 @@ snapshots: define-lazy-prop@3.0.0: {} - delayed-stream@1.0.0: {} + delayed-stream@1.0.0: + optional: true delegates@1.0.0: optional: true @@ -8620,6 +8601,7 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + optional: true duplexer3@0.1.5: {} @@ -8659,15 +8641,18 @@ snapshots: error-stack-parser-es@1.0.5: {} - es-define-property@1.0.1: {} + es-define-property@1.0.1: + optional: true - es-errors@1.3.0: {} + es-errors@1.3.0: + optional: true es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 + optional: true es-set-tostringtag@2.1.0: dependencies: @@ -8675,6 +8660,7 @@ snapshots: get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 + optional: true esbuild@0.25.12: optionalDependencies: @@ -8975,8 +8961,6 @@ snapshots: flatted@3.3.3: {} - follow-redirects@1.15.11: {} - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -8989,6 +8973,7 @@ snapshots: es-set-tostringtag: 2.1.0 hasown: 2.0.2 mime-types: 2.1.35 + optional: true forwarded-parse@2.1.2: {} @@ -9058,11 +9043,13 @@ snapshots: has-symbols: 1.1.0 hasown: 2.0.2 math-intrinsics: 1.1.0 + optional: true get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + optional: true get-stream@4.1.0: dependencies: @@ -9131,7 +9118,8 @@ snapshots: minimist: 1.2.8 optional: true - gopd@1.2.0: {} + gopd@1.2.0: + optional: true got@11.8.6: dependencies: @@ -9170,11 +9158,13 @@ snapshots: has-flag@4.0.0: {} - has-symbols@1.1.0: {} + has-symbols@1.1.0: + optional: true has-tostringtag@1.0.2: dependencies: has-symbols: 1.1.0 + optional: true has-unicode@2.0.1: optional: true @@ -9743,7 +9733,8 @@ snapshots: dependencies: p-defer: 1.0.0 - math-intrinsics@1.1.0: {} + math-intrinsics@1.1.0: + optional: true mdn-data@2.12.2: {} @@ -9768,11 +9759,13 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} + mime-db@1.52.0: + optional: true mime-types@2.1.35: dependencies: mime-db: 1.52.0 + optional: true mimic-fn@2.1.0: {} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8c5c5df1..29fc13d3 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,7 +12,6 @@ import { ApiEndpoint, WakaApiRange, type ValueOf } from '$lib/constants' import { loading } from '$lib/stores/loading' import { selectedRange } from '$lib/stores/selectedRange' - import axios from 'axios' import { onMount } from 'svelte' import type { PageData } from './$types' import { invalidate } from '$app/navigation' @@ -32,9 +31,13 @@ const onWakaRange = async () => { loading.on() - const { data } = await axios.get(`${ApiEndpoint.SupabaseSummaries}?range=${$selectedRange}`) - summaries = data - loading.off() + try { + summaries = await fetch(`${ApiEndpoint.SupabaseSummaries}?range=${$selectedRange}`).then( + (response) => response.json(), + ) + } finally { + loading.off() + } } diff --git a/src/routes/api/shortcut/iterations/+server.ts b/src/routes/api/shortcut/iterations/+server.ts index c40dc1bb..6f8d5e00 100644 --- a/src/routes/api/shortcut/iterations/+server.ts +++ b/src/routes/api/shortcut/iterations/+server.ts @@ -1,16 +1,15 @@ import { SHORTCUT_API_TOKEN } from '$env/static/private' -import { BaseUrl, RestResource, type DataContainer } from '$lib/constants' +import { BaseUrl, RestResource } from '$lib/constants' import type { IterationSlim } from '$lib/generated/openapi/shortcut' import { json, type RequestHandler } from '@sveltejs/kit' -import axios from 'axios' export const GET: RequestHandler = async () => { const headers = { 'Shortcut-Token': SHORTCUT_API_TOKEN, } - const { data: iterations }: DataContainer = await axios.get( - `${BaseUrl.Shortcut}${RestResource.Iterations}`, - { headers }, - ) + const iterations: IterationSlim[] = await fetch(`${BaseUrl.Shortcut}${RestResource.Iterations}`, { + headers, + }).then((response) => response.json()) + return json(iterations) } diff --git a/src/routes/api/shortcut/iterations/[iterationId]/stories/+server.ts b/src/routes/api/shortcut/iterations/[iterationId]/stories/+server.ts index 069762a3..c4ffd4ce 100644 --- a/src/routes/api/shortcut/iterations/[iterationId]/stories/+server.ts +++ b/src/routes/api/shortcut/iterations/[iterationId]/stories/+server.ts @@ -1,19 +1,19 @@ import { SHORTCUT_API_TOKEN } from '$env/static/private' -import { BaseUrl, RestResource, type DataContainer } from '$lib/constants' +import { BaseUrl, RestResource } from '$lib/constants' import type { StorySlim } from '$lib/generated/openapi/shortcut' import { json, type RequestHandler } from '@sveltejs/kit' -import axios from 'axios' export const GET: RequestHandler = async ({ params }) => { const headers = { 'Shortcut-Token': SHORTCUT_API_TOKEN, } - const { data: stories }: DataContainer = await axios.get( + const stories: StorySlim[] = await fetch( `${BaseUrl.Shortcut}${RestResource.IterationStories(params.iterationId ?? '')}`, { headers, }, - ) + ).then((response) => response.json()) + return json(stories) } diff --git a/src/routes/api/wakatime/current/all-time-since-today/+server.ts b/src/routes/api/wakatime/current/all-time-since-today/+server.ts index ba6ac5b6..50cb3dc8 100644 --- a/src/routes/api/wakatime/current/all-time-since-today/+server.ts +++ b/src/routes/api/wakatime/current/all-time-since-today/+server.ts @@ -1,14 +1,13 @@ import { WAKA_API_KEY } from '$env/static/private' import { json } from '@sveltejs/kit' import type { RequestHandler } from './$types' -import { BaseUrl, RestResource, type DataContainer } from '$lib/constants' +import { BaseUrl, RestResource } from '$lib/constants' import type { AllTimeSinceTodayData } from '$src/types/wakatime' -import axios from 'axios' export const GET: RequestHandler = async () => { - const { data }: DataContainer = await axios.get( + const data: AllTimeSinceTodayData = await fetch( `${BaseUrl.WakaTime}${RestResource.AllTime}?api_key=${WAKA_API_KEY}`, - ) + ).then((response) => response.json()) return json(data) } diff --git a/src/routes/api/wakatime/current/durations/+server.ts b/src/routes/api/wakatime/current/durations/+server.ts index 14a1c54a..e641c532 100644 --- a/src/routes/api/wakatime/current/durations/+server.ts +++ b/src/routes/api/wakatime/current/durations/+server.ts @@ -2,10 +2,9 @@ import dayjs from 'dayjs' import type { RequestHandler } from './$types' import { WAKA_API_KEY } from '$env/static/private' import { json, error } from '@sveltejs/kit' -import { BaseUrl, RestResource, WakaSliceBy, type DataContainer } from '$lib/constants' +import { BaseUrl, RestResource, WakaSliceBy } from '$lib/constants' import type { DurationsResult } from '$src/types/wakatime' import { DateFormat } from '$lib/helpers/timeHelpers' -import axios from 'axios' export const GET: RequestHandler = async ({ url }) => { try { @@ -13,9 +12,12 @@ export const GET: RequestHandler = async ({ url }) => { const date = url.searchParams.get('date') ?? today const slice_by = url.searchParams.get('slice_by') ?? WakaSliceBy.None - const { data: durationsResult }: DataContainer = await axios.get( + const response = await fetch( `${BaseUrl.WakaTime}${RestResource.Durations}?api_key=${WAKA_API_KEY}&date=${date}&slice_by=${slice_by}`, ) + if (!response.ok) throw error(response.status, 'WakaTime durations request failed') + const durationsResult: DurationsResult = await response.json() + return json(durationsResult) } catch (err) { throw error(400, 'This is not the way.') diff --git a/src/routes/api/wakatime/current/projects/+server.ts b/src/routes/api/wakatime/current/projects/+server.ts index 64714757..680d24d6 100644 --- a/src/routes/api/wakatime/current/projects/+server.ts +++ b/src/routes/api/wakatime/current/projects/+server.ts @@ -2,17 +2,16 @@ import { WAKA_API_KEY } from '$env/static/private' import { json } from '@sveltejs/kit' import type { RequestHandler } from './$types' import type { WakaProjectResult } from '$src/types/wakatime' -import { BaseUrl, RestResource, type DataContainer } from '$lib/constants' -import axios from 'axios' +import { BaseUrl, RestResource } from '$lib/constants' const DEFAULT_PAGE = 1 export const GET: RequestHandler = async ({ url }) => { const q = url.searchParams.get('q') ?? '' const page = url.searchParams.get('page') ?? DEFAULT_PAGE - const { data: result }: DataContainer = await axios.get( + const result: WakaProjectResult = await fetch( `${BaseUrl.WakaTime}${RestResource.Projects}?api_key=${WAKA_API_KEY}&page=${page}&q=${q}`, - ) + ).then((response) => response.json()) return json(result) } diff --git a/src/routes/api/wakatime/current/summaries/+server.ts b/src/routes/api/wakatime/current/summaries/+server.ts index d0543d75..9838cd72 100644 --- a/src/routes/api/wakatime/current/summaries/+server.ts +++ b/src/routes/api/wakatime/current/summaries/+server.ts @@ -2,7 +2,6 @@ import { WAKA_API_KEY } from '$env/static/private' import { BaseUrl, RestResource, WakaApiRange } from '$lib/constants' import type { SummariesResult } from '$src/types/wakatime' import { json, type RequestHandler } from '@sveltejs/kit' -import axios from 'axios' export const GET: RequestHandler = async ({ url }) => { const start = url.searchParams.get('start') ?? '' @@ -15,9 +14,9 @@ export const GET: RequestHandler = async ({ url }) => { range = '' } - const { data: summaries }: { data: SummariesResult } = await axios( + const summaries: SummariesResult = await fetch( `${BaseUrl.WakaTime}${RestResource.Summaries}?api_key=${WAKA_API_KEY}&range=${range}&project=${project}&start=${start}&end=${end}`, - ) + ).then((response) => response.json()) return json(summaries) } diff --git a/src/routes/page.spec.ts b/src/routes/page.spec.ts index 44b7a89f..43564fe9 100644 --- a/src/routes/page.spec.ts +++ b/src/routes/page.spec.ts @@ -1,20 +1,24 @@ import { render, screen } from '@testing-library/svelte' -import { vi } from 'vitest' +import { afterEach, vi } from 'vitest' import Home from './+page.svelte' import { summaries, supabaseDuration } from '$src/mocks/testData' import type { PageData } from './$types' -// Axios uses XHR which MSW node server does not intercept. Mock it here so -// the selectedRange store's reactive axios call doesn't produce unhandled -// rejections. Remove this mock when axios is replaced with fetch (P1.04). -vi.mock('axios', () => ({ - default: { - get: vi.fn(() => Promise.resolve({ data: summaries })), - }, -})) +afterEach(() => { + vi.unstubAllGlobals() +}) describe('Home', () => { it('it...', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve(summaries), + }), + ), + ) + const data = { summaries, durations: supabaseDuration,