RFC-TIMETABLE v0.2 Session 4 — Frontend Timetable + Test Coverage Closure #18

Merged
bert.hausmans merged 26 commits from feat/timetable-session-4 into main 2026-05-10 00:32:37 +02:00
5 changed files with 43 additions and 39 deletions
Showing only changes of commit 5c53dcd2e4 - Show all commits

View File

@@ -236,10 +236,24 @@ you are using available components rather than building custom ones.
### Forms
- VeeValidate for form state + Zod for schema validation — always together
- Zod schemas must mirror the backend Form Request rules (field names, required/optional, types)
Canonical form pattern (used everywhere in the SPA):
- `ref({ field: ... })` for form state
- `VForm` ref + per-field rules drawn from `@core/utils/validators`
(`requiredValidator`, `emailValidator`, etc.)
- A separate `errors: Ref<Record<string, string>>` for server-validation
feedback (mapped from 422 responses)
- **Zod** for runtime validation of API payloads/responses (in
`apps/app/src/schemas/*.ts`) — Zod schemas mirror backend Form Requests
(field names, required/optional, types) and are the canonical contract
- No inline validation logic in components
VeeValidate is **NOT** the form library here. It was previously listed
but never actually adopted in any page; it was removed in commit
`<sha>` (Session 4 follow-up). Reference forms: `apps/app/src/components/sections/CreateShiftDialog.vue`,
`apps/app/src/components/timetable/AddPerformanceDialog.vue`,
`apps/app/src/pages/register/[public_token].vue`.
### Naming
- DB columns: `snake_case`

View File

@@ -30,7 +30,6 @@
"@tiptap/pm": "^2.27.1",
"@tiptap/starter-kit": "^2.27.1",
"@tiptap/vue-3": "^2.27.1",
"@vee-validate/zod": "^4.15.1",
"@vueuse/core": "10.11.1",
"@vueuse/math": "10.11.1",
"apexcharts": "3.54.1",
@@ -50,7 +49,6 @@
"shepherd.js": "13.0.3",
"ufo": "1.6.1",
"unplugin-vue-define-options": "1.5.5",
"vee-validate": "^4.15.1",
"vue": "3.5.22",
"vue-chartjs": "5.3.2",
"vue-flatpickr-component": "11.0.5",

View File

