3.6 KiB
3.6 KiB
web/AGENTS.md
SvelteKit SPA (SSR off), Svelte 5 runes, Tailwind, @immich/ui, @tanstack/svelte-query. Talks to the server through the workspace @immich/sdk.
Rules
- Import with the
#lib/*alias and an explicit.tsextension:import { authManager } from '#lib/managers/auth-manager.svelte.ts'. Never$lib. Other aliases:#i18n/*,#test/*(thetest/directory). - Build URLs with the typed
Routeobject insrc/lib/route.ts.continue/previousRoutequery params must go throughRoute.continue(...)(lint-enforced). - Global state lives in
*.svelte.tssingleton managers insrc/lib/managers/.eventManageris the typed app-wide bus; subscribe witheventManager.on({...})orcomponents/OnEvents.svelte.src/lib/stores/is legacy. src/lib/services/*.service.tsonly return@immich/uiActionItems and open modals; data access belongs insrc/lib/queries/.- Unused variables must be named exactly
_.svelte-checkruns with--fail-on-warnings. @immich/sdkmust be built before the web builds or tests:mise //:sdk:install && mise //:sdk:build(mise //web:startdoes this).
Skills
- Add a query or mutation: one
src/lib/queries/<controller>.query.tsper API controller. Export a query-key constant,getXQuery(...)factories returningqueryOptions({ queryKey, queryFn: ({ signal }) => sdkFn(..., { signal }), select }), andxMutationoptions built withasMutation(sdkFn, { meta: { success: { toast }, error: { toast } } }). Cache updates andeventManageremits happen in the mutation'sonSuccess. The globalMutationCacheinsrc/lib/queries/client.tsshows the toasts and callshandleError;meta.error.toast: falsesuppresses it. Outside components run mutations withexecuteMutation(optionsFn, variables, mutateOptions). - Add a page: put it under
src/routes/(user)/for the authenticated app orsrc/routes/admin/. In+page.ts:await authenticate(url), preload withawait queryClient.query(getXQuery()),const $t = await getFormatter(), and return{ meta: { title: $t('...') } }. In the component read the data withcreatePreloadedQuery(() => getXQuery()). Route-specific components sit next to the route. - Add a modal: create
src/lib/modals/XModal.sveltewithProps = { onClose: (result?) => void }built on@immich/uiModal/FormModal. Open it withconst result = await modalManager.show(XModal, props); the promise resolves with whatever is passed toonClose. - Use a translation:
import { t } from 'svelte-i18n'and$t('key')in components;const $t = await getFormatter()from#lib/utils/i18n.tselsewhere. Keys are typed againsti18n/en.json, so add the key there first (root skill). Rich text goes throughsrc/lib/elements/FormatMessage.svelte. - Unit tests: vitest config is the
testblock invite.config.ts(happy-dom).test/unit/setup.tsinitializes svelte-i18n so$treturns the key. Build data withfactoryfrom#test/unit/factories.ts; shared mocks (SDK, observers) are insrc/lib/__mocks__/. Run one file withmise //web:test --run src/lib/utils/date-time.spec.ts. - Medium tests: Playwright specs in
test/medium/specs/run against the production build with every/apicall mocked (mise //web:test-medium). Fixtures:app(locators and gestures,test/medium/app.ts),data(seed state viaMockDatamethods),api(expect(api.updateAsset).toHaveBeenCalledWith({ params, body }); handlers live intest/medium/mock/mock.ts) andlogger(browser errors). The UI uses thedevlocale, so locate elements by translation key. Unhandled requests, unasserted mutations and unasserted browser errors fail the test.