chore: install ts-reset in both portal and app SPAs
Installs @total-typescript/ts-reset 0.6.1 as a dev-dependency in
apps/portal/ and apps/app/. Patches TypeScript's loosest default
types: Array.filter(Boolean) returns non-nullable, JSON.parse
returns unknown, fetch().json() returns unknown, Map.get() strict,
etc.
Configuration: src/reset.d.ts in each SPA imports the reset. Both
tsconfig.json files already include ./src/**/* so the .d.ts is
picked up automatically — no tsconfig edits needed.
Issues surfaced during install:
- 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 returning
unknown instead of any). Fixed inline at lines 19 + 123 via
`as ImpersonationState` casts that make the existing
trust-in-sessionStorage explicit. Backlog entry
TECH-TS-IMPERSONATION tracks proper runtime shape validation.
- apps/portal — 22 pre-existing tsc errors in own code (mostly
tiptap editor components — tracked as TECH-TS-PORTAL-TSC,
unrelated to ts-reset). Zero new errors in portal's own code.
4 additional errors surfaced in tiptap's uncompiled node_modules
.ts sources (third-party); left as-is.
Neither SPA achieves `tsc --noEmit` clean today — pre-existing
state unrelated to this work package. Build + vitest are the
actual working gates and both remain green:
- apps/portal: vitest 113/113 passing; production build succeeds
- apps/app: (no vitest setup — tracked as TECH-APP-VITEST);
production build succeeds
Documentation: /dev-docs/FRONTEND-TOOLING.md added; CLAUDE.md
quality-gates updated.
Backlog: TECH-TS-IMPERSONATION (runtime validation of stored
impersonation state), TECH-TS-PORTAL-TSC (pre-existing portal tsc
errors), TECH-APP-VITEST (Vitest coverage for apps/app).
No production behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,9 @@ Design document: `/dev-docs/design-document.md`
|
|||||||
- `composer rector` — Rector dry-run for modernisation suggestions.
|
- `composer rector` — Rector dry-run for modernisation suggestions.
|
||||||
See `/dev-docs/RECTOR.md`. Apply only in scoped sprints, never
|
See `/dev-docs/RECTOR.md`. Apply only in scoped sprints, never
|
||||||
automatically.
|
automatically.
|
||||||
|
- ts-reset patches TypeScript's loosest default types in both SPAs.
|
||||||
|
See `/dev-docs/FRONTEND-TOOLING.md`. New TypeScript code adheres
|
||||||
|
to ts-reset's stricter types automatically.
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
"@tiptap/extension-subscript": "^2.27.1",
|
"@tiptap/extension-subscript": "^2.27.1",
|
||||||
"@tiptap/extension-superscript": "^2.27.1",
|
"@tiptap/extension-superscript": "^2.27.1",
|
||||||
"@tiptap/extension-underline": "^2.27.1",
|
"@tiptap/extension-underline": "^2.27.1",
|
||||||
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@types/mapbox-gl": "3.4.1",
|
"@types/mapbox-gl": "3.4.1",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.9.2",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
|
|||||||
8
apps/app/pnpm-lock.yaml
generated
8
apps/app/pnpm-lock.yaml
generated
@@ -214,6 +214,9 @@ importers:
|
|||||||
'@tiptap/extension-underline':
|
'@tiptap/extension-underline':
|
||||||
specifier: ^2.27.1
|
specifier: ^2.27.1
|
||||||
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))
|
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))
|
||||||
|
'@total-typescript/ts-reset':
|
||||||
|
specifier: ^0.6.1
|
||||||
|
version: 0.6.1
|
||||||
'@types/mapbox-gl':
|
'@types/mapbox-gl':
|
||||||
specifier: 3.4.1
|
specifier: 3.4.1
|
||||||
version: 3.4.1
|
version: 3.4.1
|
||||||
@@ -1383,6 +1386,9 @@ packages:
|
|||||||
'@tiptap/pm': ^2.7.0
|
'@tiptap/pm': ^2.7.0
|
||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
|
|
||||||
|
'@total-typescript/ts-reset@0.6.1':
|
||||||
|
resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==}
|
||||||
|
|
||||||
'@trysound/sax@0.2.0':
|
'@trysound/sax@0.2.0':
|
||||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -6044,6 +6050,8 @@ snapshots:
|
|||||||
'@tiptap/pm': 2.27.1
|
'@tiptap/pm': 2.27.1
|
||||||
vue: 3.5.22(typescript@5.9.3)
|
vue: 3.5.22(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@total-typescript/ts-reset@0.6.1': {}
|
||||||
|
|
||||||
'@trysound/sax@0.2.0': {}
|
'@trysound/sax@0.2.0': {}
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
|
|||||||
1
apps/app/src/reset.d.ts
vendored
Normal file
1
apps/app/src/reset.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import '@total-typescript/ts-reset'
|
||||||
@@ -16,7 +16,7 @@ interface ImpersonationState {
|
|||||||
|
|
||||||
export const useImpersonationStore = defineStore('impersonation', () => {
|
export const useImpersonationStore = defineStore('impersonation', () => {
|
||||||
const stored = sessionStorage.getItem(SESSION_STORAGE_KEY)
|
const stored = sessionStorage.getItem(SESSION_STORAGE_KEY)
|
||||||
const state = ref<ImpersonationState | null>(stored ? JSON.parse(stored) : null)
|
const state = ref<ImpersonationState | null>(stored ? (JSON.parse(stored) as ImpersonationState) : null)
|
||||||
let broadcastChannel: BroadcastChannel | null = null
|
let broadcastChannel: BroadcastChannel | null = null
|
||||||
|
|
||||||
const isImpersonating = computed(() => !!state.value)
|
const isImpersonating = computed(() => !!state.value)
|
||||||
@@ -120,7 +120,7 @@ export const useImpersonationStore = defineStore('impersonation', () => {
|
|||||||
const stored = sessionStorage.getItem(SESSION_STORAGE_KEY)
|
const stored = sessionStorage.getItem(SESSION_STORAGE_KEY)
|
||||||
if (stored) {
|
if (stored) {
|
||||||
try {
|
try {
|
||||||
state.value = JSON.parse(stored)
|
state.value = JSON.parse(stored) as ImpersonationState
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
sessionStorage.removeItem(SESSION_STORAGE_KEY)
|
sessionStorage.removeItem(SESSION_STORAGE_KEY)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"@tiptap/extension-subscript": "^2.27.1",
|
"@tiptap/extension-subscript": "^2.27.1",
|
||||||
"@tiptap/extension-superscript": "^2.27.1",
|
"@tiptap/extension-superscript": "^2.27.1",
|
||||||
"@tiptap/extension-underline": "^2.27.1",
|
"@tiptap/extension-underline": "^2.27.1",
|
||||||
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@types/mapbox-gl": "3.4.1",
|
"@types/mapbox-gl": "3.4.1",
|
||||||
"@types/node": "24.9.2",
|
"@types/node": "24.9.2",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
|
|||||||
8
apps/portal/pnpm-lock.yaml
generated
8
apps/portal/pnpm-lock.yaml
generated
@@ -220,6 +220,9 @@ importers:
|
|||||||
'@tiptap/extension-underline':
|
'@tiptap/extension-underline':
|
||||||
specifier: ^2.27.1
|
specifier: ^2.27.1
|
||||||
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))
|
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))
|
||||||
|
'@total-typescript/ts-reset':
|
||||||
|
specifier: ^0.6.1
|
||||||
|
version: 0.6.1
|
||||||
'@types/mapbox-gl':
|
'@types/mapbox-gl':
|
||||||
specifier: 3.4.1
|
specifier: 3.4.1
|
||||||
version: 3.4.1
|
version: 3.4.1
|
||||||
@@ -1473,6 +1476,9 @@ packages:
|
|||||||
'@tiptap/pm': ^2.7.0
|
'@tiptap/pm': ^2.7.0
|
||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
|
|
||||||
|
'@total-typescript/ts-reset@0.6.1':
|
||||||
|
resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==}
|
||||||
|
|
||||||
'@trysound/sax@0.2.0':
|
'@trysound/sax@0.2.0':
|
||||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -6512,6 +6518,8 @@ snapshots:
|
|||||||
'@tiptap/pm': 2.27.1
|
'@tiptap/pm': 2.27.1
|
||||||
vue: 3.5.22(typescript@5.9.3)
|
vue: 3.5.22(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@total-typescript/ts-reset@0.6.1': {}
|
||||||
|
|
||||||
'@trysound/sax@0.2.0': {}
|
'@trysound/sax@0.2.0': {}
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
|
|||||||
1
apps/portal/src/reset.d.ts
vendored
Normal file
1
apps/portal/src/reset.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import '@total-typescript/ts-reset'
|
||||||
@@ -967,5 +967,49 @@ blijft handmatig.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Frontend type-safety
|
||||||
|
|
||||||
|
### TECH-TS-IMPERSONATION — runtime validation of impersonation state
|
||||||
|
|
||||||
|
**Aanleiding:** ts-reset install (april 2026) surfaced dat
|
||||||
|
`apps/app/src/stores/useImpersonationStore.ts` blindly vertrouwt
|
||||||
|
op `JSON.parse(sessionStorage.getItem(...))`. De return is nu
|
||||||
|
`unknown` in plaats van `any`; we fixten dit door twee `as
|
||||||
|
ImpersonationState` casts toe te voegen. Die maken de bestaande
|
||||||
|
trust expliciet maar valideren de shape niet.
|
||||||
|
**Wat:** vervang beide casts (lines 19 + 123) door een
|
||||||
|
narrowing-helper `parseStoredState(raw: string | null):
|
||||||
|
ImpersonationState | null` die het JSON parseert, de verwachte
|
||||||
|
keys controleert (incl. geneste `impersonatedUser: AdminUser`
|
||||||
|
fields), en `null` teruggeeft als de shape niet klopt. Zelfde
|
||||||
|
helper gebruiken bij beide call sites. sessionStorage is in theorie
|
||||||
|
tamperbaar door lokale users, dus dit is ook een kleine security
|
||||||
|
verbetering.
|
||||||
|
**Prioriteit:** Laag — defensive hardening, geen user-impact.
|
||||||
|
|
||||||
|
### TECH-TS-PORTAL-TSC — portal tsc --noEmit cleanup
|
||||||
|
|
||||||
|
**Aanleiding:** Portal heeft 22 pre-existing tsc errors (+4 in
|
||||||
|
tiptap node_modules) die niets met ts-reset te maken hebben — meest
|
||||||
|
TiptapEditor/ProductDescriptionEditor commands en één losse
|
||||||
|
`@iconify/types` missing, themeConfig type-lowercase issue. CI
|
||||||
|
checkt tsc niet (build + vitest wel), dus het wordt momenteel
|
||||||
|
genegeerd. Fix zodra tiptap upgrade beschikbaar komt of per-
|
||||||
|
component via type-casts.
|
||||||
|
**Prioriteit:** Laag — niet-blocking, bestaand probleem.
|
||||||
|
|
||||||
|
### TECH-APP-VITEST — bring Vitest coverage to apps/app
|
||||||
|
|
||||||
|
**Aanleiding:** ts-reset install (april 2026) onderstreepte dat
|
||||||
|
`apps/app/` geen test-script of `tests/` directory heeft. Portal
|
||||||
|
heeft wel Vitest-coverage (113 tests); organizer-SPA heeft alleen
|
||||||
|
build + typecheck als geautomatiseerde gates.
|
||||||
|
**Wat:** Vitest setup in `apps/app/`, met eerste coverage op de
|
||||||
|
kern-stores (waaronder `useImpersonationStore.ts` dat nu zonder
|
||||||
|
runtime-test leeft).
|
||||||
|
**Prioriteit:** Middel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
_Laatste update: April 2026_
|
_Laatste update: April 2026_
|
||||||
_Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-, UX-_
|
_Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-, UX-_
|
||||||
|
|||||||
67
dev-docs/FRONTEND-TOOLING.md
Normal file
67
dev-docs/FRONTEND-TOOLING.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# 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<T>` instead
|
||||||
|
of `Array<T | null | undefined | false | 0 | "">`
|
||||||
|
- `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 <https://github.com/total-typescript/ts-reset> 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.
|
||||||
|
|
||||||
|
## Vitest
|
||||||
|
|
||||||
|
Runs against `apps/portal/`. `apps/app/` has no test suite yet —
|
||||||
|
build + typecheck are its only automated gates. Bringing Vitest
|
||||||
|
coverage to `apps/app` is a separate backlog item.
|
||||||
Reference in New Issue
Block a user