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.
WS-7 PR-1 — bring up self-hosted GlitchTip alongside the existing
dev stack. One compose file is portable to the production monitoring
host (RFC-WS-7 §3.1).
- docker-compose.glitchtip.yml: web/worker/postgres/redis pinned, web
bound to 127.0.0.1:8200, internal network for postgres + valkey.
- docker/glitchtip/.env.example: documented dev defaults + production
checklist; .env itself ignored.
- Makefile: services / services-stop merge both compose files; new
services-glitchtip-status tail target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three load-bearing files still described the pre-WS-3 dual-SPA
reality. Surgical edits to reflect the single-SPA architecture
shipped in WS-3 PR-B (B1: portal moves; B2a: auth+routing
consolidation; B2b: server-side cookie consolidation).
CLAUDE.md:
- Quality-gates ts-reset bullet (line 27): "both SPAs" → "the SPA"
- Quality-gates Vitest bullet (lines 30-32): rewrite from "apps/portal
has 113+ tests; apps/app currently has no Vitest setup (TECH-APP-VITEST)"
to current truth: apps/app has Vitest with 213 tests as of PR-B2a.
TECH-APP-VITEST is implicitly closed.
- Repository layout (line 44): drop apps/portal/ bullet; rephrase
apps/app/ as the single workspace
- "Apps and portal architecture" → "App architecture": rewrite for
single-workspace + two access modes. Login-based covers
organizers + volunteers + crew + super_admin (context-routed
in-app via useAuthStore.availableContexts); token-based covers
artists, suppliers, press
- CORS subsection: collapse two-origin config to single origin
(localhost:5174 dev, https://crewli.app prod). Preserve the
existing crewli.nl marketing-only note
WS-TOOLING-001 sections (Larastan, Rector, Telescope tooling
configuration) verified untouched via `git diff CLAUDE.md`.
README.md (line 25): collapse the Applications table from two rows
(Organizer + Portal) to one (SPA). Adjust trailing sentence accordingly.
Makefile:
- .PHONY list: drop `portal`
- help echo: drop "make portal" line
- portal target: removed (the underlying `cd apps/portal && pnpm dev`
would fail since the directory was removed in PR-B1)
Out of scope (deferred to TECH-DOCS-APPS-PORTAL-PURGE backlog item):
.cursor/ instructions, MASTER_PROMPT_*, dev-docs/SETUP, dev-docs/dev-guide,
dev-docs/CLAUDE_CODE_TOOLING. WS-3-SESSION-1C-AUDIT.md skipped (frozen
historical doc).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mysql-client is now installed on the dev host (brew install
mysql-client + PATH=/opt/homebrew/opt/mysql-client/bin), which
unblocks the fast path that session 2.7 prepared but couldn't
activate.
Changes:
- api/database/schema/mysql-schema.sql committed (current state, 122 KB,
1749 lines, all 155 migration records).
- api/database/schema/.gitignore removed: the dump is no longer opt-in,
it's the default code path (active in dev + CI).
- Makefile schema-dump target simplified: drops the docker exec mysqldump
workaround in favour of plain `php artisan schema:dump`. Now also runs
migrate to head first so the dump always reflects the latest migration
set without manual prep.
- CLAUDE.md "Schema dumps" rewritten: "opt-in fast path" → "CI fast
path", reflects that the dump is committed by default and contributors
regenerate via `make schema-dump` after adding migrations.
Backfill test wall-time, 4 classes combined:
- Session 2.7 baseline: 127.90s
- This session: 27.55s (78% reduction)
Per class (PHPUnit Duration):
- FormFieldBindingMigrationTest: 4.59s (2 tests)
- ConditionalLogicBackfillTest: 5.45s (4 tests)
- FormFieldOptionsBackfillTest: 12.25s (9 tests)
- FormFieldValidationRuleBackfillTest: 10.75s (6 tests)
Full suite knock-on: 209.95s → 89.16s (-57%) — every RefreshDatabase
test pays the up-front migrate cost, which `schema:dump` collapses
from ~6s × N to a single ~1s SQL load.
Refs: WS-6 session 2.7 deviation #3 cleanup, Q1 closure
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Session 2.6's per-test migrate:fresh in 4 backfill test classes
replays ~120 migrations. Laravel's schema:dump produces a single
SQL file that migrate:fresh loads atomically when present —
significantly faster on environments where the host has the
\`mysql\` CLI available for Laravel's load step.
Changes:
- New Makefile target \`schema-dump\` runs mysqldump INSIDE the
bm_mysql Docker container (no host mysqldump dependency to
GENERATE the dump). Outputs api/database/schema/mysql-schema.sql.
- api/database/schema/.gitignore added: mysql-schema.sql is NOT
committed by default. Laravel's auto-load on migrate:fresh
shells out to the host's \`mysql\` CLI; on hosts without it,
the presence of the dump file actively breaks migrate (exit 127).
Treat the dump as opt-in per dev environment.
- CLAUDE.md "Schema dumps (opt-in fast path)" subsection added with
the workflow: brew install mysql-client → migrate to head →
make schema-dump → optionally commit.
phpstan-baseline.neon: removed a stale "unused use \$actor" entry
in FormSubmissionService whose underlying closure pint cleaned up
in commit 060d6f3 (Task 1).
Wall-time on this dev machine (no host mysql CLI): NO speedup,
backfill tests still ~6s per setUp. The dump file is ready for
environments that have mysql CLI. Documented as a deviation.
JSON canonicalization (commit 060d6f3) is the load-bearing
correctness fix from this branch; the schema-dump perf path is a
nice-to-have that activates per-environment.
Refs: WS-6 session 2.6 deviation #5 cleanup
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test infrastructure now uses the same MySQL 8.0 engine as local dev
and production. SQLite is no longer used anywhere in the project.
Eliminates the SQLite "rebuild on FK add" quirk that forced session 2.5
to omit a foreign key on form_schemas.default_crowd_type_id (Task 2 of
this session restores it).
Configuration:
- phpunit.xml: DB_CONNECTION=sqlite (:memory:) replaced with mysql
pointing at crewli_test database (127.0.0.1:3306, crewli/secret)
- Makefile: new test-db-create target creates crewli_test in the
bm_mysql Docker container; make test ensures it exists before
running suite
Latent-bug surfacing — fixes that MySQL exposed:
1. form_submissions.idempotency_key was declared `ulid()` (VARCHAR 26)
while FormRequest validates `string|max:30`. SQLite ignored the cap;
MySQL truncated and rejected. Column widened to string(30) to match
validation.
2. FormFieldValidationRuleService / FormFieldConfigService /
FormFieldBindingService::snapshotShapesFor — toJsonShape iterated
collection in DB-default order (insertion-stable on SQLite, undefined
on MySQL). Schema_snapshot bytes drifted across re-emits, breaking
audit-replay. Added `->sortBy('id')` (ULID = insertion-order
semantics, deterministic) on all three.
3. FormSubmissionObserverTest::test_denormalized_indexes_exist queried
sqlite_master directly. Replaced with the cross-engine
information_schema.STATISTICS query (the real production check is
on MySQL anyway).
4. JSON column key order non-determinism: MySQL JSON columns may
round-trip associative-array keys in a different order than they
were inserted. assertSame on JSON-derived associative arrays now
uses assertEquals (structural equality) where the test was previously
over-asserting on key order:
- ConditionalLogicActivityLogPayloadTest
- ConditionalLogicBackfillTest::test_rollback_reconstructs_canonical_json
- FormFieldBindingMigrationTest::test_rollback_reconstructs_json_and_drops_table
- FormFieldOptionServiceAndScopeTest::test_replace_options_emits_activity_log_on_field_only
- FormFieldOptionsActivityLogTest::test_field_updated_payload_contains_options_diff_when_options_change
- FormFieldOptionsBackfillTest::test_forward_migration_backfills_rows_strips_translations_and_rewrites_snapshot
- FormFieldOptionsSnapshotAndStrictRequestTest::test_submission_snapshot_embeds_rich_shape_options
5. Backfill / migration tests (4 classes, 21 tests) ran migrate:rollback
then migrate inside RefreshDatabase's wrapping transaction. MySQL
DDL implicit-commits the surrounding transaction, leaving Laravel
unable to ROLLBACK TO SAVEPOINT at end-of-test (1305 SAVEPOINT
does not exist). Replaced RefreshDatabase with a per-test
migrate:fresh in setUp + RefreshDatabaseState::\$migrated = false to
force the next RefreshDatabase test to re-migrate cleanly:
- FormFieldBindingMigrationTest
- ConditionalLogicBackfillTest
- FormFieldOptionsBackfillTest
- FormFieldValidationRuleBackfillTest
All 1386 tests now pass on MySQL. Larastan baseline unchanged.
Refs: WS-6 session 2.5 deviation #1 cleanup, RFC-WS-6.md v1.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>