Adds a custom ESLint rule (local-rules/require-v2-layout-meta) that
fails any src/pages-v2/**.vue page missing
definePage({ meta: { layout: 'OrganizerLayoutV2' } }) (or PortalLayoutV2
under pages-v2/portal), preventing a silent wrong-shell fallback to the
default layout (RFC-WS-GUI-REDESIGN AD-G2). Wires eslint-plugin-local-rules
+ a pages-v2 override. The RuleTester spec is called at top level (ESLint
RuleTester self-manages describe/it under Vitest) and vitest.config.ts
gains the eslint-rules test glob so the spec is discovered.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Installs Storybook 10.4 in apps/app/ as a component-development and
autodoc tool. Configures viteFinal with all seven SPA aliases so
stories resolve imports identically to the dev/build pipeline.
preview.ts reuses @/plugins/primevue's installPrimeVue() so Storybook
stays in lock-step with main.ts whenever the PrimeVue config changes.
Only the addons we need are wired: addon-docs (autodocs) and
addon-a11y (axe-core checks). addon-interactions is intentionally
omitted — interaction testing stays in Playwright CT per the testing
architecture.
Seed stories: PrimeVue Button (Primary/Secondary/Danger), Tailwind
utility box, and FormField (Default/WithError/Disabled) wrapped in
@primevue/forms Form + Zod resolver.
Adds make storybook target alongside make app / make docs.
Packages installed:
- primevue@4.5.5
- @primeuix/themes@2.0.3 (substitutes @primevue/themes per ecosystem
state — see rationale below)
- @primevue/forms@4.5.5
- primelocale@1.6.0 (pinned to ^1 per RFC)
- tailwindcss@4.3.0
- @tailwindcss/vite@4.3.0
- tailwindcss-primeui@0.6.1
Package substitution: @primevue/themes → @primeuix/themes
RFC v1.0 §6 F3 specifies @primevue/themes@^4.5, but during install pnpm
reported this package as deprecated by its maintainers (PrimeFaces) with
explicit guidance to migrate to @primeuix/themes. Web verification confirms
that the official PrimeVue 4 install documentation at primevue.org/vite/
now specifies `@primeuix/themes` directly, not the deprecated path:
pnpm add primevue @primeuix/themes
import Aura from '@primeuix/themes/aura';
@primeuix/themes is maintained by the same maintainers (mert.sincan,
cagatay.civici), has the same API surface (Aura preset, definePreset,
semantic tokens), and is the path PrimeVue 4's documentation now
prescribes. The substitution is not a deviation from PrimeVue v4
conventions — it IS the current PrimeVue v4 convention.
The RFC will be amended in B9 to align AD-2 and Appendix B with this
ecosystem state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Foundation for the upcoming component / integration / a11y tests.
vitest.config.ts now declares two projects:
- "unit" — pure-logic tests under tests/unit/, src/**/__tests__/,
and tests/*.spec.ts (the legacy sanity test).
happy-dom, no Vuetify, fast path.
- "component" — tests under tests/component/, tests/integration/,
tests/a11y/. jsdom, Vuetify inlined via SSR noExternal,
CSS imports processed (so :root token sheet loads), and
no global vue-router mock so the real router can run.
Both share the same alias map and AutoImport bag.
tests/utils/mountWithVuexy.ts (new):
- Real Vuetify with the Crewli theme tokens
- createTestingPinia (actions execute by default; stubActions opt-in)
- vue-router with memory history at the configured initialPath + ?query
- Fresh QueryClient per call (zero cross-test cache leak)
- Notification mock injected via Pinia plugin so any useNotificationStore()
resolves to { show: vi.fn(), hide: vi.fn() } — matches the actual
NotificationStore API surface (per Phase A finding A4)
- Imports `@/styles/tokens/_timetable.css` at module load so JSDOM resolves
var(--tt-…) when components call getComputedStyle()
tests/setup.component.ts (new):
- vitest-axe matcher registration
- JSDOM polyfills: scrollIntoView, ResizeObserver, visualViewport, body
bounding rect — Vuetify menus / overlays would crash without them
- Deterministic crypto polyfill (mirrors tests/setup.ts so
generateIdempotencyKey() is stable, but without the router mock)
tests/component/_smoke.test.ts (new):
- Mounts a trivial component → asserts wrapper, queryClient, pinia,
router, notificationMock all populated
- Calls getComputedStyle(documentElement).getPropertyValue('--tt-status-confirmed-bg')
→ asserts '#e8f8f0' (proves the CSS token sheet really loaded)
devDependencies added: jsdom, axe-core, vitest-axe, @pinia/testing.
Total: 319 → 321 tests; 42 → 43 files. Both projects green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strict-regex sweep of apps/app/src/ confirms zero VeeValidate usage:
no `from 'vee-validate'` imports, no <Field|Form|ErrorMessage>,
no defineRule(), no useForm(). The 15 prior fuzzy matches were
false positives where /useForm/ matched useFormDraft/useFormSteps/
useFormSchemas/useFormFailures.
Changes:
- Remove `vee-validate` and `@vee-validate/zod` from apps/app/package.json
- Regenerate pnpm-lock.yaml (no other deps shifted)
- CLAUDE.md "Forms": replace VeeValidate prescription with the actual
ref + @core/utils/validators + Zod-payload-schema pattern that the
codebase already uses everywhere
- VUEXY_COMPONENTS.md: correct the stale "Registration uses VeeValidate"
claim (the page actually uses useFormDraft + validators); update the
"Form validation" reference row
- BACKLOG.md: close VEE-001 with the audit trail
All 319 existing tests still pass; vue-tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WS-7 PR-3 commit 1. Frontend mirror of the backend SDK install
(commits bdb89a2..adab3be), wired against the existing apps/app SPA.
- pnpm add @sentry/vue@10.52.0 (pinned).
- src/observability/sentry.ts: initSentry() — empty DSN no-op (RFC §3.3),
errors-only (tracesSampleRate=0, profilesSampleRate=0; RFC §2 amend.B),
sendDefaultPii=false, Console integration off, beforeSend wired to the
scrubber, initial scope tag app=app for GlitchTip filtering.
- src/observability/scrubber.ts: TypeScript port of backend
SentryEventScrubber. RFC §3.7 frontend block — body / header / query
scrubbing, form_values wholesale replacement, cookies wholesale,
defensive strip of contexts.storage and user.cookies, max-depth guard.
- src/observability/contextBinding.ts: Vue Router beforeEach guard that
binds RFC §3.6 auth-scope tags per navigation. Three zones via
route.meta.public + route.path matching:
- portal token zone (meta.public + meta.context=portal) → actor_scope=
portal, no user_id (RFC §3.6 explicit)
- /platform/* with super_admin → actor_scope=platform, no org tag
- default authenticated → actor_scope=organisation when an active
organisation is selected (useOrganisationStore.activeOrganisationId),
otherwise actor_scope=user
- unauthenticated public pages → actor_scope=anonymous
Reads useAuthStore (user, appRoles, isSuperAdmin) and
useOrganisationStore (activeOrganisationId) — corrected vs. RFC's
speculative auth-store API.
- src/observability/index.ts: barrel.
- src/main.ts: initSentry runs before registerPlugins so Sentry's Vue
errorHandler hooks before any plugin or component initialises;
installContextBinding runs after registerPlugins so pinia is up.
- env.d.ts: VITE_SENTRY_DSN_FRONTEND + VITE_SENTRY_RELEASE typed.
- .env.example: new file (didn't exist before) documenting all SPA env
vars including the new Sentry pair.
- vite.config.ts: build.sourcemap=true (RFC §3.5 — generated, uploaded
to GlitchTip by deploy.sh, then stripped before nginx serves dist/).
Typecheck: green. Build: green, *.map files emitted alongside *.js
chunks as expected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds eslint-plugin-boundaries@6.0.2 (MIT, peerDeps eslint>=6,
engines node>=18.18) as a direct devDep in apps/app/package.json,
matching the exact-pin style of the other 14 eslint-plugin-* deps.
Direct dep — not hoisted transitive — per the
TECH-PORTAL-ESLINT-DEPS lesson (Cursor's ESLint extension uses
strict module resolution and silently fails on plugins reachable
only via pnpm hoisting).
Plugin not yet enabled in .eslintrc.cjs; enabling lands in the next
commit per WS-3 1c sequence (audit Phase A → install → enable →
refactor axios.ts → docs).
Tests + typecheck verified green post-install.
Co-Authored-By: Claude <noreply@anthropic.com>
Surfaced during WS-3 1c-prep follow-up: Cursor's ESLint extension uses
strict module resolution and crashed on every plugin in the
@antfu/eslint-config-vue extends-chain that was only resolvable via
pnpm-hoisting in terminal.
Direct deps added (versions match what was already in pnpm store —
zero version shifts):
- 12 unscoped ESLint plugins (eslint-plugin-{antfu,es-x,html,i,jest,
jsdoc,jsonc,markdown,n,no-only-tests,unused-imports,yml,
eslint-comments})
- vue-eslint-parser
- @antfu/eslint-config-basic + @antfu/eslint-config-ts (extends targets)
- @stylistic/eslint-plugin-js + @stylistic/eslint-plugin-ts
.vscode/settings.json: removed redundant root-level
editor.defaultFormatter (per-language overrides do the job).
ESLint extension now activates correctly, server runs, save-on-format
works for TS/Vue files. Verified via smoke test: double quote in
useImpersonationStore.ts:1 was auto-corrected to single quote on Cmd+S.
Note: package.json declares some deprecated dependencies that pnpm
warns about (@antfu/eslint-config-vue@0.43.1, eslint@8.57.1,
eslint-plugin-i@2.28.1, eslint-plugin-markdown@3.0.1). Those are
pre-existing — not introduced here. Migration to ESLint v9 + flat
config + @antfu/eslint-config (modern) is a separate workstream.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WS-3 session 1b-ii Task 1.
Splits the apps/app lint script:
- \`pnpm lint\` → no-fix; reports problems (used in CI, in audits).
- \`pnpm lint:fix\` → --fix; explicit autofix on demand.
Resolves the cause of the WS-3 1b-i pre-flight confusion: when 'pnpm
lint' silently ran --fix, ad-hoc invocations reported the post-fix
remainder as if it were the baseline (the wrong '105' number that
broke session 1b-i's first attempt).
No code changes. Behaviour change is opt-in per script invocation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirrors apps/portal's Vitest setup so the SPA can take frontend
unit + component tests. Required prerequisite for WS-6 sessie 3b's
admin UI work — apps/portal had 113+ tests, apps/app had zero, and
launching WS-6's organizer UI uncovered while the portal SPA is
well-tested would be asymmetric quality.
Setup:
- vitest, happy-dom, @vue/test-utils, @testing-library/vue installed
- vitest.config.ts mirrors portal config: trimmed auto-imports
(no pinia/vue-router/vue-i18n/@vueuse/math) so tests run fast
in happy-dom without loading the full Vuexy bundle
- AutoImport's dts:false prevents the trimmed test-only set from
clobbering the dev-server's full auto-imports.d.ts (apps/app's
auto-import surface is bigger than the portal's)
- tests/setup.ts mocks vue-router by default; tests that exercise
the real router can override per-suite
- Sample sanity test confirms the harness works end-to-end
Adds `pnpm test` and `pnpm test:watch` scripts to package.json.
Refs: BACKLOG TECH-APP-VITEST, WS-6 sessie 3b prerequisite
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Vulnerable dependencies upgraded:
- Backend: league/commonmark >=2.8.2 (HTML injection bypass),
phpunit/phpunit >=11.5.50, laravel/tinker (psysh LPE)
- Frontend: axios 1.13→1.15 (SSRF + metadata exfiltration),
@casl/ability updated (prototype pollution)
- Removed swiper from all 3 apps (prototype pollution CVE,
only used in Vuexy demo pages)
XSS vectors removed:
- Deleted Vuexy demo pages with v-html rendering API data:
help-center/article, academy/course-details
- Deleted all front-pages (landing, pricing, checkout, payment) —
Vuexy marketing template, not Crewli business logic
- Deleted swiper demo components and views
- Fixed admin main.ts: replaced innerHTML with template literal
with safe DOM construction using textContent
Cookie security:
- Added SameSite=Strict and Secure flags to admin cookie defaults
Cleanup:
- Removed swiper SCSS from all 3 apps
- Removed swiper custom element config from all 3 vite configs
- Portal localStorage cleanup verified: reset() clears all keys,
called on both explicit logout and 401 interceptor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>