Running 4 tests using 1 worker

  ✘  1 [chromium] › tests/e2e/player-offline.spec.ts:3:5 › lightweight player keeps cached playback when browser goes offline (20.6s)
  ✘  2 [chromium] › tests/e2e/player-runtime-side-effects.spec.ts:27:5 › lightweight player records heartbeat and impression side effects (46.5s)
  ✘  3 [chromium] › tests/e2e/tvs-offline.spec.ts:3:5 › screen runtime keeps cached playback when browser goes offline (20.5s)
  ✘  4 [chromium] › tests/e2e/tvs-runtime-side-effects.spec.ts:27:5 › screen runtime records heartbeat and impression side effects (46.5s)

  1) [chromium] › tests/e2e/player-offline.spec.ts:3:5 › lightweight player keeps cached playback when browser goes offline

    Error: expect(locator).toBeVisible() failed

    Locator: getByText('Playback: cached')
    Expected: visible
    Timeout: 20000ms
    Error: element(s) not found

    Call log:
      - Expect "toBeVisible" with timeout 20000ms
      - waiting for getByText('Playback: cached')

      13 |
      14 |   await expect(page.getByText("Shell: lightweight-html-js")).toBeVisible({ timeout: 20_000 });
    > 15 |   await expect(page.getByText("Playback: cached")).toBeVisible({ timeout: 20_000 });
         |                                                    ^
      16 |   await expect(page.getByText("Active source: blob")).toBeVisible();
      17 |   await expect(page.getByText("Cache API: available")).toBeVisible();
      18 |   await expect(page.getByText(/^Cached creatives: [1-9]\d*$/)).toBeVisible();
        at /mnt/rpi/repos/ballbox/tests/e2e/player-offline.spec.ts:15:52

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    test-results/player-offline-lightweight-65f40-k-when-browser-goes-offline-chromium/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: test-results/player-offline-lightweight-65f40-k-when-browser-goes-offline-chromium/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    test-results/player-offline-lightweight-65f40-k-when-browser-goes-offline-chromium/trace.zip
    Usage:

        pnpm exec playwright show-trace test-results/player-offline-lightweight-65f40-k-when-browser-goes-offline-chromium/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────

  2) [chromium] › tests/e2e/player-runtime-side-effects.spec.ts:27:5 › lightweight player records heartbeat and impression side effects

    Test timeout of 45000ms exceeded.

    Error: page.waitForResponse: Test timeout of 45000ms exceeded.

      31 |     (response) => response.request().method() === "POST" && response.url().endsWith("/api/tvs/heartbeat")
      32 |   );
    > 33 |   const impressionResponsePromise = page.waitForResponse(
         |                                          ^
      34 |     (response) => response.request().method() === "POST" && response.url().endsWith("/api/tvs/impression")
      35 |   );
      36 |
        at /mnt/rpi/repos/ballbox/tests/e2e/player-runtime-side-effects.spec.ts:33:42

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    test-results/player-runtime-side-effect-187f9-and-impression-side-effects-chromium/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    attachment #2: video (video/webm) ──────────────────────────────────────────────────────────────
    test-results/player-runtime-side-effect-187f9-and-impression-side-effects-chromium/video.webm
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: test-results/player-runtime-side-effect-187f9-and-impression-side-effects-chromium/error-context.md

    attachment #4: trace (application/zip) ─────────────────────────────────────────────────────────
    test-results/player-runtime-side-effect-187f9-and-impression-side-effects-chromium/trace.zip
    Usage:

        pnpm exec playwright show-trace test-results/player-runtime-side-effect-187f9-and-impression-side-effects-chromium/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────

  3) [chromium] › tests/e2e/tvs-offline.spec.ts:3:5 › screen runtime keeps cached playback when browser goes offline

    Error: expect(locator).toBeVisible() failed

    Locator: getByText('Playback: cached')
    Expected: visible
    Timeout: 20000ms
    Error: element(s) not found

    Call log:
      - Expect "toBeVisible" with timeout 20000ms
      - waiting for getByText('Playback: cached')

      12 |   await page.goto("/player/screen_001");
      13 |
    > 14 |   await expect(page.getByText("Playback: cached")).toBeVisible({ timeout: 20_000 });
         |                                                    ^
      15 |   await expect(page.getByText("Active source: blob")).toBeVisible();
      16 |   await expect(page.getByText("Cache API: available")).toBeVisible();
      17 |   await expect(page.getByText(/^Cached creatives: [1-9]\d*$/)).toBeVisible();
        at /mnt/rpi/repos/ballbox/tests/e2e/tvs-offline.spec.ts:14:52

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    test-results/tvs-offline-screen-runtime-6072b-k-when-browser-goes-offline-chromium/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: test-results/tvs-offline-screen-runtime-6072b-k-when-browser-goes-offline-chromium/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    test-results/tvs-offline-screen-runtime-6072b-k-when-browser-goes-offline-chromium/trace.zip
    Usage:

        pnpm exec playwright show-trace test-results/tvs-offline-screen-runtime-6072b-k-when-browser-goes-offline-chromium/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────

  4) [chromium] › tests/e2e/tvs-runtime-side-effects.spec.ts:27:5 › screen runtime records heartbeat and impression side effects

    Test timeout of 45000ms exceeded.

    Error: page.waitForResponse: Test timeout of 45000ms exceeded.

      31 |     (response) => response.request().method() === "POST" && response.url().endsWith("/api/tvs/heartbeat")
      32 |   );
    > 33 |   const impressionResponsePromise = page.waitForResponse(
         |                                          ^
      34 |     (response) => response.request().method() === "POST" && response.url().endsWith("/api/tvs/impression")
      35 |   );
      36 |
        at /mnt/rpi/repos/ballbox/tests/e2e/tvs-runtime-side-effects.spec.ts:33:42

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    test-results/tvs-runtime-side-effects-s-a14b9-and-impression-side-effects-chromium/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    attachment #2: video (video/webm) ──────────────────────────────────────────────────────────────
    test-results/tvs-runtime-side-effects-s-a14b9-and-impression-side-effects-chromium/video.webm
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: test-results/tvs-runtime-side-effects-s-a14b9-and-impression-side-effects-chromium/error-context.md

    attachment #4: trace (application/zip) ─────────────────────────────────────────────────────────
    test-results/tvs-runtime-side-effects-s-a14b9-and-impression-side-effects-chromium/trace.zip
    Usage:

        pnpm exec playwright show-trace test-results/tvs-runtime-side-effects-s-a14b9-and-impression-side-effects-chromium/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────

  4 failed
    [chromium] › tests/e2e/player-offline.spec.ts:3:5 › lightweight player keeps cached playback when browser goes offline
    [chromium] › tests/e2e/player-runtime-side-effects.spec.ts:27:5 › lightweight player records heartbeat and impression side effects
    [chromium] › tests/e2e/tvs-offline.spec.ts:3:5 › screen runtime keeps cached playback when browser goes offline
    [chromium] › tests/e2e/tvs-runtime-side-effects.spec.ts:27:5 › screen runtime records heartbeat and impression side effects

