# Vue Dealer Portal — Zyel Lab

By Chason Arthur. A bounded synthetic catalog → quote → human review → order story, with a compiled Vue/TypeScript browser fixture and a real isolated Laravel API implementation.

The public browser uses **fixture mode with simulated roles**. It makes no live authentication or order calls. Cart, quotes, orders and audit events remain in page memory. Only appearance, category and table configuration use browser-local preferences.

## Reproduce

From `lab/dealer-portal` with Node 24 and npm:

```sh
npm ci
npm run typecheck
npm run build
npm test
```

From `backend`, with PHP 8.4 and Composer plus mbstring/OpenSSL/PDO SQLite/DOM/XML/fileinfo enabled:

```sh
composer install --no-interaction --no-scripts --prefer-dist
mkdir -p storage/framework/sessions storage/framework/views storage/framework/cache/data bootstrap/cache
php vendor/bin/phpunit --colors=never
```

On Windows, `scripts/php-test.ps1` enables bundled PHP extension DLLs only for that process. A restricted Windows sandbox can make PHP `is_readable` fail although ordinary reads work; use the task's approved test executor in that case. Do not modify machine PHP settings.

From the repo root, start the static preview:

```sh
LAB_PORT=4195 node lab/tools/serve.mjs .
```

Then, from this demo folder:

```sh
npx playwright install chromium
npm run test:e2e
```

`LAB_ORIGIN` overrides the loopback origin. `PLAYWRIGHT_MODULE` can point to an existing installed Playwright module; `PLAYWRIGHT_BROWSERS_PATH` can select an existing browser cache. The E2E starts no external API calls.

## Computer tutorial preparation

`recording.html` contains six original SAY/SHOW/EXPECTED chapters, speaker notes, prerequisites and practical pitfalls. Existing `tutorial.html#step-1` through `#step-8` remain stable. `#scene-result`, `#scene-catalog`, `#scene-review`, `#scene-recovery`, `#scene-audit` and `#scene-evidence` focus existing demo regions after Vue/table readiness; navigation does not mutate cart, role or preferences. Invalid scenes are ignored. The optional shared tools support script download, presentation/Escape, literal gesture-copy snippets with a selectable fallback, and a future-video URL check that loads no external content. Six implementation cuts are generated from actual TypeScript/PHP source by `scripts/build-teaching.mjs`; the build refreshes code, line ranges and normalized LF source hashes. Their full source links, alternatives/pros/cons, pitfalls and verification remain readable. The guide reads local JSON evidence only; it never calls the backend. No narrated video exists.

Run `npm run test:recording` against the isolated preview. `LAB_EVIDENCE_DIR` selects a private evidence directory. On Windows use a `file:///C:/.../playwright/index.mjs` URL for `PLAYWRIGHT_MODULE` and `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` for the existing approved browser. Review screenshots separately at 1440/390; production keyframes are actual 1920×1080 browser stills.

The main page recording controls add confirmed temporary scenes. A bounded, validated first-original checkpoint contains only this tab’s synthetic workflow in private memory. Repeated reset preserves it; restore cancels pending work without replay. The persistent table is destroyed before temporary rows/category change; temporary tables use `persistState:false` and category watchers suppress preference writes. Reload loses the checkpoint. Global appearance changes are never rolled back. Run `node tests/session-e2e.mjs` for the reset/restoration boundary and `node tests/storage-scene-e2e.mjs` for actual denied/quota original→temporary→same-scope restoration. The shared store caches its original memory handle; temporary table state never replaces it.

## Backend proof and limits

`evidence/local-backend-rerun.json` records an actual expanded-package local rerun on in-memory SQLite, with sanitized output and hashes of the unchanged backend files. This is not a new hosted MySQL run or a live backend activation.

Laravel HTTP/database suite: **10 tests, 46 assertions**, passed on PHP 8.4.24 / Laravel 13.38.0 / PHPUnit 13.4.3 / Mockery 1.6.15 / SQLite 3.53.2. It checks guests, disabled API, category pagination, server prices, key/body conflicts, input limits, tenant lookup and policy, approval, idempotency, expiration, stock changes, audit-failure rollback and retry recovery.

The API has no demo login. Its default boundary is disabled and refuses non-local/non-testing environments. HTTP tests resolve synthetic users through Laravel `actingAs`; that verifies authorization but is not a production identity-provider test. The sample session driver is intentionally not a persistent login system. A future browser/API connection requires an existing approved identity/session integration and isolated hosting approval. Never use role headers as identity.

