unplugin-auto-import scans src/composables/; the new drawerRegistry
exports added global + vue-module declarations. auto-imports.d.ts is
tracked — keep it in sync (same precedent as prior composable syncs).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
unplugin-auto-import scans src/composables/, so the new useV2Nav added
a global + vue-module declaration. auto-imports.d.ts is tracked; keep
it in sync (same precedent as Plan 1's useRightDrawer sync).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
unplugin-auto-import scans src/composables/, so the new useRightDrawer
(Task 7) added a global + vue-module declaration. auto-imports.d.ts is
tracked (committed for editor/CI type resolution without a build), so
keep it in sync — same precedent as committing typed-router.d.ts. No
new symbol from useShellUiStore: src/stores is not in the auto-import
dirs (stores are imported explicitly), which is correct.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Layout-shell rewrite per RFC AD-3, B7-option-B. R-10 isolation invariant
honored — this single commit is revertible to roll back the layout
change without losing B1–B6 progress.
New component (PrimeVue-only, no Vuetify imports per F3 hard constraint):
- apps/app/src/layouts/components/AppShell.vue (~210 lines)
- Desktop sidebar (Tailwind grid, lg+ breakpoint) renders nav items
as PrimeVue Buttons + Icons. Mobile (<lg) hides sidebar; PrimeVue
Drawer slides in on hamburger toggle.
- Top bar (Tailwind) has hamburger + title (mobile) and an Avatar +
Menu (PrimeVue) for the user dropdown with "Mijn Profiel" and
"Uitloggen" actions.
- Nav items accept the existing { title, to: { name }, icon: { icon } }
shape from src/navigation/vertical so call-sites stay terse.
Five top-level layouts delegate to AppShell (filename preserved per
AD-3 so vite-plugin-vue-meta-layouts continues to resolve routes
unchanged):
- default.vue — org + (super-admin) platform nav
- OrganizerLayout — same nav as default; matches authenticated org UX
- PortalLayout — portal-specific 2-item nav ("Mijn evenementen",
"Mijn Profiel")
- blank.vue — minimal chrome-less wrapper for login etc.
- PublicLayout — minimal wrapper for public form-fill routes;
uses <main> for semantic structure
F3 functional regressions (intentional — F4 sub-packages reintroduce
each item through PrimeVue):
- NavSearchBar (Vuetify-heavy combobox/overlay) — absent from top bar
- ContextSwitcher (Vuetify VBtn + VMenu) — absent
- NavbarThemeSwitcher (Vuetify IconBtn) — absent; dark mode driven by
PrimeVue's darkModeSelector: '.dark' continues to work via the
existing @core skin classes until F6 cleanup
- NavbarShortcuts (Vuetify-heavy) — absent
- NavBarNotifications (Vuetify-heavy) — absent
- UserProfile from @/layouts/components/ (Vuetify-heavy menu) — replaced
with the minimal Avatar + Menu dropdown described above; rich profile
panel returns in F4
- ImpersonationBanner — absent; super-admin impersonation UX is F4 work
- PortalLayout event-mode vs platform-mode topbar (route.meta.navMode
driven) — absent; F4 reintroduces via AppShell prop or slot
- Suspense + AppLoadingIndicator wrapping pages — dropped; pages handle
their own loading via PrimeVue ProgressSpinner
VApp at App.vue level still wraps everything, so Vuetify components
inside still-Vuetify pages continue to render correctly during the
parallel-mode window.
Test updates (no Vuetify in layout structure to assert against anymore):
- OrganizerLayout.spec.ts — mocks AppShell instead of the deleted
DefaultLayoutWithVerticalNav reference; provides Pinia.
- PortalLayout.spec.ts — same mock pattern; new structural assertions
go through AppShell stub; the new third test verifies
PortalLayout forwards portal nav items + title to AppShell.
- PublicLayout.vue — uses <main> for semantics; PublicLayout.spec.ts
still passes unchanged.
Auto-generated component/auto-import dts files refreshed for the new
AppShell component (committed for stable dev workflow).
Verification:
- pnpm typecheck — clean.
- pnpm test — 402 tests pass (test count unchanged after spec rewrites).
- pnpm build — succeeds in 14.05s; AppShell chunk is ~57 KB raw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routing wiring (Phase D of WS-3 PR-B1):
- apps/app/src/plugins/1.router/guards.ts: add a single early-return
carve-out before the org-selection redirect — `if (to.meta.context
=== 'portal') return`. Per ARCH-CONSOLIDATION-2026-04 §4.3,
meta.context is the canonical contract; PR-B2 evolves the guards
from this key to full context-aware logic (post-login landing,
context-switcher, role checks).
- apps/app/env.d.ts: extend RouteMeta with the new layout names
('OrganizerLayout' | 'PortalLayout' | 'PublicLayout'), context,
requiresAuth, requiresToken, navMode, navTitle.
- apps/app/typed-router.d.ts: regenerated by unplugin-vue-router to
pick up portal/* and register/* route names.
- Page meta finalisation: portal pages have layout: 'PortalLayout',
context: 'portal', preserving original requiresAuth + nav fields;
register pages have layout: 'PublicLayout' + public: true (the
apps/app guard convention for public routes, since meta.public is
what the existing guard recognises).
Form-types restructure (boundaries cleanup):
- apps/app/src/composables/forms/types/formBuilder.ts → src/types/forms/
- apps/app/src/composables/forms/utils/{formValidation,validators}.ts
→ src/utils/forms/
- All `@/composables/forms/{types,utils}/*` imports rewritten across
pages, components, composables, tests.
- This avoids a `types → composables` boundaries violation at
src/types/formSchema.ts which re-exports primitives from the
inlined form-schema. types/formSchema.ts now imports from
@/types/forms/formBuilder which is in the same boundaries zone.
Lint cleanup for moved portal sources (apps/portal had no
.eslintrc.cjs; the migrated code now has to pass apps/app's stricter
config):
- axios.isAxiosError → named import { isAxiosError }
(ClaimenTab, RoosterTab, profiel.vue)
- void schemaQuery.refetch() → schemaQuery.refetch()
(register/[public_token].vue)
- if-then-else collapsed to single boolean return (formatFieldValue)
- :delay-on-touch-only="true" → delay-on-touch-only shorthand
(FieldSectionPriority)
- ml-2 class → ms-2 (FieldAvailabilityPicker)
- multi-statement-per-line splits in profiel.vue + spec files
- unused emailConfigured ref removed (profiel.vue)
- one-component-per-file disabled with TODO TECH-WS3-PORTAL-LINT-CLEANUP
ref (FieldOptionsLocale.spec.ts — multi-Wrapper test pattern)
- restored `import Draggable from 'vuedraggable'` after lint:fix
removed it (template-only usage; the import IS needed)
- camelcase param renamed in FieldOptionsLocale harness factory
- typecheck nudge: spec state.data typed via PublicFormSectionOption[] /
PublicFormTimeSlot[] aliases instead of Record<string, unknown>
- PortalLayout.vue: explicit `import { useRoute, useRouter }` so the
vitest mock can intercept (the trimmed AutoImport set doesn't pull
vue-router's auto-imports)
Vitest: 23 / 162 passing. Lint: 0 errors / 0 new warnings (only the
pre-existing boundaries v5→v6 deprecation warnings remain). Typecheck:
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VAutocomplete ignores interleaved fake header items — they were filtered
out before reaching the template. Replace with Approach A: keep only
real selectable items sorted by group, detect group boundaries in the
#item template by comparing adjacent groupName values, and render
VListSubheader before each new group.
- Remove _isGroupHeader from TimeSlotDropdownItem interface
- Rename groupTimeSlots → sortedItems (returns only selectable items)
- Add hasGroups computed for conditional header rendering
- Add isNewGroup(index) boundary detection in CreateShiftDialog
- Add scoped .time-slot-dimmed CSS class (opacity: 0.65)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>