@@ -51,9 +51,6 @@ importers:
'@tiptap/vue-3':
specifier: ^2.27.1
version: 2.27.1(@tiptap/core@2.27.1(@tiptap/pm@2.27.1))(@tiptap/pm@2.27.1)(vue@3.5.22(typescript@5.9.3))
'@vee-validate/zod':
specifier: ^4.15.1
version: 4.15.1(vue@3.5.22(typescript@5.9.3))(zod@3.25.76)
'@vueuse/core':
specifier: 10.11.1
version: 10.11.1(vue@3.5.22(typescript@5.9.3))
@@ -111,9 +108,6 @@ importers:
unplugin-vue-define-options:
specifier: 1.5.5
version: 1.5.5(vue@3.5.22(typescript@5.9.3))
vee-validate:
specifier: ^4.15.1
version: 4.15.1(vue@3.5.22(typescript@5.9.3))
vue:
specifier: 3.5.22
version: 3.5.22(typescript@5.9.3)
@@ -1931,11 +1925,6 @@ packages:
cpu: [x64]
os: [win32]
'@vee-validate/zod@4.15.1':
resolution: {integrity: sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==}
peerDependencies:
zod: ^3.24.0
'@vitejs/plugin-vue-jsx@5.1.1':
resolution: {integrity: sha512-uQkfxzlF8SGHJJVH966lFTdjM/lGcwJGzwAHpVqAPDD/QcsqoUGa+q31ox1BrUfi+FLP2ChVp7uLXE3DkHyDdQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5001,11 +4990,6 @@ packages:
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
vee-validate@4.15.1:
resolution: {integrity: sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==}
peerDependencies:
vue: ^3.4.26
vfile-message@4.0.3:
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
@@ -7013,14 +6997,6 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
'@vee-validate/zod@4.15.1(vue@3.5.22(typescript@5.9.3))(zod@3.25.76)':
dependencies:
type-fest: 4.41.0
vee-validate: 4.15.1(vue@3.5.22(typescript@5.9.3))
zod: 3.25.76
transitivePeerDependencies:
- vue
'@vitejs/plugin-vue-jsx@5.1.1(vite@7.1.12(@types/node@24.9.2)(sass@1.76.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))':
dependencies:
'@babel/core': 7.28.5
@@ -10722,12 +10698,6 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
vee-validate@4.15.1(vue@3.5.22(typescript@5.9.3)):
dependencies:
'@vue/devtools-api': 7.7.7
type-fest: 4.41.0
vue: 3.5.22(typescript@5.9.3)
vfile-message@4.0.3:
dependencies:
'@types/unist': 3.0.3

View File

@@ -2016,8 +2016,9 @@ RFC-FORM-BUILDER-UI implementatie begint. Argumenten:
hoge-prioriteit endpoints (`/auth/me`, form-builder list-endpoints, identity-match endpoints).
Verdere composable-uitrol gebeurt organisch als features worden geraakt of toegevoegd.
**Out of scope:** form-input validatie (al via VeeValidate + Zod), WebSocket-validatie
(separaat, COMM-01), publieke API-contracten voor third parties (separaat, DIFF-03).
**Out of scope:** form-input validatie (via `@core/utils/validators` + Zod
payload schemas — see CLAUDE.md "Forms"), WebSocket-validatie (separaat,
COMM-01), publieke API-contracten voor third parties (separaat, DIFF-03).
**Open beslissingen:** codegen toolchain (Scramble-pipeline vs hand-rolled), validation
failure-mode (hard fail vs soft fail per env), per-route opt-out, boundary placement
@@ -2284,3 +2285,21 @@ opgezet.
**Refs:** `apps/app/index.html`, `deploy/nginx/csp-spa.conf`,
`tests/Feature/Security/CspConnectsToObservabilityTest.php`,
RFC-WS-7-OBSERVABILITY.md §3.3, ARCH-OBSERVABILITY.md §7 + §10.4.
### VEE-001 — VeeValidate removed from stack ✅ Resolved
**Status:** Closed in `feat/timetable-session-4` follow-up.
`vee-validate` and `@vee-validate/zod` shipped in `apps/app/package.json`
since Vuexy onboarding but were never imported anywhere in the SPA. A
strict regex sweep (`from 'vee-validate'`, `<Field>`, `<Form>`,
`<ErrorMessage>`, `defineRule(`, `useForm(<args>)`) returned **zero
hits** across `apps/app/src/`. Earlier fuzzy matches were false
positives from `useForm` colliding with Crewli's own `useFormDraft` /
`useFormSteps` / `useFormSchemas` / `useFormFailures` composables.
Removed both packages from `apps/app/package.json`, regenerated
`pnpm-lock.yaml`. Canonical form pattern formalized in `CLAUDE.md`
"Forms" + `dev-docs/VUEXY_COMPONENTS.md` "Form validation" row.
**Refs:** Session 4 follow-up Step 1; `apps/app/src/components/timetable/AddPerformanceDialog.vue` and `apps/app/src/components/sections/CreateShiftDialog.vue` as canonical references.

View File

@@ -430,9 +430,12 @@ Two approaches from Vuexy:
- StatusCard shows different UI per approval status (pending/approved/rejected)
- Conditional tab visibility based on approval status
#### Registration (Multi-step Form with VeeValidate + Zod)
#### Registration (Multi-step Public Form)
**Reference:** `apps/app/src/pages/register/[public_token].vue`
- VForm with VeeValidate field binding + Zod schemas from `@/schemas/`
- VForm + `useFormDraft` composable for state, autosave, idempotency-key drafts
- Per-field validators from `@core/utils/validators` (`emailValidator`, `requiredValidator`)
- Zod schemas in `apps/app/src/schemas/registrationSchema.ts` validate the
outgoing payload at submit time
- Conditional form fields based on event configuration
- Real-time email duplicate checking
- Password creation for new users
@@ -480,7 +483,7 @@ Preferred Vuetify components for common needs. Use these, not custom solutions.
| Menu / dropdown actions | VMenu + VList + VListItem | Custom popover |
| Drag-and-drop lists | `vuedraggable` (external library) | Custom drag logic |
| Rich text editing | TiptapEditor (@core) | Custom editor |
| Form validation | VForm ref + `@core/utils/validators` + API error mapping | (VeeValidate + Zod only in portal registration) |
| Form validation | VForm ref + `@core/utils/validators` + Zod schema for payload + API 422 error map | VeeValidate (removed; was never actually adopted) |
| Multi-step wizard | AppStepper (@core) | Custom step logic |
| Drawer header | AppDrawerHeaderSection (@core) | Custom header |
| Code display | AppCardCode (@core) | Custom code block |