The migration and MySQL connection profile are implementation source. **Hosted CI run 40152057455 passed the Laravel suite on disposable MySQL 8.4.13 (10 tests, 46 assertions) and SQLite, plus the browser job.** Its observed Linux PHP runtime was 8.4.27 despite the workflow input requesting 8.4.24. Raw MySQL plans and warm-query measurements are in `evidence/mysql-query-benchmark.json`; source/run provenance is in `evidence/ci-run.json`. Row-lock/distributed concurrency, production throughput and persistent identity remain unproven. The integration writer originally promoted `ci/dealer-portal.yml` to `.github/workflows/dealer-portal.yml`; the linked hosted run is the evidence of execution, rather than the recipe alone. PHPUnit's XML defaults are not forced, so CI matrix environment variables select the database. The guarded `backend/tools/mysql-benchmark.php` uses a connection-local temporary InnoDB table with 50,000 synthetic rows, captures raw `EXPLAIN ANALYZE` before/after plus 30 warm timings, and checks identical-result hashes. It refuses to run outside the acknowledged disposable CI fixture. The owned expanded CI recipe adds `npm run test:expanded` and its ignored test-results artifacts. The integration writer must promote that updated recipe; no new hosted run is implied by preparing it. JUnit, MySQL measurement and browser evidence are uploaded as seven-day CI artifacts; a hosted run must actually finish before its results can be claimed.

Run `scripts/php-test.ps1 -Benchmark` on Windows, or `php backend/tools/benchmark.php` with PDO SQLite enabled. `evidence/query-benchmark.json` contains actual before/after plans, 30 runs after 5 warmups, exact synthetic data recipe, median/p95, environment and identical-result hashes. The 50,000-row in-memory slice is not a production or MySQL benchmark. `backend/tools/mysql-plan.sql` supplies non-destructive plan commands for an approved disposable MySQL fixture.

## Release boundary

Public static allowlist: `index.html`, `tutorial.html`, `recording.html`, `recording-plan.mjs`, `recording-guide.mjs`, `recording-snippets.mjs`, `teaching-examples.mjs`, `retry-contract.mjs`, `portal.css`, `README.md`, `package.json`, `package-lock.json`, `dist/**`, `source/**`, `evidence/**`.

**Exclude `backend/**`, `node_modules/**`, `.tools/**`, `tests/**`, `scripts/**`, `.env`, and all dependencies from public static publication.** The backend Apache deny file is a fallback, not the packaging boundary. If a separately approved isolated backend is later provisioned, serve only `backend/public` and keep the database, vendor tree and config outside public access. No real orders, payments, customer data or autonomous sends.

Build copies reviewed source excerpts to `source/*.txt`; public links do not execute PHP. Keep the prior static artifact and SHA for rollback. After an authorized publication, read back both demo/tutorial URLs and smoke-test the catalog and Celebration.js. Keep prepared, tested, independently reviewed and live statuses separate.

## Fixture retry contract

The fixture validates the buyer role, bounded 8–80-character key and strict SKU/quantity-only body before cache lookup. Canonicalization uses only contract fields and sorted SKUs, independent of item/property insertion order. Existing identical keys return the original detached quote without renewing expiry or rechecking mutable stock; new keys still validate availability. Changed bodies conflict. Order keys are bounded too. The seven domain tests include this boundary; the recording guide offers an explicit fresh-engine local check with eight derived assertions and no page state/storage/backend access. It is a fixture contract check, not server authorization or a load test. The separate backend source and historical MySQL evidence remain unchanged. Run `npm run test:retry` for the actual derived check, keyboard focus, module-failure recovery and privacy boundary.

## Table restoration checks

Bootstrap and scene restoration use `select(id,{reveal:false})` so the default selected product does not rewrite a saved page. The status discloses an off-page selection; explicit user selection still reveals it. Shared preference reset clears the table key/API/controls without reloading the synthetic workflow. `npm run test:table-state` covers saved page/length/multi-sort/visibility/reorder, exact envelopes across reload, interrupted scene reload, current global appearance and the table reset boundary at1440/390 across paper/dark/blue.

Each update package requires at least five substantive executor/independent-critic cycles with findings, concrete changes and rendered re-verification. Passing tests or preparing stills does not authorize publication.

## Maintenance

Rebuild and refresh source excerpts after changes. Recheck dependency advisories and official docs, run the meaningful tests, review the exact static manifest, and migrate table/preferences only through the shared versioned contract. Never use preference storage for business records, credentials or conversations. Browser configuration is local to that device/browser, not account sync.

Full step-by-step tutorial: `tutorial.html`. Evidence: `evidence/verification.json`, `evidence/query-benchmark.json`, rendered `evidence/e2e.json` and screenshots when produced.
