# Frontend tooling Type-safety and quality tooling for the Vue 3 + TypeScript SPAs. ## ts-reset Installed in both `apps/portal/` and `apps/app/` as a dev-dependency. Imported via `src/reset.d.ts`: ```typescript import '@total-typescript/ts-reset' ``` Both SPAs' `tsconfig.json` include `./src/**/*`, so the `.d.ts` is picked up automatically — no explicit `include` edit needed. ### What it changes ts-reset patches TypeScript's loosest default types: - `Array.filter(Boolean)` returns non-nullable `Array` instead of `Array` - `JSON.parse` returns `unknown` instead of `any` - `fetch().then(r => r.json())` returns `unknown` instead of `any` - `Array.includes()` accepts narrower types - `Map.get()` returns `T | undefined` more strictly - See for the full list ### Why this matters The default TypeScript types for these built-ins lose information that real code relies on. ts-reset fixes the types to match runtime behavior, surfacing bugs that were previously hidden. ### Adoption state **apps/app** — 0 pre-install tsc errors in own code; install surfaced 2 errors in `src/stores/useImpersonationStore.ts` (both from `JSON.parse` on sessionStorage content). Both fixed inline via `as ImpersonationState` assertions that make the existing trust-in- sessionStorage explicit. A backlog entry (TECH-TS-IMPERSONATION) tracks proper runtime validation of the stored shape. **apps/portal** — 22 pre-existing tsc errors in own code (mostly tiptap editor components — unrelated to ts-reset). ts-reset added 0 new errors in portal's own code. 4 additional errors surfaced in tiptap's third-party `.ts` sources under `node_modules` (tiptap ships uncompiled TypeScript and some of its internal `JSON.parse` call sites fall under ts-reset's tightening). Not our code; left as-is. Neither SPA achieves `tsc --noEmit` clean today — that's a pre- existing state unrelated to this work package. Build + vitest are the actual CI-relevant gates and both remain green. ### Bypassing `@ts-expect-error` with comment justification at the call site — never blanket-disable ts-reset. If a specific reset is genuinely wrong for the project, document it; the upstream issue is the backstop. ## ESLint ### Scoped lint invocation `pnpm lint -- ` is **vacuous**: pnpm's `--` passthrough forwards trailing args as workspace/script filters, not as ESLint targets, so errors in the intended files are never surfaced. In Plan 3 this made per-task lint report "clean" while real errors existed — the root cause of the GUI-redesign DoD #13 miss (fixed in `0b19e785`). Correct scoped invocation — call the project's ESLint binary directly with the paths: ```bash pnpm exec eslint ``` For per-task gates, pair a file-scoped run with a directory-scoped run so project-level rule misconfigs surface too: ```bash pnpm exec eslint apps/app/src/components-v2/shared/Foo.vue pnpm exec eslint apps/app/src/components-v2/shared ``` ## Vitest Runs against `apps/portal/` and `apps/app/`. `apps/app/` has a Vitest test suite (402 tests as of May 2026); run with `pnpm test` from `apps/app/`. ## Storybook Storybook 10.x is installed in apps/app/ as a component development and documentation tool. **Scripts (run from apps/app/):** - `pnpm storybook` — dev server on http://localhost:6006 - `pnpm build-storybook` — static build to storybook-static/ **Config files:** - apps/app/.storybook/main.ts — framework, stories glob, addons, viteFinal aliases - apps/app/.storybook/preview.ts — PrimeVue (via installPrimeVue plugin), Tailwind **Stories location:** co-located with components (ComponentName.stories.ts next to ComponentName.vue). Exception: smoke-test stories live in src/stories/. **Addons (explicit, no addon-essentials):** - @storybook/addon-docs — autodocs, prop tables - @storybook/addon-a11y — axe-core accessibility checks per story **Scope boundary:** Storybook = isolated render, autodocs, a11y. Interaction testing (click, form submit, emit) stays in Playwright CT. Do NOT install @storybook/addon-interactions. **Not in Storybook:** page-level views, components with direct useAuthStore / useOrganisationStore calls (these need to be decoupled to props first).