Command exited with code 1


bash

cd /mnt/rpi/repos/ballbox && sed -n '1,220p' /tmp/ballbox-start.log && echo '---PAGE---' && curl -s http://127.0.0.1:3000/player/screen_001 | sed -n '1,220p' | head -n 120


> my-v0-project@0.1.0 start /mnt/rpi/repos/ballbox
> next start

▲ Next.js 16.2.1
- Local:         http://localhost:3000
- Network:       http://192.168.1.33:3000
✓ Ready in 172ms
---PAGE---
<!DOCTYPE html>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Ballbox Player screen_001</title>

        <video id="player-video" class="video" autoplay muted loop playsinline controls></video>

screen_001

Lightweight player

Loading

Preparing runtime

            Manifest

Diagnostics

Shell: lightweight-html-js

              <button class="top-link" id="diag-export" type="button">Export parity snapshot</button>


cd /mnt/rpi/repos/ballbox && sed -n '1,240p' prisma/seed-data.ts | rg -n "screen_001|creative|assignment|campaign|video|mp4" -n -C 2


38-export type SeedCampaignCreative = {
39-  id: string;
40:  campaignId: string;
41-  url: string;
42-  duration: number;
--
45-export type SeedScreenCampaign = {
46-  screenId: string;
47:  campaignId: string;
48-};
49-
--
134-export const seedScreens: SeedScreen[] = [
135-  {
136:    id: "screen_001",
137-    clubId: "club_palermo",
138-    venueId: "venue_003",


cd /mnt/rpi/repos/ballbox && sed -n '120,260p' prisma/seed-data.ts


export const seedVenues: SeedVenue[] = [
  { id: "venue_001", clubId: "club_san_isidro", name: "Central" },
  { id: "venue_002", clubId: "club_san_isidro", name: "Lounge" },
  { id: "venue_003", clubId: "club_palermo", name: "Main venue" },
];

export const seedVendingMachines: SeedVendingMachine[] = [
  { id: "vm_001", venueId: "venue_001", label: "Central 1" },
  { id: "vm_002", venueId: "venue_001", label: "Central 2" },
  { id: "vm_003", venueId: "venue_002", label: "Lounge 1" },
  { id: "vm_004", venueId: "venue_003", label: "Main venue 1" },
];

export const seedScreens: SeedScreen[] = [
  {
    id: "screen_001",
    clubId: "club_palermo",
    venueId: "venue_003",
    vendingMachineId: "vm_004",
    name: "Ingreso principal",
    location: "Recepcion",
    lastSeen: "2026-03-20T16:20:00.000Z",
    externalTvDeviceId: "bb-device-1",
  },
  {
    id: "screen_002",
    clubId: "club_san_isidro",
    venueId: "venue_002",
    vendingMachineId: "vm_003",
    name: "Zona lounge",
    location: "Bar / lounge",
    lastSeen: "2026-03-20T15:48:00.000Z",
    externalTvDeviceId: "bb-device-2",
  },
];

export const seedProducts: SeedProduct[] = [
  { id: "product_tubo_pelotas", name: "Tubo de pelotas" },
  { id: "product_pack_premium", name: "Pack premium" },
];

export const seedPlayers: SeedPlayer[] = [
  { id: "player_001", name: "Micaela Torres" },
  { id: "player_002", name: "Lucas Pereyra" },
  { id: "player_003", name: "Sofia Benitez" },
];

export const seedMemberships: SeedMembership[] = [
  { id: "membership_001", playerId: "player_001", clubId: "club_palermo" },
  { id: "membership_002", playerId: "player_001", clubId: "club_san_isidro" },
  { id: "membership_003", playerId: "player_002", clubId: "club_palermo" },
  { id: "membership_004", playerId: "player_003", clubId: "club_san_isidro" },
];

export const seedCoaches: SeedCoach[] = [
  {
    id: "coach_001",
    name: "Juan Martin Silva",
    email: "juan.silva@ballbox.demo",
    clubId: "club_palermo",
    clubSelectionMode: "custom",
  },
  {
    id: "coach_002",
    name: "Valentina Acosta",
    email: "valentina.acosta@ballbox.demo",
    clubId: "club_san_isidro",
    clubSelectionMode: "custom",
  },
];

export const seedCoachAvailabilities: SeedCoachAvailability[] = [
  {
    coachId: "coach_001",
    dayOfWeek: 1,
    startTime: "10:00",
    endTime: "13:00",
    timezone: "America/Argentina/Buenos_Aires",
  },
  {
    coachId: "coach_001",
    dayOfWeek: 3,
    startTime: "18:00",
    endTime: "21:00",
    timezone: "America/Argentina/Buenos_Aires",
  },
  {
    coachId: "coach_002",
    dayOfWeek: 2,
    startTime: "09:00",
    endTime: "12:00",
    timezone: "America/Argentina/Buenos_Aires",
  },
];

export const seedCoachClubs: SeedCoachClub[] = [
  { coachId: "coach_001", clubId: "club_san_isidro" },
  { coachId: "coach_001", clubId: "club_palermo" },
];

export const seedOpenMatches: SeedOpenMatch[] = [
  {
    id: "open_match_001",
    venueId: "venue_003",
    title: "Open Match Intermedio",
    startsAt: "2026-03-24T22:00:00.000Z",
  },
  {
    id: "open_match_002",
    venueId: "venue_001",
    title: "Americano Nocturno",
    startsAt: "2026-03-26T00:00:00.000Z",
  },
];

export const seedClasses: SeedClass[] = [
  {
    id: "class_001",
    venueId: "venue_003",
    title: "Academy Avanzado",
    startsAt: "2026-03-25T19:00:00.000Z",
  },
  {
    id: "class_002",
    venueId: "venue_002",
    title: "Clinica Saque + Volea",
    startsAt: "2026-03-27T18:00:00.000Z",
  },
];

export const seedClassOffers: SeedClassOffer[] = [
  {
    id: "class_offer_001",
    coachId: "coach_001",
    venueId: "venue_003",
    title: "Clase individual de definicion",
    description:
      "Sesion 1:1 para ajustar bandeja, vibora y cierre de puntos. Ideal para jugadores intermedios que quieren ordenar decisiones.",
    startsAt: "2026-04-02T22:00:00.000Z",
    durationMinutes: 90,


cd /mnt/rpi/repos/ballbox && rg -n "seedCampaigns|seedCreatives|seedScreenCampaigns|2026-03|2026-04" prisma/seed-data.ts -n -C 3


139-    vendingMachineId: "vm_004",
140-    name: "Ingreso principal",
141-    location: "Recepcion",
142:    lastSeen: "2026-03-20T16:20:00.000Z",
143-    externalTvDeviceId: "bb-device-1",
144-  },
145-  {
--
149-    vendingMachineId: "vm_003",
150-    name: "Zona lounge",
151-    location: "Bar / lounge",
152:    lastSeen: "2026-03-20T15:48:00.000Z",
153-    externalTvDeviceId: "bb-device-2",
154-  },
155-];
--
223-    id: "open_match_001",
224-    venueId: "venue_003",
225-    title: "Open Match Intermedio",
226:    startsAt: "2026-03-24T22:00:00.000Z",
227-  },
228-  {
229-    id: "open_match_002",
230-    venueId: "venue_001",
231-    title: "Americano Nocturno",
232:    startsAt: "2026-03-26T00:00:00.000Z",
233-  },
234-];
235-
--
238-    id: "class_001",
239-    venueId: "venue_003",
240-    title: "Academy Avanzado",
241:    startsAt: "2026-03-25T19:00:00.000Z",
242-  },
243-  {
244-    id: "class_002",
245-    venueId: "venue_002",
246-    title: "Clinica Saque + Volea",
247:    startsAt: "2026-03-27T18:00:00.000Z",
248-  },
249-];
250-
--
256-    title: "Clase individual de definicion",
257-    description:
258-      "Sesion 1:1 para ajustar bandeja, vibora y cierre de puntos. Ideal para jugadores intermedios que quieren ordenar decisiones.",
259:    startsAt: "2026-04-02T22:00:00.000Z",
260-    durationMinutes: 90,
261-    priceMinor: 32000,
262-    currency: "ARS",
--
270-    venueId: "venue_001",
271-    title: "Doble tactico para parejas",
272-    description: "Bloque de trabajo para coordinacion, salidas de pared y ocupacion de espacios en partido.",
273:    startsAt: "2026-04-04T15:30:00.000Z",
274-    durationMinutes: 90,
275-    priceMinor: 54000,
276-    currency: "ARS",
--
284-    venueId: "venue_003",
285-    title: "Clinica de saque y primera pelota",
286-    description: "Cupo reducido para trabajar patrones de inicio y automatizar la primera decision despues del saque.",
287:    startsAt: "2026-04-06T18:00:00.000Z",
288-    durationMinutes: 90,
289-    priceMinor: 28000,
290-    currency: "ARS",
--
292-  },
293-];
294-
295:export const seedCampaigns: SeedCampaign[] = [
296-  {
297-    id: "campaign_001",
298-    advertiserName: "Babolat",
299-    name: "Nueva linea de pelotas",
300:    startAt: "2026-03-01T00:00:00.000Z",
301:    endAt: "2026-04-15T00:00:00.000Z",
302-  },
303-  {
304-    id: "campaign_002",
305-    advertiserName: "Club San Isidro",
306-    name: "Promo clases nocturnas",
307:    startAt: "2026-03-10T00:00:00.000Z",
308:    endAt: "2026-04-10T00:00:00.000Z",
309-  },
310-];
311-
--
315-  { id: "campaign_002_creative_1", campaignId: "campaign_002", url: "/videos/hero.mp4", duration: 15 },
316-];
317-
318:export const seedScreenCampaigns: SeedScreenCampaign[] = [
319-  { screenId: "screen_001", campaignId: "campaign_001" },
320-  { screenId: "screen_001", campaignId: "campaign_002" },
321-  { screenId: "screen_002", campaignId: "campaign_002" },


cd /mnt/rpi/repos/ballbox && date -u && node -e "console.log(new Date().toISOString())"


Sun Apr 19 07:11:34 PM UTC 2026
2026-04-19T19:11:34.958Z


edit

## Still Pending
- Real-device verification is still required before calling Streetcast migration complete.
- The missing proof is hardware/browser behavior for CacheStorage plus object-URL playback outside local desktop automation.
- The runtime now ships an on-screen `Export parity snapshot` action that downloads a JSON snapshot (screen id, manifest/playback/cache/network/reporting state, user agent, capture time) to standardize evidence capture.
- Seeded local evidence must use an active campaign window; on 2026-04-19 the current demo seed for `screen_001` resolves to an empty manifest because seeded campaign windows ended on 2026-04-15 / 2026-04-10, so cached-blob parity cannot be reproduced from stock seed data until fixtures are refreshed or a live programmed screen is used.

## Local Validation Update (2026-04-19)
- Machine-local Playwright harness is now installed outside the repo at `/mnt/rpi/repos/browser-harness` and verified separately for Chromium, CacheStorage, and object-URL baseline support.
- Ballbox production build starts successfully on this Pi with `pnpm build` + `pnpm start`; `/player/screen_001` responds and renders the lightweight HTML shell.
- Current stock seed data does **not** provide an active TV manifest on 2026-04-19:
  - `campaign_001` window ends `2026-04-15T00:00:00.000Z`
  - `campaign_002` window ends `2026-04-10T00:00:00.000Z`
  - `screen_001` therefore bootstraps with `version: "ballbox-db-empty-screen_001"` and `creatives: []`
- Result: cached-blob playback, first impression write, queued-impression recovery, and manifest-refresh-failure continuity cannot be validated locally from stock seed data because there is no active creative to cache/play.
- Additional test infra note: repo Playwright currently cannot rely on its built-in `webServer` flow on this Pi/Next 16/Turbopack combo because the spawned `pnpm dev` process exits immediately after reporting ready; local validation was instead run against a manual production server (`pnpm build` + `pnpm start`).


Successfully replaced 1 block(s) in /mnt/rpi/repos/ballbox/docs/streetcast-parity.md.

    ...
 17 ## Still Pending
 18 - Real-device verification is still required before calling Streetcast migration complete.
 19 - The missing proof is hardware/browser behavior for CacheStorage plus object-URL playback outside local desktop automation.
 20 - The runtime now ships an on-screen `Export parity snapshot` action that downloads a JSON snapshot (screen id, manifest/playback/cache/network/reporting state, user agent, capture time) to standardize evidence capture.
+21 - Seeded local evidence must use an active campaign window; on 2026-04-19 the current demo seed for `screen_001` resolves to an empty manifest because seeded campaign windows ended on 2026-04-15 / 2026-04-10, so cached-blob parity cannot be reproduced from stock seed data until fixtures are refreshed or a live programmed screen is used.
 21
+23 ## Local Validation Update (2026-04-19)
+24 - Machine-local Playwright harness is now installed outside the repo at `/mnt/rpi/repos/browser-harness` and verified separately for Chromium, CacheStorage, and object-URL baseline support.
+25 - Ballbox production build starts successfully on this Pi with `pnpm build` + `pnpm start`; `/player/screen_001` responds and renders the lightweight HTML shell.
+26 - Current stock seed data does **not** provide an active TV manifest on 2026-04-19:
+27   - `campaign_001` window ends `2026-04-15T00:00:00.000Z`
+28   - `campaign_002` window ends `2026-04-10T00:00:00.000Z`
+29   - `screen_001` therefore bootstraps with `version: "ballbox-db-empty-screen_001"` and `creatives: []`
+30 - Result: cached-blob playback, first impression write, queued-impression recovery, and manifest-refresh-failure continuity cannot be validated locally from stock seed data because there is no active creative to cache/play.
+31 - Additional test infra note: repo Playwright currently cannot rely on its built-in `webServer` flow on this Pi/Next 16/Turbopack combo because the spawned `pnpm dev` process exits immediately after reporting ready; local validation was instead run against a manual production server (`pnpm build` + `pnpm start`).
+32
 22 ## Manual Real-Device Check
 23 1. Open `/player/screen_001` on the target TV/browser with Ballbox pointed at the real/shared DB.
 24 2. Wait for the runtime diagnostics card to show:
 25    - `Playback: cached`
    ...


### OPS-2 TVs runtime parity closure
- Priority: P0
- Status: ready
- Depends on: none
- Scope:
  - manually verify real-device CacheStorage/object-URL behavior on `/player/[screenId]`
  - update `docs/streetcast-parity.md` with concrete evidence
- Progress (2026-04-14): runtime now exports a JSON parity snapshot from `/player/[screenId]` to standardize evidence capture; remaining work is one real-device run and doc evidence entry.
- Expected payoff: de-risks constrained-device production behavior

### OPS-2 TVs runtime parity closure
- Priority: P0
- Status: ready
- Depends on: none
- Scope:
  - manually verify real-device CacheStorage/object-URL behavior on `/player/[screenId]`
  - update `docs/streetcast-parity.md` with concrete evidence
- Progress (2026-04-14): runtime now exports a JSON parity snapshot from `/player/[screenId]` to standardize evidence capture; remaining work is one real-device run and doc evidence entry.
- Progress (2026-04-19): machine-local Playwright setup is now standardized outside the repo at `/mnt/rpi/repos/browser-harness` and Ballbox local validation was retried on `ballbox-first`; repo Playwright `webServer` currently exits early on this Pi/Next 16 combo, and stock TV seed data is now stale for parity because `screen_001` resolves to an empty manifest after seeded campaign windows ended on 2026-04-10 / 2026-04-15.
- Remaining closure needs:
  - refresh TV demo fixtures or use a live programmed screen with an active campaign window
  - run the documented real-device parity capture (online snapshot, offline snapshot, reconnect recovery)
  - then update this queue item and `PLAN.md` to completed
- Expected payoff: de-risks constrained-device production behavior


Successfully replaced 1 block(s) in /mnt/rpi/repos/ballbox/docs/next-steps-queue.md.

     ...
  79 - Scope:
  80   - manually verify real-device CacheStorage/object-URL behavior on `/player/[screenId]`
  81   - update `docs/streetcast-parity.md` with concrete evidence
  82 - Progress (2026-04-14): runtime now exports a JSON parity snapshot from `/player/[screenId]` to standardize evidence capture; remaining work is one real-device run and doc evidence entry.
+ 83 - Progress (2026-04-19): machine-local Playwright setup is now standardized outside the repo at `/mnt/rpi/repos/browser-harness` and Ballbox local validation was retried on `ballbox-first`; repo Playwright `webServer` currently exits early on this Pi/Next 16 combo, and stock TV seed data is now stale for parity because `screen_001` resolves to an empty manifest after seeded campaign windows ended on 2026-04-10 / 2026-04-15.
+ 84 - Remaining closure needs:
+ 85   - refresh TV demo fixtures or use a live programmed screen with an active campaign window
+ 86   - run the documented real-device parity capture (online snapshot, offline snapshot, reconnect recovery)
+ 87   - then update this queue item and `PLAN.md` to completed
  83 - Expected payoff: de-risks constrained-device production behavior
  84
  85 ## P1 After P0
  86
     ...


- TVs runtime diagnostics now also support a one-click parity snapshot JSON export (playback/cache/network/reporting state + user agent + capture timestamp) to standardize real-device evidence capture.
- OPS-2 local parity rerun on 2026-04-19 confirmed the current blocker is not missing runtime instrumentation but stale TV seed scheduling: stock `screen_001` now boots with an empty manifest because seeded campaign windows ended on 2026-04-10 / 2026-04-15, so cached-blob/device parity evidence now requires refreshed fixtures or a live programmed screen.

- Local quality gate now includes `pnpm lint`, `pnpm typecheck`, `pnpm test:contracts`, `pnpm test:e2e`, and `pnpm build`.
- On `ballbox-first`, repo Playwright's built-in `webServer` path currently exits early under the current Next 16 dev-server setup, while `pnpm build` + `pnpm start` does run successfully for manual/browser validation.


Successfully replaced 2 block(s) in /mnt/rpi/repos/ballbox/PLAN.md.

     ...
  22 - TVs manifest/admin scheduling now respects campaign and assignment time windows.
  23 - TVs now expose a strict Zod-backed API contract plus contract tests.
  24 - TVs runtime now exposes on-screen diagnostics for cache/object-URL/manual parity verification on real devices.
  25 - TVs runtime diagnostics now also support a one-click parity snapshot JSON export (playback/cache/network/reporting state + user agent + capture timestamp) to standardize real-device evidence capture.
+ 26 - OPS-2 local parity rerun on 2026-04-19 confirmed the current blocker is not missing runtime instrumentation but stale TV seed scheduling: stock `screen_001` now boots with an empty manifest because seeded campaign windows ended on 2026-04-10 / 2026-04-15, so cached-blob/device parity evidence now requires refreshed fixtures or a live programmed screen.
  26 - TVs player runtime cache/media/impression/heartbeat/manifest-refresh logic now lives in one shared core used by both the React player and the lightweight plain-JS player shell.
  27 - Maintainability review for the next refactor cycle is documented in `docs/maintainability-priorities-2026-03.md`.
  28 - Payments are now being consolidated into Ballbox host app: native MercadoPago webhook intake exists under `/notification/mercadopago` with signature validation and order fetch.
  29 - Payments host app now also exposes native QR order creation under `/api/integrations/payments/orders`, generating canonical `externalReference` values required for approved-session persistence correlation.
     ...
  95 - `/admin` overview now reads lightweight aggregate counts instead of hydrating full product datasets during SSR.
  96 - Coach experience discovery is still early: current research input is an online form, so Ballbox does not yet have enough real-world coach workflow evidence to lock the sports product thesis.
  97 - ATC admin/write is still not integrated; only a small public-read availability boundary now exists for `/classes` offer context and must degrade cleanly when unavailable.
  98 - Local quality gate now includes `pnpm lint`, `pnpm typecheck`, `pnpm test:contracts`, `pnpm test:e2e`, and `pnpm build`.
+100 - On `ballbox-first`, repo Playwright's built-in `webServer` path currently exits early under the current Next 16 dev-server setup, while `pnpm build` + `pnpm start` does run successfully for manual/browser validation.
  99 - Vitest taxonomy now includes layered includes for `tests/contracts`, `tests/unit`, and `tests/integration`, with dedicated scripts for each layer.
 100
 101 ## Route Map
 102 - `/`: marketing home
     ...


- Runtime diagnostics now include user-agent plus capture timestamp and a built-in `Export parity snapshot` action that downloads a JSON evidence file for real-device parity runs.
- As of 2026-04-19, stock demo seed scheduling for `screen_001` is stale relative to current date, so local parity/e2e runs that expect active cached playback require refreshed campaign windows or another actively programmed screen.

- Playwright e2e covers:
  - lightweight `/player/[screenId]` cached blob playback and runtime side effects
  - admin CRUD for screens, tv devices, campaigns, creatives, and assignments
  - cached blob playback on a real browser engine
  - runtime continuity after the browser context goes offline
  - browser-level heartbeat and impression side effects against the real Ballbox DB
  - absence of page-level runtime errors during the offline transition

- Playwright e2e covers:
  - lightweight `/player/[screenId]` cached blob playback and runtime side effects when the target screen has active programming
  - admin CRUD for screens, tv devices, campaigns, creatives, and assignments
  - cached blob playback on a real browser engine
  - runtime continuity after the browser context goes offline
  - browser-level heartbeat and impression side effects against the real Ballbox DB
  - absence of page-level runtime errors during the offline transition
- On `ballbox-first`, the current repo Playwright `webServer` flow exits early with the present Next 16 dev-server setup, so local browser validation currently runs more reliably against a manual `pnpm build` + `pnpm start` server until that infra issue is cleaned up.


Successfully replaced 2 block(s) in /mnt/rpi/repos/ballbox/docs/implemented-tvs.md.

    ...
 43 - Cache/media/impression/heartbeat/manifest-refresh behavior now comes from one shared runtime core in `lib/tvs-player-runtime-core.ts`; React and lightweight HTML+JS are delivery adapters over the same logic.
 44 - Browser e2e coverage now verifies cached blob playback survives an offline transition on `/player/[screenId]`.
 45 - Runtime now exposes operator-facing diagnostics for playback source, Cache API support, cached creative count, network state, and manifest refresh status to support real-device parity checks.
 46 - Runtime diagnostics now include user-agent plus capture timestamp and a built-in `Export parity snapshot` action that downloads a JSON evidence file for real-device parity runs.
+47 - As of 2026-04-19, stock demo seed scheduling for `screen_001` is stale relative to current date, so local parity/e2e runs that expect active cached playback require refreshed campaign windows or another actively programmed screen.
 47 - TVs admin and `/admin/screens` link operators to the lightweight player path.
 48 - This is the main special-case surface in Ballbox: TVs stay inside the same repo/deploy/domain, but the device runtime is intentionally served outside the Next client/React runtime as plain HTML + JavaScript.
 49 - `GET /api/tvs/manifest/[screenId]` returns:
 50   - `404` when the screen does not exist
    ...
 69   - scheduling invariants
 70   - strict schema parsing with no extra fields
 71   - runtime helper invariants for cache naming, manifest continuity, and bounded retry behavior
 72 - Playwright e2e covers:
-73   - lightweight `/player/[screenId]` cached blob playback and runtime side effects
+74   - lightweight `/player/[screenId]` cached blob playback and runtime side effects when the target screen has active programming
 74   - admin CRUD for screens, tv devices, campaigns, creatives, and assignments
 75   - cached blob playback on a real browser engine
 76   - runtime continuity after the browser context goes offline
 77   - browser-level heartbeat and impression side effects against the real Ballbox DB
 78   - absence of page-level runtime errors during the offline transition
+80 - On `ballbox-first`, the current repo Playwright `webServer` flow exits early with the present Next 16 dev-server setup, so local browser validation currently runs more reliably against a manual `pnpm build` + `pnpm start` server until that infra issue is cleaned up.


    gracefulShutdown: {
      signal: "SIGKILL",
      timeout: 0,
    },


Successfully replaced 1 block(s) in /mnt/rpi/repos/ballbox/playwright.config.ts.

    ...
 25     url: "http://127.0.0.1:3010",
 26     reuseExistingServer: true,
 27     stdout: "pipe",
 28     stderr: "pipe",
-29     gracefulShutdown: {
-30       signal: "SIGKILL",
-31       timeout: 0,
-32     },
 33     env: {
 34       ...process.env,
 35       BALLBOX_ADMIN_PASSWORD: "",
 36       ATC_PUBLIC_API_BASE_URL: "http://127.0.0.1:3010/api/atc-public",
    ...


bash

cd /mnt/rpi/repos/ballbox && rm -rf .codex && if [ -f /tmp/ballbox-start.pid ]; then kill $(cat /tmp/ballbox-start.pid) >/dev/null 2>&1 || true; fi && git status --short


 M PLAN.md
 M docs/implemented-tvs.md
 M docs/next-steps-queue.md
 M docs/streetcast-parity.md


Avancé `OPS-2` hasta el bloqueo real.

Qué hice:
- abrí branch nueva desde `origin/main`
  - `ops2-runtime-parity`
- revisé runtime, tests y docs de TVs
- dejé Playwright general ya montado fuera del repo
- intenté correr la validación local completa de `OPS-2`
- documenté el bloqueo real en repo docs

Hallazgo clave:
- hoy `OPS-2` no está bloqueado por falta de código del player
- está bloqueado por fixture/data stale

Bloqueo exacto:
- `screen_001` arranca con manifest vacío
- las campañas seed vencieron:
  - `campaign_001` termina `2026-04-15`
  - `campaign_002` termina `2026-04-10`
- hoy es `2026-04-19`
- entonces `/player/screen_001` devuelve bootstrap con:
  - `version: "ballbox-db-empty-screen_001"`
  - `creatives: []`

Impacto:
- no hay creative activa para cachear
- no se puede validar:
  - `Playback: cached`
  - `Active source: blob`
  - primera impresión
  - recovery de cola
  - continuidad offline real del player

Además detecté otro tema de infra local:
- en esta Pi, el `webServer` de Playwright del repo está fallando con `pnpm dev` / Next 16
- el proceso reporta “ready” y sale
- pero `pnpm build` + `pnpm start` sí funciona bien
- eso lo documenté, pero no toqué la config final del repo

Docs actualizadas:
- `PLAN.md`
- `docs/implemented-tvs.md`
- `docs/next-steps-queue.md`
- `docs/streetcast-parity.md`

Estado del branch:
- solo cambios de documentación
- sin cambios de runtime/código
- branch actual:
  - `ops2-runtime-parity`

Mi lectura:
- para cerrar `OPS-2` de verdad hay que hacer una de estas dos:
  1. refrescar seed TV para que haya campañas activas hoy
  2. usar una pantalla real/programada con campaña activa

Recomiendo:
- opción 1 primero
- es la forma más autónoma y deja de romper también los e2e de parity

Si querés, siguiente paso lo hago ya:
- actualizo seeds de TVs para que `screen_001` tenga campañas activas otra vez
- corro e2e relevantes
- dejo `OPS-2` listo para el último paso de evidencia real en dispositivo

Confirmame y lo hago.


si. pero necesitas que prenda un monitor? no podes arreglar plawright?


No necesito que prendas un monitor para el próximo paso.

Sí puedo arreglar bastante con Playwright, pero no todo `OPS-2`.

Separado:

Qué sí puedo hacer solo:
- refrescar seed data para que haya creatives activas
- arreglar o esquivar el problema de Playwright en esta Pi
- correr e2e del player
- validar cache/blob/offline en Chromium real de Playwright
- dejar docs y estado mucho más cerrados

Qué no reemplaza Playwright:
- la prueba en dispositivo/TV real del navegador objetivo
- ese es justamente el riesgo que `OPS-2` quiere cerrar

Sobre “arreglar Playwright”:
- sí, vale la pena
- el problema no es Playwright browser en sí
- el problema parece ser la integración `playwright.config.ts -> webServer: pnpm dev` en esta máquina con Next 16/Turbopack
- de hecho:
  - `pnpm build` funciona
  - `pnpm start` funciona
  - Playwright corre
  - lo que falla es el arranque administrado del dev server dentro de Playwright

Entonces el plan correcto es:
1. arreglar la base de datos seed de TVs
2. arreglar la estrategia de Playwright del repo para esta Pi
   - ideal: usar `start` sobre build previa, o una variante estable para e2e
3. correr e2e de `OPS-2`
4. dejar pendiente solo la evidencia real de hardware si sigue faltando

Eso no requiere monitor todavía.

Si querés, hago ahora ambas cosas:
- fix de seeds activas
- fix de Playwright e2e server strategy para `ballbox-first`
y después te digo exactamente si ya quedó listo o si resta solo el test en TV real.


si


subagent

Seed update recon

You are a focused recon agent. Inspect Ballbox TV seed data and seeding flow. Recommend the smallest safe change so screen_001 has active campaigns/creatives as of 2026-04-19 without inventing new fixtures unnecessarily. Return exact file pointers and preferred patch shape. Do not edit files.

Audit prisma/seed-data.ts and prisma/seed.ts for TVs seed flow. Recommend the minimal durable fix to make screen_001 have active programming now and remain useful for near-future local e2e parity runs.

Playwright infra recon

You are a focused test-infra agent. Inspect Ballbox Playwright setup and recommend the smallest stable fix for this Pi where webServer using pnpm dev exits early. Prefer not to reinvent. Return exact config changes and tradeoffs. Do not edit files.

Audit playwright.config.ts, package.json scripts, and any build helpers. Recommend the smallest stable e2e server strategy on ballbox-first for Playwright. Consider using production build/start, a dedicated script, or alternative stable config. Return exact proposed change.