Per RFC-WS-PRIMEVUE-PLAN-2-5 §5.6–§5.10. Final code phase of Plan 2.5
before closure docs (P7 tokens, P8 closure).
Changes:
- Fix 9: sidebar full-height. The desktop <aside> now carries
`h-screen sticky top-0` so it fills the viewport vertically and
pins to top on body scroll. Without this the aside sized to its
children's intrinsic heights (~250-400px) and ended mid-viewport
even though the surrounding grid row stretched to 100vh. With
h-screen, SidebarNav's `flex-1` claims the remaining column space
and WorkspaceSwitcher anchors to the true viewport bottom — its
`border-t` (existing from P5) is now the divider above the
switcher per crewli-starter. Mobile Drawer untouched (PrimeVue's
internal pt classes already give it 100% panel height).
- Fix 10: density toggle promoted to the store. New
useShellUiStore.toggleDensity() flips comfortable ⇔ compact and
calls applyDomAttributes() synchronously. AppTopbar's local
toggleDensity wrapper deleted; the button now invokes
shell.toggleDensity() directly and carries a stable
data-testid="density-toggle" plus a `title` matching its
aria-label. Density icons swapped from generic flex-alignment
glyphs (tabler-layout-distribute-{vertical,horizontal}) to the
literal density metaphor (tabler-baseline-density-{small,medium}).
Both new icons verified present in the loaded
@iconify-json/tabler set. Topbar right-side order
(search → density → dark → notifications → user) was already
correct from P5; locked with a new ordering spec.
Verified (no code change):
- Fix 6 (§5.6): dark mode `.dark` on <html> confirmed in
useShellUiStore.applyDomAttributes (AD-2.5-D1, P3 complete).
Component-level dark coverage remains a separate backlog item
(DARKMODE-V2-COVERAGE).
- Fix 8 (§5.8): the ▼ arrow is the Vue DevTools v8.0.2 dev-only
toggle button injected by the devtools vite plugin, not Crewli
code — diagnosed, no action.
- Fix 7 (§5.7): non-reproducible at code level. Topbar is
`sticky top-0` and is a SIBLING flex item of <main> inside the
shell's flex-col right column; normal flow stacks <main> below
the topbar at first paint, so the title cannot fall behind a
sticky topbar in this composition. Documented as no-op; if
Bert reproduces it after Fix 9 lands, the symptom is something
else (likely a per-page negative margin or a separate scroll-
container interaction worth its own ticket).
Density enum corrected against runtime data-density: 'comfortable'
(not 'comfy' — the earlier RFC assumption is wrong; the store has
always typed `'comfortable' | 'compact'`).
Tests:
- +2 useShellUiStore.spec.ts: toggleDensity flips comfortable ⇔
compact AND writes data-density via applyDomAttributes;
toggleDensity from compact returns to comfortable on call 2.
- +2 AppTopbar.spec.ts: density button reachable by
data-testid="density-toggle"; topbar right-side order locked
via HTML index comparison (search → density → dark → notif →
user). Existing density-flip specs adapted to spy on
toggleDensity (the new direct call site).
Suite delta: 554 → 558 (+4). vue-tsc clean. Scoped ESLint clean
(0 errors, pre-existing warnings only).
Manual smoke pending Bert:
1. Sidebar full-height, switcher pinned to viewport bottom (Fix 9)
2. Page title clears topbar (Fix 7 — expected no change needed)
3. Density toggle visible between search and dark with the
density icon (Fix 10)
4. Click density toggle → spacing visibly changes, <html
data-density> flips between comfortable and compact (Fix 10)
5. Topbar order: search → density → dark → notifications →
avatar (Fix 10)
6. Dark mode still toggles (Fix 6 regression)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Crewli
Multi-tenant SaaS platform for event and festival operations: planning, people, accreditation, artist advancing, volunteer shifts, briefings, and show-day tooling (Mission Control). The backend is a JSON-only Laravel API; all user interfaces are Vue 3 single-page apps.
What Crewli covers
- Organisations & events — Multi-tenant data with organisation-scoped access; events move through a defined lifecycle (draft → published → registration → buildup → show day → teardown → closed).
- Festival structure — Sections, time slots, shifts, assignments, claiming/approval flows for volunteers and crew.
- People & crowds — Crowd types (crew, volunteers, artists, guests, press, partners, suppliers), persons, lists, and (planned) rich accreditation (items, zones, hand-out).
- Artists & advancing — Booking status, stages/timetable concepts, advance sections and token-based portal access for external artists.
- Communication — Briefings, campaigns, and operational messaging (see architecture doc for target modules).
- Portal (external users) — One portal app, two modes: login (Sanctum) for long-term users such as volunteers, and token access for per-event links (e.g. artists, suppliers).
Implementation is phased; the authoritative feature and schema list lives in the architecture and design references below.
Applications
| App | Path | Port | Role |
|---|---|---|---|
| SPA | apps/app/ |
5174 | Single-SPA product covering organizers, volunteers, crew, super admins (context-routed in-app), plus token-based access for artists, suppliers, press. Includes Platform Admin section for super admins (/platform/*). |
The SPA talks to the API over CORS with Laravel Sanctum tokens.
Tech stack
| Layer | Technology |
|---|---|
| API | PHP 8.2+, Laravel 12, Sanctum, Spatie Permission (and Activity Log / Media Library where used) |
| Data | MySQL 8, Redis (cache/queues) |
| Frontends | Vue 3, TypeScript, Vite, Vuexy + Vuetify, Pinia, TanStack Query, VeeValidate + Zod |
| Local services | Docker Compose (MySQL, Redis, Mailpit) |
Rule of thumb: business tables use ULID primary keys; event-related data is scoped by organisation (global scopes + policies), not ad hoc where clauses in controllers.
Project structure
crewli/
├── api/ # Laravel 12 REST API (JSON only)
├── apps/
│ ├── app/ # Organizer SPA (primary UI + platform admin)
│ └── portal/ # External portal SPA
├── docker/ # Docker / Compose assets
├── docs/ # SETUP, API notes, schema notes
├── resources/
│ ├── design/ # Product source of truth (design docs, see table below)
│ └── vuexy-admin-*/ # Vuexy template reference (bundled kit)
├── .cursor/ # ARCHITECTURE.md, instructions.md, rules for AI/helpers
└── Makefile # Dev commands
Vuexy @core/ and @layouts/ in each app should stay untouched; customize via app config, navigation, and app-level components.
Quick start
# 1. Infrastructure
make services
# 2. API env, dependencies, database (see docs/SETUP.md)
cd api && cp .env.example .env && composer install && php artisan key:generate && php artisan migrate
# 3. Run API + the SPAs you need (separate terminals)
make api
make app
make portal
Detailed setup: docs/SETUP.md.
Development URLs
| Service | Development | Env / notes |
|---|---|---|
| API | http://localhost:8000/api/v1 | Base path /api/v1 |
| Organizer | http://localhost:5174 | FRONTEND_APP_URL |
| Portal | http://localhost:5175 | FRONTEND_PORTAL_URL |
| Mailpit | http://localhost:8025 | Local mail capture |
Production (crewli.app)
Domains: crewli.app is this product (API + organizer + portal SPAs, transactional email from the app, seeds, etc.). crewli.nl is reserved for a future public marketing site only — do not point this codebase's APP_URL, CORS, Sanctum, or app mail at crewli.nl.
Typical layout (configure the same values in api/.env — see api/.env.example):
| Service | URL | Env variable |
|---|---|---|
| API | https://api.crewli.app |
APP_URL |
| Organizer | https://crewli.app |
FRONTEND_APP_URL |
| Portal | https://portal.crewli.app |
FRONTEND_PORTAL_URL |
Frontends: set VITE_API_URL=https://api.crewli.app/api/v1 in each app's env for production builds. SANCTUM_STATEFUL_DOMAINS must list the hostnames only of the two SPAs (e.g. crewli.app,portal.crewli.app).
Makefile commands
make services # MySQL, Redis, Mailpit
make services-stop
make api # Laravel on :8000
make app # Organizer on :5174
make portal # Portal on :5175
make migrate
make fresh # migrate:fresh --seed
make db-shell
Documentation
| Resource | Contents |
|---|---|
| CLAUDE.md | Project conventions, vibe-coding principles, Vuexy-first decision tree (auto-loaded by Claude Code). |
| .cursor/rules/ | Workspace, Laravel, Vue, testing conventions. |
| dev-docs/SETUP.md | Environment and local setup. |
| dev-docs/SCHEMA.md | Database schema (kept in sync with migrations). |
| dev-docs/API.md | API contract. |
| dev-docs/design-document.md | Product specification. |
Testing
cd api && php artisan test
Feature tests should cover happy paths plus 401 (unauthenticated), 403 (wrong organisation), and 422 (validation) where applicable.