4 component tests via mountWithVuexy:
- happy path: valid form values → POST /performances called with the
correct body shape (engagement_id, event_id mapped from dayId,
stage_id, start_at, end_at)
- end_at < start_at → submit blocked, schema-level error visible on
the end_at field
- empty engagement_id → submit blocked, error visible on the engagement_id
field
- cancel button → emits update:modelValue=false
Test seam: AddPerformanceDialog.vue gains `defineExpose({ form, errors,
submit })` so jsdom tests can drive validation deterministically without
piping through Flatpickr / VAutocomplete plumbing. Three lines, exposes
internal refs only — no behavioural change.
VDialog stubbed in the test (it teleports to body, which puts content
outside the wrapper); App* wrappers stubbed (we test the schema +
submit pipeline, not Flatpickr ergonomics).
Test count: 360 → 364.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- PerformancePopover.vue — teleported floating panel; closes on Esc; shows
status chip, advancing %, computed Buma/VAT/total cost; deal-summary +
delete + open-detail buttons. Position math (340px wide, 12px margin,
flip side if no room) ports prototype's pickPos verbatim.
- AddPerformanceDialog.vue — Vuetify VDialog + raw ref form pattern (matches
CreateShiftDialog and the rest of the codebase). Uses createPerformancePayloadSchema
for client-side validation; falls back to surface-level errors map per field.
- StageEditor.vue — single-stage CRUD modal with name + capacity + 10-swatch
palette picker. Window.confirm cascade-park warning on delete.
- LineupMatrix.vue — stages × sub-events checkbox matrix; only dirty stages
fire replaceStageDays (atomic per stage).
- Wachtrij.vue — sidebar with search + 9 toggleable status chips with counts;
reads/writes useTimetableStore.statusFilter and searchQuery.
- WachtrijCard.vue — initials avatar + status dot + dot label + cancelled
strike-through. role=button, tabindex=0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>