53 KiB
RFC-WS-FRONTEND-PRIMEVUE — Vuetify+Vuexy → PrimeVue Migration
| Field | Value |
|---|---|
| Status | Approved (decision date: 2026-05-10) |
| Version | 1.0 |
| Author | Architecture (Claude Chat) |
| Audit input | dev-docs/MIGRATION-AUDIT-PRIMEVUE.md (commit 5d9399b, branch audit/primevue-migration) |
| Audit base SHA | main @ 62afbde (audit cutoff) |
| Decision basis | Strategic analysis 2026-05-08 + F1 codebase audit 2026-05-10 |
| Sprint identifier | WS-FRONTEND-PRIMEVUE |
| Estimated effort | 10–12 working days, serial |
| Concurrent work | Artist Management module continues on Vuetify+Vuexy stack; migrated as part of F4b |
| Related docs | CLAUDE.md, dev-docs/VUEXY_COMPONENTS.md (to be replaced), AUTH_ARCHITECTURE.md, BACKLOG.md |
1. Decision Summary
Migrate the apps/app/ SPA from Vuetify 3.10.8 + Vuexy template 9.5/10.11 to PrimeVue 4.5 (Aura preset) + Tailwind CSS v4, before public SaaS launch. Form layer adopts @primevue/forms + Zod resolver as the new canonical pattern (no VeeValidate — never adopted in this codebase). Flatpickr-based date input, Iconify-Tabler icon system, Pinia stores, TanStack Query composables, and route file structure remain unchanged. Total scope is six work packages (F2 through F6, F1 already complete) executed serially, with each package gated by a Claude Chat review before the next begins. Out of scope: design system overhaul, vue-i18n adoption, Form Builder organizer UI (S3b backlog), notification framework.
2. Background & Rationale
The 2026-05-08 strategic analysis concluded that Vuetify+Vuexy carries three structural risks for Crewli's enterprise SaaS profile: documented v-data-table performance degradation at festival-scale row counts, the Vuetify project's publicly disclosed funding crisis (OpenCollective exhausted, contributor compensation suspended), and Vuexy template lock-in to a Material-Design aesthetic that reads as dated against 2026 SaaS norms. PrimeVue 4 with the Aura preset addresses all three: DataTable handles 1000+ rows natively with frozen columns and lazy virtual scroll, PrimeTek's commercial backing is materially stronger, and the design-token architecture supports future evolution without component rewrites.
The F1 audit confirmed that the migration cost is bounded and predictable: 46 pages, 75 distinct Vuetify components, 14 datatables, 38 form-bearing components, zero UI-framework coupling in stores or query composables, and zero VeeValidate to preserve. The single largest CSS chunk is 3.14 MB (the entire Vuexy template); reducing this is the most concrete bundle win available. The pre-launch window is the cheapest possible moment to migrate — switching Vuetify→PrimeVue post-launch was estimated at 3–5 weeks against ~10 days now.
The Vuexy template Extended-license has not been purchased and will not be. There is no sunk cost in the template ecosystem.
3. Audit-Derived Facts (F1 Reference)
The following facts are anchored to the F1 audit and drive every decision in this RFC. Re-validate if migrating against a later main SHA.
| Fact | Value | Implication |
|---|---|---|
| Total pages | 46 (30 organizer / 8 platform / 7 portal / 2 register / -1 nesting) | F4 sub-package sizing |
| Vuetify components in use | 75 distinct, 6 use ≥ 100× | Mapping table required (§8) |
| Top 5 components by usage | VBtn (420), VCol (331), VIcon (267), VCard (250), VCardText (208) | Highest mapping leverage |
| DataTables | 14 (5 server, 9 client) | F4 per-tree distribution |
Forms (<VForm>) |
64 occurrences across 38 components | Largest single migration cost |
| Form library | None (no VeeValidate, no FormKit) | New form layer = greenfield |
| Zod schemas | 2 (registration, timetable) | Expand opportunity, not constraint |
| Pinia stores | 8, zero UI-framework imports | Migration risk = 0 in this layer |
| TanStack Query composables | 29, zero UI-framework imports | Migration risk = 0 in this layer |
| Vuetify imports in tests | 0 | Single setup-file flip in F5 |
| Mount-rendering tests | 22 | Validation surface for F5 |
| Main JS chunk (gzip) | 213 KB | Target post-migration: ≤ 180 KB |
| Single largest CSS chunk | 3.14 MB | Target post-migration: ≤ 400 KB |
| Iconify-Tabler references | 593 | Retain (PrimeVue is icon-agnostic) |
| AppDateTimePicker | 548 lines, Flatpickr-based, framework-agnostic | Retain in fase 1 |
| Layout selection | Filename match via vite-plugin-vue-meta-layouts |
Retain mechanism |
| Portal route prefix | /portal/* (not /p/* as docs claim) |
CLAUDE.md correction in F2 |
4. Scope
4.1 In scope
- Replacement of every Vuetify component (
V*) used inapps/app/src/with PrimeVue equivalents. - Replacement of all Vuexy
@coreand@layoutsfiles with project-owned equivalents or PrimeVue-native solutions. - Introduction of
@primevue/forms+ Zod as the canonical form layer, including a<FormField>wrapper component. - Introduction of Tailwind CSS v4 for layout utilities (replacing VRow/VCol/VSpacer).
- Aura theme preset configured with Crewli teal (
#0D9394) and dark mode support. - Locale wired to
nl-NLviaprimelocale. - Layout shell rewrites for
default.vue,blank.vue,OrganizerLayout.vue,PortalLayout.vue,PublicLayout.vue. - Toast service (replacing VSnackbar) and ConfirmDialog service.
- Documentation rewrite:
dev-docs/PRIMEVUE_COMPONENTS.md(replaces VUEXY_COMPONENTS.md),CLAUDE.mdfrontend section,.cursorrulesfrontend rules. - Test runtime registration flip from Vuetify to PrimeVue (single setupFile).
- Removal of Vuetify, vite-plugin-vuetify, Vuexy SCSS tree, and Vuexy
@core/composable/useSkins.tsfrom the bundle. - Bundle size verification against targets in §11.
4.2 Out of scope
- Adoption of vue-i18n with locale message files. UI strings remain hardcoded Dutch in templates. Multi-language is a separate future sprint.
- Migration of
AppDateTimePickerfrom Flatpickr to PrimeVue's native DatePicker. Tracked asFRONTEND-DATEPICKER-PRIMEVUE-NATIVEin BACKLOG.md (post-launch). - Form Builder organizer UI (drag-drop schema builder). Tracked as S3b in BACKLOG.md.
- Notification framework (Laravel Echo + Soketi) — separate planned sprint.
- Design system token expansion beyond what the Aura preset offers out-of-the-box.
- White-label per-tenant theming beyond primary-color and logo overrides.
- Any backend changes. This sprint is frontend-only.
- Visual redesign of any page. Migration preserves existing UX and information architecture; only the rendering layer changes.
4.3 Concurrent work tolerance
The Artist Management module is currently being developed on the existing Vuetify+Vuexy stack. This RFC does not block that work. Artist Management's UI will be migrated to PrimeVue as part of F4b (organizer tree). The maintainer of Artist Management should commit working Vuetify code as if no migration were happening; the F4b prompt will translate it.
5. Architectural Decisions
Each decision below is binding for this sprint. Deviations require an RFC amendment.
AD-1: Form layer = @primevue/forms + Zod resolver
The current <VForm> ref + per-field :rules array pattern is replaced by <Form> from @primevue/forms with a zodResolver(schema) configuration. API 422 errors are merged into the form's error state via a documented hook. A project-owned <FormField> wrapper (spec in Appendix A) abstracts the row template (label + input slot + validation message) so call-sites stay terse.
Rationale: PrimeVue v4 ships @primevue/forms as the official form integration with built-in resolvers for Zod, Yup, Joi, Valibot, Superstruct. Crewli already uses Zod for the two existing schemas. There is no VeeValidate to preserve. This decision unifies form validation under one library that owns both the rendering and the validation contract.
Implication: The 27 deep vuetify/components/VForm imports become a single <Form> import per page, often eliminated entirely by the <FormField> wrapper. The 11 validators in @core/utils/validators.ts (requiredValidator, emailValidator, etc.) are replaced by Zod schema methods (.min(1), .email(), .regex(), custom .refine()). New Zod schemas are added under apps/app/src/schemas/ per feature module.
AD-2: Theme = Aura preset, Crewli teal primary, dark mode preserved
The Aura preset is configured via @primeuix/themes definePreset(Aura, { semantic: { primary: { ... } } }). The Crewli teal (#0D9394 light, #0B7F80 dark) maps to primary.500 and primary.600 semantic tokens. Surface, success, info, warning, error tokens use Aura defaults unless explicit Crewli equivalents exist. Dark mode is wired via PrimeVue's darkModeSelector: '.dark' config, integrated with the existing @core/composable/useSkins.ts until F6 cleanup.
Drift correction (F3 B9): RFC v1.0 referenced
@primevue/themes@^4.5. That package was deprecated by its maintainers (PrimeFaces) during the F3 sprint with explicit guidance to migrate to@primeuix/themes. PrimeVue 4's official install documentation at primevue.org/vite/ now specifies@primeuix/themesas the theme package. Same maintainers, same API surface (Aura preset,definePreset, semantic tokens). F3 commit B1 installed@primeuix/themes@^2directly; this paragraph and Appendix B are aligned with that ecosystem state.
Per-tenant overrides: primary color and logo only (per project-level decision). Implementation: a Pinia computed reads useOrganisationStore().branding.primaryColor and patches the active preset via usePrimeVue().config.theme.preset at organisation-switch time. Full white-labeling is explicitly out of scope.
Rationale: Aura is PrimeTek's flagship modern preset, design-token-based, and matches the "tighter spacing, subtle borders, emerald-adjacent accent" aesthetic identified as more contemporary than Vuetify Material in the strategic analysis. The teal already brand-identifies Crewli; Aura's token system makes per-tenant primary swaps trivial.
AD-3: Layout shell = filename-match preserved, contents rewritten
vite-plugin-vue-meta-layouts continues to do filename-based layout selection. The five layout files (default.vue, blank.vue, OrganizerLayout.vue, PortalLayout.vue, PublicLayout.vue) are rewritten to use PrimeVue components: Drawer for the side navigation, Menubar for the top bar, Avatar+Menu for the user-profile dropdown. The meta.layout: 'blank' override remains the only explicit layout selector. The 12 @layouts/components/* files (VerticalNav, VerticalNavLayout, etc.) are deleted in F6 — they are replaced inline within the new shells.
Rationale: Routing-mechanism preservation minimises blast radius. The shell rewrite is bounded (~5 files, ~600 lines new code) and contained to F3 Foundation. No route definition changes.
AD-4: Date layer = Flatpickr retained in fase 1
AppDateTimePicker.vue (548 lines, Flatpickr-based) is retained as-is. Its dependency on <VTextField> is replaced with <InputText> (one-line change at the trigger element). Flatpickr itself is framework-agnostic and survives the migration.
Rationale: AppDateTimePicker encodes the Crewli-specific Dutch UX for date+time selection (locale, formatting, range constraints). Rewriting it during the framework migration is unnecessary risk. PrimeVue's native DatePicker is excellent and we will likely migrate to it post-launch (tracked as FRONTEND-DATEPICKER-PRIMEVUE-NATIVE), but that is polish work, not launch-blocker.
AD-5: Icons = Iconify-Tabler retained, PrimeIcons not installed
The 593 tabler-* icon references in templates remain unchanged. PrimeVue's components accept any element in icon slots and :icon props can render arbitrary content. The custom Vuetify icon adapter in apps/app/src/plugins/vuetify/icons.ts is replaced by a tiny generic <Icon> component (at apps/app/src/components/Icon.vue) that wraps @iconify/vue's <Icon> component and emits real <svg> markup. Call-site migration substitutes <VIcon icon="tabler-X" /> with <Icon name="tabler-X" />. The 5 SVG checkbox/radio overrides in @images/svg/ become unused (PrimeVue's Checkbox/RadioButton ship their own styling) and are deleted in F6.
Drift correction (F3 B9): RFC v1.0 described the new
<Icon>component as rendering<i :class="i-tabler-X">utility classes. That approach would require UnoCSS to resolvei-tabler-*into real icon output, and UnoCSS is not in the Crewli stack — the project already uses@iconify/vue(installed at 4.1.2) for SVG icon rendering through Vuetify's icon adapter. F3 commit B6 implementsIcon.vueas a thin wrapper around@iconify/vueaccordingly. The substitution preserves existing icon naming (tabler-eye,tabler-user, etc.) and produces equivalent SVG output to the current Vuetify path. UnoCSS-style utility-class icon rendering was considered but not adopted; the wrapper retains Crewli's continuity.
Rationale: Throwing away 593 icon references is needless cost; PrimeVue does not require its own icon set; the project's Tabler aesthetic is preserved.
AD-6: Locale = nl-NL via primelocale, vue-i18n unused
PrimeVue is configured with locale: nlLocale where nlLocale is imported from primelocale/nl.json. This drives DatePicker month names, "no records" text, ARIA labels, aria-live announcements, and PaginatorReportTemplate text. UI strings in templates remain hardcoded Dutch. vue-i18n (already installed at 11.1.12) stays installed but unused — its removal is out of scope.
Rationale: Day-one Dutch locale costs ~5 lines of config and prevents English UI noise from leaking into the Crewli interface. Vue-i18n adoption is a separate decision (multi-language sprint) unrelated to this migration.
AD-7: DataTable strategy
All 14 existing DataTables migrate to PrimeVue's <DataTable> + <Column> API. Server-paginated tables (5) use :lazy="true" + @page + @sort + @filter events wired to TanStack Query parameters. Tables expected to display ≥ 200 rows simultaneously enable virtual scroll via :scrollable="true" :scroll-height="..." :virtual-scroller-options="{ itemSize: 44 }". The #expanded-row slot pattern (used by 2 audit/log tables) maps to PrimeVue's :expandedRows v-model + #expansion template. Per-cell #item.<col> slots map to PrimeVue's #body template per <Column>.
Frozen columns and column reordering: introduced opportunistically where UX benefits (e.g., the festival-roster Persons page), not blanket-applied.
Rationale: The strategic analysis identified DataTable as the highest-leverage component upgrade. PrimeVue's DataTable handles Crewli's 1000+ row scenarios (festival rosters, cross-tenant audit logs, user lists) with documented enterprise references and no degradation patterns matching Vuetify's GH #20335 / #20601.
AD-8: Stores and queries unchanged
The 8 Pinia stores and 29 TanStack Query composables under apps/app/src/stores/ and apps/app/src/composables/api/ are not touched by this sprint, except where they happen to import from vuetify (zero today, per audit) or @core (zero today, per audit). The audit confirmed zero coupling. Any incidental useToast() usages currently going through Vuetify's snackbar pattern migrate to PrimeVue's useToast() from primevue/usetoast — this is the only API change in this layer.
AD-9: SCSS strip = three-stage
Stage F3 (introduction): PrimeVue + Tailwind load alongside Vuetify. Both stylesheets present. Temporary CSS bloat (~3.5 MB) is accepted. New code uses PrimeVue.
Stage F4a–d (per-tree migration): Vuetify components removed per route tree. Per-component SCSS in @core/scss/template/libs/vuetify/components/ (24 files) becomes dead but stays imported. Incremental SCSS stripping is forbidden — too risky, too easy to break unrelated pages.
Stage F6 (cleanup): Three entry imports removed in one commit (@core/scss/template/index.scss from main.ts:11, @core/scss/template/libs/vuetify/index.scss from plugins/vuetify/index.ts:13, vuetify/styles from plugins/vuetify/index.ts:14). The entire @core/scss/template/ tree (146 files) deleted. Bundle size measured against §11 targets as acceptance criterion.
Rationale: Single-direction strips; no per-component CSS archaeology; one measurable cleanup commit at the end.
AD-10: Test runtime = single setupFile flip
The Vitest setupFile that registers Vuetify globally is identified during F3 (likely apps/app/vitest.config.ts setupFiles or apps/app/src/testing/setup.ts). In F3 it is updated to register PrimeVue alongside Vuetify (transitional). In F5 the Vuetify registration is removed and the 22 mount-tests are validated to pass against PrimeVue-only registration. Component-level test changes are made only where tests assert on Vuetify-specific class names or DOM structure — those assertions are rewritten to PrimeVue equivalents on a per-test basis.
AD-11: Toast and ConfirmDialog services
VSnackbar usage (39 occurrences across 35 files) migrates to PrimeVue's <Toast> mounted once in App.vue plus useToast() composable at call-sites. Existing useNotificationStore continues to drive the API; only the underlying renderer changes.
VDialog confirm-pattern usage (subset of 86 VDialog occurrences) where the dialog is a yes/no confirmation migrates to PrimeVue's <ConfirmDialog> + useConfirm() service. Complex dialogs (forms in modals, multi-step) migrate to plain <Dialog> components.
AD-12: Tailwind CSS v4 added for layout utilities
Tailwind CSS v4 is installed in F3 (tailwindcss@^4, @tailwindcss/vite@^4). The Aura preset is configured with cssLayer: { name: 'primevue', order: 'tailwind-base, primevue, tailwind-utilities' } so utility classes win over component defaults. VRow / VCol / VSpacer occurrences (524 total) migrate to Tailwind grid utilities (grid grid-cols-12 gap-4, flex items-center justify-between, etc.). PrimeFlex is not installed (deprecated by PrimeTek).
Rationale: Tailwind v4 is the layout substrate PrimeVue 4 documents and Aura examples assume. Without it we would write ~30 lines of custom layout CSS plus lose the utility-class vocabulary that the broader ecosystem now uses. Cost: one additional dev dependency, ~10 KB to the production bundle, and a learning curve for grid-cols-N / flex utilities. Benefit: future styling work moves at ecosystem-standard speed.
6. Sprint Breakdown
Six work packages, executed strictly serially. Each package ends with: (a) all tests green, (b) Bert review and approval, (c) commit pushed, (d) Claude Chat re-syncs to confirm before issuing the next prompt.
F2 — Documentation rewrite (1 day)
Output:
- New:
dev-docs/PRIMEVUE_COMPONENTS.md(~700 lines, 9 sections matching VUEXY_COMPONENTS.md structure but for PrimeVue). Includes component mapping reference, page patterns,<FormField>API, theme tokens, three-state pattern (loading/empty/error) in PrimeVue idioms, DataTable lazy + virtual scroll cookbook. - Updated:
CLAUDE.mdfrontend section — 4-step decision tree retargeted to PrimeVue, 11 component rules updated, mandatory pre-task check now referencesPRIMEVUE_COMPONENTS.md. The/p/*shorthand corrected to/portal/*(matches reality per audit). - Updated:
.cursorrulesfrontend rules. - Deleted:
dev-docs/VUEXY_COMPONENTS.md(replaced byPRIMEVUE_COMPONENTS.md). - Updated:
dev-docs/SYNC_MANIFEST.md(post-commit hook).
Definition of Done: all docs committed, npm run sync:docs regenerates .claude-sync/, Bert uploads new manifest to Project Knowledge.
Note: F2 produces no code changes. Documentation lands first so subsequent prompts can reference the canonical component map.
F3 — Foundation (2 days)
Output:
- Dependencies installed:
primevue@^4.5,@primeuix/themes@^21 ,@primevue/forms@^4.5,primelocale@^1,tailwindcss@^4,@tailwindcss/vite@^4.
- Dependencies retained:
flatpickr,vue-flatpickr-component,@iconify-json/tabler,@iconify-json/mdi,@iconify-json/fa, all Pinia/TanStack/Vue Query/Zod packages. - New:
apps/app/src/plugins/primevue/index.ts— registers PrimeVue plugin with Aura preset, theme tokens, nl-NL locale. - New:
apps/app/src/plugins/primevue/theme.ts— AuradefinePresetwith Crewli teal semantic tokens. - New:
apps/app/src/plugins/primevue/defaults.ts— globalpt(PassThrough) defaults replacing Vuetify component defaults fromdefaults.ts. - New:
apps/app/src/components/forms/FormField.vue— wrapper component (spec in Appendix A). - New:
apps/app/src/components/Icon.vue— generic Iconify renderer (~20 lines). - Rewritten:
apps/app/src/layouts/default.vue,blank.vue,OrganizerLayout.vue,PortalLayout.vue,PublicLayout.vue. - New:
apps/app/tailwind.config.js(Tailwind v4 minimal config). - Updated:
apps/app/vite.config.ts(add Tailwind Vite plugin; vite-plugin-vuetify retained until F6). - Updated:
apps/app/src/main.ts(register PrimeVue plugin, importtailwindcss/index.css, retain Vuetify imports until F6). - Updated:
apps/app/src/App.vue(mount<Toast>and<ConfirmDialog>). - New:
apps/app/src/composables/useFormError.ts— merges Zod resolver errors with API 422 responses. - Identified: Vitest setupFile location documented in F3 closing summary (no flip yet — that's F5).
Definition of Done:
- App boots, login page renders with PrimeVue OrganizerLayout but old pages still use Vuetify (parallel mode).
<FormField>wrapper has at least one usage in a sample page (e.g., login).- Tailwind utility classes work (
<div class="flex gap-4">renders). - All 402 existing tests still pass.
- Bundle size measured (expected: temporary increase to ~2.4 MB JS, ~3.5 MB CSS — both stylesheets loaded).
Risk: Layout-shell rewrite is the largest single discrete change. Recommend committing the shell change in isolation before any component migration, so rollback is one-commit-revert if needed.
⚠️ SUPERSEDED (2026-05-16): F4a–F4d below are superseded by
dev-docs/RFC-WS-GUI-REDESIGN-CREWLI-STARTER.md. The page-migration strategy changed (crewli-starter is now the design source, parallel/v2/*routes, page-by-page cutover). AD-1..AD-12 remain binding. F2/F3/F5/F6 are unaffected.
F4 — Component migration (5–6 days, 4 sub-packages)
Each sub-package migrates one route tree. Within a sub-package: pages migrate one at a time, smallest pages first, dialogs/modals/components used by those pages migrate alongside their parent page.
F4a — /portal/* (1 day)
7 pages, 2 with forms. Smallest blast radius — proves the migration pattern end-to-end. Includes the dropdown UX from UX_SPEC_FESTIVAL_HIERARCHY.md (4 scenarios). At F4a closure, the pattern (component mappings, FormField usage, Toast invocations, layout integration) is locked and re-applied uniformly in F4b–d.
F4b — Organizer root (3 days)
30 pages, 7 with forms. Includes 2 datatables (members, organisation/companies) and the Artist Management module (whatever state it is in at F4b start — committed-Vuetify or partially-built). Highest absolute page count. Sub-divided into:
- F4b.1: top-level pages (login, account-settings, dashboard, members, organisation/*) — ~10 pages
- F4b.2: events/* tree (12 pages) including event-detail tabs
- F4b.3: dialogs and components consumed across organizer pages
F4c — /platform/* (1 day)
8 pages, 4 with datatables (organisations index, users index, activity-log, form-failures). Three of these are 1000+ row tables — perfect testing ground for DataTable lazy + virtual scroll patterns. Server-side filtering wired to existing TanStack Query params.
F4d — Public registration + Form Builder (1.5 days)
register/[public_token].vue, register/success.vue, plus the public form renderer composables under apps/app/src/composables/forms/. Most complex due to dynamic field rendering (the form-renderer reads form_schema, renders field-by-field based on field_type enum). Form-renderer field components map case-by-case to PrimeVue equivalents. Form Builder organizer UI (S3b) is explicitly NOT part of F4d — that work begins post-launch on the new PrimeVue foundation.
Definition of Done (per sub-package):
- All pages in the sub-package render with zero Vuetify components in the visible tree.
- All forms in the sub-package use
<Form>+<FormField>+ Zod. - Tests for migrated components pass (failures investigated; assertion-on-Vuetify-class-name updates allowed).
- No new TypeScript errors.
- Bundle size measured at sub-package end, recorded in commit message.
F5 — Tests, accessibility, performance (1 day)
Output:
- Vitest setupFile flipped: PrimeVue-only registration, Vuetify registration removed.
- 22 mount-tests verified passing.
- axe-core audit run on top 10 screens (login, dashboard, events list, event detail, members, organisation settings, platform users, platform activity-log, public registration, portal events). Issues triaged: blocker bugs fixed, non-blockers logged in BACKLOG.md as
A11Y-PRIMEVUE-*items. - DataTable performance benchmark: 5000-row mock dataset rendered in
pages/platform/users/index.vueconfiguration (lazy, virtual scroll, sortable). Acceptance: < 800 ms initial render on M-class laptop, < 100 ms scroll-frame time. - Bundle size measured: targets in §11.
Definition of Done: all tests pass, axe-core blockers fixed, perf benchmark meets targets, commit message documents measurements.
F6 — Cleanup (0.5 day)
Output:
- Removed dependencies:
vuetify,vite-plugin-vuetify,@mdi/js(if present),vue-flatpickr-component(only if Flatpickr replaced — not in this sprint, so retained). - Removed:
apps/app/src/@core/(entire tree, 137 files). - Removed:
apps/app/src/@layouts/(entire tree, 29 files). - Removed:
apps/app/src/plugins/vuetify/(entire directory). - Removed from
apps/app/src/main.ts: Vuetify plugin registration, Vuetify CSS imports. - Removed from
apps/app/vite.config.ts:vite-plugin-vuetifyplugin. - Removed: 5 SVG form-control overrides under
@images/svg/. - Updated:
tsconfig.jsonpaths (remove@core/*,@layouts/*aliases). - Updated:
.cursorrulesif it still references Vuetify. - Verified:
pnpm buildsucceeds;pnpm test --runshows 402 tests passing; bundle size measured against §11 targets.
Definition of Done: clean package.json, no orphaned imports, no orphaned SCSS, bundle size at or below targets, closure-PR opened with summary commit.
7. Component Mapping Reference (top 25 by usage)
The full 75-component mapping lives in dev-docs/PRIMEVUE_COMPONENTS.md after F2. The table below is the binding subset for sprint planning.
| Vuetify | Uses | PrimeVue replacement | Notes |
|---|---|---|---|
| VBtn | 420 | Button |
variant='tonal' → severity='secondary' outlined; variant='text' → text prop; :loading → :loading |
| VCol | 331 | Tailwind col-span-N |
<div class="md:col-span-6"> etc. |
| VIcon | 267 | <Icon name="tabler-X"> (custom 20-line wrapper) |
Iconify class output |
| VCard | 250 | Card |
Slot mapping: #title, #subtitle, #content, #footer |
| VCardText | 208 | <div class="p-6"> (or Card #content slot) |
Just padding |
| VAlert | 116 | Message |
type='success/info/warning/error' → severity |
| VChip | 102 | Tag (preferred) or Chip |
Tag for status badges; Chip for removable filters |
| VRow | 102 | Tailwind grid grid-cols-12 gap-4 |
|
| VSpacer | 91 | Tailwind flex-1 |
Or surrounding justify-between |
| VDialog | 86 | Dialog |
:max-width → :style="{ width: '500px' }" |
| VCardActions | 82 | <template #footer> (in Card) |
Footer slot of Card |
| VListItem | 82 | MenuItem (in menu) or <li> (in list) |
Context-dependent |
| VForm | 64 | Form from @primevue/forms + <FormField> |
See Appendix A |
| VAvatar | 59 | Avatar |
variant='tonal' → :style="{ background: ... }" |
| VCardTitle | 56 | <template #title> (in Card) |
|
| VDivider | 56 | Divider |
:vertical → layout='vertical' |
| VSkeletonLoader | 52 | Skeleton |
type='card' → manual structure |
| VList | 51 | Listbox (selection) or DataView (cards) |
Context-dependent |
| VSnackbar | 39 | useToast() + <Toast> mounted in App.vue |
Notification store unchanged |
| VSwitch | 28 | ToggleSwitch |
|
| VTextField | 27 | InputText + FloatLabel + Message (via <FormField>) |
density='compact' → size='small' |
| VMenu | 21 | Menu (anchor) or Popover (rich content) |
|
| VTabs / VTab / VWindow | 11 / 17 / 6 | Tabs + TabList + Tab + TabPanels + TabPanel |
|
| VDataTable / VDataTableServer | 9 / 5 | DataTable + Column (:lazy="true" for server) |
See AD-7 |
| VAutocomplete | 4 | AutoComplete |
|
| VSelect | 5 | Select |
Long-tail (≤ 11 uses) treated case-by-case during F4 — full mapping in PRIMEVUE_COMPONENTS.md.
8. Risk Register
| ID | Risk | Probability | Impact | Mitigation |
|---|---|---|---|---|
| R-1 | DataTable virtual scroll regressions on specific column types (custom slots) | Medium | High | Benchmark in F5 with the heaviest real table (pages/platform/users/index.vue); rollback to non-virtual mode as Plan B |
| R-2 | <FormField> wrapper API doesn't compose well for complex forms (multi-section, conditional fields) |
Medium | High | Build the wrapper in F3 against the most complex form in F4d (public registration with sections); revise API before F4a if signs of strain |
| R-3 | Tailwind utility class name collisions with existing custom classes | Low | Medium | Tailwind v4 prefix config (tw- if needed) — decide in F3 based on namespace audit |
| R-4 | Mount-tests fail in F5 due to PrimeVue-specific DOM structure | High | Low | Expected. Rewrite assertions per-test; this is anticipated work, not a defect |
| R-5 | Concurrent Artist Management work creates merge conflicts during F4b | Medium | Medium | F4b explicitly absorbs Artist Management code as it stands at F4b start; communication before F4b begins |
| R-6 | Bundle size targets (§11) not met | Low | Medium | Iterate in F6: tree-shake harder, defer non-critical imports, consider PrimeVue per-component imports vs. global registration |
| R-7 | Aura preset doesn't match Crewli brand once seen at scale | Medium | Medium | F3 includes a brand-review checkpoint: render the dashboard + 2 representative pages, get Bert sign-off, before F4 starts |
| R-8 | useToast() API differs enough from VSnackbar that useNotificationStore needs API changes |
Low | Low | Wrap in useNotificationStore to keep call-site API stable |
| R-9 | Flatpickr CSS conflicts with PrimeVue tokens (z-index, color) | Low | Low | Verify in F3 with a date-input render; scope CSS overrides if needed |
| R-10 | Layout shell rewrites break responsive behavior on mobile | Medium | Medium | Manual mobile testing in F3 closure; layout shells are < 600 lines so issues are bounded |
9. Rollback Plan
The migration is committed on a feature branch (migration/primevue). Each F-package commits incrementally with descriptive messages. Rollback strategies by stage:
- During F2: revert documentation commits; no code touched.
- During F3: revert the foundation commit and dependency installs. Vuetify still registered, app reverts to current state.
- During F4 (sub-package level): revert the sub-package commit. Other migrated trees keep working because parallel-mode (Vuetify + PrimeVue both registered) is in force until F6.
- During F5: test fixes are reversible per-test commit.
- After F6: rollback requires reinstalling Vuetify + Vuexy SCSS. Practical but expensive (~1 day to undo). F6 is the point of no return.
The migration branch is squash-merged to main only after F6 closure and full test pass. Production deploy follows the standard process. If a critical bug surfaces in production within 7 days of deploy, the rollback is a main revert of the squash commit and a re-deploy.
10. Definition of Done (Sprint-Level)
The sprint is closed when all of the following are true:
apps/app/package.jsondoes not listvuetify,vite-plugin-vuetify, or any Vuexy template-related dev dependency.apps/app/src/@core/,apps/app/src/@layouts/,apps/app/src/plugins/vuetify/directories do not exist.rg '<V[A-Z]' apps/app/src --glob '*.vue'returns 0 matches.rg "from ['\"]vuetify" apps/app/srcreturns 0 matches.rg "from ['\"]@core/" apps/app/srcreturns 0 matches.rg "from ['\"]@layouts/" apps/app/srcreturns 0 matches.pnpm test --runreports ≥ 402 tests passing (count may grow if new tests added).pnpm buildsucceeds.- Bundle size measurement: main JS chunk ≤ 180 KB gzip, total CSS ≤ 400 KB uncompressed.
- Manual smoke test of all 4 route trees passes (login → dashboard, organisation switch, /platform/* admin tasks, /portal/* event flow, /register/[token] flow).
- axe-core audit of top 10 screens shows 0 critical violations.
dev-docs/PRIMEVUE_COMPONENTS.mdexists;dev-docs/VUEXY_COMPONENTS.mddoes not.CLAUDE.mdand.cursorrulesreference PrimeVue patterns; no remaining Vuetify or Vuexy references.dev-docs/SYNC_MANIFEST.mdregenerated,.claude-sync/uploaded to Project Knowledge.- Memory updated to reflect completion (this RFC moved from "Approved" to "Implemented").
11. Bundle Size Targets
| Metric | F1 baseline | F6 target | F6 stretch |
|---|---|---|---|
| Main JS chunk (gzip) | 213 KB | ≤ 180 KB | ≤ 150 KB |
| Total JS uncompressed | 1.90 MB | ≤ 1.40 MB | ≤ 1.20 MB |
| Total CSS uncompressed | 3.32 MB | ≤ 400 KB | ≤ 300 KB |
| Largest single CSS chunk | 3.14 MB | ≤ 300 KB | ≤ 200 KB |
| Initial paint resources | 597 KB JS + 3.14 MB CSS | < 1 MB combined | < 700 KB combined |
Targets are advisory; F6 closure is approved if F6-target column is met. Stretch targets identify opportunities for post-launch optimization sprints.
12. Backlog Items Created by This RFC
These are added to BACKLOG.md during F2 and tracked independently:
FRONTEND-DATEPICKER-PRIMEVUE-NATIVE— replace Flatpickr with PrimeVue DatePicker. Post-launch, low priority.FRONTEND-VUE-I18N-ADOPTION— extract Dutch UI strings to vue-i18n message files for future multi-language support. Medium priority, separate sprint.A11Y-PRIMEVUE-*— placeholder for any non-blocker axe-core findings during F5.FRONTEND-DESIGN-SYSTEM-EXPANSION— extend Aura semantic tokens for Crewli-specific spacing, type ramp, motion. Low priority, post-launch.FRONTEND-VOLT-EVALUATION— assess PrimeVue Volt (Tailwind v4 unstyled wrapper) as a future style-management approach. Low priority.
13. Decisions That Were NOT Made
To prevent ambiguity, decisions deliberately deferred:
- PrimeVue Pro (PrimeBlocks) license purchase. Free tier is sufficient for sprint scope. Re-evaluate after launch if dashboard pages need premium block components.
- Sakai or other PrimeVue admin templates. None will be adopted; layout shells are hand-rolled to avoid template lock-in.
- CSS-in-JS migration (e.g., Pinceau, Vanilla Extract). Out of scope; Aura tokens + Tailwind utilities cover all cases.
- State machine for form wizards (e.g., XState). Out of scope; existing
useFormSteps.tsremains. Storybook or component playground.Landed post-RFC (Storybook 10, commitebb8e3bc). Seedev-docs/FRONTEND-TOOLING.md§ Storybook.
14. Approval & Next Action
This RFC is approved on 2026-05-10. The next concrete action is to commit the RFC to dev-docs/RFC-WS-FRONTEND-PRIMEVUE.md, regenerate the sync manifest, upload to Project Knowledge, then issue the F2 prompt to Claude Code (documentation rewrite). Architectural decisions in this RFC are binding for the duration of the sprint; deviations require RFC amendment with explicit rationale.
Appendix A — <FormField> API Specification
Project-owned wrapper at apps/app/src/components/forms/FormField.vue. Goal: terse call-site syntax that abstracts the @primevue/forms boilerplate.
Usage example
<script setup lang="ts">
import { Form } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import { z } from 'zod'
import FormField from '@/components/forms/FormField.vue'
import InputText from 'primevue/inputtext'
import Select from 'primevue/select'
import Button from 'primevue/button'
const schema = z.object({
email: z.string().email('Geen geldig e-mailadres'),
role: z.enum(['organizer', 'volunteer'], { message: 'Selecteer een rol' }),
})
const onSubmit = async ({ valid, values }: any) => {
if (valid) await api.invite(values)
}
</script>
<template>
<Form :resolver="zodResolver(schema)" @submit="onSubmit" v-slot="$form">
<FormField name="email" label="E-mailadres" required>
<InputText name="email" />
</FormField>
<FormField name="role" label="Rol" required>
<Select name="role" :options="roleOptions" />
</FormField>
<Button type="submit" label="Uitnodigen" :loading="$form.submitting" />
</Form>
</template>
Component contract
// FormField.vue props
interface FormFieldProps {
name: string // matches Zod schema key
label?: string // optional — rendered as <label>
required?: boolean // shows asterisk; does NOT add validation (Zod owns that)
hint?: string // grey helper text below input
apiError?: string | null // overrides validation message (for 422 surfacing)
}
// Slots:
// default — the actual PrimeVue input (must have :name matching `name`)
//
// Renders:
// <div class="field">
// <label>{{ label }}<span v-if="required">*</span></label>
// <slot />
// <Message v-if="error" severity="error" size="small">{{ error }}</Message>
// <small v-else-if="hint">{{ hint }}</small>
// </div>
//
// Error precedence: apiError > Zod resolver error from $form context
API 422 integration
useFormError(formRef) composable merges API 422 responses into the form state:
import { useFormError } from '@/composables/useFormError'
const formRef = ref()
const { applyApiErrors, clearApiErrors } = useFormError(formRef)
const onSubmit = async ({ valid, values }: any) => {
if (!valid) return
clearApiErrors()
try {
await api.invite(values)
} catch (e) {
if (e.response?.status === 422) applyApiErrors(e.response.data.errors)
else throw e
}
}
applyApiErrors injects per-field error messages into <FormField> via the apiError prop pattern — no manual error refs needed.
Appendix B — Aura Theme Token Plan
// apps/app/src/plugins/primevue/theme.ts
// Drift correction (F3 B9): imports use @primeuix/themes (the
// maintained successor that PrimeVue 4's install docs now prescribe).
import { definePreset } from '@primeuix/themes'
import Aura from '@primeuix/themes/aura'
export const CrewliPreset = definePreset(Aura, {
semantic: {
primary: {
50: '#E6F4F4',
100: '#CCE9EA',
200: '#99D3D4',
300: '#66BDBE',
400: '#33A7A8',
500: '#0D9394', // Crewli teal
600: '#0B7F80', // Crewli teal dark
700: '#086B6C',
800: '#055758',
900: '#034344',
950: '#012F30',
},
colorScheme: {
light: {
primary: { color: '{primary.500}', contrastColor: '#ffffff', hoverColor: '{primary.600}', activeColor: '{primary.700}' },
// surface, formField, list, navigation, overlay, content tokens use Aura defaults
},
dark: {
primary: { color: '{primary.400}', contrastColor: '{surface.900}', hoverColor: '{primary.300}', activeColor: '{primary.200}' },
},
},
},
})
Component-level overrides use the pt (PassThrough) prop globally configured in defaults.ts, replacing Vuetify's defaults: { VBtn: { ... } } pattern.
Appendix C — Version Pinning Policy
PrimeVue moves fast (4.5.0 → 4.5.5 in Oct–Dec 2025). Sprint pins:
primevueand@primevue/forms: pin to same exact patch version (e.g.,4.5.5) in F3. They must always upgrade together to avoid token-mismatch bugs.@primeuix/themes: pin to^2(own release cadence, decoupled from PrimeVue's 4.x patch line — same maintainers, separate package per the F3 B9 drift correction).tailwindcssand@tailwindcss/vite: pin to same exact version.primelocale: caret range fine (^1).
Post-launch upgrade policy: monthly review, upgrade in lockstep, regression-test against the F5 axe-core + perf benchmarks before merging.
End of RFC.
Amendment 2026-05-10: TEST-INFRA-001 inserted before F2
| Field | Value |
|---|---|
| Amendment ID | A-1 |
| Status | Approved |
| Date | 2026-05-10 |
| Author | Architecture (Claude Chat) |
| Trigger | Timetable stabilization sprint (PR #18, #19) surfaced three diagnostic incidents that the RFC v1.0 sequencing did not anticipate |
| Scope of change | Sequencing addition + F5 scope extension + Risk register + DoD. No changes to F2–F6 internal architecture, no changes to Aura preset, no changes to <FormField> API, no changes to Tailwind v4, no changes to bundle size targets. |
A.1 Background
This RFC v1.0 was approved on 2026-05-10. On the same day, the closing PR of the timetable stabilization sprint (fix/timetable-stabilization, PR #19) merged to main. That sprint formalized a new project principle in CLAUDE.md titled "Diagnostic discipline: audit before assume", anchored to three empirical incidents:
- B1 — controller assumed buggy; schema-verify gate against
SCHEMA.md:1285proved the seeder was wrong, controllers correct - B5 — enum-shape assumed drifted; field-by-field response audit proved decimal-as-string was the actual drift
- UX divergence — 397 jsdom-tests green, but manual browser test against the prototype revealed substantial divergence (PerformanceBlock missing genre tag, drag broken, resize handles missing, etc.) — tracked as
ART-S4-UX-PARITYin BACKLOG.md
The common pattern across all three: the existing Vitest+jsdom test layer did not detect what the product actually does in a real browser. Tests asserted against mocks that agreed with schemas; schemas didn't agree with the backend wire format; layouts didn't match the prototype; jsdom couldn't compute layout cascade, sticky positioning, or visual regressions.
This RFC's F4 sub-packages (a/b/c/d) constitute a structurally larger refactor than the timetable module — every .vue page in the SPA changes, 75 distinct Vuetify components are replaced, 38 form-bearing components migrate to <FormField>. The probability that the same pattern of incidents will recur during F4 — without test infrastructure capable of detecting them — is high enough to warrant addressing pre-emptively.
The RFC v1.0 author did not have access to the timetable-stabilization context (the work was done in a separate Claude Chat that focused exclusively on the migration audit). Hence this amendment, not a v1.1 rewrite.
A.2 What changes
| Aspect | RFC v1.0 | After amendment |
|---|---|---|
| Sequence | F1 → F2 → F3 → F4 → F5 → F6 | F1 → TEST-INFRA-001 → F2 → F3 → F4 → F5 → F6 |
| F5 scope | axe-core + 22 mount-tests + perf benchmark | All of v1.0 plus visual regression baselines (intermediate for Artist Management, first-capture for other surfaces) |
| Risk register | 10 risks (R-1 through R-10) | 10 + R-11 (visual regression coverage gap) |
| Definition of Done | 15 items | 15 + 5 new items (DoD-16 through DoD-20) |
| Sprint effort | 10–12 working days | 15–19 working days (+5–7 for TEST-INFRA-001) |
| Concurrent Artist Management work | Continues on Vuetify until F4b absorbs it | Same — TEST-INFRA-001 does not modify application code, only test infrastructure |
A.3 New sprint: TEST-INFRA-001 (5–7 working days)
This sprint is prerequisite to F2. F2 cannot begin until TEST-INFRA-001 closes. Sprint scope at high level — full sprint prompt is authored separately and is the next concrete action after this amendment is committed.
Goals:
-
Playwright Component Testing foundation: install
@playwright/test+@playwright/experimental-ct-vue+ axe-core dependencies. Configure component-test runner with Vuetify+Pinia+TanStack+router providers. Demonstrate one working component test that mounts a current Vuetify component (proves the foundation) and one that mounts a prototype HTML region (proves visual baseline pipeline). -
Visual regression infrastructure: pixel-diff tooling, baseline storage in Git LFS (self-hosted per Bert's preference), prototype-HTML rendering pipeline (the prototype at
./resources/Crewli - Artist Timetable Management/is React+Babel via unpkg — Playwright renders it once, captures baselines, stores them as authoritative). CI integration with PR-comment diff visibility. -
Real-backend e2e setup: Playwright e2e configuration that spins up Laravel test-server, authenticates via Sanctum, runs against seeded fixtures. First flow: the 409 conflict contract test from
TEST-CONTRACT-001. -
Architecture documentation:
dev-docs/ARCH-TESTING.mdcodifying the test pyramid (unit / component / integration / e2e / visual), scope per layer, when something belongs in which layer, conventions for mock-vs-real-backend choices, baseline update workflow. -
No migration of existing Vitest tests. Existing 402 Vitest+jsdom tests remain unchanged. They will be naturally replaced during F4 component migration (each migrated component gets a fresh Playwright Component Test, the corresponding Vitest+jsdom test is removed at that point). This is a deliberate scope decision — migrating tests now and again during F4 is duplicate work.
Baseline scope:
Visual regression baselines in this sprint are limited to Artist Management surfaces (PerformanceBlock, PerformancePopover, AddPerformanceDialog, Wachtrij, StageRow, plus the timetable canvas at three states: empty / partial / full). Reason: these are the only surfaces with a canonical prototype to baseline against. Other modules (Volunteers, Events, Forms, Platform Admin) get baselines progressively as they're migrated through F4 — and those baselines are taken against the PrimeVue implementation post-migration, not against the current Vuetify implementation that we are about to discard.
Definition of Done:
- All five goals delivered, branch merged to main,
.claude-sync/re-uploaded - Existing 402 Vitest+jsdom tests still pass unchanged
- At least one Playwright component test passing in CI (smoke proof)
- At least one visual regression baseline committed (smoke proof)
- One real-backend e2e test passing (TEST-CONTRACT-001 first flow)
ARCH-TESTING.mdreviewed and committed
BACKLOG consolidation:
This sprint consumes and closes three pre-existing BACKLOG entries from the timetable-stabilization PR:
TEST-INFRA-001— closed by goal 1+2 aboveTEST-CONTRACT-001— closed by goal 3 aboveTEST-VISUAL-001— closed by goal 2 above (with explicit prototype-HTML baseline source)
A.4 Updated sequencing
F1 ─► TEST-INFRA-001 ─► F2 ─► F3 ─► F4a ─► F4b ─► F4c ─► F4d ─► F5 ─► F6 ╰──── 5-7 d ────╯ ╰1d╯ ╰2d╯ ╰1d╯ ╰3d╯ ╰1d╯ ╰1.5d╯ ╰1.5d╯ ╰0.5d╯ ╰── visual & e2e ──╯ ╰── infra only ────╯ After F6: ART-S4-UX-PARITY (Artist Management UX parity on PrimeVue, with visual regression baselines as objective measure) After ART-S4-UX-PARITY: Sessie 5 (Engagement Detail), Accreditation Engine, etc.
Total estimated effort: 15–19 working days for TEST-INFRA-001 + F2–F6.
A.5 F5 scope extension
RFC v1.0 §6, F5 (Tests, accessibility, performance) is extended with one additional output. The handling depends on whether a prototype-baseline exists for the surface being validated:
Artist Management surfaces (PerformanceBlock, PerformancePopover, AddPerformanceDialog, Wachtrij, StageRow, timetable canvas — originally baselined against prototype HTML in TEST-INFRA-001):
- F5 captures the PrimeVue-implementation baselines as a separate intermediate baseline set. These are explicitly NOT expected to match the prototype baselines.
- Reason: UX parity with the prototype is out of scope for F4 (F4b absorbs the Artist Management module "as it stands at F4b start" per RFC §6, which is its current Vuetify implementation including all gaps documented in
ART-S4-UX-PARITY). PrimeVue migration converts those Vuetify components to PrimeVue equivalents but does not close the UX gaps. - F5 acceptance for these surfaces requires only that the PrimeVue implementations render structurally correctly (no missing components, no broken layouts, no console errors, no a11y blockers) — not pixel-equivalence with the prototype.
ART-S4-UX-PARITY(post-F6 sprint) closes the gap by refactoring the PrimeVue Artist Management surfaces to match the prototype, and at that point updates the intermediate baselines to align with the prototype baselines.
Surfaces outside Artist Management (Volunteers, Events, Forms, Platform Admin, etc. — no prototype baseline exists):
- F5 captures the first PrimeVue-implementation baseline for each surface. This becomes the regression anchor going forward.
- F5 acceptance requires the baseline to be reproducible (re-running the capture produces an identical baseline within configured pixel tolerance).
The existing F5 outputs (axe-core audit, mount-test verification, perf benchmark) are unchanged.
A.6 Risk register addition
| ID | Risk | Probability | Impact | Mitigation |
|---|---|---|---|---|
| R-11 | F4 component migration introduces visual regressions that jsdom-tests do not detect, mirroring the timetable-stabilization pattern | High (without TEST-INFRA-001) / Low (with) | High | TEST-INFRA-001 sprint inserted before F2 establishes Playwright + visual regression baselines against prototype HTML. F4 sub-packages must show passing baselines before sub-package closure. F5 re-validates all baselines against PrimeVue final state with the dual-tier scope defined in §A.5. |
A.7 Definition of Done — additional items
The sprint-level DoD in RFC v1.0 §10 is extended with:
dev-docs/ARCH-TESTING.mdexists and is referenced fromCLAUDE.md.- Playwright Component Testing infrastructure is operational;
pnpm test:component(or equivalent) runs in CI. - Visual regression baselines exist for all Artist Management surfaces;
pnpm test:visual(or equivalent) passes against them. - At least one real-backend e2e test (the 409 conflict contract test from TEST-CONTRACT-001) passes against a Laravel test-server in CI.
- BACKLOG entries
TEST-INFRA-001,TEST-CONTRACT-001, andTEST-VISUAL-001are marked closed inBACKLOG.md, with references to the sprint commits.
A.8 Effort impact
| Phase | RFC v1.0 estimate | After amendment |
|---|---|---|
| F1 | already complete | already complete |
| TEST-INFRA-001 | – | 5–7 days (new) |
| F2 | 1 day | 1 day |
| F3 | 2 days | 2 days |
| F4a | 1 day | 1 day |
| F4b | 3 days | 3 days |
| F4c | 1 day | 1 day |
| F4d | 1.5 days | 1.5 days |
| F5 | 1 day | 1.5 days (visual regression validation extends scope per §A.5) |
| F6 | 0.5 day | 0.5 day |
| Total | 10–12 days | 15–19 days |
No launch-pressure exists per current planning (Bert confirmation 2026-05-10: minimum 6 months). The 5–7 day investment is justified against the high probability that R-11 materializes during F4 without it.
A.9 What this amendment does NOT change
To prevent ambiguity in future review:
- F2 documentation rewrite scope is unchanged (
PRIMEVUE_COMPONENTS.md, CLAUDE.md update,.cursorrulesupdate,VUEXY_COMPONENTS.mddeletion). - F3 foundation outputs are unchanged. Tailwind v4 still installs,
<FormField>wrapper still gets built per Appendix A, layout shells still get rewritten, Aura preset still gets configured with Crewli teal. - F4 sub-package boundaries and order are unchanged. F4a (
/portal/*) → F4b (organizer root + Artist Management) → F4c (/platform/*) → F4d (public registration + Form Builder). - F6 cleanup deletions are unchanged.
- Bundle size targets in §11 are unchanged.
- Architectural decisions AD-1 through AD-12 are unchanged.
<FormField>API specification in Appendix A is unchanged.
A.10 Approval & next action
This amendment is approved on 2026-05-10. The next concrete action is to commit this amendment to dev-docs/RFC-WS-FRONTEND-PRIMEVUE.md on the audit/primevue-migration branch, regenerate the sync manifest, upload .claude-sync/ to Project Knowledge, then issue the TEST-INFRA-001 sprint prompt (authored separately).
After TEST-INFRA-001 closes and merges to main, F2 begins per RFC v1.0 §6.
-
RFC v1.0 specified
@primevue/themes@^4.5. That package was deprecated during F3 with explicit guidance to migrate to@primeuix/themes(same maintainers, same API surface). PrimeVue 4's official install documentation now prescribes@primeuix/themes. See F3 commit B1 for the substitution rationale and B9 for the AD-2 + Appendix B alignment. ↩︎