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>
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>