diff --git a/.agents/MEMORY.md b/.agents/MEMORY.md new file mode 100644 index 00000000..e64f65f9 --- /dev/null +++ b/.agents/MEMORY.md @@ -0,0 +1,33 @@ +# Repo Memory + +Curated, reviewed knowledge for coding agents working in this repository. +Memory is an accelerator, not a second source of truth. + +## Precedence (highest first) + +1. Source code and tests +2. Executable configuration +3. Accepted design records / docs +4. `AGENTS.md` +5. This memory + +If memory conflicts with anything above it, the memory is stale — propose a fix. + +## Topics + +- `memory/architecture.md` — durable architectural decisions and constraints +- `memory/conventions.md` — validated conventions not obvious from the code +- `memory/lessons.md` — hard-won lessons and recurring landmines +- `memory/decisions.jsonl` — structured decision records (one JSON object per line) + +## Write policy + +Agents MAY read everything here and MAY add candidates under `proposals/` +(one file per proposal, named `YYYY-MM-DD-.md`, with evidence links). +Agents MUST NOT edit `memory/` directly; trusted memory changes only through +reviewed commits that promote a proposal. + +## Prohibited content + +Credentials or secrets, personal data, raw transcripts or tool logs, session +TODOs, high-frequency agent state, unverified external claims. diff --git a/.agents/memory/architecture.md b/.agents/memory/architecture.md new file mode 100644 index 00000000..f55c7835 --- /dev/null +++ b/.agents/memory/architecture.md @@ -0,0 +1,17 @@ +# Architecture + +Durable architectural decisions and constraints for bitvid. + +## AGENTS.md is a thin constitution; long-form standards live in docs/ + +On 2026-08-12 AGENTS.md was split into a thin agent constitution plus +reference docs under `docs/testing/` and `docs/lessons/`, because the file had +outgrown the 32 KiB harness context budget. AGENTS.md keeps only the invariant +and a pointer per topic; new long-form material belongs in `docs/`, not in +AGENTS.md. + +Authoritative source: +- docs/testing/test-integrity.md +- docs/testing/agent-playwright-harness.md +- docs/lessons/ +- git commit 911afb1c ("Split AGENTS.md reference sections into docs/; keep thin agent constitution") diff --git a/.agents/memory/conventions.md b/.agents/memory/conventions.md new file mode 100644 index 00000000..ae5d83f7 --- /dev/null +++ b/.agents/memory/conventions.md @@ -0,0 +1,5 @@ +# Conventions + +Validated conventions not obvious from the code. + +No entries yet. diff --git a/.agents/memory/decisions.jsonl b/.agents/memory/decisions.jsonl new file mode 100644 index 00000000..9a95f9cd --- /dev/null +++ b/.agents/memory/decisions.jsonl @@ -0,0 +1 @@ +{"kind":"decision","subject":"docs/agents-md-split","summary":"AGENTS.md was split on 2026-08-12 into a thin agent constitution plus reference docs under docs/testing/ and docs/lessons/ to stay within the 32 KiB harness context budget; AGENTS.md keeps only invariants and pointers, and new long-form material belongs in docs/.","status":"active","evidence":[{"type":"repository","ref":"docs/testing/test-integrity.md"},{"type":"repository","ref":"docs/testing/agent-playwright-harness.md"},{"type":"repository","ref":"docs/lessons/"},{"type":"git","ref":"911afb1c"}],"created":"2026-08-12","supersedes":[]} diff --git a/.agents/memory/lessons.md b/.agents/memory/lessons.md new file mode 100644 index 00000000..42dac15b --- /dev/null +++ b/.agents/memory/lessons.md @@ -0,0 +1,24 @@ +# Lessons + +Hard-won lessons and recurring landmines. + +## Cloudflare/S3 upload-path gotchas + +`js/services/r2Service.js` and `js/services/s3UploadService.js` are near-identical +copies that must be changed in lockstep, and storage keys must stay +content-addressed (`computeStorageContentHash`) or URL-first uploads silently +overwrite objects. Full audited writeup in the doc below. + +Authoritative source: +- docs/lessons/cloudflare-s3-upload-gotchas.md + +## NIP-07 signer reliability + +"DMs / lists / watch-history won't load after login" is almost always an +unresponsive NIP-07 signer extension, not bitvid — diagnose with a raw +`window.nostr` probe first, and never regress the five resilience invariants +(capped relay fan-out, NIP-07 circuit breaker, re-thrown transient decrypt +errors, generous decrypt budgets, lazy modal population). + +Authoritative source: +- docs/lessons/nip07-signer-reliability.md diff --git a/.agents/plans/README.md b/.agents/plans/README.md new file mode 100644 index 00000000..36bcc9a5 --- /dev/null +++ b/.agents/plans/README.md @@ -0,0 +1,8 @@ +# Agent plans + +Durable, multi-session execution plans only — the canonical backlog stays in +the repo's TODO file or GitHub Issues, and session-local checklists are never +committed. One plan per task, named `.md`, in `active/`; +move finished plans to `archive/`. A plan should be resumable from the file +alone: Objective, Context and constraints, Acceptance criteria, Progress, +Decisions, Validation, Remaining work. diff --git a/.agents/plans/active/.gitkeep b/.agents/plans/active/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.agents/plans/archive/.gitkeep b/.agents/plans/archive/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.agents/proposals/README.md b/.agents/proposals/README.md new file mode 100644 index 00000000..89c95bdd --- /dev/null +++ b/.agents/proposals/README.md @@ -0,0 +1,7 @@ +# Memory proposals + +Agents: drop candidate memories here, one file per proposal, named +`YYYY-MM-DD-.md`. State the claim, the evidence (file paths, commits, +test results), and why it is durable. A human reviews and either promotes it +into `../memory/` in a reviewed commit or deletes it. Nothing in this +directory is trusted context. diff --git a/.agents/schemas/memory.schema.json b/.agents/schemas/memory.schema.json new file mode 100644 index 00000000..e14a43d9 --- /dev/null +++ b/.agents/schemas/memory.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Repo Memory Decision Record", + "type": "object", + "additionalProperties": false, + "required": ["kind", "subject", "summary", "status", "evidence", "created"], + "properties": { + "kind": {"enum": ["decision", "constraint", "lesson", "observation", "workaround"]}, + "subject": {"type": "string", "minLength": 1}, + "summary": {"type": "string", "minLength": 1, "maxLength": 4096}, + "status": {"enum": ["proposed", "active", "superseded", "expired"]}, + "evidence": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["type", "ref"], + "properties": { + "type": {"enum": ["repository", "git", "issue", "test", "external"]}, + "ref": {"type": "string", "minLength": 1} + } + } + }, + "created": {"type": "string"}, + "expires": {"type": ["string", "null"]}, + "supersedes": {"type": "array", "items": {"type": "string"}} + } +} diff --git a/.agents/skills/agent-fleet/SKILL.md b/.agents/skills/agent-fleet/SKILL.md new file mode 100644 index 00000000..d28d82ec --- /dev/null +++ b/.agents/skills/agent-fleet/SKILL.md @@ -0,0 +1,12 @@ +--- +name: agent-fleet +description: Use fleet-mcp tools for backlog, mailbox, landing queue, and area ownership. Do not use it to start sessions or push main. +--- + +Follow the fleet rules in `AGENTS.md`. Call the `agent-fleet` MCP tools for backlog, mailbox, landing-queue, area questions, and commits. Editing those files directly is also fine. Sandboxed Codex cannot write Git's index: use the `commit` tool (or `fleet commit -m "..." --area -- `), not `git commit`. `submit: true` queues the landing and starts a drain. Go back to work. At your next natural checkpoint, `land_status` (or `fleet landq status `) can report that submission. Waiting is optional. Do not infer the lander or the land result from silence. Other providers may use either commit path. + +If you installed this fleet, you stay on as the interpreter. Read `docs/agents/prompts/interpreter.md`. Run `fleet up` yourself. Talk to the session manager with the MCP tools `tell` and `hear`. Do not send those with the shell `fleet tell`: that is a sandboxed command, and the MCP approval mode does not apply to it. Do not scrape the tmux pane, and do not send the user to a terminal. A routine yes you relay counts. Money, pushes, deploys, deletes, secrets, outgoing messages, and a bigger structural or directional change go to the user. Uncommitted setup files use `setup_commit`, not `git commit`. Workers do not start the fleet. `fleet crontab` stays unused unless the user asks. + +You may use this harness's own subagents for parallel research, investigation, test runs, or review. You coordinate edits in a worktree so subagents do not edit the same files at the same time. Landing still goes through your `commit` and the queue. + +To update the kit: `git pull` in `~/agent-fleet-kit`, reinstall, run `fleet update`, and restart agents after handoff if it says so. Do not re-run init to upgrade. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..d41162ce --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Agent instruction and memory surfaces require maintainer review +/AGENTS.md @PR0M3TH3AN +/CLAUDE.md @PR0M3TH3AN +/.agents/ @PR0M3TH3AN +/SECURITY.md @PR0M3TH3AN +/.github/workflows/agent-governance.yml @PR0M3TH3AN diff --git a/.github/workflows/agent-governance.yml b/.github/workflows/agent-governance.yml new file mode 100644 index 00000000..cc88ebe7 --- /dev/null +++ b/.github/workflows/agent-governance.yml @@ -0,0 +1,65 @@ +name: Agent governance + +on: + push: + paths: + - "AGENTS.md" + - "CLAUDE.md" + - ".agents/**" + pull_request: + paths: + - "AGENTS.md" + - "CLAUDE.md" + - ".agents/**" + +jobs: + lint-agent-artifacts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate agent artifacts + run: | + python3 - <<'EOF' + import json, os, sys + hard_fail = False + + # decisions.jsonl: every line must be valid JSON with required keys + path = ".agents/memory/decisions.jsonl" + if os.path.exists(path): + required = {"kind", "subject", "summary", "status", "evidence", "created"} + for i, line in enumerate(open(path, encoding="utf-8"), 1): + line = line.strip() + if not line: + continue + try: + rec = json.loads(line) + except json.JSONDecodeError as e: + print(f"::error file={path},line={i}::invalid JSON: {e}") + hard_fail = True + continue + missing = required - rec.keys() + if missing: + print(f"::error file={path},line={i}::missing keys: {sorted(missing)}") + hard_fail = True + + # structure: memory dir implies index + if os.path.isdir(".agents") and not os.path.exists(".agents/MEMORY.md"): + print("::error::.agents/ exists but .agents/MEMORY.md index is missing") + hard_fail = True + + # warnings (advisory for now — flip to errors once clean history is established) + if os.path.exists("AGENTS.md"): + size = os.path.getsize("AGENTS.md") + if size > 32768: + print(f"::warning file=AGENTS.md::{size} bytes exceeds the 32 KiB context budget") + if os.path.exists("CLAUDE.md"): + lines = sum(1 for _ in open("CLAUDE.md", encoding="utf-8")) + if lines > 40: + print(f"::warning file=CLAUDE.md::{lines} lines — expected a thin wrapper over AGENTS.md") + for f in ("architecture.md", "conventions.md", "lessons.md"): + p = f".agents/memory/{f}" + if os.path.exists(p) and os.path.getsize(p) > 25 * 1024: + print(f"::warning file={p}::exceeds 25 KB — curate or split") + + sys.exit(1 if hard_fail else 0) + EOF diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 408725a1..8bcb9e8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ concurrency: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: matrix: @@ -96,7 +96,7 @@ jobs: # deploy-cache-state CDN purge below. Real build/lint failures still gate the # deploy via the build job. visual-regression: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: matrix: @@ -401,7 +401,7 @@ jobs: run: npm run test:dm:unit e2e-tests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 35 strategy: fail-fast: false @@ -489,7 +489,7 @@ jobs: run: npm run test:dm:integration e2e-headless: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: matrix: diff --git a/.github/workflows/daily-design-system-audit.yml b/.github/workflows/daily-design-system-audit.yml index c6cd2e78..9e59cbde 100644 --- a/.github/workflows/daily-design-system-audit.yml +++ b/.github/workflows/daily-design-system-audit.yml @@ -1,8 +1,6 @@ name: Daily Design System Audit on: - schedule: - - cron: '0 9 * * *' # Run daily at 09:00 UTC workflow_dispatch: permissions: diff --git a/.gitignore b/.gitignore index 25e13636..a77e5760 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,14 @@ TEST_LOG.mdtest_logs/nip-hits.txt # Local site archive for manual nsite upload (npm run archive) bitvid-site.zip .env* + +# Agent runtime state (local only) +memory-update.md +memory-update.json + +# Agent-local memory state (never committed) +.agents/private/ +.agents/state/ +fleet/inbox/ +.task +.fleet/ diff --git a/AGENTS.md b/AGENTS.md index 96dd4b72..a9c61640 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,12 +65,8 @@ For any nostr related work, please review the nip documentation located in /docs ### 4a. Cloudflare/S3 upload-path gotchas (audited 2026-06-16) -* **Two near-identical upload services exist** — `js/services/r2Service.js` (Cloudflare R2) and `js/services/s3UploadService.js` (generic S3). They share `buildR2Key`, `resolveUploadIdentifier`, and the magnet build but are *copies*. **Any fix to one must be mirrored in the other** or they silently drift. -* **Storage-key collisions = data loss — FIXED 2026-06-16.** `buildR2Key` (`js/r2.js`) builds `u///.` where `namespace` used to fall back to the literal `"uploads"` and `slug` to `"video"` (e.g. a non-ASCII filename, or no info-hash) — so a URL-first upload with no info-hash and a duplicate filename overwrote the previous object while the old note still pointed at that URL. WebTorrent uploads were always safe (info-hash namespaces the key = content-addressed). Fix: `computeStorageContentHash(file)` (`js/r2.js`) derives a content-based namespace when no info-hash is available (full SHA-256 for ≤512 MB, sampled metadata+edge fingerprint above that to avoid buffering huge files), wired into **both** `r2Service` and `s3UploadService`. Test: `tests/storage-key-collision.test.mjs`. **If you add a third upload path, reuse `computeStorageContentHash`.** -* **Whole-file hash before upload.** When the modal doesn't pre-seed an info-hash, `resolveUploadIdentifier` runs `calculateTorrentInfoHash(file)`, which reads the *entire* file before the upload begins and before any progress is emitted — on multi-GB files this looks like a hang. Emit a "preparing/hashing" status or parallelize. -* **Publish can report success unconfirmed.** `publishEventToRelay` (`js/nostrPublish.js`) has an optimistic-success fallthrough when the relay handle exposes no `on()`/`then()` — a note may show "Published" with no relay ACK. Don't tighten blindly (legacy `seen`-only relays rely on it), but log it. -* **Silent partial failures.** Thumbnail and `.torrent` upload failures are caught and `warn`-only; the video note still publishes with no user signal. Empty (0-byte) files hit `CompleteMultipartUpload` with `Parts:[]` and surface a cryptic error — guard `file.size === 0`. -* **Object cleanup (deletes/edits).** `R2Service.deleteVideoStorage()` removes the backing objects so "deleted" videos aren't left publicly downloadable. Contract to preserve: it's **best-effort and never throws** (must not block note deletion), it derives keys via `collectVideoStorageKeys()` and **only ever touches URLs under the owner's bucket base** (never external/3rd-party URLs), and it skips cleanly when storage is locked/credential-less. Wired into delete (`app.handleDeleteModalConfirm`) and edit-with-replaced-URL (`editModalController.handleSubmit`). Edit cleanup intentionally does **not** delete thumbnails (the new note may still reference them) and only fires when both old and new URLs are present and different. +**Invariants:** `js/services/r2Service.js` and `js/services/s3UploadService.js` are near-identical copies — any fix to one must be mirrored in the other; storage keys must stay content-addressed (reuse `computeStorageContentHash` in `js/r2.js` for any new upload path); `R2Service.deleteVideoStorage()` stays best-effort, never throws, and only touches URLs under the owner's bucket base. +Full writeup: [`docs/lessons/cloudflare-s3-upload-gotchas.md`](docs/lessons/cloudflare-s3-upload-gotchas.md). --- @@ -324,127 +320,8 @@ Three automated lint checks protect the codebase from common growth problems. Th ## 14. Playwright Agent Testing Infrastructure -bitvid includes a test harness and fixtures that let Playwright CLI agents programmatically log in, seed relay data, and inspect app state — no browser extensions or real relays required. - -### Activating Test Mode - -Add `?__test__=1` to the URL, or set `localStorage.__bitvidTestMode__ = "1"` via `addInitScript`. This installs `window.__bitvidTest__` on the page. - -### Overriding Relays - -Point the app at a local mock relay instead of production: - -``` -?__test__=1&__testRelays__=ws://127.0.0.1:8877 -``` - -Or set `localStorage.__bitvidTestRelays__` to a JSON array of relay URLs. - -### Test Harness API (`window.__bitvidTest__`) - -| Method | Returns | Purpose | -|--------|---------|---------| -| `loginWithNsec(hexKey)` | `Promise` (pubkey) | Programmatic login, bypasses the modal | -| `logout()` | `void` | Clear active signer | -| `getAppState()` | `{ isLoggedIn, activePubkey, relays, ... }` | Inspect current state | -| `getFeedItems()` | `Array<{ title, pubkey, dTag, hasUrl, hasMagnet }>` | Scrape video cards from DOM | -| `waitForFeedItems(n, ms)` | `Promise` | Wait for N cards to appear | -| `waitForSelector(sel, ms)` | `Promise` | Wait for a DOM element | -| `getRelayHealth()` | `{ relays, unreachable, backoff }` | Relay connection status | -| `applyRelayOverrides(urls)` | `boolean` | Redirect relay connections | -| `setTestRelays(urls)` | `{ ok, relays }` | Align relayManager + nostrClient to a deterministic relay set | -| `setSignerDecryptBehavior(mode, opts?)` | `{ ok, mode, ... }` | Force decrypt behavior (`passthrough`, `timeout`, `error`, `delay`) for signer-based list sync tests | -| `getListSyncEvents()` | `Array<{ source, at, detail }>` | Read captured sync timeline (`bitvid:auth-loading-state` + `userBlocks` status) | -| `clearListSyncEvents()` | `void` | Reset captured sync events between assertions | -| `waitForListSyncEvent(criteria, ms?)` | `Promise` | Wait for sync milestones (`status`, `reason`, `source`) | -| `nostrClient` | `NostrClient` | Direct access for advanced use | - -### Mock Relay (`scripts/agent/simple-relay.mjs`) - -Start with `startRelay(port, { httpPort })`. Alongside the Nostr WebSocket protocol, exposes an HTTP API: - -| Endpoint | Method | Purpose | -|----------|--------|---------| -| `/seed` | `POST` | Inject signed events (single object or array) | -| `/events` | `GET` | List all stored events | -| `/events` | `DELETE` | Clear all events | -| `/health` | `GET` | Event count + connection count | - -Programmatic API: `relay.seedEvent(event)`, `relay.clearEvents()`, `relay.getEvents()`. - -### Playwright Fixture (`tests/e2e/helpers/bitvidTestFixture.ts`) - -Import `test` and `expect` from the fixture for tests that need the full stack: - -```typescript -import { test, expect } from "./helpers/bitvidTestFixture"; - -test("agent can seed and view videos", async ({ page, gotoApp, loginAs, seedEvent }) => { - await seedEvent({ title: "Test Video", url: "https://example.com/v.mp4" }); - await gotoApp(); - await loginAs(page); - // ... assertions -}); -``` - -**Available fixture values:** - -| Fixture | Type | Purpose | -|---------|------|---------| -| `relay` | Relay instance | Auto-started/stopped per test | -| `seedEvent(video)` | `(TestVideoEvent) => Promise` | Create a signed kind 30078 event and inject it | -| `seedRawEvent(event)` | `(any) => Promise` | Inject a pre-built event | -| `clearRelay()` | `() => Promise` | Wipe all relay events | -| `gotoApp(path?)` | `(string?) => Promise` | Navigate with test mode + relay overrides | -| `loginAs(page)` | `(Page) => Promise` | Login with deterministic test key | -| `setTestRelays(page, relays)` | `(Page, string[]) => Promise<{ok, relays}>` | Force relay alignment through harness + relayManager | -| `setDecryptBehavior(page, mode, opts?)` | `(Page, mode, opts?) => Promise` | Force signer decrypt behavior (`passthrough`, `timeout`, `error`, `delay`) | -| `startDiagnostics(page, opts?)` | `(Page, opts?) => Promise<{stop()}>` | Capture console/page errors and list-sync telemetry for assertions | -| `testPubkey` | `string` | Hex pubkey of the test key | -| `relayUrl` | `string` | WebSocket URL of the mock relay | - -### `data-testid` Selectors - -Use these for stable element targeting: - -| Selector | Element | -|----------|---------| -| `[data-testid="login-button"]` | Header login button | -| `[data-testid="upload-button"]` | Header upload button (hidden until logged in) | -| `[data-testid="profile-button"]` | Header profile button (hidden until logged in) | -| `[data-testid="profile-permission-prompt"]` | Profile list-sync permission CTA container | -| `[data-testid="profile-permission-prompt-button"]` | Profile list-sync permission/retry CTA button | -| `[data-testid="search-input"]` | Header search field | -| `[data-testid="login-modal"]` | Login modal container | -| `[data-testid="login-provider-button"]` | Login provider option buttons | -| `[data-testid="nsec-secret-input"]` | Private key textarea in nsec login | -| `[data-testid="nsec-submit"]` | Nsec login submit button | -| `[data-testid="upload-modal"]` | Upload modal container | -| `[data-testid="upload-title"]` | Video title input | -| `[data-testid="upload-url"]` | Video URL input | -| `[data-testid="upload-magnet"]` | Magnet link input | -| `[data-testid="upload-submit"]` | Publish button | -| `[data-testid="hashtags-sync-status"]` | Profile hashtag sync status message | -| `[data-testid="subscriptions-sync-status"]` | Profile subscriptions sync status message | -| `[data-testid="blocked-refresh-button"]` | Profile blocked creators refresh button | -| `[data-testid="blocked-sync-status"]` | Profile blocked creators sync status message | -| `[data-testid="blocked-list"]` | Blocked creators list container | -| `[data-testid="blocked-empty-state"]` | Blocked creators empty state | -| `[data-testid="video-modal"]` | Video player modal | -| `[data-testid="video-card"]` | Individual video card in the feed | -| `[data-testid="video-list"]` | Video feed grid container | - -### Key Files - -| File | Purpose | -|------|---------| -| `js/testHarness.js` | Test harness module (installs `window.__bitvidTest__`) | -| `scripts/agent/simple-relay.mjs` | Mock relay with HTTP seeding API | -| `tests/e2e/helpers/bitvidTestFixture.ts` | Reusable Playwright fixture | -| `tests/e2e/agent-testability.spec.ts` | Infrastructure validation tests | -| `scripts/playwright/run-extension-persistent.mjs` | Dedicated persistent-context launcher for extension smoke tests | -| `docs/testing/playwright-integration-recommendations-2026-02-22.md` | Playwright/extension integration recommendations and follow-ups | -| `docs/testing/playwright-function-coverage.md` | Playwright coverage integration, latest baseline metrics, and improvement plan | +bitvid ships a test harness (`window.__bitvidTest__`, enabled via `?__test__=1`), a mock relay (`scripts/agent/simple-relay.mjs`), and a reusable Playwright fixture (`tests/e2e/helpers/bitvidTestFixture.ts`) so agents can programmatically log in, seed relay data, and inspect app state — no browser extensions or real relays required. +Full reference (harness API, mock relay, fixtures, `data-testid` selectors): [`docs/testing/agent-playwright-harness.md`](docs/testing/agent-playwright-harness.md). --- @@ -553,137 +430,37 @@ When finishing a task or handing off, provide: ## 16. Scenario-First Tests & Test Integrity (Dark Factory Standard) -In this repo, **validation replaces code review**. That means our test/scenario system is the *only* thing standing between “working software” and “green-but-worthless software.” - -Agents will naturally optimize for short-term goals (e.g., “get CI green”) unless we constrain them. If we ever allow “edit tests until they pass,” we destroy the signal, and over time the suite becomes meaningless. - -### Core philosophy: scenarios > checklists - -We treat tests as **behavioral specifications** expressed as **scenarios**: -- “User stories” / end-to-end behaviors (Given/When/Then or equivalent) -- Assertions focus on **externally observable outcomes** at boundaries (API responses, persisted state, emitted events, CLI output, UI state), not internal call sequences. -- We prefer *minimal coupling* to implementation details so refactors don’t require rewriting “truth.” - -(StrongDM reached the same conclusion: repo-local tests are easy to reward-hack; scenarios and satisfaction-based validation reduce “teaching to the test.”) - -### Non-negotiable constitution (anti-cheat) - -**Hard rules (always on):** -1) **Never weaken, delete, or rewrite a test just to make CI pass.** -2) **Never change expected behavior to match buggy output** (“rubber-stamp the snapshot”, “update golden”, “adjust assertion”) unless it is a **spec correction** (see below). -3) Do not “fix” flaky tests with retries, sleeps, timeouts, or looser assertions. **Remove nondeterminism** instead (control time, randomness, IO, network). -4) Prefer black-box assertions at system boundaries; avoid tests that merely mirror internal logic. - -### Definitions - -- **Scenario**: a behavioral spec (often end-to-end) describing what must happen for a user/system in a real environment. -- **Invariant**: a property that must always hold across many inputs/states (great for property-based or metamorphic testing). -- **Holdout scenario**: an evaluation scenario kept outside the agent-editable area to reduce overfitting / reward-hacking. - -### When tests may change (spec correction protocol) - -Changing a test expectation is allowed **only** when the test was enforcing the wrong behavior. - -If you believe a test should change: -1) Identify the **scenario/spec** that defines correct behavior. -2) Explain precisely why the old expectation was wrong. -3) Replace it with an **equally strict or stricter** behavior-based check. -4) Record a **Test Integrity Note** (below) and, if applicable, a short spec note (e.g., `docs/spec_changes/-.md`). - -If you cannot point to scenario/spec truth, **do not change the test**. Add a “Needs Spec Clarification” note instead. - -### “Test Integrity Note” (required for any PR touching tests) - -Every PR that adds/changes tests must include this machine-readable block in the PR description (or in a `TEST_INTEGRITY.md` entry referenced by the PR): - -```yaml -test_integrity_note: - change_type: ["new_tests" | "refactor_tests" | "spec_correction" | "flake_fix"] - scenarios: - - id: SCN- - given: "" - when: "" - then: "" - observable_outcomes: - - "" - determinism_controls: - - "" - anti_cheat_rationale: - prevents: - - "hard-coded return value" - - "over-mocking internal logic" - - "snapshot rubber-stamping" - - "retry/sleep-based flake masking" - relaxation: - did_relax_any_assertion: false - if_true_explain_spec_basis: "" -``` - -### Test portfolio guidance (scenario-first, still layered) - -We want a balanced suite: - -- **Scenario/E2E**: user-story validation at boundaries (few, high value, high fidelity) -- **Integration/contract**: service boundaries, serialization, persistence, workflows -- **Unit/invariants**: properties that kill trivial cheats (“return true” shouldn’t survive) - -For cheat resistance, strongly prefer: -- invariants/property-based tests for critical logic -- metamorphic relations where “exact expected output” is hard -- periodic mutation testing to “test the tests” (surviving mutants indicate missing assertions) - -### External dependency realism (service virtualization / “digital twins”) - -Where third-party services are involved, we prefer deterministic “behavioral clones” (mocks/stubs at the API boundary that reproduce edge cases and contracts) over live calls. This keeps scenarios realistic without being flaky or rate-limited, and it makes high-volume scenario validation affordable. - -### Role: Test Integrity & Scenario Spec Agent (Enforcer) - -This repo includes (or assumes) a dedicated agent role whose only job is to protect validation integrity. - -This agent’s goal is NOT “make CI green.” Its goal is “make the suite reflect reality and reject fake passes.” - -**Authority:** -- May add new scenarios, invariants, and tests. -- May refactor tests to be more behavioral and less procedural. -- May improve determinism (fixed seeds, fake clocks, hermetic env). -- May only change expectations via the Spec Correction Protocol above. - -**Prohibitions:** -- Must never weaken tests to pass. -- Must never edit holdout scenarios (if configured). -- Must never solve flakiness via retries/sleeps/loosening. - -**Deliverables each run:** -- Scenario list (Given/When/Then) -- Proposed test diffs -- Test Integrity Note -- “Cheat vectors blocked” summary (what trivial implementations it prevents) +**Invariant:** validation replaces code review in this repo — never weaken, delete, or rewrite a test merely to make it pass, and never mask flakiness with retries/sleeps/looser assertions. Expectation changes are allowed only via the Spec Correction Protocol with an accompanying Test Integrity Note. +Read the full standard before adding or modifying any test: `docs/testing/test-integrity.md`. --- ## 17. NIP-07 Signer Reliability & Encrypted-List Loading (hard-won, 2026-06-16) -The single biggest source of "DMs / hashtags / watch-history / block & subscription lists won't load after login" is **not** bitvid — it's an **unresponsive NIP-07 signer**. The extension's MV3 background service-worker can die or its content-script↔worker channel can orphan, after which raw `window.nostr` calls hang forever. No client change can force a dead signer to answer. +**Invariant:** the #1 cause of "DMs / hashtags / watch-history / lists won't load after login" is an unresponsive NIP-07 signer, not bitvid — diagnose with a raw `window.nostr` probe first, and do not regress the five resilience invariants (capped relay fan-out, NIP-07 circuit breaker, re-thrown transient decrypt errors, generous decrypt budgets, lazy modal population). +Full writeup: `docs/lessons/nip07-signer-reliability.md`. See also `docs/KNOWN_BUGS.md` #0. -* **Diagnose first, don't guess.** Run a single raw `window.nostr` probe in the page console (`getPublicKey → nip04.encrypt → nip04.decrypt`) that bypasses bitvid entirely. If *that* hangs, it's the extension/environment. Recommend a well-maintained signer (nos2x, Alby); KeysBand's dead worker was the root cause and switching to nos2x fixed everything. -* **Resilience invariants — do not regress these** (see `docs/KNOWN_BUGS.md` #0 for the full history and the files): - 1. **Cap relay fan-out** (`js/nostr/toolkit.js` `capReadRelays`, ≤8, user-relays-first + 2 reserved default slots). An uncapped cold-login REQ storm to ~20 dead NIP-65 relays starves the single-threaded signer's postMessage round-trips. - 2. **Circuit breaker** on the NIP-07 channel (`js/nostr/nip07Permissions.js`): after N consecutive *timeouts* fast-fail instead of hanging ~15s each. Channel-death errors must count toward opening (not reset it); interactive permission prompts bypass it; one periodic probe detects recovery. - 3. **Never swallow a transient decrypt error as an empty result.** Re-throw channel-death/timeout sub-errors so the retry path runs — returning `[]` turns "signer is slow" into "user has no blocks/lists" and kills retries. - 4. **Generous decrypt budget** (~25–30s/call, ~60s backoff cap); a 6s timeout kills slow-but-responsive signers mid-decrypt. Handshake variant timeout is ~20s for the same reason. - 5. **One actionable user notice** (`js/utils/signerHealthNotice.js`) after a few timeouts with `signerStatus: "present"` — not a silent forever-retry. -* **Don't render into closed modals at login.** Once a responsive signer makes decryption instant, eagerly populating every profile panel at login (friends avatars, subscriptions, blocks, DM summaries) freezes the main thread ~10–15s. Populate each pane lazily on open (`selectPane`) and gate data-change re-render listeners on "is the view open". -* **Deterministic testing.** Reproduce signer-dependent bugs headlessly with a fake `window.nostr` (Playwright `exposeBinding` + `addInitScript`) + a mock relay + configurable channel models (healthy/slow/overload/dead, latency override). See `scripts/perf/nip07-channel-sim.mjs`. +--- + +## Repo memory + +Curated agent memory lives in `.agents/` (index `.agents/MEMORY.md`); read it first. Propose additions in `.agents/proposals/`; `.agents/memory/` changes only via reviewed commits. Code/tests/config outrank memory. --- ## Next -Please read these documents next. +Read `docs/nostr-event-schemas.md` and `js/nostrEventSchemas.js` next; keep new nostr kinds centralized there and the doc up to date. -* `docs/nostr-event-schemas.md` -* `js/nostrEventSchemas.js` +# Agent fleet -And if you need to create new nostr kinds please keep the logic centralized there in `nostrEventSchemas.js` and the `nostr-event-schemas.md` up to date. +This repo uses agent-fleet-kit. The `agent-fleet` MCP server (`fleet-mcp`) is a convenience. Editing backlog JSON and mailbox files directly is fine. Do not hand-edit the landing queue while a drain is merging, and do not push main. -**End of AGENTS.md** +- `area_info` for the owner of a path. +- `backlog_claim` with an item id marks it in-progress. Pick the item from PROGRAM.md. Without an id the tool does not choose one. Idle is a correct result. +- `mailbox_read` at the start of a turn; `mailbox_send` to reach a peer. Mail is always delivered. The prompt says when to act. +- `commit` to stage and commit in your area worktree. Codex `workspace-write` keeps `.git` read-only, so do not run `git commit` yourself. Pass `message` and the file paths (or `all: true`), and `cwd` or `area` when this server is not already in the worktree. `submit: true` also queues the tip. A doesNotOwn path warns and still commits. The same operation is `fleet commit -m "..." --area [--submit] -- `. +- `landq_submit` queues the committed tip and starts a drain. A dirty worktree warns; only HEAD is queued. Then go back to work. At your next natural checkpoint, `land_status` (or `fleet landq status `) is a quick lookup, and its wait is optional. Never infer the lander or the land result from silence. States: queued, gating, landed with the main sha, rejected with the reason, or unknown. If no drain is alive, the answer says so and names `fleet lander start`. +- A scarce machine is a prompt convention: say so in `.task` and wait if a peer has it. There is no slot lock. + +Sessions, worktrees, and cron are the `fleet` CLI. Submit starts the drain; do not run a second merge by hand. diff --git a/CLAUDE.md b/CLAUDE.md index 85967aca..259a306b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,8 @@ This document provides context and guidance for AI assistants working with the bitvid codebase. It explains the project structure, development workflows, conventions, and key architectural decisions. +**Canonical guidance:** `AGENTS.md` is the canonical agent guidance for this repo; `CLAUDE.md` holds Claude-oriented codebase orientation that layers on top of it. + --- ## Project Overview @@ -312,7 +314,7 @@ const dTag = event.tags.find(t => t[0] === 'd')?.[1]; ### NIP-07 Signer Reliability (hard-won) -The #1 cause of "DMs / hashtags / watch-history / lists won't load after login" is an **unresponsive NIP-07 signer**, not bitvid. The extension's MV3 background worker can die and `window.nostr` calls then hang forever — no client change fixes a dead signer. **Diagnose with a raw `window.nostr` probe** (`getPublicKey → nip04.encrypt → nip04.decrypt`) in the console; if that hangs, it's the extension (recommend nos2x/Alby). See `AGENTS.md` §17 and `docs/KNOWN_BUGS.md` #0. +The #1 cause of "DMs / hashtags / watch-history / lists won't load after login" is an **unresponsive NIP-07 signer**, not bitvid. The extension's MV3 background worker can die and `window.nostr` calls then hang forever — no client change fixes a dead signer. **Diagnose with a raw `window.nostr` probe** (`getPublicKey → nip04.encrypt → nip04.decrypt`) in the console; if that hangs, it's the extension (recommend nos2x/Alby). See `docs/lessons/nip07-signer-reliability.md` (summarized in `AGENTS.md` §17) and `docs/KNOWN_BUGS.md` #0. Resilience invariants — **do not regress**: @@ -326,78 +328,7 @@ Resilience invariants — **do not regress**: ## Testing & Validation: Scenario-First, Cheat-Resistant (Dark Factory Rules) -You are operating inside a “Dark Factory” workflow where **validation replaces human code review**. Passing tests is only meaningful if tests are truthful. - -### Prime directive - -**Never alter a test merely to make it pass.** -If you change tests to match buggy behavior, you are destroying the only feedback signal and producing code with no real-world value. - -StrongDM describes the exact failure mode we are preventing: agents discovering shortcuts (“return true”) and narrow tests being reward-hacked, leading to a shift from “tests” to scenario-based validation. - -### How to think about tests here - -Tests are not procedural checklists. They are **behavioral specifications**: -- Write tests as **scenarios** (Given/When/Then or equivalent). -- Assert **externally observable outcomes** at boundaries. -- Avoid “testing the implementation” (internal call sequences, private methods, mirrored logic). -- Prefer deterministic/harmonic execution: fixed seeds, controlled clocks, hermetic IO, virtualized dependencies. - -### Prohibited behaviors (reward-hacking patterns) - -Do NOT: -- weaken assertions, broaden tolerances, or reduce coverage to get green -- update snapshots/goldens without proving the change is intended behavior -- add retries/sleeps/timeouts to hide flakes -- mock so heavily that tests can pass with a broken system -- “hard-code to the test” (single fixtures, single input paths, brittle constants) - -### Allowed test changes: Spec Correction Protocol - -You may change test expectations only if the test is wrong relative to intended behavior. - -Required steps: -1) Identify the scenario/spec that defines correct behavior. -2) Explain why the previous expectation was incorrect. -3) Replace it with an equally strict (or stricter) behavioral assertion. -4) Provide a **Test Integrity Note** (machine-readable) documenting the change. - -If you cannot justify a change with scenario/spec truth: **do not change the test**. Instead, output a “Needs Spec Clarification” note and propose candidate scenarios. - -### Required output when you touch tests - -Include this YAML block in your PR description (or reference it from a `TEST_INTEGRITY.md` entry): - -```yaml -test_integrity_note: - change_type: ["new_tests" | "refactor_tests" | "spec_correction" | "flake_fix"] - scenarios: - - id: SCN- - given: "" - when: "" - then: "" - observable_outcomes: - - "" - determinism_controls: - - "" - anti_cheat_rationale: - prevents: - - "hard-coded return value" - - "over-mocking internal logic" - - "snapshot rubber-stamping" - - "retry/sleep-based flake masking" - relaxation: - did_relax_any_assertion: false - if_true_explain_spec_basis: "" -``` - -### If external services are involved - -Prefer boundary-level service virtualization (“digital twins” / behavioral clones) so scenario tests remain: -- high fidelity (realistic edge cases) -- deterministic and replayable -- not rate-limited / not flaky -(StrongDM’s “Digital Twin Universe” is the reference pattern.) +The testing and validation invariant is stated in `AGENTS.md` §16 "Scenario-First Tests & Test Integrity (Dark Factory Standard)", and the full standard lives in [`docs/testing/test-integrity.md`](docs/testing/test-integrity.md). In short: validation replaces human code review here, tests are behavioral scenarios that assert externally observable outcomes at boundaries, and you must **never alter a test merely to make it pass** — expectation changes are allowed only via the Spec Correction Protocol with an accompanying Test Integrity Note. Read `docs/testing/test-integrity.md` in full before adding or modifying any test. --- @@ -438,7 +369,7 @@ View with: `./scripts/show-artifacts.sh` ### Agent E2E Testing (Playwright) -bitvid includes a test harness for programmatic Playwright testing without browser extensions or real relays. See `AGENTS.md` Section 14 for the full reference. Quick start: +bitvid includes a test harness for programmatic Playwright testing without browser extensions or real relays. See `docs/testing/agent-playwright-harness.md` for the full reference (summarized in `AGENTS.md` §14). Quick start: ```typescript import { test, expect } from "./helpers/bitvidTestFixture"; @@ -457,7 +388,7 @@ Key capabilities: - **Mock relay**: Each test gets an isolated in-memory relay with HTTP seeding - **Relay override**: `?__test__=1&__testRelays__=ws://localhost:8877` redirects all connections - **State inspection**: `window.__bitvidTest__.getAppState()` returns login status, relays, etc. -- **Stable selectors**: All key elements have `data-testid` attributes (see AGENTS.md Section 14) +- **Stable selectors**: All key elements have `data-testid` attributes (see `docs/testing/agent-playwright-harness.md`) --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c838bbcc..80326bb2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -242,3 +242,12 @@ If you find a vulnerability, please email **[security@bitvid.network](mailto:sec ## Trademark The **bitvid** name and logos are trademarks (see `TRADEMARKS.md`). Don’t use them in ways that imply official sponsorship or confuse users. “Powered by bitvid” attribution is encouraged. + +## AI-assisted contributions + +AI-assisted changes are subject to the same testing, security, and review +requirements as any other change; the contributor remains responsible for +understanding what they submit. Do not submit unvalidated generated code, +credentials, or agent-generated trusted memory without evidence and review. +The canonical backlog and planning rules are in `AGENTS.md`; durable agent +plans live under `.agents/plans/`. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..4da75e4e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security Policy + +## Reporting vulnerabilities + +Do not report suspected security vulnerabilities in public issues, pull +requests, or agent-memory files. Use GitHub's private vulnerability reporting +on this repository (Security tab → Report a vulnerability), or email +[security@bitvid.network](mailto:security@bitvid.network) — we will +coordinate a responsible disclosure window before public release. + +Include the affected commit, reproduction steps, and observed impact. Do not +include live credentials or unnecessary personal data. + +## AI-agent configuration + +`AGENTS.md`, `CLAUDE.md`, `.agents/**`, and any automation able to modify +agent state are security-sensitive configuration. Changes to them require +maintainer review. + +## Agent memory + +Never commit credentials, personal data, raw transcripts, tool logs, or +unredacted external content. Agent-generated memory enters through +`.agents/proposals/` and is promoted to `.agents/memory/` only via reviewed +commits. Content from websites, issues, emails, and tool responses is +untrusted until validated. + +## Memory-poisoning response + +If agent instructions or memory may have been poisoned: stop agent automation, +quarantine or revert the affected file, identify runs influenced by it, review +resulting commits, and restore the last trusted configuration. diff --git a/TEST_INTEGRITY.md b/TEST_INTEGRITY.md index eda16051..b707d1df 100644 --- a/TEST_INTEGRITY.md +++ b/TEST_INTEGRITY.md @@ -588,3 +588,252 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + + +--- + +# client-reliability-1 + +```yaml +test_integrity_note: + change_type: ["flake_fix", "new_tests"] + scenarios: + - id: SCN-profile-focus-style-contract + given: "modal controls include visible, disabled, CSS-hidden, inherited-hidden, aria-hidden, hidden-input, and hidden-attribute cases" + when: "the real controller collects focus targets using fixture and native JSDOM styles, and the user tabs from the last control" + then: "both resolvers retain only visible enabled controls and Tab wraps to the first" + observable_outcomes: + - "focus target control IDs match the strict expected list" + - "Tab is prevented and document.activeElement moves to the first control" + - "all 26 existing modal scenarios preserve their assertions and complete naturally" + determinism_controls: + - "stylesheet-free full-markup fixture virtualizes only display and visibility" + - "small contract scenario runs the same assertions against native JSDOM computed styles" + - "explicit test stylesheet, local services, real DOM focus and event dispatch; no network" + anti_cheat_rationale: + prevents: + - "hard-coded focus list: varied hidden and visible controls" + - "over-mocking controller behavior: real updateFocusTrap and keyboard listener" + - "style resolver rubber-stamping: native resolver must satisfy the same scenario" + - "retry/sleep-based flake masking: no timeouts, retries, or sleeps added" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Mutation evidence: removing the product display/visibility exclusion causes this scenario to fail with the hidden control IDs in its focus list (exit 1, 1 failed/0 passed). Mutant reverted. This fixes expensive JSDOM style evaluation in the test harness, not a demonstrated browser defect. Application CSS/layout remains outside this unit fixture's scope. + +Fixture mutation: omitting native hidden-attribute filtering also fails the contract (exit 1, 1 failed/0 passed); restored before final checks. + +--- + +# client-reliability-2 + +```yaml +test_integrity_note: + change_type: ["flake_fix", "new_tests"] + scenarios: + - id: SCN-concurrent-init-hermetic-subscription + given: "two init callers share a pool initialization held at a deterministic barrier, with a virtual successful relay connection" + when: "pool initialization is released and the second caller opens then closes a video subscription" + then: "the second caller waits for pool readiness, real L1 feed setup reaches the fake pool, and unsubscribe closes the transport exactly once with no reconnect timer" + observable_outcomes: + - "second init promise remains pending until the pool barrier is released" + - "pool is ready after awaiting the second caller; both init promises settle" + - "post-init subscription requests video events from a nonempty relay set" + - "subscription transport closes once, including repeated unsubscribe" + - "the process exits naturally with no public relay IO or reconnect retries" + determinism_controls: + - "deferred pool barrier replaces the old 50ms delay" + - "fake pool at the actual transport boundary; real init, L1, and feed cleanup" + - "manager connection boundary reports success rather than triggering background reconnect" + - "local-data/signer restore are virtualized; teardown awaits init, closes feed and resets reconnect state even after failure" + anti_cheat_rationale: + prevents: + - "early-return init mutant fails while pool initialization is held" + - "no-op subscription cannot satisfy the observed transport request" + - "no-op cleanup cannot satisfy exact close count" + - "failure fixture cannot hide a reconnect timer behind a passing assertion" + - "retry/sleep/forced-exit masking: no timeout changes or forced exit" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Original concurrent-promise, pool-ready and post-init no-throw assertions remain. The old empty connection result accidentally modeled failure; init correctly scheduled manager-owned retries that bypassed its client-level stub. The fixture now models successful init at the manager boundary and releases its feed subscription. This is harness cleanup, not a Nostr product change. + +Mutants: a test-local historical early-return init variant fails the pending-barrier assertion (exit1, 1 failed); a connection-failure fixture variant fails the no-retry assertion (exit1, 1 failed). Both exit naturally and are reverted. No product file was edited for either mutant. + +--- + +# client-reliability-3 + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-last-probe-owner-disposal + given: "five real DOM cards register through actual gridHealth/cardObserver; four probes are active and one queued" + when: "the feed is disposed/removed and active probes finish" + then: "zero obsolete probes start" + - id: SCN-shared-queued-owner + given: "two cards share one queued torrent source" + when: "one registration disposes while its DOM remains connected" + then: "the live owner still probes and becomes healthy; disposed owner receives no stale UI/callback" + - id: SCN-cancelled-replacement-inflight + given: "last owner cancels queued work then a replacement requests the same source before old settlement" + when: "old completion drains and another owner joins before a slot frees" + then: "no unhealthy cancellation cache is written, only one new probe starts, and both replacements become healthy/cacheable" + - id: SCN-probe-priority-concurrency + given: "four active probes, queued mixed priorities and a cancelled source" + when: "slots free one at a time" + then: "four-slot maximum and stable priority order remain; cancelled work never starts" + - id: SCN-hidden-healthy-reveal + given: "a hide-all card is hidden pending liveness" + when: "registration eagerly probes and returns healthy" + then: "raw magnet/webseed bytes reach probe IO and the card reveals without an intersection" + - id: SCN-active-shared-late-entry + given: "an active source loses its owner, receives a late observer entry and gains a live owner" + when: "the unchanged active probe completes" + then: "no duplicate starts or disposed result writes; live owner becomes healthy" + - id: SCN-repeat-disposal-reattach + given: "a disposed container has a new queued registration" + when: "the old disposer is called again" + then: "new owner survives and becomes healthy" + - id: SCN-ownerless-active-cache + given: "an already active probe has no remaining owner" + when: "it finishes before a later live card requests that source" + then: "the active result remains cached; no new probe or stale UI write occurs" + - id: SCN-existing-probe-cache-ttl + given: "completed healthy and negative results" + when: "cards request before, at, and after the existing TTL boundary" + then: "both cached outcomes reuse until the existing inclusive TTL expires, then both probe again" + observable_outcomes: + - "actual probePeers start count/order, arguments and maximum active count" + - "real DOM badge health/hidden state, observer disposal and callback counts" + - "storage cache absence for cancellation and reuse/expiry for completed probes" + determinism_controls: + - "actual gridHealth/cardObserver and DOM, controllable observer and deferred probePeers IO" + - "explicit viewport geometry, unique sources and isolated storage; fake Date.now only for TTL scenario" + - "task-turn promise draining without wall-clock sleeps, polling or retries" + - "native Chromium/Firefox observer smoke against built dist, external traffic blocked" + anti_cheat_rationale: + prevents: + - "unconditional queue cancellation: shared-owner mutant fails" + - "no-op cancellation, stale inflight deletion or cancelled cache poisoning" + - "changed concurrency/priority or hidden eager path" + - "stale result/listener/observer effects and repeated old disposal" + - "cancelling or dropping cache from already active work" + - "timeout/retry/sleep masking or hard-coded healthy results" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Grouped mutation coverage: last-owner cancellation mutant kills SCN-last-probe-owner-disposal; unconditional cancel kills SCN-shared-queued-owner; unconditional inflight deletion and cancelled-normalization mutants each kill SCN-cancelled-replacement-inflight; reversed queue priority kills SCN-probe-priority-concurrency; disabled eager path kills SCN-hidden-healthy-reveal; stale result guard and late-entry membership mutants each kill SCN-active-shared-late-entry; non-idempotent disposer kills SCN-repeat-disposal-reattach; owner-dependent active caching kills SCN-ownerless-active-cache; missing cache writes kills SCN-existing-probe-cache-ttl. All11 variants exit1 with1 failed scenario,0 cancelled, and are reverted. Browser checks corroborate the same disposal/shared-owner/hidden-reveal/source-visibility scenarios with native observers; no browser CPU or actual media fallback claim. + +Additional client-reliability-3 scenarios (same invariant and controls): + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-shared-badge-listener-lifetime + given: "two feed registrations share the app callback" + when: "badge updates occur and registrations dispose one at a time" + then: "each event notifies once, one surviving owner retains the callback, last disposal stops notifications" + - id: SCN-checking-callback-disposal + given: "a live card's checking callback synchronously disposes the feed" + when: "badge notification returns to probe submission" + then: "no probe is submitted after unregister" + observable_outcomes: + - "callback event count/state sequence and external probe start count" + determinism_controls: + - "real badge/observer path, synchronous disposal callback and deferred probe IO" + anti_cheat_rationale: + prevents: + - "duplicated shared callbacks or dropping live callback owners" + - "post-disposal work submitted after synchronous reentrant teardown" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Duplicate wrapper and unconditional listener release variants each fail SCN-shared-badge-listener-lifetime. Removing the post-checking membership guard fails SCN-checking-callback-disposal (actual starts1 vs expected0). These3 additional variants exit1 with1 failed/0 cancelled and are reverted; total14 killed variants for11 lifecycle scenarios. Original source/priority/observer expectations remain unchanged. + + +## client-reliability-4 — queued URL media ownership + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-url-last-owner + given: "four cards, three active media and one queued" + when: "last registration is disposed and an active slot frees" + then: "queued promise settles null, no cache entry or obsolete media start" + - id: SCN-url-shared-owner + given: "two feeds share a queued event/source" + when: "one feed disposes" + then: "exactly one media probe serves the live badge; disposed connected badge stays checking" + - id: SCN-url-last-shared-owner + given: "two feeds share a queued event/source" + when: "both feeds dispose" + then: "queued promise settles null and never starts media" + - id: SCN-url-cancel-rerequest + given: "cancelled queued event/source has pending promise continuations" + when: "a new feed requests the same source immediately" + then: "new inflight record survives old settlement and live badge becomes healthy" + - id: SCN-url-cancel-mirror + given: "queued primary has a mirror" + when: "last owner disposes" + then: "cancelled work settles without mirror HEAD/media or stale UI/cache" + - id: SCN-url-registration-reuse + given: "hidden old feed queues an eager callback" + when: "dispose, reuse container, deliver old callback and old disposer" + then: "only the new event reaches onCheck and starts IO" + - id: SCN-url-active-cache + given: "active media belongs to a connected card" + when: "dispose its registration and complete active media" + then: "active result caches healthy, disposed badge stays checking, new card uses cache" + - id: SCN-url-hidden-reveal + given: "URL-only card is hidden pending health" + when: "eager media confirmation succeeds" + then: "card becomes visible with healthy badge" + - id: SCN-url-media-limit + given: "three active media probes plus FIFO queued work" + when: "slot releases while a new card enters" + then: "existing queued source runs first; active media stays three" + - id: SCN-url-mirror-order + given: "primary and two mirrors exist" + when: "primary errors then first mirror succeeds" + then: "primary then first mirror are requested; second mirror is untouched" + - id: SCN-url-legacy-callback + given: "connected badge lacks observer ownership" + when: "legacy caller invokes controller" + then: "media settles and healthy badge paints" + - id: SCN-url-cross-controller + given: "shared real inflight cache and two controllers" + when: "creator feed disposes while other feed remains" + then: "queued shared source starts once and other controller paints healthy" + observable_outcomes: + - "HEAD and native media IO starts, promise settlement, real cache/inflight records, connected badge and card visibility" + - "Observer callback emissions after disposal and container reuse" + determinism_controls: + - "jsdom cards, controllable IntersectionObserver, explicit loadeddata/error events and stubbed fetch boundary; real observer/controller/cache" + - "Unique event/source keys and task-turn drain; no live traffic, arbitrary sleeps, retries or relaxed assertions" + anti_cheat_rationale: + prevents: + - "hard-coded healthy or cancelled results" + - "testing a copied queue instead of real controller and observer" + - "cancelling shared live work or caching cancellation as offline" + - "stale connected DOM callbacks hidden by isConnected guards" + - "retry/sleep-based flake masking" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +All existing assertions preserved. `tests/url-health-lifecycle.test.mjs` adds12 scenarios. Mutations are temporary edits restored before gates. `python3 /tmp/item4-mutants.py` final run:13/13 variants naturally exit1 with named assertion failures (no timeouts); logs `/tmp/item4-mutant-*.log`, results `/tmp/item4-mutants.json`. No production test runner/gate changes. + +Coverage grouping: no-unregister→last owner; cancel-shared→shared owner/cross-controller; reuse-cancelled-inflight→immediate retry/new inflight; cache-cancellation→last/shared owner/retry/mirror cache outcomes; mirror-fallthrough→cancelled primary; grouped stale guards→shared/active disposed UI (remove both redundant owner guards together; two equivalent variants); eager-callback-stale→registration reuse; no-eager-reveal→hidden reveal; concurrency-four→three-media cap/FIFO; reverse-mirrors→mirror ordering; break-legacy-consumer→callback compatibility; controller-local-records→shared cache ownership across controllers. Initial mutation pass exposed callback-emission and first-source gaps; new assertions were strengthened, then all mutants rejected. Initial no-unregister full-file run failed assertions but hung in another scenario awaiting its intentionally broken promise; final targeted last-owner variant naturally exits1. No timeout is counted as successful mutant evidence. diff --git a/context/CONTEXT_2026-10-19_19-45-53_dispatch-startup.md b/context/CONTEXT_2026-10-19_19-45-53_dispatch-startup.md new file mode 100644 index 00000000..628ee1ee --- /dev/null +++ b/context/CONTEXT_2026-10-19_19-45-53_dispatch-startup.md @@ -0,0 +1 @@ +Goal: TODO backlog progress, bug fixes, robustness and usability. Scope: dispatch triage and proposed first team. Non-goals: product edits, worker launch before approval, deploys, spending, production data. Done: goal recorded, baseline measured, concrete first-team proposal presented. diff --git a/context/CONTEXT_2026-10-19_20-05-00_client-reliability.md b/context/CONTEXT_2026-10-19_20-05-00_client-reliability.md new file mode 100644 index 00000000..21978665 --- /dev/null +++ b/context/CONTEXT_2026-10-19_20-05-00_client-reliability.md @@ -0,0 +1,3 @@ +- Goal: classify and fix profile-modal-controller suite hang at current HEAD 9e6c310d (unchanged from filedAt 8b204687). +- Scope: owned test and narrow modal lifecycle helpers only. Preserve assertions; no retries, sleeps, timeout increases, quarantine or forced success exit. +- Done: deterministic regression, failing mutant, isolated natural exit, full 60s/file unit run, lint; build/browser if product changes; fleet commit, merge unstable, checks, queue and durable landing. diff --git a/context/CONTEXT_2026-10-19_20-22-00_client-reliability-2.md b/context/CONTEXT_2026-10-19_20-22-00_client-reliability-2.md new file mode 100644 index 00000000..d5a1e45b --- /dev/null +++ b/context/CONTEXT_2026-10-19_20-22-00_client-reliability-2.md @@ -0,0 +1,5 @@ +- Goal: make tests/race/nostr-client-init-race.test.mjs hermetic and naturally exit while preserving concurrent init, pool readiness, and post-init subscription coverage. +- Temporary ownership granted by dispatch via landed RULE-CHANGES/prompt and owner resume instruction; no Nostr product/shared helper/global runner edits. +- Revalidated unchanged from filedAt f303ab0e72; prior isolated assertion60.91ms passes but process timeout10s/exit124, full gate timeout60s at file227. +- Plan: inspect NIP01 and real init/L1 subscription lifecycle; replace delay with deferred pool barrier; virtualize current pool boundary, close subscription; add observable boundary/cleanup assertions; mutant-check, run both isolated suites, commit separately, merge unstable, lint and full60s/file gates, land normally or route further blockers. +- Preserve prior profile-modal commits and all assertions. No browser, real wallet, signer, external relay or deploy operations. diff --git a/context/CONTEXT_2026-10-19_21-52-00_client-reliability-3.md b/context/CONTEXT_2026-10-19_21-52-00_client-reliability-3.md new file mode 100644 index 00000000..8492bb2d --- /dev/null +++ b/context/CONTEXT_2026-10-19_21-52-00_client-reliability-3.md @@ -0,0 +1,4 @@ +- Goal: cancel unstarted torrent health probes when last feed/card owner unregisters, preserving live shared owners and active probes. +- Scope: gridHealth.js, optional queue helper and focused tests/integrity/area notes. Excludes app, URL health, webtorrent implementation, playback/magnet helpers, cache TTLs/concurrency, runner/kit. Dispatch completion mailbox required; no scheduler. +- Revalidated unchanged gridHealth/cardObserver from filedAtf9adfc8f through startup66832dc3. Adapted actual dispatch five-card reproduction to own imports:4 start,1 queues, dispose/remove then release ->1 obsolete start; assertion exit1. +- Plan: introduce cancellable pending queue records and per-card leases; release at existing onCardUnregister; preserve active completion and cache, identity-check inflight deletion; reject stale observer/UI effects; scenario tests for shared owners, cancellation/re-request race, priority/concurrency, hidden reveal; grouped mutants; focused/full60s-file/lint/build/browser; commit/merge/normal landq/ancestry then close and report. diff --git a/context/CONTEXT_2026-10-20_03-10-00_client-reliability-4.md b/context/CONTEXT_2026-10-20_03-10-00_client-reliability-4.md new file mode 100644 index 00000000..518f5a81 --- /dev/null +++ b/context/CONTEXT_2026-10-20_03-10-00_client-reliability-4.md @@ -0,0 +1,3 @@ +# client-reliability-4 + +Investigation only at4c54858c. URL queued media last-owner disposal reproduced: obsolete starts1 expected0; shared live-owner control passes. See test_logs/TEST_LOG_2026-10-20_03-10-00_client-reliability-4.md and /tmp/bitvid-client-reliability-4-url-lifecycle.mjs. Await dispatch scope before product/tests. diff --git a/context/CONTEXT_2026-10-20_03-29-00_client-reliability-4-landed.md b/context/CONTEXT_2026-10-20_03-29-00_client-reliability-4-landed.md new file mode 100644 index 00000000..8a1c5799 --- /dev/null +++ b/context/CONTEXT_2026-10-20_03-29-00_client-reliability-4-landed.md @@ -0,0 +1,3 @@ +# client-reliability-4 complete + +Product landed89ccb907, normal gate exit0 and7055496b ancestry verified. Backlog closed. All required local checks pass; evidence test_logs/TEST_LOG_2026-10-20_03-29-00_client-reliability-4-landed.md. Finish normal closure metadata landing/final event, then idle. No active abort, fallback/live peers,CPU or whole Stage3 claim. diff --git a/context/CONTEXT_2026-10-20_13-30-00_client-reliability-5.md b/context/CONTEXT_2026-10-20_13-30-00_client-reliability-5.md new file mode 100644 index 00000000..4cf5fa83 --- /dev/null +++ b/context/CONTEXT_2026-10-20_13-30-00_client-reliability-5.md @@ -0,0 +1,3 @@ +# client-reliability-5 + +Investigation at0c1dd7e8:bounded active URL success/timeout cleanly settle; disposed active primary media error starts1 new mirror HEAD(expected0). Shared/rejoin and later same-source controls pass. Evidence test_logs/TEST_LOG_2026-10-20_13-30-00_client-reliability-5.md; scratch /tmp/bitvid-client-reliability-5-active-url.mjs. Await dispatch routing; no product/regression changes. diff --git a/context/CONTEXT_2026-10-20_13-36-00_client-reliability-5.md b/context/CONTEXT_2026-10-20_13-36-00_client-reliability-5.md new file mode 100644 index 00000000..894b1ca5 --- /dev/null +++ b/context/CONTEXT_2026-10-20_13-36-00_client-reliability-5.md @@ -0,0 +1,3 @@ +# Item5 implementation + +Controller-only6-line pre-candidate ownership guard. Six new lifecycle scenarios reproduce2 baseline failures and pass18/18 fixed;6 reverted mutants rejected. Unique integrity note test_logs/TEST_INTEGRITY_client-reliability-5.md. Focused/lint/build running; awaiting explicit dispatch full-suite/browser slots and verified unstable merge before final checks/landing. No active abort/observer/cache/limit changes. diff --git a/context/CONTEXT_2026-10-20_13-39-30_storage-cors.md b/context/CONTEXT_2026-10-20_13-39-30_storage-cors.md new file mode 100644 index 00000000..bd2cf829 --- /dev/null +++ b/context/CONTEXT_2026-10-20_13-39-30_storage-cors.md @@ -0,0 +1,6 @@ +# storage-cors-1 +- Start UTC: 2026-10-20T13:38:00Z; launch reported by dispatch ~13:37:59Z; acknowledgement 13:38:24Z. +- Goal: validate current-HEAD existing-origin loss via real helper and offline SDK/transport; propose preservation semantics before edits. +- Scope: investigation/evidence first; product/test changes require dispatch routing. No browser/cloud credentials/network bucket mutations; preserve preview3000. +- Done: claimed; traced callers; 5 characterization cases reproduced. See docs/testing/storage-cors-integrity.md. +- Pending: dispatch preservation semantics/fix routing, regression/mutants/focused checks, coordinated normal gates/build/landing. diff --git a/context/CONTEXT_2026-10-20_13-58-00_client-reliability-5-landed.md b/context/CONTEXT_2026-10-20_13-58-00_client-reliability-5-landed.md new file mode 100644 index 00000000..7b6c4f06 --- /dev/null +++ b/context/CONTEXT_2026-10-20_13-58-00_client-reliability-5-landed.md @@ -0,0 +1,3 @@ +# Item5 complete + +Product landed0bd0b1c3 via4bc52f9d,normal gate exit0/unstable ancestry verified. Backlog closed;expensive slot and browser released to dispatch13:57UTC. Local427files/28focused/18lifecycle/6mutants/lint11/build/native browser smoke pass. Finish docs-only closure queue/final event then idle. Evidence test_logs/TEST_LOG_2026-10-20_13-58-00_client-reliability-5-landed.md. No active abort/playback/CPU claim. diff --git a/context/CONTEXT_2026-10-20_15-01-40_storage-cors-2.md b/context/CONTEXT_2026-10-20_15-01-40_storage-cors-2.md new file mode 100644 index 00000000..49f52ea5 --- /dev/null +++ b/context/CONTEXT_2026-10-20_15-01-40_storage-cors-2.md @@ -0,0 +1,6 @@ +# storage-cors-2 +- Current route: read-only remaining storage/delete-safety audit per dispatch14:58:59; former feed scope deferredstorage3. +- Actual real-service malformed cached bucket/base reproduction2rejects vs0required;6controls pass,existing focused11scenarios/3files pass. docs/testing/storage-cors-delete-audit.md has fidelity/callers/limits. +- Product/test read-only until routing. Proposed defensive primitive-string handling/structured skip in r2Service.deleteVideoStorage + focused new regression. s3UploadService contains no duplicate delete method; no upload/key changes proposed. +- Pending: dispatch fix routing;claim item2 after queued metadata visibility. QA owns full/normal/browser;no full/build/browser/cloud. +- Scratch /tmp/storage-cors-delete-audit.mjs;deferred feed invalid-seed scratch /tmp/storage-cors-feed-audit.mjs preserved. No secret/private-data use. diff --git a/context/CONTEXT_2026-10-20_15-04-00_client-reliability-6.md b/context/CONTEXT_2026-10-20_15-04-00_client-reliability-6.md new file mode 100644 index 00000000..8c9fd17c --- /dev/null +++ b/context/CONTEXT_2026-10-20_15-04-00_client-reliability-6.md @@ -0,0 +1,3 @@ +# Item6 audit + +Atd88716e3 real profile remove local/synced controls pass;historical runtime no-op fails both actual-wiring diagnostics after cleanup but existing profile27 stillpass. Valuable missing regression,notcurrentproductfailure. Verified metadata merge tobbf57388/claimed;profile product/tests unchanged. Await test-only routing;notes only,no product/test commits. See test_logs/TEST_LOG_2026-10-20_15-04-00_client-reliability-6.md. QA full/normal slot respected. diff --git a/context/CONTEXT_2026-10-20_15-12-00_client-reliability-6-retention.md b/context/CONTEXT_2026-10-20_15-12-00_client-reliability-6-retention.md new file mode 100644 index 00000000..55a0c77a --- /dev/null +++ b/context/CONTEXT_2026-10-20_15-12-00_client-reliability-6-retention.md @@ -0,0 +1,3 @@ +# Item6 retention + +New tests/profile-history-removal-wiring.test.mjs retains3 actual profile/factory/DOM/controller/service scenarios;five runtime mutants fail with0cancelled. Product and existing suites unchanged. Focused/lint running;no full/browser/build until explicit resource grant. Unique integrity in test_logs/TEST_INTEGRITY_client-reliability-6.md. No real signer/relay/fullapp/reload claim. diff --git a/context/CONTEXT_2026-10-20_16-14-00_client-reliability-7.md b/context/CONTEXT_2026-10-20_16-14-00_client-reliability-7.md new file mode 100644 index 00000000..b6f68339 --- /dev/null +++ b/context/CONTEXT_2026-10-20_16-14-00_client-reliability-7.md @@ -0,0 +1,6 @@ +# Item7 cold live feed notification + +- Goal: cold cache then eligible live updates must notify actual loadVideos consumer;latest feed owns reused subscription and clear invalidates late callbacks. +- Scope: nostrService loadVideos notification state plus constructor/clear lifecycle only;new focused service test and unique evidence/backlog. No app/feed/filter/cache policy/runner changes. +- Plan: reproduce original service with controlled subscribeVideos boundary;prove latest/force/clear controls;minimal current-consumer token;mutants/focused/lint;explicit full slot then one unchanged normal gate;QA owns actual-app browser after landing/preview rebuild. +- Original4e904349: new first5 scenarios4fail1pass/0cancel;first8line callback fix later5pass2fail on7. Revised service19 added lines passes7/7. No actual browser/full/build yet. diff --git a/context/CONTEXT_2026-10-20_16-30-00_dispatch-local-review.md b/context/CONTEXT_2026-10-20_16-30-00_dispatch-local-review.md new file mode 100644 index 00000000..f372cd61 --- /dev/null +++ b/context/CONTEXT_2026-10-20_16-30-00_dispatch-local-review.md @@ -0,0 +1,7 @@ +# Local review scope + +- Owner milestone:Ready for me to review and test locally. +- Codefixb1174163/full432gate0;preview6da89895/HTTP200/indexcmp0. +- Actualcoldfeed+separate25step3viewportcoverage complete;originalUpload2/4pxoverflowfailureopen,notwholeallpass. +- Finish durable named QA/dispatch docs and final runtime/preview audit;thenIDLE,nooptionalnewwork. +- Live and deferred gates:docs/agents/LOCAL-REVIEW.md andfleet/QUALITY.md. diff --git a/context/CONTEXT_2026-10-20_20-51-00_client-reliability-8.md b/context/CONTEXT_2026-10-20_20-51-00_client-reliability-8.md new file mode 100644 index 00000000..21807e86 --- /dev/null +++ b/context/CONTEXT_2026-10-20_20-51-00_client-reliability-8.md @@ -0,0 +1,4 @@ +- Goal: measure active URL-media cancellation at integrated HEAD5b77fd20 before proposing minimal fix. +- Scope: real observer/controller/cache with controlled external HEAD/media IO; scratch/evidence only. +- Done: resource/listener/timer/promise/slot/FIFO/cache/inflight/stale-write measurements; shared/rejoin/cross-controller/legacy controls; exact reproduction and scoped proposal to dispatch. +- No product/committed-test changes or full/build/browser without routing/grant. Preserve prior queued-only assertions pending explicit contract correction. diff --git a/context/CONTEXT_2026-10-22_14-05-00_dispatch-campaign.md b/context/CONTEXT_2026-10-22_14-05-00_dispatch-campaign.md new file mode 100644 index 00000000..c40edef7 --- /dev/null +++ b/context/CONTEXT_2026-10-22_14-05-00_dispatch-campaign.md @@ -0,0 +1 @@ +Six-priority campaign resumed via interpreter; see docs/agents/CAMPAIGN-2026-10-22.md. Scope administrative coordination and evidence-first routing only. No product edits. Definition of done covers all six and final integrated local review with explicit live/manual limits. diff --git a/context/CONTEXT_2026-10-22_14-10-00_client-reliability-9.md b/context/CONTEXT_2026-10-22_14-10-00_client-reliability-9.md new file mode 100644 index 00000000..2339cc57 --- /dev/null +++ b/context/CONTEXT_2026-10-22_14-10-00_client-reliability-9.md @@ -0,0 +1 @@ +Client9 at c5b678f7; investigate HEAD owner cancellation only via actual observer/controller and controlled fetch. No product/retained tests/full/build/browser. HLS/torrent remain separate. Success: counts for disposal/shared/rejoin/cache/timers/media and scoped proposal to dispatch. diff --git a/context/CONTEXT_2026-10-22_14-12-00_storage-cors-3.md b/context/CONTEXT_2026-10-22_14-12-00_storage-cors-3.md new file mode 100644 index 00000000..3dc119ab --- /dev/null +++ b/context/CONTEXT_2026-10-22_14-12-00_storage-cors-3.md @@ -0,0 +1,4 @@ +- storage-cors-3 resumed by dispatch/campaign; baseline c5b678f7. Read-only product/retained tests, offline synthetic data only. +- Goal: real deletion ingest followed by stale refresh/late older/view switch; preserve unrelated/newer content under existing semantics. +- Seven scratch scenarios: three controls pass, four strict failures. Evidence docs/testing/storage-cors-3-investigation-2026-10-22.md; script /tmp/bitvid-storage-cors-3-audit.mjs. +- Await independent reproduction/precise ownership routing. No browser/full/build/normal submission or live services authorized for this investigation. diff --git a/context/CONTEXT_2026-10-22_14-25-00_client-reliability-9.md b/context/CONTEXT_2026-10-22_14-25-00_client-reliability-9.md new file mode 100644 index 00000000..60ef49a3 --- /dev/null +++ b/context/CONTEXT_2026-10-22_14-25-00_client-reliability-9.md @@ -0,0 +1 @@ +Client9 HEAD-only candidate ready: controller cancel hook/abort+raced prompt settlement/timer identity cleanup;23new HEAD+37existing scenarios pass,16failing isolated mutants/cancelled0,lint11 pass. No existing test expectation edits. Explicit strict contract/integrity notes. Next commit candidate, merge current unstable, request full/browser slot; do not submit or run expensive jobs without grant. HLS/torrent separately open. diff --git a/context/CONTEXT_2026-10-22_20-45-00_storage-cors-4.md b/context/CONTEXT_2026-10-22_20-45-00_storage-cors-4.md new file mode 100644 index 00000000..6101f46e --- /dev/null +++ b/context/CONTEXT_2026-10-22_20-45-00_storage-cors-4.md @@ -0,0 +1,5 @@ +- storage-cors-4: prevent obsolete registered feed results repainting deleted/replaced DOM. +- Baseline26e9a787; storage3 durable63e2b519 normalgateexit0, ancestry verified; close3 after durable lookup. +- Own coordinator only plus unique tests/notes. No app/view/engine/service/buffer edits. +- Plan: retain actual registered pipeline/JSDOM baseline; guard refresh/load ownership; cover async runtime/engine success+error, disposal/remount, telemetry and current controls; kill restored mutants; focused/lint; request serialized normal gate; verify durable landing. +- Definition of done: strict regressions+mutants, existing focused/lint, merged normal gate, durable status+ancestry. No live/browser qualification inferred. diff --git a/context/CONTEXT_2026-10-22_20-48-00_client-reliability-9.md b/context/CONTEXT_2026-10-22_20-48-00_client-reliability-9.md new file mode 100644 index 00000000..ebb3c5e7 --- /dev/null +++ b/context/CONTEXT_2026-10-22_20-48-00_client-reliability-9.md @@ -0,0 +1 @@ +Client9 HEAD-only fix durably landed f41ac656 after normal435-filegate exit0, ancestry0; sequential build/artifact exit0. Heavy slot released. Await separate bounded native browser slot; no browser started. HLS/torrent/RangeGET and wholeStage3 remain open. No additional product changes. diff --git a/context/CONTEXT_2026-10-22_20-52-00_client-reliability-9.md b/context/CONTEXT_2026-10-22_20-52-00_client-reliability-9.md new file mode 100644 index 00000000..688e6234 --- /dev/null +++ b/context/CONTEXT_2026-10-22_20-52-00_client-reliability-9.md @@ -0,0 +1 @@ +HEAD slice complete locally: durablef41ac656 normal435unique/exit0/ancestry0; build/artifact0; controlled native Chromium+Firefox lifecycle pass with synthetic media completion/public-deny. Browser/heavy resources released. Pending docs-only evidence landing; HLS/torrent/RangeGET remain open, await bounded routing/HEAD closure disposition. diff --git a/context/CONTEXT_2026-10-22_20-54-00_client-reliability-9.md b/context/CONTEXT_2026-10-22_20-54-00_client-reliability-9.md new file mode 100644 index 00000000..6e093d04 --- /dev/null +++ b/context/CONTEXT_2026-10-22_20-54-00_client-reliability-9.md @@ -0,0 +1 @@ +Item9 HEAD slice done/landedf41ac656. Read-only HLS diagnostic atd04d501e actualobserver/controller/helper reproduces5cancellationfailures/7controls pass/0cancelled/naturalexit1. Pending manifest GET/body retains timer and promise after final-owner disposal; rejoin original,late cache. Need dispatch explicit controller+HLShelper ownership/contract before edits. No browser/full/build/product/retainedtest changes. diff --git a/context/CONTEXT_2026-10-22_21-01-00_client-reliability-9-hls.md b/context/CONTEXT_2026-10-22_21-01-00_client-reliability-9-hls.md new file mode 100644 index 00000000..85169ee8 --- /dev/null +++ b/context/CONTEXT_2026-10-22_21-01-00_client-reliability-9-hls.md @@ -0,0 +1 @@ +HLS-only implementation perdispatch20:55:13Z/confirmed newestRULE. Controller passes optional lifecycle; manifesthelper cancellation covers fetch+body, synchronous invalidation/promptcancelled/abort/identitysafehook+timercleanup. No engine/player/session/HEAD/media/torrent/RangeGET changes. New35tests+adjacent94=129pass/6files;19isolatedmutantsfailnaturally/cancelled0. Final lint pending; record then commit+merge currentunstable,READY for serialized full/browser/build/normal queue. No expensive stage/submission without grant. diff --git a/context/CONTEXT_2026-10-24_19-46-17_client-reliability-9-resume.md b/context/CONTEXT_2026-10-24_19-46-17_client-reliability-9-resume.md new file mode 100644 index 00000000..3c498ed7 --- /dev/null +++ b/context/CONTEXT_2026-10-24_19-46-17_client-reliability-9-resume.md @@ -0,0 +1 @@ +Client9 resumed at5f8e58be, existing item reclaimed. Runtime identical unstable and prior146076dd handoff;4 browser evidence docs retained. Current narrow goal: diagnose original Firefox HLS body-entry prerequisite using preserved unexecuted d0100521 instrumented scratch, only after fresh dispatch slot+cooling clearance. No product/test edits or broader cancellation work. Preserve all failures and strict5000ms bound; no repeat full/build needed for unchanged runtime. See matching test log; dispatch blocker sent. diff --git a/context/CONTEXT_2026-10-26_13-45-00_client-reliability-9-source-only.md b/context/CONTEXT_2026-10-26_13-45-00_client-reliability-9-source-only.md new file mode 100644 index 00000000..5b2f702d --- /dev/null +++ b/context/CONTEXT_2026-10-26_13-45-00_client-reliability-9-source-only.md @@ -0,0 +1,4 @@ +Goal: reconcile existing HLS evidence and recommend remaining lifecycle boundary under interpreter13:41 routing. +Success: preserve prior failure/control results, verify current source ancestry/equality and raw evidence hash, commit existing dirty notes, send scoped recommendation. +Non-goals: no product/tests/browser/full/build/submission, no live or wholeStage3 claim; QA owns sole browser. +See matching reconciled test log. Next: await explicit offline active-torrent investigation routing. diff --git a/context/CONTEXT_2026-10-26_15-25-00_client-reliability-9-torrent.md b/context/CONTEXT_2026-10-26_15-25-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..247ad3bd --- /dev/null +++ b/context/CONTEXT_2026-10-26_15-25-00_client-reliability-9-torrent.md @@ -0,0 +1,4 @@ +Goal: interpreter-authorized current-HEAD offline active torrent lifecycle investigation, no implementation. +Baseline HEAD e9af76df, real observer/grid/queue/probePeers, fake WebTorrent IO+clock. +Result: corrected12cases7pass5fail/naturalexit1; cleanup0remaining resources; existing lifecycle11/11 naturalexit0. See matching TEST_LOG. +Next: dispatch independent reproduction and explicit gridHealth/probePeers-only ownership plus spec correction before fixes/tests. QA owns browser; no full/build/submission. diff --git a/context/CONTEXT_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md b/context/CONTEXT_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md new file mode 100644 index 00000000..d32ab81d --- /dev/null +++ b/context/CONTEXT_2026-10-26_18-55-00_client-reliability-9-torrent-implementation.md @@ -0,0 +1 @@ +Approved dispatch routing read locally: actual baseline independently reproduced12=7pass5fail, no repeat needed. Scope gridHealth+optional probePeers lifecycle only. Goal final-owner null settlement/resource cleanup/fresh identity; preserve shared/standalone/live/cap4/FIFO/TTL/deferred destroy safety. Spec and integrity written first. Focused offline/mutants/targeted lint allowed; no full/build/browser/submission until resource grant. diff --git a/context/CONTEXT_2026-10-27_01-35-00_client-reliability-10.md b/context/CONTEXT_2026-10-27_01-35-00_client-reliability-10.md new file mode 100644 index 00000000..7ae40abb --- /dev/null +++ b/context/CONTEXT_2026-10-27_01-35-00_client-reliability-10.md @@ -0,0 +1,7 @@ +# Client10 goal and scope + +- Authority exact dispatch ROUTING-2026-10-27-client10-animation-cleanup.md read; sourcef01ce012 after safe ff, untracked client9 note preserved. +- Goal recover already-finished transient interface fade/delay classes during delayed cleanup init; preserve active/future interface animation and thumbnail end/cancel semantics. Existing listener-only cleanup cannot observe an event already dispatched. +- Done now: minimal index block extraction + helper, scenario/wiring regressions, killing mutants for all cases, lint under freshhealth. Fullgate/landing/build/nativebrowser are later explicit slots. No other bootstrap/CSS/app edits or oldoverflow closure. +- Plan: create/claim10; record contract/testintegrity; extract existing cleanup; reconcile finished own-target CSS interface animations with no stillactive counterpart; test real helper and extracted bootstrap wiring; isolated mutants; lint; namedpathcommit/merge and READY only, no submit. +- getAnimations/finished/fill reference https://www.w3.org/TR/web-animations-1/; finished forwards effects remain relevant and can retain stacking context. Avoid sleeps, timers, layout reads, arbitrary phase-duration guesses and broad class stripping. diff --git a/decisions/DECISIONS_2026-10-19_19-45-53_dispatch-startup.md b/decisions/DECISIONS_2026-10-19_19-45-53_dispatch-startup.md new file mode 100644 index 00000000..d1ee800a --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_19-45-53_dispatch-startup.md @@ -0,0 +1 @@ +Use the existing backlog as the starting point; verify stale reports. Keep the initial team as small as evidence permits. No product edits by dispatch. Conservative operating limits are manager defaults, not attributed owner quotes. diff --git a/decisions/DECISIONS_2026-10-19_20-00-57_dispatch-worker-bootstrap.md b/decisions/DECISIONS_2026-10-19_20-00-57_dispatch-worker-bootstrap.md new file mode 100644 index 00000000..a5298308 --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_20-00-57_dispatch-worker-bootstrap.md @@ -0,0 +1,5 @@ +# Approved worker bootstrap + +- Accept scoped routine-work relay with explicit provenance; spend/publish/deploy approvals remain session-local and unauthorized. +- Staff one client-reliability worker, no subagents. Preserve preview and schedules. +- One-time administrative-only queue gate verifies exact expected roster and doc/backlog diff; no persistent gate modification or product/test change. This resolves roster-before-launch versus existing suite-hang dependency under the owner's routine local landing authority. Report the unresolved test baseline. diff --git a/decisions/DECISIONS_2026-10-19_20-10-00_client-reliability.md b/decisions/DECISIONS_2026-10-19_20-10-00_client-reliability.md new file mode 100644 index 00000000..9c461b7e --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_20-10-00_client-reliability.md @@ -0,0 +1,9 @@ +# client-reliability-1: classify the profile-modal timeout + +- Product/test source unchanged between filedAt 8b204687 and startup HEAD 9e6c310d. +- Clean-environment 60s/file run reproduced timeout. Per-scenario tracing then completed all 26 scenarios naturally in 57041.55ms; no blocked await or retained resource was found. Timing near the bound varies with cold/warm JSDOM style caches. +- CPU profile of the Escape scenario points to CSS color/border normalization and computed-style resolution. The full markup has many controls; updateFocusTrap asks JSDOM for a full computed declaration for each control on repeated pane refreshes. +- Fix only the stylesheet-free test fixture: virtualize display/visibility, with explicit fixture CSS and a new native-JSDOM contract scenario. Preserve every existing assertion and real controller, focus events, DOM and pane operations. No timeout/retry/sleep/runner or product change. +- Regression compares fixture/native style filtering for visible, disabled, CSS-hidden, inherited-visibility-hidden, aria-hidden, and hidden-type controls, then checks actual Tab event focus wrapping. +- Limits: fixture does not model application CSS/layout. This suite never loaded those styles; browser and build checks are not required for this test-only fix. No release, browser, signer, network or wallet qualification claimed. +- Slowdown: initial cold worktree lacked dependencies; cached offline install resolved it. Native DOM assertion diffs made the first mutant diagnostic expensive; comparing control IDs yields a bounded useful failure. Tool improvement: show per-scenario start/end timestamps when a file exceeds its diagnostic bound, distinguishing slow progress from a blocked await. diff --git a/decisions/DECISIONS_2026-10-19_20-20-00_dispatch-race-routing.md b/decisions/DECISIONS_2026-10-19_20-20-00_dispatch-race-routing.md new file mode 100644 index 00000000..14346b04 --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_20-20-00_dispatch-race-routing.md @@ -0,0 +1,5 @@ +# Bounded follow-up routing + +- Retain one worker: this is another test-harness boundary; no evidence warrants another product-code owner. +- Grant one race-test file plus regression/integrity notes. Product/shared-helper scope needs explicit routing. +- Preserve separate commits, assertions, natural process exit, and normal full landing gates. No test fix landed yet. diff --git a/decisions/DECISIONS_2026-10-19_20-24-00_client-reliability-2.md b/decisions/DECISIONS_2026-10-19_20-24-00_client-reliability-2.md new file mode 100644 index 00000000..84189538 --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_20-24-00_client-reliability-2.md @@ -0,0 +1,8 @@ +# client-reliability-2: hermetic concurrent init + +- NIP01 REQ/CLOSE and docs/architecture-refactor.md L1 contract require callers to close subscriptions. Retain real init/L1/feed behavior; fake only IO boundaries. +- Root cause: original client.connectToRelays stub returned [], explicitly signaling initial connection failure. init schedules ConnectionManager's timer, whose callback calls manager.connectToRelays (not the client stub). That path attempts configured public relays and retains retries. Original subscription was also never unsubscribed. +- Replace 50ms mock sleep with deferred entry/release barriers. Successful manager-level connection fixture prevents retries without disabling product retry logic. Pin timer absence with assertion; teardown resets as failure-path hygiene, not a success substitute. +- Add stricter outcomes: pending second caller, observed pool feed filter, nonempty relays, and exactly-once transport closure. Preserve all original expectations and await both callers. +- Mutate via test-local IO/init overrides only; product Nostr/shared helpers/global runner remain untouched. Both early-return and failure-connection variants fail naturally. +- Prior profile-modal commits preserved separately. No actual signer/wallet/relay/browser/build/deploy qualification. Normal full gates required before either item lands. diff --git a/decisions/DECISIONS_2026-10-19_21-47-12_dispatch-probe-routing.md b/decisions/DECISIONS_2026-10-19_21-47-12_dispatch-probe-routing.md new file mode 100644 index 00000000..01b69b2e --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_21-47-12_dispatch-probe-routing.md @@ -0,0 +1,6 @@ +# Next bounded task and coordination qualification + +- Keep one worker; prior test tasks done, product grid-health scope is now isolated. +- Require actual observer-to-probe lifecycle reproduction, not source-extracted queue inspection. Dispatch sees1 obsolete start after grid disposal, expected0. +- Bound fix to queued work. Shared live owners and later requests must survive; cancelled work must not populate negative cache. +- Mailbox files are delivery/storage, not wakeup. Record missing automatic dispatch continuation and require explicit completion evidence; no unattended qualification, scheduler or kit change. diff --git a/decisions/DECISIONS_2026-10-19_21-55-00_client-reliability-3.md b/decisions/DECISIONS_2026-10-19_21-55-00_client-reliability-3.md new file mode 100644 index 00000000..05ac3678 --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_21-55-00_client-reliability-3.md @@ -0,0 +1,9 @@ +# client-reliability-3: queued probe ownership + +- Actual baseline at66832dc3 (source unchanged from filedAtf9adfc8f): actual attachHealthBadges + DOM + observer, four deferred probePeers calls active and fifth queued; disposing/removing feed then releasing active work starts one obsolete probe (expected0), assertion exit1. +- Queue records own a Set of live card requests. onCardUnregister releases the matching request; only an unstarted job with no remaining owner can cancel. Cancellation resolves null, skips normalization/cache, and removes inflight synchronously. Promise finalization checks record identity so an old cancellation cannot delete replacement inflight work. +- Active jobs are never aborted or removed from inflight on disposal; their normal cache result remains usable. Existing raw magnets/webseeds, four-slot concurrency, stable priority ordering, healthy/negative TTL remain unchanged. +- Live-card membership rejects late observer entries. Each request also guards result writes by ownership/identity even while disposed DOM remains connected. Per-registration badge listener removal and idempotent disposer prevent stale callbacks or an old disposer unregistering a newly attached feed. +- Existing cardObserver unregister hook is sufficient; no shared helper, URL health, WebTorrent implementation, app, cache policy, concurrency, styling, runner or kit edits. +- Browser smoke uses actual built modules/native observers and fake external probe IO only; both Chromium and Firefox pass. No external request attempted. Hosted-only/torrent-only source visibility checked; real playback fallback, live torrent peers, CPU/RSS and active abort behavior unmeasured/out of scope. +- Fixture correction before acceptance: use explicit viewport geometry so observer priorities are deterministic; initial unpinned JSDOM viewport reversed two fixture assumptions. No existing assertion changed. Eleven mutants independently kill the nine grouped lifecycle scenarios (details in integrity note). diff --git a/decisions/DECISIONS_2026-10-19_22-02-00_client-reliability-3.md b/decisions/DECISIONS_2026-10-19_22-02-00_client-reliability-3.md new file mode 100644 index 00000000..419b6663 --- /dev/null +++ b/decisions/DECISIONS_2026-10-19_22-02-00_client-reliability-3.md @@ -0,0 +1,7 @@ +# Final callback ownership corrections + +- Caller review shows app.boundStreamHealthBadgeHandler is shared across feeds; preserve global callback dedup with registration reference counts. Disposing one owner must retain the handler for another owner; final disposal removes it. Wrapping each registration separately would duplicate callbacks. +- Synchronous checking notifications can dispose/rerender the feed before queue submission. Measured1 start after disposal, expected0; check live membership after notification before creating queued work. +- Late observer scenario also asserts no disposed-card notifications reach a remaining registration, not just final badge state. This rejects a removed observer guard even when the reentrant guard still prevents queue submission. +- Final source has11 lifecycle scenarios;14 independent mutants were rerun against this exact product tree, all exit1 with1 failed/0 cancelled, then restored. Source outside gridHealth.js remains untouched. +- Corrected build and Chromium/Firefox built-dist/native-observer smoke both pass; no external requests or page errors. Actual media playback fallback and CPU/RSS remain unverified. diff --git a/decisions/DECISIONS_2026-10-20_03-10-00_client-reliability-4.md b/decisions/DECISIONS_2026-10-20_03-10-00_client-reliability-4.md new file mode 100644 index 00000000..cb6bb709 --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_03-10-00_client-reliability-4.md @@ -0,0 +1,3 @@ +# client-reliability-4 + +Stop at smallest reproduced queued URL media defect. Keep active URL/torrent cancellation separate. Exercise real cache/observer/controller and only virtualize external IO. Await explicit routing before product/regression tests. diff --git a/decisions/DECISIONS_2026-10-20_03-24-00_client-reliability-4.md b/decisions/DECISIONS_2026-10-20_03-24-00_client-reliability-4.md new file mode 100644 index 00000000..86e042bd --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_03-24-00_client-reliability-4.md @@ -0,0 +1,7 @@ +# client-reliability-4 implementation + +- Existing observer unregister boundary owns per-badge lifecycle tokens, read by controller via WeakMap. Existing app/feed callback arguments unchanged; direct legacy controller consumers remain live until settlement. +- Module-scoped promise→ownership WeakMap mirrors shared real inflight cache across controllers. Last-owner release only cancels queued media jobs. Already-running media/cache behavior remains; no active HEAD/media/HLS/torrent abort. +- Queue resumes reserve a slot synchronously to preserve three-media cap. Cancellation settles with an explicit internal outcome propagated through mirrors and omitted from health cache. Existing state-cache promise identity guard preserves immediate re-request. +- Guards bind UI/RAF work to captured registration; late observer callbacks/eager microtasks and stale disposer cannot affect reused registrations. +- No app/shared observer/cache-module or policy/TTL edits. Scope/read-only predecessor evidence at77d486fb; dispatch implementation routing merged before code. diff --git a/decisions/DECISIONS_2026-10-20_13-30-00_client-reliability-5.md b/decisions/DECISIONS_2026-10-20_13-30-00_client-reliability-5.md new file mode 100644 index 00000000..7cbc7512 --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_13-30-00_client-reliability-5.md @@ -0,0 +1,3 @@ +# client-reliability-5 + +Do not equate retained active work with a leak: explicit success and controlled real timeout release resources/slots and cache correctly. Route only demonstrated new mirror HEAD after disposal; guard candidate continuation in controller before considering active abort or broader resource policy. Existing observer token is sufficient. diff --git a/decisions/DECISIONS_2026-10-20_13-36-00_client-reliability-5.md b/decisions/DECISIONS_2026-10-20_13-36-00_client-reliability-5.md new file mode 100644 index 00000000..20e9e04b --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_13-36-00_client-reliability-5.md @@ -0,0 +1,6 @@ +# Item5 continuation boundary + +- Keep bounded already-active success/failure caching. Check existing lifecycle owners only BEFORE starting each candidate in probeUrlList; zero owners marks existing record cancelled and returns explicit cancellation. No probe abort or observer/cache/TTL/concurrency change. +- Shared and rejoining owners remain eligible because record owners are checked at continuation time. Completed final-source failure keeps original cache behavior; cancelled mirror chains never cache offline. +- Six focused regressions exercise actual observer/controller/cache plus external IO twins; unique integrity file during parallel trial. +- Dispatch resource coordination: local full suite is next after administrative drain but requires explicit grant; browser belongs initially to playback QA. All CLI/gate launches clear inherited NODE_TEST_CONTEXT operationally; no kit/runner change. diff --git a/decisions/DECISIONS_2026-10-20_13-39-30_storage-cors.md b/decisions/DECISIONS_2026-10-20_13-39-30_storage-cors.md new file mode 100644 index 00000000..5803feac --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_13-39-30_storage-cors.md @@ -0,0 +1,6 @@ +# storage-cors-1 proposed semantics (awaiting dispatch) +- Preserve every existing rule and field; do not union new origins into arbitrary rules (would grant their permissions to new origins). +- Read existing policy before writing; only explicit NoSuchCORSConfiguration indicates absent policy. Other failures propagate without PUT. +- Add the existing default rule for newly requested origins separately. Skip PUT when equivalent existing default rules already cover every requested origin. +- Preserve wildcard rules verbatim; never infer extra methods/headers or edit unknown fields. Exact equivalence/coverage details require routing before implementation. +- No dependencies/SDK version change. Inspect SDK export read-only until routing. diff --git a/decisions/DECISIONS_2026-10-20_15-04-00_client-reliability-6.md b/decisions/DECISIONS_2026-10-20_15-04-00_client-reliability-6.md new file mode 100644 index 00000000..0fe4432d --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_15-04-00_client-reliability-6.md @@ -0,0 +1,3 @@ +# Item6 + +Classify historical no-op as a regression gap: actual wiring works,but existing fake-renderer profile suite passes27/27 even with historical override. Propose focused new real-wiring test and unique integrity,not product or app/history-service/cache edits. Exercise DOM event and backing persistence/recreation;fake only synced external IO and unrelated constructor dependencies. No live signer/relay or full app/reload claim. diff --git a/decisions/DECISIONS_2026-10-20_15-09-00_storage-cors-2.md b/decisions/DECISIONS_2026-10-20_15-09-00_storage-cors-2.md new file mode 100644 index 00000000..8e51ee3c --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_15-09-00_storage-cors-2.md @@ -0,0 +1,6 @@ +# storage-cors-2 +- Reject malformed connection fields as invalid-connection; no String(object) or arbitrary trim invocation for cleanup authorization/base/key material. +- Reject nonstring owner identifiers as invalid-owner before resolution. Null/non-object parameter options safely use existing no-videos result. +- Preserve existing owned-base/key collector,credential lock reasons,per-object failure summaries,provider-aware client branches and content-addressed upload semantics. +- s3UploadService contains no affected delete counterpart; no upload-copy edits per explicit dispatch ruling. Tests prove both provider branches instead. +- No broad catch; existing client-construction and each-delete failures retain structured results. Arbitrary throwing getters/proxies beyond serializable cache metadata are outside this proof. diff --git a/decisions/DECISIONS_2026-10-20_15-12-00_client-reliability-6-retention.md b/decisions/DECISIONS_2026-10-20_15-12-00_client-reliability-6-retention.md new file mode 100644 index 00000000..dfa45ae3 --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_15-12-00_client-reliability-6-retention.md @@ -0,0 +1,7 @@ +# Item6 test design + +- Use actual profile config/factory and DOM click with original renderer removal/controller/local service. Observe actual removal promise only for deterministic completion. Required unrelated constructor services and read/metadata boundary are controlled; no fake renderer/remove implementation. +- Local and synced removal include final-item case and renderer recreation; local Clear is a separate scenario with real queue reset. No synced Clear assumption. +- Synced IO twin respects replace vs merge, so missing replace is observable rather than rubber-stamped. Assert all published payload/options plus publication occurrence, without requiring duplicate implementation calls. +- Isolated runtime mutants exercise historical config no-op,clear no-op,drop-replace,wrong actor and final-item no-op. Explicit teardown runs on assertion failure and restores queue/app/client/globals. +- No product/existing suite edits; focused/lint only until dispatch allocates full/normal slot. Build/browser remain unverified/outside current grant for test-only retention. diff --git a/decisions/DECISIONS_2026-10-20_16-14-00_client-reliability-7.md b/decisions/DECISIONS_2026-10-20_16-14-00_client-reliability-7.md new file mode 100644 index 00000000..1d887b7c --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_16-14-00_client-reliability-7.md @@ -0,0 +1,6 @@ +# Item7 notification ownership + +- Live updates notify the same full ordered cached list emitted/persisted already,including deletion-only empty results. Preserve event payload/deleted metadata and filtering. +- A service-owned notification object carries latest loadVideos consumer across reused subscription. Creating replacement subscription uses fresh identity;clear invalidates before unsub. Old subscription callback cannot change cache,notify consumer,emit or persist after disposal. +- Interpreter independently identified stale captured consumer;our A→B and force/clear tests fail firstcallbackpatch,pass revisedtoken. Service lifecycle state explicitly routed by dispatch16:10:24. +- Keep existing cache/persistent callbacks and callback-throw isolation. Event-only consumers remain supported. No productpolicy change/forced UI refresh. Browser qualification belongs to QA. diff --git a/decisions/DECISIONS_2026-10-20_16-30-00_dispatch-local-review.md b/decisions/DECISIONS_2026-10-20_16-30-00_dispatch-local-review.md new file mode 100644 index 00000000..2058a7ec --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_16-30-00_dispatch-local-review.md @@ -0,0 +1,6 @@ +# Local review decisions + +- Fix demonstrated cold-feed hardblocker with actualservice regression/currentconsumer token;no forcedrefresh substitute. +- One unchanged normal fullgate authoritative;do not duplicate whole suite solely to produce fullerlogs. Storedtail is not complete process transcript. +- Preserve original strictUploadoverflowFAIL;separatefollowon captures remainingcoverage withoutallpassclaim. Minor2/4pxoverflow controlsusable/sourceunproven staysopen forlocalreview;no speculativeCSSfix/optionalpolishbatch. Interpreter independently agreesclassification. +- Stop alloptionalwork at ownerlocalreviewmilestone;real signer/cloud/peer/production gates stayopen. diff --git a/decisions/DECISIONS_2026-10-20_20-56-00_client-reliability-8.md b/decisions/DECISIONS_2026-10-20_20-56-00_client-reliability-8.md new file mode 100644 index 00000000..93a6254d --- /dev/null +++ b/decisions/DECISIONS_2026-10-20_20-56-00_client-reliability-8.md @@ -0,0 +1,4 @@ +- Implement only controller retainUrlProbe final-owner hook and active media settle/cleanup. Existing shared WeakMap owners and cancelled result paths suffice; no AbortController or observer/cache edit needed for video-element cleanup. +- Mark cancelled synchronously before promise continuations so same-turn rejoin cannot attach to obsolete work. Clear active cancellation hook on completion; reuse existing finally for exactly-once slot/FIFO release. +- Strict spec correction authorized at3dac0afb and recorded in docs/spec_changes/2026-10-20-client-reliability-8-active-media.md; retain live-owner cache/timeout/mirror contracts independently. +- No full/build/browser before dispatch allocation; one unchanged normal full landing gate can be authoritative after merged focused/lint. diff --git a/decisions/DECISIONS_2026-10-22_14-05-00_dispatch-campaign.md b/decisions/DECISIONS_2026-10-22_14-05-00_dispatch-campaign.md new file mode 100644 index 00000000..de4935b5 --- /dev/null +++ b/decisions/DECISIONS_2026-10-22_14-05-00_dispatch-campaign.md @@ -0,0 +1,5 @@ +- Reuse3 existing workers; disjoint investigations are useful, no extra agent justified yet. +- Firefox gets browser/expensive slot first; cancellation and tombstone evidence use offline focused checks. +- All6 priorities have durable accountable items; staged means unfinished, not cancelled. +- Workers synchronize own clean branches; dispatch never alters peer worktrees. +- No tests rerun for administrative docs; historical gates remain historical. diff --git a/decisions/DECISIONS_2026-10-22_14-12-00_client-reliability-9.md b/decisions/DECISIONS_2026-10-22_14-12-00_client-reliability-9.md new file mode 100644 index 00000000..00ae39b5 --- /dev/null +++ b/decisions/DECISIONS_2026-10-22_14-12-00_client-reliability-9.md @@ -0,0 +1,3 @@ +- HEAD slice only. Current media cancellation hook does not cover fetch; reproduce real ownership flow before proposing hook reuse. +- Do not change product/tests without dispatch contract routing. Keep HLS/torrent and wide Stage3 completion open. +- No redundant full/lint/build/browser for scratch investigation; QA slot untouched. diff --git a/decisions/DECISIONS_2026-10-22_14-12-00_storage-cors-3.md b/decisions/DECISIONS_2026-10-22_14-12-00_storage-cors-3.md new file mode 100644 index 00000000..282c0487 --- /dev/null +++ b/decisions/DECISIONS_2026-10-22_14-12-00_storage-cors-3.md @@ -0,0 +1,4 @@ +- Treat kind30078 deleted markers using current timestamp semantics; do not infer new NIP09 or root/author ownership policy. +- Use actual subscribe/buffer/client/service with fake transport and synthetic verifier; retain real filtered localStorage writes. Stub unrelated network hydration and IndexedDB scheduling only. +- Capture actual coordinator render boundary with deferred engine decorator; explicitly leave full UI/DOM qualification open. +- Route discovered shared service/buffer/coordinator changes before edits. No upload/cloud/CORS code changes. diff --git a/decisions/DECISIONS_2026-10-22_14-21-00_storage-cors-3.md b/decisions/DECISIONS_2026-10-22_14-21-00_storage-cors-3.md new file mode 100644 index 00000000..a52c42dd --- /dev/null +++ b/decisions/DECISIONS_2026-10-22_14-21-00_storage-cors-3.md @@ -0,0 +1,4 @@ +- Dispatch independent audit confirmed7=3pass/4fail and routed buffer/service only; merged routing076ec802 after evidence67140d89, merged HEAD89c5494a. +- Buffer defers deletion of active versions to existing client.recordTombstone; preserve guarded-event same-ID cleanup. No new deletion timestamp/key policy. +- Service rejects deleted/invalid/client-tombstone-covered objects before author/access bypass and direct caching; prune affected cached copies using authoritative guard plus existing explicit deletion IDs. Persist cleaned optimistic snapshots and empty active results. +- Separate buffer commit ed5fb649; service commit follows. No app/feed coordinator/client.js writes. Coordinator race remains open pending faithful registered-pipeline/view/disposal evidence and new routing. diff --git a/decisions/DECISIONS_2026-10-22_20-45-00_storage-cors-4.md b/decisions/DECISIONS_2026-10-22_20-45-00_storage-cors-4.md new file mode 100644 index 00000000..9ae26f90 --- /dev/null +++ b/decisions/DECISIONS_2026-10-22_20-45-00_storage-cors-4.md @@ -0,0 +1,6 @@ +- Per-app WeakMap request and loader tokens avoid exposing a public API. Capture actual view, mounted container and telemetry context; only eligible completion may publish metadata/map/render/telemetry/impressions/similar content. +- Capture mounted view.container (not mount adapter return); existing lightweight adapters need not expose a container. Real destroyed view loses its container and cannot be resurrected by completion. +- Preserve started engine caller result: stale success resolves original videos/metadata; stale engine error resolves existing fallback/error metadata. loadFeedVideos still resolves undefined. +- A request superseded while awaiting history preload never starts engine: resolve {videos: fallback, metadata: {reason, engine: "superseded"}}. This explicitly distinguishes a skipped run while preserving the resolved payload shape, and avoids new obsolete engine-stage subscriptions. No changes to current valid failure/unavailable fallback or content policies. +- Old service callbacks and recommendation activation must also match the original load/view/context, including a same-feed replacement. Loader continuations recheck after each await before state writes. +- No existing assertions changed. New failure injection wraps actual engine execution and rejects only after production stages, since moderation subscription rejection itself is deliberately caught by the production stage. diff --git a/decisions/DECISIONS_2026-10-22_21-01-00_client-reliability-9-hls.md b/decisions/DECISIONS_2026-10-22_21-01-00_client-reliability-9-hls.md new file mode 100644 index 00000000..c5db8999 --- /dev/null +++ b/decisions/DECISIONS_2026-10-22_21-01-00_client-reliability-9-hls.md @@ -0,0 +1,4 @@ +- Explicit dispatch20:55:13Z Stage3 HLS scope extension and helper/controller ownership; keep broaderitem9OPEN. Existing HEAD/media/engine/player/nativepreference behavior and standalone manifest callers preserved. +- Pass optional lifecycle as third helper argument; helper owns its own hook/controller/timer through fetch AND response.text, races logical cancellation independently of AbortController cooperation. Native media remains on existing path. +- Test actual observer/controller/cache/helper with controlled IO/clock boundaries, new unique suite only; existing tests/expectations unchanged. Baseline35newcases13pass/22fail before fix (isolated copies); original12mode5fail/7pass retained. +- Mutants isolate copies in/tmp,never mutate worktree/shared modules. Initial retainedtimer mutant hit engineclearTimeout outside manifesthelper;corrected exacthelper suffix and final19allfail naturally. Formatting outside manifesthelper restored to keep engine read-only. diff --git a/decisions/DECISIONS_2026-10-26_13-45-00_client-reliability-9-source-only.md b/decisions/DECISIONS_2026-10-26_13-45-00_client-reliability-9-source-only.md new file mode 100644 index 00000000..5fc1d4e0 --- /dev/null +++ b/decisions/DECISIONS_2026-10-26_13-45-00_client-reliability-9-source-only.md @@ -0,0 +1,5 @@ +- Preserve seven existing dirty notes verbatim alongside a new reconciliation, rather than rewriting old failed/incomplete results. +- Treat separate Firefox prefix3/3 as controlled qualification; original header-onlyFAIL remains separate. No speculative product fix or repeated browser. +- Use actual product SHA83fd2495 for ancestry; c95abe22 is a build identifier. +- Next recommended investigation is active torrent final-owner disposal under item9/priority2, with real production chain and controlled IO; source hints alone are not reproduced defect or implementation authority. +- Notes-only changes need source/hash/whitespace verification, no redundant full/lint/build. Commit evidence locally; no queue submission while source-only routing holds. diff --git a/decisions/DECISIONS_2026-10-26_15-25-00_client-reliability-9-torrent.md b/decisions/DECISIONS_2026-10-26_15-25-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..4047f7cd --- /dev/null +++ b/decisions/DECISIONS_2026-10-26_15-25-00_client-reliability-9-torrent.md @@ -0,0 +1,5 @@ +- Investigation uses actual production chain, fake only observer delivery/WebTorrent IO/timers/persistence; no implementation or committed tests. +- Preserve first12=5pass/7fail fixture: two control failures are JSDOM storage-event timers; separate memoryStorage IO fixture12=7pass/5fail retains identical assertions and original evidence. +- Treat five failures as planned Stage3 active-cancellation gap, not regression against existing intentional active-sharing/cache contracts. Need explicit routed spec correction before changing those tests. +- Smallest fix boundary gridHealth ownership/identity/cache plus probePeers lifecycle only. Preserve deferred temporary-torrent destruction after callback unwinds and release queue slot after teardown; retain shared probe client/other owners. +- No live browser/WebTorrent/network/CPU/RangeGET claim; no product edits until independent routing. diff --git a/decisions/DECISIONS_2026-10-26_19-15-00_client-reliability-9-torrent.md b/decisions/DECISIONS_2026-10-26_19-15-00_client-reliability-9-torrent.md new file mode 100644 index 00000000..6f2974eb --- /dev/null +++ b/decisions/DECISIONS_2026-10-26_19-15-00_client-reliability-9-torrent.md @@ -0,0 +1,6 @@ +- Grid publishes owner/release/inflight promise before synchronous client IO so disposal or same-source registration inside constructor/add is safe; cancelled identity removed synchronously and cache guarded. +- Optional third probePeers lifecycle follows existing owner-record convention; standalone behavior unchanged. Cancel settlesnull after deferred temporary-torrent destroy call returns/throws; timers clear immediately, cancel hook/owned listeners removed at final teardown. +- Retain inert error handler through deferred teardown, even cancellation insideadd; repeated/late callbacks are settled guards. Shared probeClient remains alive; queue slot only releases after destroy, preserving event unwind/cap4/FIFO. +- Original tests367/417 contract corrected only after routed spec+integrity note; preserve separate shared-live/cache/TTL/timeout controls and strict noobsoletecache/freshrequest/resource assertions. +- No live WebTorrent transport/byte teardown qualification from fake IO. Async low-level network shutdown not measured; existing teardown call ordering preserved. +- Stop at completedfocused boundary for requested kit restart; no commit/merge/submission before restart, all snapshots saved. diff --git a/decisions/DECISIONS_2026-10-27_01-39-00_client-reliability-10.md b/decisions/DECISIONS_2026-10-27_01-39-00_client-reliability-10.md new file mode 100644 index 00000000..72fa89c3 --- /dev/null +++ b/decisions/DECISIONS_2026-10-27_01-39-00_client-reliability-10.md @@ -0,0 +1,7 @@ +# Client10 decisions + +- Extract only existing animationcleanup block into59line ui/fadeInCleanup helper; index1456→1405lines. No other bootstrap/CSS/app files changed. Existing thumbnail eligibility retained on end/cancel; pseudo effects are excluded from element cleanup. +- Install one capture listenerpair then query Document.getAnimations once for already-finished own-target interface CSS animations. Skip running/paused/idle effects and finished effects with same-target live counterpart. No elapsedtime guesses/timers/MutationObserver/layout reads/promises to retain. +- Unsupported animation inspection retains event-based cleanup; reducedmotion animation:none retains original hooks without animation effects or new motion. No unsupported-browser late-recovery guarantee. Reference https://www.w3.org/TR/web-animations-1/ describes relevant finished/filling effects and stacking contexts. +- Per-document WeakMap makes repeated init idempotent and gives safe explicit disposal/reinstall. Stale disposer cannot erase replacement registration. +- Focused scenarios use real helper/JSDOM and controlled CSSAnimation metadata; wiring executes real isolated indexcleanup statements with actual imported helper, not fullapp/bootstrap dependencies. Native CSS animation/hit-testing requires later granted QA. Preserve originaloverflow362/360+394/390 and live/wholeStage3 boundaries. diff --git a/docs/README.md b/docs/README.md index dd08f31d..eeadabd8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,16 @@ Welcome to the bitvid documentation. This directory contains detailed architectu - **[Design System](./design-system.md)**: Guide to the token-based design system and CSS architecture. - **[Menus & Popovers](./menus.md)**: Documentation for the menu and popover system. +## Testing + +- **[Playwright Agent Testing Infrastructure](./testing/agent-playwright-harness.md)**: Full reference for the agent test harness (`window.__bitvidTest__`), mock relay, Playwright fixtures, and `data-testid` selectors. +- **[Test Integrity (Dark Factory Standard)](./testing/test-integrity.md)**: The scenario-first testing constitution — anti-cheat rules, the Spec Correction Protocol, and the Test Integrity Note format. + +## Lessons & Incident Writeups + +- **[Cloudflare/S3 Upload-Path Gotchas](./lessons/cloudflare-s3-upload-gotchas.md)**: Audited 2026-06-16 — duplicated upload services, storage-key collisions, and object-cleanup contracts. +- **[NIP-07 Signer Reliability](./lessons/nip07-signer-reliability.md)**: Hard-won 2026-06-16 — diagnosing unresponsive NIP-07 signers and the resilience invariants that must not regress. + ## Operations - **[Instance Configuration](./instance-config.md)**: Guide to configuring a bitvid instance. diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 00000000..91519d35 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,147 @@ +# TODO — what each area did NOT finish + +## dispatch + +- 2026-10-23 buildc95abe22/artifactpass at103eb04c;SWcompatcheckSKIPPED Gitdelta unavailable,retainlimit. HLSevidencea482d9d2 independentlydurable. QA4 realappready360,Uploadclickblockedfirst-run disclaimer30s,no geometry/focus/touch;sourceexactinterceptclass maps components/disclaimer.html18,runneromitsaccept. QA genuineUIacceptpreponly/no productbugclaim; HLSnativebrowser solegrant13:04:31. FreshpowerACADonline1/BAT100%Discharging;no lowbatterythreshold,resourceadvisoryreviewed. + +- 2026-10-23 HLS product83fd2495 durable independentlystatus/log/ancestry; current savedfulllog independently437starts/437unique,lastzap-totals;normalgateexit0. StorageRangeaudit103eb04c durable. Client solebuild/artifact granted; QA4 conditionalhostbrowser afterbuildrelease+freshhealth, HLSnativebrowser later. Priorpublish/thermalrejections+398partial+firstfull-logloss preserved. Torrent/Rangeimplementation/FirefoxCI/Amber/mobile/previewintegration remainopen. + +- 2026-10-23 HLS replacement280f043a durablyREJECTED after resource-triggered own-gateSIGINT, worker sustained88.8→85.1C and398/437unique partial/lastvideo-modal-controllers interrupted. No fullgreen/productassertionfailure claim. Interpreter kitRELEASE12:46:56 reports84pass/9guards/upstreambeta-onlypush; exactstart/end requested to assess overlap before nextlonggate. No HLSretry/build/browser until cooling/no competingjob assessment; normalgate unchanged/required. + +- 2026-10-23 resume: actual QA4 bootEPERM/release consumed; browser/runtimechecks0, prior mobile strict failures unchanged. HLS sole full/normal grant12:32:43Z at clean79a9a130 after safe merge latest672ca953; START/results pending. Health cooling77→71C/RAM68.3%/swap90% advisory; one heavy max. QA host escalation disposition/browser grant later; no app-fix/cron/autoapproval-effect claim. Overnight client wake left composer yet unread55642s, recovery now consumed. + +- 2026-10-22 storage3+4DONE: storage4 product7edc7ea8/closure8317da4d independently durable status/log/ancestry; worker normalgateexit0/discovery436/build29cbe6f0 artifactPASS, no independent436start audit. QA4 now sole bounded mobile browser grant21:04:02; HLS candidatecdc398b0/merged79a9a130 next full after QA release. Dispatch exact candidate35/35 natural321ms; worker129focused/19mutants/lint11. HLS gates/preview/campaign remain unfinished. + +- 2026-10-22 HLS follow-on: dispatch independent actual observer/controller/helper scratch atd04d501e12=7pass/5fail/cancelled0/naturalexit1/165.17ms. Final-owner GET/timer/promise retained, rejoin stale, late healthy cache, body-read disposal fail; no media-slot leak. Controller HLS handoff + optional manifest-helper cancellation only routed with strict spec/integrity/regressions. Focused/mutants/lint only while storage4 holds heavy slot; torrent/RangeGET remain separate, no HLS fix landed. + +- 2026-10-22 HEAD slice productf41ac656/evidenced04d501e durable status/log/ancestry verified. Worker normal435unique/buildartifact/nativeChromium+Firefox controlledHEAD pass; loadeddata synthetic, no decoder/transport-byte proof. All client resources released20:51:23Z. Storage4 sole full/normal START ACK20:52:16; client9 HLS read-only measurement ACK, broader9 remainsOPEN; QA4 HOLD. Integrated preview and other campaign priorities remain unfinished. + +- 2026-10-22 client9 product normally landedf41ac656: dispatch durable status/log/ancestry0, worker gateexit0/435unique starts. Sequential build active, separate bounded HEAD browser after build conditionally allocated; results pending. Storage4 candidateb21ce86c focused16/9mutants/lint11 ready, next full slot after client release; QA4 held. Integrated preview and broader campaign gates unfinished. + +- 2026-10-22 restart20:41UTC: a79d49df verified durably landed26e9a787, queue0. Fleet health cooling74 to66C/load3.81 to3.30, >20GiB available RAM, swap52 to51% sole suggested-limit crossing. Client9 sole full/normal slot granted; START/results pending. Storage4/QA4 held. Own sync blocked read-only Git metadata; no new product/tests/build/browser qualification. + +- 2026-10-22 storage4 registeredfeed/JSDOM independent4=2pass/2fail:obsolete result restores deletedDOM/overwrites replacementExplore;coordinator-only separate fix routed afterstorage3 submit. Storage3 fullgate/build grant active(client9 next full,QA4 mobile held/sourceprep). Client9 exactcandidate independent23pass. No product landing yet. + +- 2026-10-22 QA3 native originalfixture3pass/no retries,CI2assertions unreproduced/open(logs403/no artifact),no fixjustified. QA4 now actual-app mobile360/390/1280 strictoverflow/source/focus/touch investigation,exclusiveboundedChromium/noCSSwrites. Remaining6priority campaign not complete. + +- 2026-10-22 client9 HEAD independently11modes(3fail/8controls pass),finalowner fetch/timer retained(no media slot leak);controller HEAD-only fix+strict contract/identity/ignoringabort regressions routed. HLS/torrent/RangeGET remain open;QA offline genuineBuffer rebuild prep, mobile/Amber staged. + +- 2026-10-22 storage3 independent7/3pass/4fail: buffer newer-version loss and service deleted snapshot/cache leakage routed separate small commits; coordinator stale render still open/read-only pending registered lifecycle evidence. QA3 app-less3pass not CI proof; genuine cached Buffer followup granted exclusive narrow browser. + +- 2026-10-22 resumed six-priority campaign: Firefox QA3, active cancellation client9, tombstones storage3 now; playback dispatch4, Amber dispatch5, mobile dispatch6 staged with acceptance criteria. See docs/agents/CAMPAIGN-2026-10-22.md. All six remain unfinished; old idle/quiet-hours hold superseded for this foreground scope. Final integrated checks/build/preview and live/manual disclosure outstanding. + +- Next bounded item requested by interpreter2026-10-21T02:40:06Z under continuing owner instruction: existing client read-only currentHEAD active HEAD-request last-owner abort reproduction/shared/rejoin controls;no product/test edits before evidence. HLS/torrent separate. Quiet-hours22:00–08:00 hold for new starts/messages;route next foreground checkpoint at/after08:00 local without another permission loop. No scheduler created. + +- client8 FINAL:product43cb1baf/closure2ea6f068/handoff9cbb53f8 verified;432fullgate0,dispatch29/29/build08620a71/artifact0/preview200+cmp0,nativecontrolledbothbrowserPASS/released. AllIDLE;HEAD/HLS/torrent activeprobe work remains unfinished pending nextboundedrouting. No decoder/bytes/CPU/liveproof. + +- 2026-10-20 client8 active URL-media product43cb1baf/full432gate0,dispatch29/29/build08620a71/artifact0/previewHTTP200+cmp0. WorkercontrollednativeChromium+Firefox smokePASS/released;closuremetadata/browsernotes normaldocsqueue pending. No realdecoder/networkbyte/CPU/HEAD/HLS/torrent qualification;broaderactiveprobe scope remains unfinished. Finishdocs/handoff thenawaitnextboundedrouting. + +- 2026-10-20 bounded continuation: client8 active URL-media last-owner/shared/rejoin reproduction after maintenance. Await measured evidence then independent confirmation/minimal fix and explicit test-contract routing. Other optional work remains deferred; no active-abort completion claim. + +- 2026-10-20 LOCAL REVIEW: storage2/history6/cold-feedclient7 normally landed;latest codeb1174163 full432filegate0,dispatchnew7/7/build-artifact0. Preview3000 build6da89895/sourceb1174163/runtime-equivalentdocsdescendants. Actualcold3cardsPASS;separate25-step360/390/1280navigation/Home/Profile/Uploadvalidation/focuscoveragecomplete. OriginalUploadoverflowFAIL remainsopen2/4px(controlsusable/sourceunproven),notwholebaselineallpass. FinalQA/docsqueuehandoff thenallIDLE;nooptionalwork. Feed3/publicplaybackpromises/liveAmber/cloud/peers/manualrelease remainunfinished. See docs/agents/LOCAL-REVIEW.md. + +- 2026-10-20 local-review checkpoint: storage2 producte7b9afb3/closurec5818cfe and history6 regressionb3fab032/closure386cc60e durably complete. Normal latest gate431files exit0; dispatch cleanup13/13 and history3/3. Integrated preview1834b6a9 runtime equals current test/docs-only descendants. Final actual-app/mobile QA2 browser pass active; readiness awaits actual flow evidence/handoff. Feed3 and public-promised playback roadmap deferred/open; real Amber/cloud/peers/release gates remain. No optional new tasks. + +- 2026-10-20 trial outputs complete:QA regression1bb3a4fc/closurebbf57388 verified,dispatch independent4/4/runtime unchanged so no extra build. Same3 workers continue:client6 real history test-only retention after independent current pass/historical-noop failure;storage2 method-only malformed metadata no-throw guard after independent2rejects/6controls;QA2 actual-app local core/mobile3viewports browser-only grant. Full slots on validated-ready after QA browser release;no feature/readiness/live-proof claim. + + +- 2026-10-20 owner continuous-quality direction recorded;fleet/QUALITY.md tracks local vs real release evidence. QA full/normal slot active;client item6 profile-history wiring audit and storage item2 feed/view tombstone audit assigned now,read-only before fix routing. Remaining:actual coverage/reproduction decisions,current QA landing,local mobile/core-flow baselines and unqualified live gates. + + +- 2026-10-20 storage productd3f4c215/closure7f99c045 verified;dispatch CORS9/9/build-artifact/previewHTTP200+dist match. QA exclusive full/normal slot actual grant14:49:35/read14:49:46 after65m54wait;storage-release→grant39m57 wake gap. Remaining:QA durable regression landing,final trial throughput/stall assessment,next bounded routing. Actual cloud/IAM/noCAS concurrent safety/live peers unverified. + + +- 2026-10-20 trial first product landed: item5 product0bd0b1c3,closuredc852597;dispatch lifecycle18/18/build-artifact/previewHTTP200+dist match. Storage slot grant13:58:27 after949s wait,full/normal landing underway;QA next,ready95a77a7f. Remaining: storage/QA durable landings,preview refresh after product changes,final trial evidence;real cloud/peers/activeabort/CPU unverified. + + +- 2026-10-20 trial current: all3 pickups verified; admin notes durableb922c186. Client item5 owns exclusive full/browser/product landing from13:43:51 (~10m57s wait); storage CORS ready9/9+8mutants,next slot; QA retained4/4+6mutants,third slot,browser released. Remaining: full merged-tree validation,durable product/test landings,preview refresh and final trial outcome. No real cloud/peers/CPU qualification. + + +- 2026-10-20 parallel trial: rosterbbc1be79 durably green,3 workers launched/read-next-action verified. Item5 ready12f92732 focused28/6mutants/lint/build awaits full+browser; storage-cors real origin loss independently reproduced/fix routed; playback-qa controlled native local fallback3/browser reviewed,test retention approved. Remaining trial product/test full gates,orderly durable landings and preview; actual WebTorrent/live peers/CPU unverified. Initial gate transport/capture failure corrected only by clean environment; QA browser coordination deviation recorded. + + +- 2026-10-20: continuing owner direction: item5 investigation assigned to same worker, active URL media last-owner/shared-owner controls first. Remaining: reproduction and narrow fix routing or next evidenced TODO. No product fix assumed; active abort/live playback/CPU-RSS unverified. + + +- 2026-10-20: item4 done: product89ccb907,closuref116f7c8 durable landed. Dispatch independent URL tests20/20,build/artifact exit0,previewHTTP200 and index matches dist. Worker full427/427,focused22,13 mutants,lint11,browser smoke recorded. Worker idle; next active-probe candidate needs fresh reproduction/routing. Actual playback/live peers/CPU-RSS/full Stage3 unfinished. + + +- 2026-10-20: item4 reproduction confirmed independently: obsolete URL media starts1(expected0), shared owner control pass. Worker routed to queued URL ownership/cancellation in observer/controller and focused tests. Remaining: implementation, mutant/full/browser gates, landing, preview refresh; active abort excluded. + + +- 2026-10-20: item4 assigned to existing worker for investigation only: reproduce remaining URL/active torrent cancellation at HEAD, then route a narrow fix. No product fix yet; live playback/CPU-RSS remain unverified. Owner continuation relayed; quiet-hours exception does not change schedule. + + +- 2026-10-19 latest: item3 normal product landing7d20a509 and closure7fde734d verified; dispatch synced and preview rebuilt(HTTP200). Independent original five-card scenario now0 obsolete starts(previous1), lifecycle11/11. Worker evidence426/426 full files,lint11/11,14 rejected mutants,Chromium+Firefox controlled-IO lifecycle smoke. Worker idle; no new task launched in this mail checkpoint. Remaining: actual media fallback/live-peer qualification,CPU/RSS measurements,active-probe/URL cancellation and other performance-plan slices. + + +- 2026-10-19: dispatch-2 ongoing coordination. Synced to f9adfc8f; normal-gate fixes de6918ce verified (425/425 file evidence, lint11/11, independent profile27/27/race1/1). Route client-reliability-3 to same worker for reproduced queued probe-after-feed-disposal bug; scope/evidence in area prompt. Next: verify actual worker resume and completion reports, keep preview current after verified product landing. +- Coordination limitation: operator checkpoint resumed dispatch; file mailbox does not itself wake idle Codex. Completion handoff now explicitly requires mailbox evidence, but unattended dispatch wakeup is unqualified. No scheduler/kit changes at this checkpoint. + +## client-reliability + +- 2026-10-27 client10 DONE: product5b0adf6b normal439gate exit0/ancestry0, exactcandidateQA native4flowPASS peerattributed, build+artifact86fc5236 PASS/helpercmp0. Required build/landing evidence23c69eba durablyaf8e6540/docs-only/ancestry0; closed after durableevidence. AllslotsRELEASED, no artifactpublication/preview/deploy. Prioroverflow362/360+394/390, nativeactivecancel/visualAA/item9live/CPU/wholeStage3 remain separate/open. No new task/runtime self-selected. + +- 2026-10-27 01:54UTC: client10 separate localbuild+artifactverifierPASS/naturalexit0/version86fc5236/indexSHA50a89bd6/helperdistbyteequalcmp0. No trackedartifactdelta/preview/deploy. SolebuildslotRELEASED; land build+normal439gate evidence docs-only then close10; historicaloverflow/nativeactivecancel/AA/item9live limits retained. test_logs/TEST_LOG_2026-10-27_01-54-00_client-reliability-10-build.md. + +- 2026-10-27 01:52UTC: client10 productLANDED5b0adf6b via exactaff88416 normal439gate exit0/discovery439unique/missingextra0/ancestry0. QA exactcandidate nativepoint acceptance PASS attributeddispatch/QA; alloldoverflow/activecancelnative/AA/live limits retained. Completegateevidence state/evidence/client10-animation-cleanup-20261027; slotRELEASED. Separate remainingbuild/artifact/native routing pending, item10open. test_logs/TEST_LOG_2026-10-27_01-52-00_client-reliability-10-landed.md. + +- 2026-10-27 client10 scoped cleanup READY: late-init recovery extracted from index animation block to59line helper;17newscenarios pass/19distinct killedmutants/all17covered/lint11pass. Existing tests/CSS/app/bootstrap untouched outside block/import. No full/build/browser/submission grant; native360point+390/1280/reducedmotion and normalgate/landing remain. Preserve prior362/360+394/390 overflow and item9live/native limits. Evidence test_logs/TEST_LOG_2026-10-27_01-39-00_client-reliability-10-ready.md. + +- 2026-10-26 19:43UTC: separately granted local build/artifact passes exit0 at7594c54d (runtimeaf61c271); artifact15868a91/indexSHA9c48e0fe, clean tree, no previewserver/deploy. Sandbox build swcompat history probe skipped; prior unchanged-input hostlint11pass retained. Slot RELEASED; native torrent/browser+QA timing need distinct routing, item9 staysopen. test_logs/TEST_LOG_2026-10-26_19-43-00_client-reliability-9-build.md. + +- 2026-10-26 19:41UTC: item9 active-torrent product LANDED af61c271 via normal eafa364d queue gate exit0; independent438discovered/438unique starts/missingextra0 and ancestry0. Complete log/result/discovery hashed under state/evidence/client9-torrent-validation-20261026, prior rejected275partial intact. lint11/savedfocused60/mutants19 retained. Normalgate slot RELEASED; separate build/native-browser routing remains, item9 open. No real peers/byte teardown/wholeStage3 claim. See test_logs/TEST_LOG_2026-10-26_19-41-00_client-reliability-9-torrent-landed.md. + +- 2026-10-26 19:16UTC: item9 full lint11PASS; normal queue c20635bc interrupted for sustained90.1C→86C over38s. Durable rejected/red exit1, unstable04c8c7cf unchanged, not landed. Partial275/438starts/275unique,163notstarted; original log/result preserved in state/evidence/client9-torrent-validation-20261026. Slot RELEASED, fresh resource+new gate grant required; no auto-resubmit/build/browser. Evidence test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md. + +- 2026-10-26 resumed item9 active-torrent candidate `1b2362d2`, merged unstable `04c8c7cf` at `1bf50c1f`; exact saved hashes/focused60/60/19distinct killed mutants preserved. Remaining full lint/full/build/browser/normal landing require dispatch slot plus fresh health; TCPU97.1C at19:07:29 exceeds85C. No new tests or submission. Hold recorded and blocker mailed; real peers/byte teardown/wholeStage3 unverified. Evidence: test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md. + +- 2026-10-20: item6 DONE: retained actual profile-history wiring regression landed b3fab032;normal gate exit0 with431 discovered/431 unique file starts,focused38/38,5 killed mutants,lint11/11,submitted ancestry0. Test-only,existing product behavior passes. Evidence test_logs/TEST_LOG_2026-10-20_15-41-00_client-reliability-6-landed.md. Real signers/relays/encryption/full-app modal/browser/build/synced Clear unverified. Expensive slot released;IDLE for owner local review,no optional work. + +- 2026-10-20: item6 test-only retention ready:new real-profile wiring3/3,focused5files/38scenarios,5 isolated runtime mutants failnaturally/0cancelled,lint11pass. Local remove/lastitem/clear/recreation andfake-synced reduced+empty replacement covered;historicalno-op rejected. No product/existing suite edits;full/normal slot pendingexplicit grant,full/browser/build unrun;real signers/relays/fullapp/syncedClear unverified. Unique integrity/evidence in test_logs/*client-reliability-6*. + +- 2026-10-20: item6 audit atd88716e3(profile paths unchanged throughbbf57388):real local profile remove/clear and fake-synced remove/recreation pass,0network;historical runtime no-op failsboth aftercleanup but existingprofile27/27 stillpasses;lowerlayers3files/8pass. Classify missing real-wiring regression,notcurrentproductfailure. Proposed test-only focused file+uniqueintegrity;await routing,QA expensive slot respected. Evidence test_logs/TEST_LOG_2026-10-20_15-04-00_client-reliability-6.md. No product/test changes or live signer/relay/fullapp proof. + +- 2026-10-20: item5 done/closed after normal product landing0bd0b1c3(submission4bc52f9d,gate exit0,unstable ancestry exit0). Full427files/focused28/lifecycle18/six reverted mutants/lint11/build-artifact/Chromium+Firefox controlled smoke pass;obsolete mirror HEAD1→0. Browser+expensive slot released13:57UTC after10m57s initial wait. Dispatch landing/release event sent;preview refresh dispatch-owned. Closure metadata/final follow;no next item. Active abort/actual fallback/livepeers/CPU/fullStage3 unverified. + +- 2026-10-20: item5 ready atmergedf2a392fa(product12f92732):full427/427 files60s-bound exit0,focused28/lifecycle18,six reverted mutants,lint11/build-artifact/native Chromium+Firefox smoke pass;obsolete mirror HEAD1→0,shared/rejoin/retry/hidden reveal pass,0 external/pageerrors. Full-slot wait10m57s recorded;browser released,expensive slot held through normal landing. Remaining queue/durable ancestry/close;active abort/real playback/CPU unverified. + +- 2026-10-20: item5 routed controller-only pre-candidate guard implemented; baseline16pass/2fail→fixed18/18 lifecycle,6 rejected/reverted mutants,focused28/4files,lint11/build+artifact pass. No full/browser launch while dispatch administrative drain/QA reservation holds; slot requested13:32:54UTC,await explicit grant and verified unstable merge. No active abort/observer/cache/limits change; actual fallback/CPU unverified. Unique integrity/evidence in test_logs/*client-reliability-5*. + +- 2026-10-20: item5 investigation at0c1dd7e8:active URL success/real4000ms controlled timeout release media/slot correctly;disposed active primary media error starts1 obsolete mirror HEAD(expected0),strict assertion naturally exits1 after cleanup/retry. Shared/rejoin owner and later same-source controls pass. Proposed controller-only pre-candidate ownership guard + focused tests; await dispatch routing. No product/tests changed; active abort/browser/fallback/CPU unverified. See test_logs/TEST_LOG_2026-10-20_13-30-00_client-reliability-5.md. + +- 2026-10-20: item4 done/closed after normal landing89ccb907(submission7055496b,gate exit0,unstable ancestry exit0). Full427/427 files,focused22,lifecycle12,13 reverted mutants,lint11,build/artifact/native Chromium+Firefox controlled IO pass;obsolete starts1→0. Dispatch landing event sent; preview refresh owned by dispatch. No further item without routing. Actual media fallback/live peers/active abort/CPU and whole Stage3 unverified. Closure metadata/final event follow. + +- 2026-10-20: item4 ready atce1381f5; full427/427 file exits0 with60s/file bound,focused22/22,lifecycle12/12,13 rejected/reverted mutants,lint11/11,build/artifact and Chromium+Firefox controlled native-observer smoke pass (obsolete starts0). Remaining normal queue/durable landing/ancestry then close/report. Actual fallback/live IO/active abort/CPU remain unverified. + +- 2026-10-20: item4 routed implementation in URL observer/controller complete locally;12 lifecycle scenarios and13 reverted mutants, focused/lint/build pass. Queued last-owner work settles without cache/mirror/stale UI; shared owners, immediate retry and hidden reveal covered. Remaining: native browser smoke/full merged-tree gate/normal landing/closure; active abort/fallback/CPU unverified. + +- 2026-10-20: client-reliability-4 investigation at4c54858c reproduces queued URL media start after feed disposal: active3/queued1 before and after disposal, then1 obsolete start(expected0); shared live-owner control deduplicates and receives healthy badge. Strict assertion exits1, diagnostic/control naturally exit0. No product/tests changed; awaiting dispatch fix routing. Active abort/browser/fallback/CPU unverified. Evidence: test_logs/TEST_LOG_2026-10-20_03-10-00_client-reliability-4.md. + +- 2026-10-19: client-reliability-1 assigned: reproduce/classify and fix profile-modal suite hang without weakening assertions. Initial evidence: three isolated 60s timeouts (one clean environment), full baseline stopped at file 220/425; read docs/agents/prompts/client-reliability.md and claim the item. Scope excludes app.js and unrelated subsystems. + +- 2026-10-19: client-reliability-1 fix committed at dbdac18d (test-only fixture, original assertions preserved; native focus contract and both mutants fail when broken). Isolated27/27 natural exit2.44s, lint11/11. Full60s/file run passed226/425 files including profile-modal; file227 tests/race/nostr-client-init-race.test.mjs timed out;198 later files untested. Await dispatch routing for this unowned blocker before normal landing/close. No product/browser/build qualification or landing claimed. See test_logs/TEST_LOG_2026-10-19_20-13-00_client-reliability-final.md. + +- 2026-10-19: client-reliability-1 and client-reliability-2 ready for normal landing at181eae95: profile27/27 and race1/1 natural exit; all425 unit files passed with60s/file bound; lint11/11. Four deliberately broken variants fail and are reverted. Dispatch routing unblocked the second item; no product Nostr/shared-runner changes. Remaining: durable normal queue landing, unstable verification, then backlog close. Browser/build and real IO unverified (test-only scope). See test_logs/TEST_LOG_2026-10-19_20-35-00_client-reliability-both.md. + +- 2026-10-19: client-reliability-1 and client-reliability-2 finished: durable normal-gate landing de6918ce (submission683cedf0), gate exit0 and unstable ancestry verified. Profile27/27, race1/1 natural exit, lint11/11, full425/425 file exits0; all four mutants rejected. Backlog closed after landing. No unfinished product work for these items; browser/build/real IO unverified (test-only scope). Closure metadata queued separately; see test_logs/TEST_LOG_2026-10-19_20-34-50_client-reliability-landed.md. + +- 2026-10-19: client-reliability-3 implemented at current worktree: actual five-card obsolete starts1→0 after last-owner disposal; shared queued/live owners and active probe cache preserved; inflight identity and stale registration guards. Focused25/25 across4 files,11 mutants rejected, lint11/11, build/artifact verification pass, native Chromium/Firefox built-dist lifecycle smoke pass with0 external requests/page errors. Remaining: committed merged-tree full426-file gate, normal landq, durable ancestry/close and dispatch events. Active abort, URL probes, actual media fallback/peers and CPU/RSS unverified/out of scope; entire Stage3 performance plan is not complete. + +- 2026-10-19: client-reliability-3 ready for normal queue at607d5ce1: full426/426 files exit0 with60s/file bound;27 focused scenarios,14 reverted mutants rejected, lint11/11, build/artifact verification pass. Corrected Chromium/Firefox built-dist/native-observer smoke:0 obsolete starts/stale callbacks/page errors/external requests; hidden reveal and source-health visibility pass. Remaining: normal queue/durable landing/ancestry then close and dispatch landing/final evidence events. Actual playback fallback/live peers, active abort/URL cancellation, CPU/RSS and full Stage3 remain unverified/out of scope. + +- 2026-10-19: client-reliability-3 finished and closed after verified normal landing7d20a509 (submissione522efc1, gate exit0, unstable ancestry exit0). Full426/426 files,focused27/27,14 reverted mutants,lint11/11,build/artifact verification and native Chromium/Firefox controlled-IO smokes pass; obsolete starts1→0. Dispatch landing evidence sent; closure metadata/final report follow. Dispatch owns preview sync/rebuild after its resume; mailbox does not wake idle sessions. Actual media fallback/live peers/CPU-RSS, active abort/URL cancellation and whole Stage3 remain outside qualification; no new work without routing. + +## storage-cors + +- 2026-10-20 complete:productd3f4c215/closure7f99c045,full428/428,focused13/new9,10mutants,lint11/build;dispatch independent9/9+preview verification. Idle while QA trial output integrates. Live bucket/IAM/provider serialization/concurrent noCAS safety unverified. + + +- Parallel trial item1: reproduce CORS existing-origin preservation offline before routing fix; no cloud changes. + +## playback-qa + +- 2026-10-20 item1 done:retained regression1bb3a4fc/closurebbf57388,full429/429/4focused/6mutants/lint11,dispatch independent4/4. Item2 nowactual-app local mock-relay/private-port core/mobile baseline,onebounded Chromium pass/noUIedits/browser-only allocation. Real peers/fullapp qualification stays open until actual evidence. + + +- Parallel trial item1: local fallback qualification/regression; browser initially reserved to QA; product read-only. diff --git a/docs/agents/BITVID-SCHEDULER.md b/docs/agents/BITVID-SCHEDULER.md new file mode 100644 index 00000000..1b60e1ee --- /dev/null +++ b/docs/agents/BITVID-SCHEDULER.md @@ -0,0 +1,15 @@ +# Bitvid scheduled-job policy + +Owner direction, 2026-10-23: "I would like to not have extra processes like this running when the fleet is down or paused". + +All seven installed Bitvid cron jobs enter through `tools/fleet/bin/bitvid-schedule-guard.mjs`, not directly through `sched.sh`. + +- A missing/dead/stopped interactive fleet means no scheduled headless launch. At least one configured interactive area must have a running record and a live PID. +- `fleet/PAUSED` is represented by the existing state-directory `PAUSED` marker. It unconditionally blocks every job, including the weekly restart job; `ignorePause` does not bypass this outer guard. +- A running scheduled job is checked every second. On pause/down, the guard sends TERM only to the process group it created. Fleet's nested process runners forward termination to their own agent group. A bounded KILL fallback handles a group that does not exit. +- A cron tick while down or paused starts only the brief guard check, which exits without an AI session or persistent watcher. There is no always-on supervisor added. +- Unpausing and bringing the fleet up lets the next normal cron tick run; this policy does not automatically resume a paused fleet. +- Existing quiet hours, per-job non-overlap locks and time limits still apply inside `sched.sh`. The one-heavy-job convention still applies. +- This is a Bitvid-specific file, separate from the kit-managed scheduler. Kit upgrades should retain it. `fleet crontab` prints generic, unguarded commands: do not overwrite the installed guarded entries without preserving this policy. + +Validation: `env -u NODE_TEST_CONTEXT node --test tools/fleet/test/bitvid-schedule-guard.test.mjs` passed 8 tests with natural exit; fixtures only, no real fleet shutdown/pause or headless model run. Test integrity: new behavioral coverage, no existing assertions/gates changed. Process stop tests use a disposable sleep child and verify the unrelated test parent remains alive. Nested production runner cleanup relies on the kit's existing TERM forwarding; this is not a real-model stop qualification. diff --git a/docs/agents/CAMPAIGN-2026-10-22.md b/docs/agents/CAMPAIGN-2026-10-22.md new file mode 100644 index 00000000..a86fe7ec --- /dev/null +++ b/docs/agents/CAMPAIGN-2026-10-22.md @@ -0,0 +1,39 @@ +# Six-priority campaign — 2026-10-22 + +Owner: "yeah lets get the fleet spun up and address all of these", relayed by interpreter at14:02:34Z. Routine YES14:03:29Z. Prior holds superseded only within this foreground scope. Baseline unstable fb54cea3. Team is dispatch plus three existing workers; windows alive. Coordination c6022f21 landed c5b678f7 (durable status/log/ancestry checked). All three assignments acknowledged at c5b678f7: storage14:08:34Z, QA14:08:41Z, client14:08:44Z; each reports safe fast-forward and claim. No browser/expensive process started at acknowledgement; QA retains initial narrow slot. No current tool blocker reported. + +| Priority | Accountable owner / item | Immediate action | Acceptance / boundary | +|---|---|---|---| +| 1 Firefox deterministic CI | playback-qa / playback-qa-3 | Inspect run39536282396/job119013565277 and local workflow/fixture; identify actual failing step/assertion, reproduce narrowly | Preserve first failure, exact commands/counts and natural exit; diagnose environment vs product. Root cause before fix/test/helper/config ownership. Strict retained regression + failing mutant for changes; targeted Firefox then unchanged required gates. Empty remote logs do not establish a cause. | +| 2 Active cancellation | client-reliability / client-reliability-9 | Actual observer/controller HEAD last-owner disposal, shared owner, same-turn and later fresh rejoin with controlled fetch | Count abort/settlement/slots/cache/mirrors/stale writes; cancelled last-owner work releases promptly, shared live work continues, replacement identity safe, live success/error/timeout/cache/FIFO preserved. HLS and torrent separately measured before any completion claim. Investigation first; no product/test edits until routing and explicit contract note. | +| 3 Playback reliability | dispatch / dispatch-4 (staged; client and QA next) | Audit publicly promised Range GET, persisted health/host scoring, retained warmup, hosted/P2P startup against current source and tests after cancellation slice | Range probe has bounded first chunk/cancel and HEAD-incompatible host fixture; persistence survives reload with bounded expiry/malformed/quota controls and source/host isolation; warmup is reused by playback and cancelled on owner loss with measured resource caps; hosted/P2P startup preserves configured preference, one winning playback and losing cleanup. Establish exact policy/ownership before writes, route real ambiguities. All four subitems open. Other four Oct playback followups remain tracked in QUALITY/canonical TODO. | +| 4 Feed/view tombstones | storage-cors / storage-cors-3 | Actual delete/tombstone ingest then stale refresh using synthetic offline events/controlled relay boundary | Deleted video stays absent through cache refresh, late older event and view switching; unrelated live/newer valid content preserved under existing protocol semantics. Report real path/defect before edits; no new deletion-policy invention or real cloud/delete/signing. | +| 5 Amber sessions | dispatch / dispatch-5 (staged; client next) | Re-audit per-account restore/switch/logout using existing NIP46 lifecycle tests and fake transport, read docs/nips first | Restore selects expected identity; switching and logout preserve other accounts; transient/denied restore handled without stale unknown identity or lost saved sessions. Pin real UI/service wiring where possible. No credential/live signer operation; real phone Amber remains explicit manual gate, not simulated proof. | +| 6 Mobile | dispatch / dispatch-6 (staged; QA next) | Reproduce retained Upload overflow at360/390 plus1280 control; inspect actual app focus/touch and source | Strict document width <= viewport at360/390/1280; critical controls/touch/focus reachable, keyboard containment and restore retained, token/contrast rules followed. Locate culprit before CSS ownership/fix, preserve original failing baseline; real-device accessibility/manual check remains open. | + +## Plan and sequencing + +1. Inspect every worktree and preserve all dirty/unlanded work. Done: six clean, each HEAD-only count0. Dispatch ff-only current; workers sync own branches, never touch peers. +2. Land coordination docs/backlog; route three disjoint investigations and collect explicit read/next-action acknowledgements. +3. QA gets exclusive initial browser/expensive slot for narrow Firefox reproduction. Client/storage use cheap offline checks. No broad suite/build/normal product queue without dispatch grant; announce UTC start/release in .task and mailbox. +4. Independently reproduce demonstrated defects, record expected contract/ownership and test-integrity requirements, then authorize smallest fixes. No app.js writer currently assigned. +5. Stage remaining playback/Amber/mobile next as each owner frees up; do not close or omit them because another task completes. Add agents only for demonstrated disjoint work and permitted scope. +6. Verify required focused/lint/mutants and unchanged normal gates; land via queue, verify durable status + unstable ancestry before closing. Integrated build/artifact/local preview and final six-row status, honest live/manual blockers. + +No deploy/push/spend/production data/real-wallet/credential/live signer/cron authority. No retries, sleeps, force exits, weaker assertions or speculative fixes. No background monitoring claim. Read KNOWN_ISSUES before debugging; existing Firefox flakes and logout failure are baselines, not presumed causes of this CI failure. + +## 14:12UTC evidence/routing checkpoint + +QA released first browser14:11:17Z: original3 scenarios pass but app bootstrap failed because Buffer CDN was denied, so not CI reproduction. Next exclusive narrow Firefox diagnostic authorized only with pinned genuine cached Buffer and actual readiness, preserving assertions and isolated control; no broad/full/build. + +Storage3 dispatch-independent scratch on7369f1fe/runtime-equivalentc5b678f7:7scenarios/3pass/4fail/network0, naturalexit1/29.89ms. Service snapshot/persistent cache leaks original deleted version; old tombstone unconditionally removes newer live active version in buffer; overlapped coordinator older result renders deleted reference at renderVideoList boundary. Two smallest service/buffer fixes routed in separate commits with mutants/controls; coordinator remains open/read-only pending faithful registered lifecycle evidence. No DOM/production proof. + +## 14:23UTC QA checkpoint / mobile routing + +QA3 native Playwright runner + original instrumentedTest/global setup/3callbacks/assertions PASS3/0fail/0flaky/0skip/retries0, workers1/private genuine Buffer/public-deny/scratch static server. Worker naturalexit0,9955ms,release around14:22:11UTC. Original CI184pass/2fail/3flaky remains OPEN/unreproduced; no speculative fix. App-ready3 and isolated3 and original-timing3 are distinct prior controls, not repeated CI proof. Access to failed CI logs403/no E2E artifact limits diagnosis; offline mirror/serial/serviceWorker setting/server differ. Retain QA3 evidence and stage further reproduction only on new evidence. + +Priority6 now ACTIVE via QA4: actual-app Chromium360/390/1280 Upload width+culprit/focus/touch measurement, strict overflow baseline preserved, no CSS/product edits before independent evidence+routing. QA exclusive bounded browser, no full/build. Priority1 remains open; priorities2/4 fixes ongoing,3/5 staged. + +## 14:31UTC gate and coordinator checkpoint + +Storage3 existing buffer/service candidate owns full/normalgate/build slot since14:26:33, QA4 confirms mobile browser unconsumed/HOLD. Client9 exactcandidatee887ceaf independently23/23 naturalexit0/282.24ms and next fullgate, browser separate. Storage4 now separately routed coordinator-only after actual registered-feed/JSDOM2pass/2fail independent repro at89f8923c; freeze/submit storage3 first, keep follow-on separate. No product landing/CI clearance/live proof claimed. diff --git a/docs/agents/CHECKIN-2026-10-24-1922.md b/docs/agents/CHECKIN-2026-10-24-1922.md new file mode 100644 index 00000000..60cacd10 --- /dev/null +++ b/docs/agents/CHECKIN-2026-10-24-1922.md @@ -0,0 +1,25 @@ +# Hourly check-in — 2026-10-24 19:22 UTC + +- Read PROGRAM, WIND-DOWN, current RULE-CHANGES, repo memory, active backlog, + task lines, dispatch mailbox (empty), and landing results/log. +- `node tools/fleet/bin/doctor.mjs --json`: exit 0 at 19:22:16Z. Dispatch DIRTY4, + client ONE-DISK2, storage clean, QA DIRTY3 plus one WAITING line. Existing work + was preserved. Doctor also reports unowned paths; ownership was not changed. +- Every WAITING line read: QA waits on dispatch since 03:00:28Z, over 16 hours. + Sent dispatch one scheduler mailbox notice at 19:23:03Z asking acknowledgement + and resolution or explicit parking. Recorded delivery in today's sent ledger; + retained the heavy HOLD and granted no execution slot. Consumption unverified. +- `node tools/fleet/bin/landq.mjs status`: exit 0, queue 0, lander NOT running. + Appended dated owner line; an empty-queue drain normally exits by design. +- Sole failed result `d7184f07bd` is publication divergence, already known to + submitter and recovered per fleet/LOG.md. Append-only log also preserves two + red gates overwritten by later same-SHA success results. Added these to + STRIKES with environment/thermal context, one per area this week. No tier + move threshold reached. No outstanding conflict found in retained records. +- Usage governor: `usage.enabled=false`, `modules.usageGovernor=false`; + configured wind-down threshold 95%. Adapter usage hook was not called; + usage-based PAUSED step does not apply. +- No product edits, tests, builds, browser runs, push, deploy, approvals, + scheduler installation or interactive session starts. Main/product health, + usage percentage, live playback, owner acknowledgement and message consumption + were not verified. Documentation landing is checked separately after submit. diff --git a/docs/agents/CHECKIN-2026-10-26-1922-hourly.md b/docs/agents/CHECKIN-2026-10-26-1922-hourly.md new file mode 100644 index 00000000..6974f321 --- /dev/null +++ b/docs/agents/CHECKIN-2026-10-26-1922-hourly.md @@ -0,0 +1,12 @@ +# Hourly check-in 2026-10-26 19:22 UTC + +- Scope: fleet health, all WAITING text, landing results, daily notification deduplication, strikes and usage wind-down. No product changes or heavy validation. +- Plan completed: read PROGRAM/WIND-DOWN/backlog/STRIKES and daily ledger; run doctor/queue/fleet status; inspect every area .task including inline WAITING; review retained adverse landing log; record new strike and owner decision; submit only this audit and STRIKES. +- doctor --json, landq status, fleet status, landq status c20635bc, backlog list: exit0. Doctor: dispatch DIRTY21/WAITING, client ONE-DISK9, QA DIRTY10, storage behind22. Existing work retained. +- All .task files read. Dispatch thermal WAITING since19:16Z and inline client WAITING since19:15:42Z are under2h. No overdue reminder. Storage .task still mentions its historical queued landing; durable result492557400c is landed, not a fresh blockage. +- Fleet reports lander DOWN, queue0; workers unavailable/dead, storage stopped with1 unread, interpreter3 unread. No status says stalled; a dead/unknown window proves neither progress nor a live stall. Lander absence recorded in owner.md. +- Retained log adds one new red c20635bc at19:15:32.782Z; durable result rejected. Existing client MCP notice19:16:19Z is in the daily ledger, so no duplicate send or ledger append. No additional conflict/withdrawal found in retained landing log. Session-only incident completeness unverified. +- STRIKES records thermal interruption without claiming assertion failure. Two client reds within7days straddle configured Monday03:00 usage reset: current configured week1. Owner proposal conditional on rolling-week interpretation; no tier/config edit. +- usage.enabled=false and modules.usageGovernor=false; no usage-hook query or PAUSED creation. Latest40 wake records contain no five-hour-nearly-gone warning; panes not scraped. +- No lint/unit/build/browser rerun for Markdown-only audit during thermal hold. No product/main/live qualification, real session liveness, worker acknowledgement or hold-compliance verification. +- Verification: git diff --check limited to audit files before commit; docs-only landing result and target ancestry checked separately after submission. diff --git a/docs/agents/LOCAL-REVIEW.md b/docs/agents/LOCAL-REVIEW.md new file mode 100644 index 00000000..d760b193 --- /dev/null +++ b/docs/agents/LOCAL-REVIEW.md @@ -0,0 +1,46 @@ +# Local review handoff — 2026-10-20 + +Status: ready for owner local review with the known minor Upload overflow below. Owner subsequently authorized bounded active-probe/URL cancellation work;the active URL-media slice is now landed43cb1baf. New localpreview build08620a71 replaces prior6da89895:build/artifact exit0,hostHTTP200/indexbyteequalcmp0. Dispatch independently29/29 lifecycle;unchanged normal432-filegateexit0. Worker native Chromium+Firefox controlled media smoke passed and owned resources released. These use actual controller/observer/native src/load plus controlled state/HEAD/intercepted media IO and synthetic completion;pause is stubbed. Not full-app decoder/live playback or universal networkabort/byte/CPU proof. HEAD/HLS/torrent cancellation remains open. Closure/browser evidence durably2ea6f068;item8 done,team idle until next bounded scope. + +Open [the existing local preview](http://127.0.0.1:3000). It is already running; no terminal command is needed. The existing dispatch preview serves built `dist`. Current build08620a71 is from integrated43cb1baf. Historical core/mobile QA build6da89895 (an opaque artifact identifier, not a Git SHA) was produced from integrated unstableb1174163, including the cold-feed fix. HTTP200 and byte equality of served and built index were verified; runtime equality for final documentation descendants will be checked before handoff. Dispatch branch is area/dispatch. Final documentation landing SHA is reported in the owner handoff; runtime/build-input equality through QA closurec017069c was verified without rebuilding. + +## Changes available to review + +- Disposed feed owners no longer start obsolete queued torrent/URL media probes; an active failed media probe also cannot start an orphan mirror HEAD. Shared owners and active work retain existing semantics. +- Fresh relay video updates now notify the current feed consumer, so a cold empty feed can render newly arrived eligible videos. Obsolete subscription callbacks cannot update a replacement feed. +- Bucket CORS updates preserve existing rules and append only missing origin coverage. Unexpected reads cannot overwrite policy. +- Storage cleanup safely skips malformed cached primitive fields rather than rejecting; R2 and generic S3 deletion share this service. Ownership boundaries remain enforced. +- Actual playback fallback/magnet behavior and actual profile-history removal wiring now have regression coverage. The history behavior already worked; the new test catches the historical no-op implementation. + +## Evidence + +Latest unchanged normal gate candidate b1174163 completed successfully across432 configured test files, with empty quarantine and no shard. Durable landing and submitted ancestry were checked independently. Retained runner output contains432 unique starts; the stored gate result is a tail, not a complete install/process transcript, and its final stdout marker is absent. No suite or gate was weakened. + +Dispatch independently checked URL lifecycle18/18, CORS9/9, fallback4/4, malformed cleanup13/13, new real history wiring3/3, and cold-feed current-consumer notification7/7. Workers also recorded failing restored mutants, focused suites, lint11 stages, and normal full gates. [History evidence](../../test_logs/TEST_LOG_2026-10-20_15-41-00_client-reliability-6-landed.md) and [dispatch evidence](../../test_logs/TEST_LOG_2026-10-20_15-45-00_dispatch-review.md) retain exact scope. + +Before the fix, the actual-app reproduction was: cold Trending started from an empty cache, received three valid eligible local video events, and retained an empty DOM. Passive trace proves relay EVENT/EOSE, all client/app maps and filtered videos contain3, while view remainsEMPTY. Client7 fix landedb1174163 with active-consumer and stale-callback lifecycle controls plus regression coverage. The rebuilt artifact6da89895 renders all three cold-feed cards without a forced refresh. The controlled browser uses genuine cached Buffer and a genuine local VP8 fixture outside immutable app files, synthetic identity/notes and local mock relays. This does not qualify real signers, public relays or deployed CDN behavior. + +## Actual-app baseline and known finding + +Controlled Chromium154 on immutable build6da89895 completed Trending/Explore/ForYou/Home navigation, three-card hydration, synthetic login, Profile and Upload open/close, six Tab steps contained in each modal, exact trigger focus restoration, missing-title and missing-source validation, and no video publish from invalid forms at360×800,390×844 and1280×720. Feed cards/document fit those viewports. Genuine cached Buffer, real toolkit signature checks and local VP8 were used; public HTTP/WebSocket traffic was denied, with no page errors or local asset failures in these final runs. + +The original strict baseline stopped on Upload horizontal document overflow. It remains FAILED/open: document width362 at360px and394 at390px; no overflow at1280. Targeted follow-on coverage completed independently; critical inputs, Publish and Close remain horizontally inside the viewport and close restores focus. This is not a wholly passing baseline. Exact overflowing source element is not yet established; no speculative CSS fix was made. Some measured controls are below44px; real-device usability/accessibility review remains open. + +The browser, private app/relay servers and temporary dependency link were closed after the follow-on run at16:28:10.112UTC. The owner preview on3000 remains running. + +See [QA evidence and screenshots](../testing/playback-qa-2-final-evidence.md) for exact final and follow-on results, preserved failures and fixture provenance. + +## Suggested review in the browser + +1. At desktop and narrow mobile widths, browse Trending/Explore/For You, open and close Profile and Upload, and check keyboard focus and horizontal scrolling. +2. In Upload external mode, check missing title and missing playable source errors. Avoid publishing or using real bucket credentials during this local review. +3. Check playback source/fallback messaging with content you intend to test. Controlled fallback tests do not establish live torrent availability. +4. Check history removal/clear and reload behavior using the account/signing workflow you intend to qualify; real account evidence is still open. + +## Remaining scope + +Actual full-app media playback/Firefox, real Amber/NIP46 account/switch/logout, encrypted relay persistence, live storage/IAM/provider behavior and concurrent CORS writes, real WebTorrent peers, production service-worker/offline/CDN behavior, real devices and manual release QA remain unverified. Known parallel Firefox hydration and isolated logout issues remain documented in KNOWN_ISSUES.md. + +Feed/view tombstone audit remains deferred as storage-cors-3. Public-promised playback backlog remains open: RangeGET health compatibility, persistence/per-host scoring, parallel startup, hover/viewport warmup, complete mirror/Blossom discovery, ranking/status and service-worker followups. Partial existing implementations are not whole-item completion. See [quality matrix](../../fleet/QUALITY.md). + +This milestone is local review/testing, not production readiness or completion of the broader roadmap. Workers idle after evidence closure; no push, deploy, spend, real cloud/account/peer operation or scheduler activation was performed. diff --git a/docs/agents/PROGRAM.md b/docs/agents/PROGRAM.md new file mode 100644 index 00000000..6692332b --- /dev/null +++ b/docs/agents/PROGRAM.md @@ -0,0 +1,92 @@ +# Program — the owner's priorities and rulings + +Last updated: 2026-10-23 + +## Current milestone + +Six-priority campaign resumed 2026-10-22; prior completion statements below are historical evidence. See CAMPAIGN-2026-10-22.md. + +Active URL-media cancellation slice is landed43cb1baf after normal432-file gate exit0;dispatch independent29/29,build08620a71/artifact pass/previewHTTP200+index equality. Worker controlled native Chromium+Firefox smoke passed and resources released. Closure/browser evidence durably landed2ea6f068;item8 done,team idle awaiting next bounded scope. HEAD/HLS/torrent active cancellation remains open; broader optional work remains deferred. + +Local-review handoff: storage2/history6 and demonstrated cold-feed blockerclient7 are normally landed. Build6da89895/preview3000 verified;actualcold3cards and separate25-step360/390/1280core/modal coverage complete. Original strictUploadoverflow FAIL remains open(2/4px,controlsusable),not an all-green baseline. Finish docs-only evidence/handoff,then allIDLE for owner review;no optional backlog work. Broader roadmap/live signer/cloud/peer/release gates remain explicit in QUALITY/LOCAL-REVIEW. + +## Priorities + +1. Owner direction: TODO backlog progress, bug fixes, robustness, and usability. +2. Session-manager sequencing (vetoable): prioritize reproducible failures in core user flows and small changes with clear completion checks. + +## Rulings log (newest first) + +## 2026-10-23 — Campaign continuation after resource interruption + +> "ok lets get the fleet working some more" — owner, relayed by interpreter at2026-10-23T12:48:12Z; not typed in dispatch. + +Continue six existing priorities, with client HLS exact-candidate prep/normal gate when safe, QA bind-permission diagnosis/host-launch prep, and disjoint storage read-only playback audit. Preserve all rejected/partial evidence. Interpreter freezes direct/setup target writes during product gates and requires actual dispatch exclusive-slot acknowledgement before heavy validation; queue snapshots do not reserve a slot. No new restricted-action authority. + +## 2026-10-23 — Scheduled processes must respect down and pause + +> "I would like to not have extra processes like this running when the fleet is down or paused" — owner, relayed by interpreter at2026-10-23T12:35:35Z; not typed in dispatch. + +Interpreter is implementing a local Bitvid schedule guard and focused tests in unique files. Intended scope: all seven existing schedules require a live interactive fleet run; PAUSED blocks every job including weekly restart, and a supervised scheduled child is terminated only within its owned process group when fleet pauses/goes down. At12:37:56Z interpreter reports guard installed for all seven entries and8focused fixture tests passing naturally. Dispatch inspected guard/policy source at printed unstable3a89abd0; installed crontab execution and real-model shutdown remain unverified. No always-on watcher; inactive cron ticks run only the brief guard. Existing HLS gate/landings remain untouched; no unrelated process stops, global kit change or restricted authority added. + +## 2026-10-23 — Existing seven schedules authorized in interpreter session + +> "All seven jobs shown above" — owner, relayed by interpreter at2026-10-23T12:32:43Z; not typed in dispatch. + +Interpreter reports installing the existing checkin, hygiene, triage, summary, winddown, restart and resources jobs in user crontab, preserving prior entries and verifying readback. Scoped PATH/FLEET_CONFIG; resources every15minutes, only while laptop awake. Installation/readback are interpreter evidence, not independently verified here. This records scope and provenance; dispatch did not install or alter cron. One-heavy-job/resource guidance and all restricted-action limits remain. It adds no push/deploy/spend/delete/credential authority. Interpreter separately reports owner-approved MCP-only autoapproval setting saved; effective state in existing sessions is unverified and does not override a denied action. + +## 2026-10-22 — Six-priority campaign resumed via interpreter + +> "yeah lets get the fleet spun up and address all of these" — owner, relayed by interpreter at 2026-10-22T14:02:34Z; not typed in dispatch. + +The relay identifies six priorities: Firefox deterministic CI failure; remaining active HEAD/HLS/torrent cancellation; playback reliability promises; feed/view tombstone stale-refresh; Amber sessions; mobile usability. This supersedes the prior idle/local-review and quiet-hours hold for this foreground campaign. Routine investigation, evidence-routed fixes, tests/builds and normal local unstable landing are approved. No push/deploy/spend/production data/real wallet/credential/live signer/scheduler authority. Track all six in docs/agents/CAMPAIGN-2026-10-22.md. Preserve worktrees, use three existing workers with disjoint scope, stage before fixes, serialize browser/full gates, keep app.js single-writer. Final integrated local build/preview and honest live/manual limits required. + +### 2026-10-20 — Resume bounded active-probe cancellation, relayed by interpreter + +> "Then continue with the active-probe/URL cancellation work." — owner, relayed by interpreter at20:44:41UTC; not represented as typed in dispatch. + +Route existing client worker to client-reliability-8: reproduce current active URL-media last-owner disposal through actual observer/controller entrypoints and controlled IO, with shared live-owner and fresh-rejoin controls. Measure resources, settlement, slot release, cache and stale callbacks before any fix. Start media before broader HEAD/HLS/torrent policy. Existing queued/orphan-mirror fixes are complete; active abort is not. Existing tests protect the previous queued-only contract; expectation changes require explicit routing and Test Integrity Note after evidence. No app.js/kit/scheduler/external/cloud/signer/push/deploy/spend authority. Other workers idle unless needed for bounded independent QA. + +### 2026-10-20 — Explicit local-review stopping milestone,relayed by interpreter + +> "Ready for me to review and test locally" — owner, relayed by interpreter;not represented as typed in dispatch. + +Finish current storage2/history6 gates/landings,faithful local actual-app/core/mobile baseline and preview refresh,then provide local-review handoff before declaring this milestone met. Do not clear optional roadmap or claim production/live Amber/cloud/peer proof. No new restricted-action authority. + + +### 2026-10-20 — Continuing quality objective, relayed by interpreter + +> "ok try to keep them working until the project is in a good place." — owner, relayed by interpreter; not represented as typed in dispatch. + +Continue the existing robustness/usability backlog beyond the trial, with bounded evidence-first tasks and adaptive staffing. Local proof and real Amber/cloud/live-peer/manual release gates stay distinct. This does not add push/deploy/spend/real-wallet/production-data/scheduler authority or authorize new product definitions. Track current quality and next work in fleet/QUALITY.md. + + +### 2026-10-20 — Bounded parallel trial, owner words relayed by interpreter + +> "id really like to stress test the multi agent process so if it needs more agents to get more done faster. do it" — owner, relayed by interpreter; not represented as typed by owner in dispatch. + +Interpreter requests executing dispatch plus up to3 workers now with disjoint worktrees/scopes. Routine launches are authorized; retain existing restricted-action limits. Trial measures acknowledged assignments, actual parallel evidence/checks, durable queued landings and contention, not process count. + + +### 2026-10-19 — Roster-only bootstrap gate (session manager call, VETOABLE) + +The first approved worker is assigned to the pre-existing profile-modal test hang. Normal roster-before-launch ordering otherwise invokes that same hanging full-suite gate before a worker can investigate. For this one administrative roster/brief commit only, dispatch will use a temporary queue configuration whose gate verifies the exact committed change contains only the expected area registration and documentation/backlog metadata, with product code, tests, and persistent gates unchanged. No product fix is covered by this exception. Preserve normal full gates for subsequent code. Record the actual gate result and disclose the unresolved unit failure. + +### 2026-10-19 — Routine fleet authority, relayed from Codex desktop chat + +> "yes please approve whatever it wants and let me know about it" — owner, as relayed in this session from the Codex desktop chat; not represented as typed by the human in this terminal. + +The relay instruction received here explicitly authorizes operating tmux as the fleet interface, approving and starting the proposed client-reliability worker, and making routine staffing, local test/build, backlog prioritization, fixes, and local unstable landing decisions within the recorded goal without repeated approval. Keep the team small and scopes separate. This does not authorize deploys, spending, production data, real-wallet operations, or scheduler activation. Refresh the existing local preview after verified changes; send concise owner-mailbox updates for launches, assignment changes, landings, and blockers, including team size, areas, tasks, and verification. This supersedes the initial waiting state for first-worker approval only within that routine-work scope. + +### 2026-10-19 — Initial goal +> "lets try to work our way through some of the issues in the TODO backlog and find and bugs and generaly make the client more robust and usable. ." — owner +Applies to: all areas. Recorded by: dispatch. + +### 2026-10-19 — Conservative startup limits (session manager call, VETOABLE) + +No deploys, spending, production data, real-wallet operations, or scheduler activation. Present the first team for owner approval before starting workers. + +## Standing rules + +- Approvals for spend, publishing and deploys are typed by the owner in the session that acts. +- Agreed work is never silently dropped: a priority change defers it; only the owner cancels, with a reason. diff --git a/docs/agents/RESUME-BRIEF.md b/docs/agents/RESUME-BRIEF.md new file mode 100644 index 00000000..e1d20536 --- /dev/null +++ b/docs/agents/RESUME-BRIEF.md @@ -0,0 +1,34 @@ +# Current checkpoint — 2026-10-23 13:00UTC + +- Campaign continues under owner relay "ok lets get the fleet working some more"; all six priorities remain tracked. HEAD productf41ac656/evidenced04d501e; HLS product83fd2495 normalgateexit0, dispatch independently437starts/437unique. Storage tombstone/coordinator3+4 done, coordinator7edc7ea8/closure8317da4d; Rangeaudit103eb04c source-only proves gap, not implemented. +- Client solebuild/artifact START13:00:15 at103eb04c, result/release pending. QA4 conditionalsolebrowser after actualclientbuildRELEASE+freshhealth: same node /tmp/playback-qa-4-run.cjs host escalation for sandboxbindEPERM, publicdeny/genuinehash/strict360/390/1280 unchanged. Directpeerrelease instructed; no extra routineyes. HLSnativebrowser later, torrent/Rangeimplementation/Amber/FirefoxCI/mobile/integratedpreview stillopen. +- Prior HLSd7184 publishdivergence: directsetup3a89abd0 changedtargetduringgate. Second280f thermalinterrupt398/437 with kitvalidationoverlap confirmed12:42:49–12:46:15. Thirdsamecandidate normal437gate landed83fd2495; source/hashes unchanged. Bothpriorresults+398partial retained; originalfirst437full log overwritten beforecopy and only contemporaneouscount remains. Currentcomplete437log uniquelysaved state/evidence/client-reliability-9. SameSHAstatus returnedstalered duringnewrun; use timestamp/inflight/aliveprocess/log, finalnewlandstatus+ancestry. +- Oneheavyjob, direct/setup targetwritesfrozen duringproductgates, actualdispatchslotACK required. Freshhealth beforestages; sustained85Cstop, no tests weakened/timeouts increased/forceexit. Heat13:00:31max74C/load1.92/RAM65.8%/swap88% onlyadvisory. Two cooling snapshots<75C~30sapart required for newlonggate, no runnerchanges(serial). No Bitvidpush/deploy/spend/credentials/nonfleetprocessstops. +- Scheduler ownerquotes/provenance in PROGRAM/GOAL. Interpreter reports all7cronentriesguarded/down+PAUSEDblockedincludingrestart,8localfixturetests; source3a89abd0 inspected, realmodelstop/cronexecution unverified. Upstreamkit84tests/9guards/beta push2b6570a interpreter-onlyevidence; noautomaticupdate/restart. Pendingdispatchdocs capture restriction/resourcehistory, priorcoordination29556452 durablydd092ed8. + +Historical briefs below retain prior evidence and do not override this checkpoint. + +# Resume brief: active URL-media cancellation slice complete + +Read PROGRAM, GOAL, QUALITY, LOCAL-REVIEW and newest RULE-CHANGES. Owner continuation "Then continue with the active-probe/URL cancellation work." was relayed by interpreter20:44:41UTC. Bounded client8 active URL-media slice complete;no new optional task. All workers IDLE/resources released. HEAD/HLS/torrent active cancellation remains open and is not qualified by this slice;route next bounded work explicitly. No push/deploy/spend/cloud/signer/peer/scheduler authority. + +- Product3474668a durably43cb1bafe69f0bb3bce698e0b748281d1a04c061,unchanged normalgate exit0. Dispatch independent discovery432=432unique retainedstarts/missingextra0,durable status/log/ancestry0. Focused37/lifecycle29/14restored failingmutants/lint11 worker evidence;dispatch exactcandidate29/29 natural313ms. +- Final-owner active media src/listener/timer cleanup,nullsettlement/no cache,no obsolete mirrors,slot/FIFO release. Shared/cross/legacy/live cache+timeout/retry preserved;same-turn freshowner starts replacement,oldcallbacks/finalizer cannot affect it. Explicit strict spec correction in docs/spec_changes/2026-10-20-client-reliability-8-active-media.md and test_logs/TEST_INTEGRITY_2026-10-20_client-reliability-8.md. Product delta controller only18+/2-. +- Closure/browser evidence cfc20107 durably2ea6f0682bd6433cbbf0a2782fcb6ba14bd3de42,docsOnly/no gates. Dispatch handoff f233eaf2 durably9cbb53f8;runtime/buildinputs diff0 vs43cb1baf. Current .task/queue/log show final docs landing;never infer from silence. +- Preserved preview http://127.0.0.1:3000 rebuilt artifact08620a71 from43cb1baf (artifact identifier,notGit SHA). Build+artifactexit0;hostHTTP200/indexbyteequalcmp0. No deploy/server replacement. Native Chromium+Firefox controlled smoke bothPASS/resourcesreleased21:05:25UTC:actualcontroller/observer/native src/load,controlledstate+HEAD/interceptedmedia/syntheticcompletion,pause stubbed. No decoder/liveplayback/networkbyte/CPU/universalabort proof.8requests each,requestfailed7/8 observed;not all pendingabort asserted. Scratchscript/output inspected,not duplicate native run. +- Earlier localreview core/mobile QA remains historical build6da89895:actualcold3cards+25namedsteps360/390/1280. Original strictUpload overflowFAIL362/360,394/390 remains open/controlsusable. Do not call wholebaselineallgreen. See docs/agents/LOCAL-REVIEW.md;realAmber/cloud/peers/manualrelease gates remain open. Storage2/history6/coldfeed7 previously durable;feedtombstone3/publicplaybackpromises deferred/open. +- Final dispatch evidence test_logs/TEST_LOG_2026-10-20_20-57-00_dispatch-client8-candidate.md;no duplicate full after authoritative432gate. Initial sandbox autodraindied before running;worker approved env-cleared hostlander restart recovered same submission,no resubmission/gatechange. Routine acknowledgements need no reply;send outcome/blocker/release only. + +Earlier local-review history below remains provenance,not current preview version or active assignment. + +# Resume brief: 2026-10-20 local review handoff + +Read PROGRAM, GOAL, QUALITY, LOCAL-REVIEW and newest RULE-CHANGES. Owner stopping milestone is "Ready for me to review and test locally" (relayed by interpreter). No optional work starts until owner review/confirmed actionable feedback. No push/deploy/spend/real cloud/account/peer/scheduler authority. + +- Storage cleanup malformed metadata producte7b9afb3/closurec5818cfe,history regressionb3fab032/closure386cc60e done. +- Actual-app cold feed bug:eligible/cache3 butDOM0;live service omitted onVideos. Minimal current-consumer/stale-subscription notification fixb1174163556e2705ba85868e21adcaf51b44bd5f,closure09b13219. Unchanged normal432-filegateexit0,focused27/9failingmutants/lint11;dispatch independentnew7/7. +- Previewhttp://127.0.0.1:3000 alreadyrunning,node135479/cwddispatch;preserve. Build6da89895 fromb1174163,build/artifactexit0,hostHTTP200/indexcmp0. Laterdocs-only descendants need runtime equality check,not redundantbuild. +- QA actualcold3cardsPASS/noforcedrefresh. Original360Uploadstrictwidth assertionFAIL retained. Separate25namedsteps cover360/390/1280Trending/Explore/ForYou/Home/Profile/Uploadrequiredvalidation/6Tabcontainment/closefocusrestore. Document362at360/394at390,1280none;criticalcontrolsinside,minorusablefindingopen,exactculpritunproven. Notwholebaselineallpass. Browser/privateQAserversclosed16:28:10.112UTC. +- Controlled genuineBuffer/cachemirror+realcrypto/localVP8/syntheticidentity+mockrelay;publicnetworkdenied. No realAmber/signers/relay/cloud/IAM/peers/production/offlineCDN/manualreleasequalification. +- Feedtombstoneauditstorage3 andpublic-promisedplaybackbacklogremainexplicit/deferred. Priortrialparallelimplementation+95m57prepared-outputwalltime has no controlledserialbaseline/speedupclaim;39m57dispatchwakegap+serializedgates recordedLOG. +- Client/storageIDLE;QA finishingdocs-onlyevidence/closure thenIDLE. Dispatchfinishesdurabledocs/handoff+runtimepreviewaudit,thenIDLE. See current.task/mailbox/landq for finaldocsSHA;do notinferlandingfromsilence. diff --git a/docs/agents/STRIKES.md b/docs/agents/STRIKES.md new file mode 100644 index 00000000..3cc54889 --- /dev/null +++ b/docs/agents/STRIKES.md @@ -0,0 +1,40 @@ +# Model tier strikes + +Evidence loop for per-area model tiers. A strike is one of: red landing, withdrawn +submission, reviewer rejection, peer-caught false green, owner-reported regression. + +- Two strikes in a week (week = from the usage reset) -> propose moving the area UP a tier. +- Two clean weeks -> offer moving it DOWN a tier. +- Loses the thread after compaction -> raise its context. +Changes go into fleet.config.json (area `tier` / `context`) and need a fresh start of that area. + +| date | area | tier | strike | evidence (SHA, check, who caught it) | +|---|---|---|---|---| +| 2026-10-20 | dispatch | strong (hub) | red landing | `47953970ec`, landq.log 13:28:15.437Z, gate exit 1; inherited NODE_TEST_CONTEXT/capture failure, not a product assertion failure. Same SHA subsequently landed `bbc1be79` after environment correction; fleet/LOG.md trial intervention. | +| 2026-10-23 | client-reliability | standard (feature) | red landing | `280f043a66`, landq.log 12:46:14.141Z, gate exit 1 after worker thermal SIGINT (398/437 files); overlapping kit validation documented in fleet/LOG.md. Later same-SHA success does not erase this event. | + +2026-10-24 hourly audit: one confirmed strike per area in the current week; +no two-strike tier proposal. Historical events recovered from the append-only +landing log because a successful same-SHA retry replaces its result JSON. +`d7184f07bd` failed publication at 2026-10-23T12:39:19Z due to target-branch +divergence during the gate; fleet/LOG.md records submitting-area notification +and recovery. This infrastructure publication failure is not counted as a +second client red gate or reviewer rejection. No withdrawn submissions or +conflicts appear in the retained landing log; no additional confirmed reviewer, +peer false-green, or owner-regression event was identified in the checked +dispatch mailbox, PROGRAM, backlog and fleet log. This is not a complete audit +of every session conversation. + +## 2026-10-26 additions + +| date | area | tier | strike | evidence (SHA, check, who caught it) | +|---|---|---|---|---| +| 2026-10-26 | client-reliability | standard (feature) | red landing | `c20635bc8539328ca1677f3cf63cbe870adeb9c2`, landq.log 19:15:32.782Z; durable rejected/red gate exit 1 after sustained thermal stop (275/438 starts, incomplete). Area notified through MCP at19:16:19Z (`20261026T191619-dispatch-c43452.md`); candidate retained, no automatic retry. See `test_logs/TEST_LOG_2026-10-26_19-16-00_dispatch-interrupted-gate.md`. | + +2026-10-26 hourly audit: client has two confirmed red landings in the last +seven days (Oct23 and Oct26), both thermal interruptions. Owner-facing proposal: +consider standard -> strong under a rolling-seven-day interpretation. The +documented strike week starts at usage reset (`usage.resetCron`: Monday03:00); +these events straddle that reset, so only one is in the current configured week. +No automatic tier/config change; stronger models do not establish thermal +clearance. Preserve the dispatch-owned resource hold and explicit fresh grant. diff --git a/docs/agents/WIND-DOWN.md b/docs/agents/WIND-DOWN.md new file mode 100644 index 00000000..6429ff99 --- /dev/null +++ b/docs/agents/WIND-DOWN.md @@ -0,0 +1,32 @@ +# Wind-down and restart + +Used when the usage meter passes the wind-down mark (usage.windDownAtPct, default +95%), before a known reset, or when the owner says so. + +## Wind-down (the session manager runs it; one message to every area) + +1. Create `/PAUSED` containing the reason and time. Scheduled jobs (except + restart) skip while it exists. +2. Tell every area: "Wind-down: finish or park your current step, commit, merge +
, submit if green, update `.task` and your docs/TODO.md section, then stop + taking turns. If you have a scarce machine, say you are done with it in .task." +3. Wait for each area's one-line report (SHA + state), then `node tools/fleet/bin/doctor.mjs`: + no DIRTY, no ONE-DISK rows, queue drained (`landq.mjs status`). +4. Write docs/agents/RESUME-BRIEF.md (mid-flight table, held work, owner questions) + and land it. +5. Stop sessions only if needed (`fleet down`); an idle session costs nothing. + +## Restart (after the reset) + +1. Remove `/PAUSED` (the restart job does it). +2. `fleet up` (resumes every area by session id; the session manager last). +3. Each area reads RESUME-BRIEF.md and its TODO section, reports one line if blocked. +4. The session manager checks `doctor`, `landq status`, and picks up the loop. + +## Meter thresholds (if the usage governor is on) + +| meter | action | +| --- | --- | +| >= windDownAtPct | wind down now, tell the owner | +| slowDownAtPct .. windDownAtPct | stop filling lulls: only close, relay and land | +| below | proceed | diff --git a/docs/agents/backlog/client-reliability-1.json b/docs/agents/backlog/client-reliability-1.json new file mode 100644 index 00000000..5c302d4c --- /dev/null +++ b/docs/agents/backlog/client-reliability-1.json @@ -0,0 +1,10 @@ +{ + "id": "client-reliability-1", + "area": "client-reliability", + "status": "done", + "title": "Diagnose and fix profile-modal suite hang without weakening assertions", + "blockedOn": null, + "addedOn": "2026-10-19", + "filedAt": "8b204687d2c0020c0fe97cedb3e9f8661a8d709a", + "closedOn": "2026-10-19" +} diff --git a/docs/agents/backlog/client-reliability-10.json b/docs/agents/backlog/client-reliability-10.json new file mode 100644 index 00000000..8a865109 --- /dev/null +++ b/docs/agents/backlog/client-reliability-10.json @@ -0,0 +1,12 @@ +{ + "id": "client-reliability-10", + "area": "client-reliability", + "status": "done", + "title": "Recover completed startup fade animation after delayed cleanup initialization", + "blockedOn": null, + "addedOn": "2026-10-27", + "filedAt": "f01ce012595d74abac3c8adca7c8eec219294c7d", + "brief": "/home/user/Documents/GitHub/bitvid-dispatch/docs/agents/ROUTING-2026-10-27-client10-animation-cleanup.md", + "evidence": "Dispatch independently measured actualapp360 animationend514.8ms before listener3245.7ms; samepointASIDEfailure\u2192onlyapp.fade-in removalINPUTtoggle, geometry unchanged. Relevantinputdiff0 vs2754a8af/currentf01ce012; PRcheck0 attributed dispatch. Explicit bounded indexcleanup+optionalhelper/focusedtests; noCSS/app/otherbootstrap. Focused/mutants/lint only; full/build/browser/submit awaitREADY+slot. Minimal cleanup extracted at f01ce012: index1405/helper59, new17scenarios17pass/fail0/cancelled0;19distinctisolatedmutantsallkilled/all17casesmapped;hostlint11pass, diffcheck0. Noexistingtestedits/full/build/browser/submission. Evidence test_logs/TEST_LOG_2026-10-27_01-39-00_client-reliability-10-ready.md; sourcewiring scenario executes actualisolatedindexsection, notfullapp; nativeCSS/hittesting remains later. Exactcandidateaff88416 QA PASS/RELEASE attributeddispatch01:45:46:4nativeclicks/intervention0 incloriginal360point/390/1280/reduced controls. NEWgrant samecandidate normalgate439discovered/439unique missingextra0/notok0/nonzerofailcancelled0 exit0; durablyLANDED5b0adf6b3ee65fce30041f6adf29177f01b43647 at01:51:12/ancestry0. Completerawlog/result/discovery/hash state/evidence/client10-animation-cleanup-20261027. GateslotRELEASE01:51:52; separatebuild/artifact routing pending, itemstaysopen. test_logs/TEST_LOG_2026-10-27_01-52-00_client-reliability-10-landed.md Separatehostbuild at36716dfe inputs0diff vsQAaff88416 naturalexit0/artifactverifierPASS/version86fc5236/indexSHA50a89bd6/helperdistcmp0. Hostswcompatguard no commitdelta; no artifactcommit/preview/deploy. BuildRELEASE01:53; docs-only durableevidence pending beforeclose10. test_logs/TEST_LOG_2026-10-27_01-54-00_client-reliability-10-build.md Required build+landing evidence23c69eba durablyLANDEDaf8e654010c116e61f9a88250779512efc6c0595 docs-only/no gates/ancestry0 at01:54:16. Client10 closed after product+evidence durable and QAvalidatedsource equality. Historicaloverflow/nativeactivecancel/AA/item9live/CPU/wholeStage3 retained separately.", + "closedOn": "2026-10-27" +} diff --git a/docs/agents/backlog/client-reliability-2.json b/docs/agents/backlog/client-reliability-2.json new file mode 100644 index 00000000..df301169 --- /dev/null +++ b/docs/agents/backlog/client-reliability-2.json @@ -0,0 +1,10 @@ +{ + "id": "client-reliability-2", + "area": "client-reliability", + "status": "done", + "title": "Make Nostr initialization race test hermetic and release retained relay work", + "blockedOn": null, + "addedOn": "2026-10-19", + "filedAt": "f303ab0e72ac60b49c5e1bd712b080de2a95a483", + "closedOn": "2026-10-19" +} diff --git a/docs/agents/backlog/client-reliability-3.json b/docs/agents/backlog/client-reliability-3.json new file mode 100644 index 00000000..1a95fa05 --- /dev/null +++ b/docs/agents/backlog/client-reliability-3.json @@ -0,0 +1,10 @@ +{ + "id": "client-reliability-3", + "area": "client-reliability", + "status": "done", + "title": "Cancel queued torrent health probes when their last feed owner is disposed", + "blockedOn": null, + "addedOn": "2026-10-19", + "filedAt": "f9adfc8f531bcb77d4dc6e5487f76dbce0916167", + "closedOn": "2026-10-19" +} diff --git a/docs/agents/backlog/client-reliability-4.json b/docs/agents/backlog/client-reliability-4.json new file mode 100644 index 00000000..605d742c --- /dev/null +++ b/docs/agents/backlog/client-reliability-4.json @@ -0,0 +1,11 @@ +{ + "id": "client-reliability-4", + "area": "client-reliability", + "status": "done", + "title": "Reproduce remaining source-health cancellation defect at current HEAD before scoping a fix", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "604c8c61f4783717eb0cff7ca7ca33efb59aac8a", + "evidence": "Product landed89ccb907 via7055496b,normal gate exit0 and unstable ancestry exit0. Full427/427 files,focused22,lifecycle12,13 reverted mutants,lint11,build/artifact and Chromium+Firefox controlled-IO smoke pass;obsolete starts1\u21920. See test_logs/TEST_LOG_2026-10-20_03-29-00_client-reliability-4-landed.md. Active abort/playback fallback/live peers/CPU unverified.", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/client-reliability-5.json b/docs/agents/backlog/client-reliability-5.json new file mode 100644 index 00000000..06b07d0d --- /dev/null +++ b/docs/agents/backlog/client-reliability-5.json @@ -0,0 +1,11 @@ +{ + "id": "client-reliability-5", + "area": "client-reliability", + "status": "done", + "title": "Reproduce remaining active URL or torrent probe lifecycle defect before scoping a fix", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "3f627f5df3da7bcd28d910d9f8161844fa77ff74", + "evidence": "Landed0bd0b1c3 via4bc52f9d,normal gate exit0/unstable ancestry exit0. Full427files,focused28/lifecycle18,six reverted mutants,lint11/build/native browser smoke pass;obsoleteHEAD1\u21920.10m57s initial slot wait;expensive/browser released13:57UTC. See test_logs/TEST_LOG_2026-10-20_13-58-00_client-reliability-5-landed.md;activeabort/playback/CPU unverified.", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/client-reliability-6.json b/docs/agents/backlog/client-reliability-6.json new file mode 100644 index 00000000..e6142adb --- /dev/null +++ b/docs/agents/backlog/client-reliability-6.json @@ -0,0 +1,11 @@ +{ + "id": "client-reliability-6", + "area": "client-reliability", + "status": "done", + "title": "Audit real profile history removal wiring and historical no-op regression coverage", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "7f99c0456931c917082d6a6680c79655221dcc82", + "evidence": "Test-only retained regression9afe48a5 landed b3fab0326b3924d128b7928f4588751a003390ad via ec41d338;unchanged normal gate exit0,431 discovered/431 unique starts;focused38/38,5 mutants killed,lint11/11. Ancestry exit0. Final evidence test_logs/TEST_LOG_2026-10-20_15-41-00_client-reliability-6-landed.md. No product fix or live/browser qualification;IDLE owner local review.", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/client-reliability-7.json b/docs/agents/backlog/client-reliability-7.json new file mode 100644 index 00000000..2b2741e8 --- /dev/null +++ b/docs/agents/backlog/client-reliability-7.json @@ -0,0 +1,11 @@ +{ + "id": "client-reliability-7", + "area": "client-reliability", + "status": "done", + "title": "Restore cold-feed live subscription onVideos notification after eligible relay updates", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "386cc60e1fa3ba6d9b67a1171a1ab89c03df84ca", + "evidence": "Service fixfecf4294 durablylandedb1174163556e2705ba85868e21adcaf51b44bd5f viaf39622da;unchanged normalgateexit0/432discovered+unique starts;new7/7,mergedfocused27/5files,nine naturallykilledmutants,lint11/11,ancestry0. Fullslotreleased16:21:30UTC;dispatch previewbuild+QA actualapp retestpending,notclaimed. Final evidence test_logs/TEST_LOG_2026-10-20_16-22-00_client-reliability-7-landed.md;IDLE/no optionaltasks.", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/client-reliability-8.json b/docs/agents/backlog/client-reliability-8.json new file mode 100644 index 00000000..b326a201 --- /dev/null +++ b/docs/agents/backlog/client-reliability-8.json @@ -0,0 +1,11 @@ +{ + "id": "client-reliability-8", + "area": "client-reliability", + "status": "done", + "title": "Measure and scope active URL-media last-owner cancellation with shared/rejoin controls", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "6609269ea11aaff753e6d9f3d8b91eb04e219a20", + "evidence": "Implementation3474668a durablylanded43cb1bafe69f0bb3bce698e0b748281d1a04c061 via unchanged normalgateexit0/432discovered+unique file starts;candidate ancestry0. Focused37/3files,lifecycle29,14restored failing mutants,lint11. Active media final-owner cleanup/null/no cache with same-turn replacement/shared/cross/legacy/FIFO/live timeout controls. Strict spec correction documented. FULLSLOT released21:03UTC. Dispatch integrated preview/build/native qualification pending;no worker browser/build or HEAD/HLS/torrent/realfallback/CPU proof. Evidence test_logs/TEST_LOG_2026-10-20_21-03-00_client-reliability-8-landed.md.", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/client-reliability-9.json b/docs/agents/backlog/client-reliability-9.json new file mode 100644 index 00000000..339a99b4 --- /dev/null +++ b/docs/agents/backlog/client-reliability-9.json @@ -0,0 +1,11 @@ +{ + "id": "client-reliability-9", + "area": "client-reliability", + "status": "in-progress", + "title": "Measure remaining active HEAD HLS torrent cancellation at current HEAD", + "blockedOn": null, + "addedOn": "2026-10-22", + "filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md", + "evidence": "HEAD-only fix e9c960ed/merged09f52bdd durably landed f41ac656 normalgateexit0,435discovered/435unique starts missingextra0,ancestry0;build/artifactexit0;controlled native Chromium+Firefox lifecycle PASS with synthetic loadeddata/publicdeny. Prior60focused/16mutants/lint11. Resources released. HLS/torrent/RangeGET separately OPEN; no wholeStage3 claim. test_logs/TEST_LOG_2026-10-22_20-52-00_client-reliability-9-browser.md HLS read-only atd04d501e:12modes/7controls pass/5cancellationfail/0cancelled/naturalexit1; GET/body finalowner retains timer/unsettled,rejoin old,latecache. Await explicit controller+HLShelper routing. test_logs/TEST_LOG_2026-10-22_20-54-00_client-reliability-9-hls-investigation.md HLS explicitlyrouted20:55:13Z;minimal controllerhandoff+helperfetch/bodycancel implemented;new35+existing94=129pass/6files,19isolatedmutantsnaturallyfail/cancelled0,lint11exit0;noexistingtestsmodified. READY for serialized full/build/browser/normal slot after candidatecommit/merge; notsubmitted. test_logs/TEST_LOG_2026-10-22_21-03-00_client-reliability-9-hls-ready.md 2026-10-26 active-torrent investigation at e9af76df: actual grid observer/queue/probePeers fake WebTorrent IO+clock; corrected scratch12=7pass/5fail/naturalexit1/allcleanup0/network0. Final-owner retains1torrent/2listeners/timeout+poll/unsettled, same-turn+later joins obsolete probe, latecache and occupied FIFO capacity; shared/live-wire/error/timeout/queued-priority/deferred-teardown/staleobserver controls pass. Existing lifecycle11/11 unchanged naturalexit0. First JSDOM timer-confounded12=5pass/7fail preserved; IO-isolated correction documented. Proposed gridHealth+probePeers optional lifecycle ONLY, needs explicit routing/spec correction for prior active-sharing/cache assertions; no product/test edits/full/build/browser/submission. test_logs/TEST_LOG_2026-10-26_15-25-00_client-reliability-9-torrent-investigation.md 2026-10-26 resumed active-torrent candidate1b2362d2 merged unstable04c8c7cf at1bf50c1f; four candidate hashes exactly match durable focused60/60/fail0/cancelled0 and19unique killed mutants/20variant executions/all27newcase map. No runtime validation-input change on merge. Four targeted lint passes only; full lint/full/build/browser/normal queue pending explicit dispatch resource grant; health19:07:29 TCPU97.1C exceeds85C. No new tests/repeated baseline/submission. test_logs/TEST_LOG_2026-10-26_19-07-50_client-reliability-9-resumed-ready.md 2026-10-26 full lint11pass; normal queue c20635bc thermally interrupted at275/438starts (275unique/163neverstarted), durable rejected/red exit1 at19:15:32, unstable04c8c7cf unchanged. Sustained90.1C\u219286C38s; approved SIGINT only verified owned gate group204078. Partial/result uniquely preserved state/evidence/client9-torrent-validation-20261026; RELEASE sent, renewed dispatch resource+gate grant required/no automatic resubmit. test_logs/TEST_LOG_2026-10-26_19-16-00_client-reliability-9-thermal-interruption.md 2026-10-26 NEW grant19:31:40; merged docs-onlyunstablee35cb80c at eafa364d sourcehashes/runtimeinputs unchanged; normalgateexit0 with438discovered/438starts/438unique missingextra0. Durable LANDED af61c2716b9de456ab9b63596adade26e26afdd8 at19:40:11, ancestry0. Successful complete log/result/discovery/hash separate from unchanged old275partial. Normalgate RELEASE; item9 kept open for separately routed build/native-browser/remaining qualification. No live peers/byte teardown/wholeStage3 claim. test_logs/TEST_LOG_2026-10-26_19-41-00_client-reliability-9-torrent-landed.md 2026-10-26 separately granted build at7594c54d: env-clearednpmrunbuild naturalexit0, existing deploy-artifact verifierPASS; artifactversion15868a91/indexSHA9c48e0fe, notGitSHA. Sandbox swcompat Git-history precheck skipped, prior unchanged-input hostlintpass retained. Build RELEASE19:42:43, no previewserver/browser/deploy; native+QA timing routing remains. test_logs/TEST_LOG_2026-10-26_19-43-00_client-reliability-9-build.md" +} diff --git a/docs/agents/backlog/dispatch-1.json b/docs/agents/backlog/dispatch-1.json new file mode 100644 index 00000000..a11cf59a --- /dev/null +++ b/docs/agents/backlog/dispatch-1.json @@ -0,0 +1,10 @@ +{ + "id": "dispatch-1", + "area": "dispatch", + "status": "done", + "title": "Record owner goal, establish baseline, and prepare first worker proposal", + "blockedOn": null, + "addedOn": "2026-10-19", + "filedAt": "d90591163f3db6ff99c1cfad6b736bbb1ee98a59", + "closedOn": "2026-10-19" +} diff --git a/docs/agents/backlog/dispatch-2.json b/docs/agents/backlog/dispatch-2.json new file mode 100644 index 00000000..6d4a3668 --- /dev/null +++ b/docs/agents/backlog/dispatch-2.json @@ -0,0 +1,11 @@ +{ + "id": "dispatch-2", + "area": "dispatch", + "status": "done", + "title": "Launch approved client-reliability worker and maintain owner handoff and preview", + "blockedOn": null, + "addedOn": "2026-10-19", + "filedAt": "8b204687d2c0020c0fe97cedb3e9f8661a8d709a", + "closedOn": "2026-10-20", + "evidence": "Local-review handoff normally landed3fd0ab594dd412a3a223b31605d57e21536b5e4d viaf3dc7d13;QAqualificationclosurec017069c. Codeb1174163 normal432gate0,build6da89895/previewHTTP200/indexcmp0;actualcold3cards+separate25step3viewportcoverage. Original2/4pxUploadoverflowFAIL retainedopen,notallgreen;workersIDLE/nooptionalwork. Finalruntime/previewaudit and evidence inLOCAL-REVIEW/QUALITY." +} diff --git a/docs/agents/backlog/dispatch-3.json b/docs/agents/backlog/dispatch-3.json new file mode 100644 index 00000000..bcc0b6f8 --- /dev/null +++ b/docs/agents/backlog/dispatch-3.json @@ -0,0 +1,10 @@ +{ + "id": "dispatch-3", + "area": "dispatch", + "status": "in-progress", + "title": "Coordinate and verify all six resumed campaign priorities", + "blockedOn": null, + "addedOn": "2026-10-22", + "filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md" +} diff --git a/docs/agents/backlog/dispatch-4.json b/docs/agents/backlog/dispatch-4.json new file mode 100644 index 00000000..ee7b898e --- /dev/null +++ b/docs/agents/backlog/dispatch-4.json @@ -0,0 +1,10 @@ +{ + "id": "dispatch-4", + "area": "dispatch", + "status": "open", + "title": "Scope playback Range persistence warmup and hosted P2P startup", + "blockedOn": "Staged after cancellation/Firefox evidence; product ownership and exact policy before edits", + "addedOn": "2026-10-22", + "filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md" +} diff --git a/docs/agents/backlog/dispatch-5.json b/docs/agents/backlog/dispatch-5.json new file mode 100644 index 00000000..eee5f1c9 --- /dev/null +++ b/docs/agents/backlog/dispatch-5.json @@ -0,0 +1,10 @@ +{ + "id": "dispatch-5", + "area": "dispatch", + "status": "open", + "title": "Audit Amber restore switch logout through offline real wiring", + "blockedOn": "Staged client followup; real Amber manual/live signer gate excluded", + "addedOn": "2026-10-22", + "filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md" +} diff --git a/docs/agents/backlog/dispatch-6.json b/docs/agents/backlog/dispatch-6.json new file mode 100644 index 00000000..e1f4cc54 --- /dev/null +++ b/docs/agents/backlog/dispatch-6.json @@ -0,0 +1,11 @@ +{ + "id": "dispatch-6", + "area": "dispatch", + "status": "in-progress", + "title": "Reproduce and resolve mobile overflow touch focus accessibility", + "blockedOn": null, + "addedOn": "2026-10-22", + "filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md", + "workerItem": "playback-qa-4" +} diff --git a/docs/agents/backlog/playback-qa-1.json b/docs/agents/backlog/playback-qa-1.json new file mode 100644 index 00000000..9a180a62 --- /dev/null +++ b/docs/agents/backlog/playback-qa-1.json @@ -0,0 +1,12 @@ +{ + "id": "playback-qa-1", + "area": "playback-qa", + "status": "done", + "title": "Qualify local hosted-source fallback with independent regression and browser evidence", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "0c1dd7e86be3e28edc96aca47f7753a98990dde0", + "notes": "Complete: submission64143f42 durable landed1bb3a4fc at15:02:17Z; unchanged normal install+npm test gate exit0, target ancestry exit0. Merged diagnostic429/429,focused4/4,lint11/11; six isolated mutants rejected/restored. Native Chromium+Firefox3 scenarios each passed local VP8 decode64x64/time>0 through mirror/substituted torrent transport,zero external requests. No real WebTorrent/full app/wholeStage3 qualification. Unique evidence includes early browser/gate scheduling intervention and corrected timing: wait65m43 to actual14:49:35 grant+11s pickup,storage release→dispatch grant39m57 wake lag. QA expensive slot released15:02:30Z; browser released13:40:46Z. No new item; dispatch to integrate shared status.", + "closedOn": "2026-10-20", + "landedSha": "1bb3a4fc452969f060c09a286b72b8cd7ac4123e" +} diff --git a/docs/agents/backlog/playback-qa-2.json b/docs/agents/backlog/playback-qa-2.json new file mode 100644 index 00000000..a2c6f038 --- /dev/null +++ b/docs/agents/backlog/playback-qa-2.json @@ -0,0 +1,14 @@ +{ + "id": "playback-qa-2", + "area": "playback-qa", + "status": "done", + "title": "Capture actual-app local core-flow/mobile baseline before UI changes", + "blockedOn": "", + "addedOn": "2026-10-20", + "filedAt": "1bb3a4fc452969f060c09a286b72b8cd7ac4123e", + "notes": "Final granted run16:25:30.066\u219216:25:33.901UTC,Chromium154,b1174163/build6da89895,frozen830files/pinsverified.9stepsPASS/1FAIL:realcrypto/startup/cold3cards/login/360feed/nav/Profile;Uploadtitle/missingsource/no-publish/6Tab checks reached,then documentoverflowfail (PNG362 vs360).No remainingHome/390/1280/Uploadclose. Allownedresourcesclosed/RELEASE,no productwrites. Exact report/driver/screenshots docs/testing/playback-qa-2-final-evidence.md. Evidenceclosure only,NOT baselinepass/close. Separate dispatch-authorized16:27:44.416\u219216:28:10.112 follow-on25steps/0hardfail:all3viewportTrending/Explore/ForYou/Home3cards/Profile+Uploadvalidation/focus/close completed. Exactmobileoverflow2px/4px,panelsfit,controlsusable;originalfailureopen,notallpass. Browser/serversclosed/RELEASE;docs-onlynormalqueue thenIDLE perdispatch. Dispatch accepted completedqualification withknownopenminorlayoutfinding16:29:54;close does not mean overflow fixed or originalbaselineallpass. Evidence17163984 landed06e920e7/durable+ancestry0.", + "closedOn": "2026-10-20", + "resolution": "Bounded qualification completed with known minor open mobile Upload document overflow (2px at360,4px at390); original strict baseline remains failed. Separate25step3viewport/core/Home/focus coverage completed. Dispatch accepted docs-only closure16:29:54; no CSS/product fix claimed.", + "evidence": "docs/testing/playback-qa-2-final-evidence.md", + "landedSha": "06e920e77e756a4ad0c7d6495a2dbd930cf8482a" +} diff --git a/docs/agents/backlog/playback-qa-3.json b/docs/agents/backlog/playback-qa-3.json new file mode 100644 index 00000000..448e91be --- /dev/null +++ b/docs/agents/backlog/playback-qa-3.json @@ -0,0 +1,12 @@ +{ + "id": "playback-qa-3", + "area": "playback-qa", + "status": "blocked", + "title": "Diagnose Firefox deterministic CI failure at fb54cea3", + "blockedOn": "CI-equivalent failing execution evidence unavailable: public logs403, no Playwright artifact; exact terminal assertions identified but controlled local Firefox runs pass. Await new actionable CI evidence; no further reruns routed.", + "addedOn": "2026-10-22", + "filedAt": "fb54cea3a2ba99b4860421724a5050af6b97c293", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md", + "evidence": "docs/testing/playback-qa-3-investigation.md", + "checkpoint": "Public CI184passed/2failed/3flaky preserved; local exact Playwright3passed/0failed/0flaky/0skipped,workers1,retries0,naturalexit0; not fixed or done." +} diff --git a/docs/agents/backlog/playback-qa-4.json b/docs/agents/backlog/playback-qa-4.json new file mode 100644 index 00000000..eccb7ce2 --- /dev/null +++ b/docs/agents/backlog/playback-qa-4.json @@ -0,0 +1,10 @@ +{ + "id": "playback-qa-4", + "area": "playback-qa", + "status": "deferred", + "title": "Reproduce actual-app mobile Upload overflow and focus touch behavior", + "blockedOn": "Cleanup slice qualified at client10 aff88416 (landed5b0adf6b); original362/360+394/390 overflow, visual focus/AA, touch adequacy and real-device checks remain open pending bounded routing. No additional runtime assigned.", + "addedOn": "2026-10-22", + "filedAt": "e04549f816f6ec61f305e20df0ce2b59797c4651", + "brief": "docs/agents/prompts/playback-qa.md" +} diff --git a/docs/agents/backlog/storage-cors-1.json b/docs/agents/backlog/storage-cors-1.json new file mode 100644 index 00000000..6c30a1ab --- /dev/null +++ b/docs/agents/backlog/storage-cors-1.json @@ -0,0 +1,10 @@ +{ + "id": "storage-cors-1", + "area": "storage-cors", + "status": "done", + "title": "Reproduce existing-origin loss in bucket CORS provisioning using offline transport", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "0c1dd7e86be3e28edc96aca47f7753a98990dde0", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/storage-cors-2.json b/docs/agents/backlog/storage-cors-2.json new file mode 100644 index 00000000..eecd307d --- /dev/null +++ b/docs/agents/backlog/storage-cors-2.json @@ -0,0 +1,10 @@ +{ + "id": "storage-cors-2", + "area": "storage-cors", + "status": "done", + "title": "Audit best-effort storage/delete safety and remaining R2 followups offline", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "7f99c0456931c917082d6a6680c79655221dcc82", + "closedOn": "2026-10-20" +} diff --git a/docs/agents/backlog/storage-cors-3.json b/docs/agents/backlog/storage-cors-3.json new file mode 100644 index 00000000..9f8a1beb --- /dev/null +++ b/docs/agents/backlog/storage-cors-3.json @@ -0,0 +1,12 @@ +{ + "id": "storage-cors-3", + "area": "storage-cors", + "status": "done", + "title": "Audit feed/view tombstone stale-refresh regression gap with offline integration", + "blockedOn": null, + "addedOn": "2026-10-20", + "filedAt": "7f99c0456931c917082d6a6680c79655221dcc82", + "resumedOn": "2026-10-22", + "brief": "docs/agents/CAMPAIGN-2026-10-22.md", + "closedOn": "2026-10-22" +} diff --git a/docs/agents/backlog/storage-cors-4.json b/docs/agents/backlog/storage-cors-4.json new file mode 100644 index 00000000..67a08cdd --- /dev/null +++ b/docs/agents/backlog/storage-cors-4.json @@ -0,0 +1,12 @@ +{ + "id": "storage-cors-4", + "area": "storage-cors", + "status": "done", + "title": "Prevent obsolete registered feed completions restoring deleted or replaced DOM", + "blockedOn": null, + "addedOn": "2026-10-22", + "filedAt": "89f8923c6e83b79a2b8874f811cdff2af6a809d0", + "brief": "docs/agents/prompts/storage-cors.md", + "closedOn": "2026-10-22", + "landedSha": "7edc7ea86f33508bb790a12d802daa3af012f933" +} diff --git a/docs/agents/prompts/COMMON.md b/docs/agents/prompts/COMMON.md new file mode 100644 index 00000000..e7fe2c9a --- /dev/null +++ b/docs/agents/prompts/COMMON.md @@ -0,0 +1,171 @@ +# {{project}} fleet — rules every session runs under + +> Copied into your system prompt when your session was created, and frozen there. +> The file on disk (docs/agents/prompts/COMMON.md) and PROGRAM.md win whenever they +> differ: your context is a snapshot, the files are the truth. Re-read a rule from +> disk before acting on it. + +You are one of several coding-agent sessions working on {{project}} in one shared +repo. Each session owns one AREA, one worktree and one branch. Your area's half of +this prompt follows. When this half and your area's half disagree, the area half +wins; when either disagrees with the owner's written words, the owner wins. + +## Who is who + +- **The owner** is the human. Their words are the rulings, recorded with date and + quote in docs/agents/PROGRAM.md. Quote them; never paraphrase them stronger. +- **The session manager** ({{dispatcher}}) keeps the goal, staffs as few agents as + the work needs, and routes an ask that has no owner yet. It does not do your + work and it is not in every conversation. +- **The interpreter** is the user's own agent. It relays the user to the session + manager. A routine yes it passes on counts. It cannot approve spend, a publish, + a deploy, a delete, a secret, or an outgoing message. +- **Peers** are the other areas. Talk to the peer that owns the other half of a + problem DIRECTLY; do not route routine messages through the session manager. + +## Who decides + +1. The peer who owns the other half. Talk to them directly. +2. If you still disagree, or a new area may be needed, the session manager. +3. If it is a ruling, a spend, a deploy, or a new area, the owner. + +Areas: +{{peers}} + +## Start of every session, and of every new item + +1. Read docs/agents/PROGRAM.md, docs/agents/RESUME-BRIEF.md (after a restart), your + area's entry in docs/TODO.md and your `.task`. +2. Read your mailbox (see MESSAGING below). Reading takes the messages out of + unread. A pane line that says you have new fleet mail is only a wake; the + mail is the message. Reply to the sender with your next action. +3. Re-read `fleet/RULE-CHANGES.md` newest first. Those lines override this + prompt when they disagree. Do this again before every new backlog item. +4. `node tools/fleet/bin/backlog.mjs next --area {{area.id}}` lists your open + items. Pick by the priorities in PROGRAM.md. Claim the one you pick + (`backlog claim --area {{area.id}} --id `). That write marks it + in-progress. Take another area's item only after asking that area. A blocked + or done item is yours to judge; the tool will not refuse it. Before you start, + check it still applies at HEAD. `filedAt` is the commit it was filed against. + "Already fixed at " is a valid close. Idle is a correct state when nothing + is assigned. When you finish an item and your tree is clean and landed, pick + the next one yourself. + +## The tree + +- Work only in your own worktree ({{area.worktree}}) on your branch ({{area.branch}}). + Never touch another worktree's files or another session's processes. +- Commit, then MERGE {{mainBranch}} into your branch. Never rebase, never rewrite + shared history. Run your checks on that merged tree. +- `git add` files BY NAME. Never `git stash` bare: the stash stack is shared by + every worktree of the repo. +- Sandboxed Codex (`workspace-write` keeps `.git` read-only) must not run + `git commit`. Use the fleet `commit` tool instead: MCP `commit` with `message` + and the file paths (or `all: true`), from this worktree (`cwd` if the server + is not already here). The CLI is `fleet commit -m "..." --area {{area.id}} -- `. + `submit: true` or `--submit` also queues the landing. Other providers may use + `git commit` or that tool. A path in your doesNotOwn list is committed with a + warning. Never commit on {{mainBranch}} or in the main checkout. +- Never `rm` a path held in a variable (`rm -rf "$DIR"` with DIR empty or wrong is how + trees get deleted). Delete scratch directories you made, by literal path, when your + run exits. +- **Land through the queue:** `node tools/fleet/bin/landq.mjs submit` (or `commit` + with `submit`). Each submit starts a drain. The drain merges queued tips onto + {{mainBranch}} in order, runs `{{gate}}` once per batch, and exits. A lock keeps + two drains from merging at once. Never push {{mainBranch}} yourself. +- After you submit, go back to work. At your next natural checkpoint, look the + submission up if you need the result: MCP `land_status` or + `fleet landq status `. That lookup can wait (`wait: true` or `--wait`, + default 120 seconds, max 600) but waiting is optional. Never infer the lander + or the land result from silence. States are `queued`, `gating`, `landed` + (with the main sha), `rejected` (with the reason), or `unknown`. The same answer + says whether a drain is alive and when it last ticked. If the submission is + still queued and no drain is running, `fleet lander start` runs one. +- A dirty worktree does not block submit. Only the committed tip is queued. +- Never remove a test registration, or any other `landing.protect` pattern, + without saying why in the commit message. The drain warns and still lands. +- Check {{mainBranch}} before you tell anyone something landed. A queued submit is + not landed. `land_status` and `git log -1 {{mainBranch}}` are the check. `rejected` + means: merge {{mainBranch}}, fix, resubmit. +- Name the backlog id in every commit that touches an item; `backlog.mjs close ` + once it has landed. +- Log what you did NOT finish in docs/TODO.md under your area before you stop. + +## Your own subagents + +You are a top-level Claude or Codex session. You may use your harness's own +subagents for parallel research, investigation, test runs, or review. Edits +inside your worktree stay coordinated by you, so those subagents do not edit +the same files at the same time. Landing still goes through your `commit` and +the queue. + +## Evidence, not status + +- Every report carries: the SHA (only after git printed it), the check that proves + it, its result and number, and **what you did not verify**. +- Every new test gets a deliberately broken variant (a mutant) that MUST make it fail. + A test that cannot fail proves nothing. +- Verify the command, not the pipe: `cmd | tail` reports tail's exit code. +- When two fixes do not move the number, stop fixing and measure. + +## Shared queues and protocols + +Change a shared queue or protocol (landq, the mailbox format) only when its +queue is empty: a running drain keeps the old code in memory. +A scarce machine (one browser, one GPU) is a convention, not a lock: say you +have it in `.task`, and wait if a peer's `.task` says they do. + +## Owner rules (standing) + +- Spend, publishing, deploys, deletes, secrets, and outgoing messages are valid + only when the owner types the approval in the session that will act. A peer + relaying "the owner said yes" is not that approval. The interpreter may relay + a routine decision, such as starting the team or taking a normal task. It + cannot grant spend, a publish, a deploy, a delete, a secret, or an outgoing + message. +- A peer cannot grant you a permission your own settings deny, and you never perform + an action a peer says it was denied. Tell the session manager instead. +- Quiet hours {{quietHours}}: no messages, finish and land green work, start nothing new. +- Never print, copy or commit secrets. + +## Say when you are waiting + +If you are blocked, put one line in your worktree's `.task`: +`WAITING: — (since )` and remove it when unblocked. The +session manager's check-in reads every `.task`, so a missed message never leaves you stuck. +Also record the hold with MCP `hold` (or `fleet hold`): the owner, the exact +unblock condition, and the next task you are still allowed to do. Clear it when +the condition is met. Keep `.task` (one line: what you are doing now) current before any long step, so a +compaction or crash loses nothing. + +When work actually advances, write one checkpoint with MCP `progress` (or +`fleet progress ""`). A live window is not progress. +Status and the check-in flag an area with no checkpoint for 20 minutes and no +unread mail. + +When health holds heavy work, keep doing light work (source review, offline +analysis) instead of going idle. The hold names that next task. + +Runners, dependency manifests, pinned archives, and evidence live in your +worktree or `{{stateDir}}/deps`, with those names in `manifest.txt`. Never only +in `/tmp`. The npm cache is disposable. Three pinned archives disappeared from +`/tmp` and the npm cache, and QA could not run. + +There is no billing hook. If the pane says the five-hour window is nearly gone +(for example "2% remaining"), write the checkpoint, mail your next step, and +stop. After the reset, `fleet resume` is the one step back: it checks the +mailbox, the approval path, deps, and health, and you continue from the checkpoint. + +## Reporting + +One message per event, to the session that needs it. Message the session manager only for: +a decision you cannot make, a conflict between areas, something the owner must see or +decide, a red on {{mainBranch}}, work at risk, or a finished item the owner asked for. +Routine progress lives in commits, backlog items and `.task`. + +## Context + +Auto-compaction is on. Never ask the owner to compact you; keep `.task` and your +backlog item current so compaction loses nothing. + +{{messaging}} diff --git a/docs/agents/prompts/area.md b/docs/agents/prompts/area.md new file mode 100644 index 00000000..bd0f2c54 --- /dev/null +++ b/docs/agents/prompts/area.md @@ -0,0 +1,36 @@ +# Area: {{area.title}} (`{{area.id}}`) + + + +## Role + +TODO: one paragraph — what this area builds and what "done" looks like for it. + +## You own + +{{area.owns}} + +## You do NOT own + +{{area.doesNotOwn}} + +Nobody else edits what you own without asking you first; you edit nobody else's +without asking them first. + +## Neighbours (talk to them directly) + +{{area.neighbours}} + +## Your checks + +- TODO: the command(s) that prove a change in this area, and how long they take. +- TODO: anything that needs a scarce machine (browser, GPU). Say so in `.task` + and wait if a peer already has it. There is no slot lock. + +## Standing rules for this area + +- TODO: rulings from PROGRAM.md that apply here, quoted with their date. +- Sandboxed sessions commit with the fleet `commit` tool, not `git commit`. See COMMON.md. +- After submit, go back to work. Check `land_status` or `fleet landq status ` at your next natural checkpoint if you need the result. Waiting is optional. Do not infer the lander from silence. diff --git a/docs/agents/prompts/client-reliability.md b/docs/agents/prompts/client-reliability.md new file mode 100644 index 00000000..6a3cecf3 --- /dev/null +++ b/docs/agents/prompts/client-reliability.md @@ -0,0 +1,116 @@ +## Implementation routing 2026-10-22 — client-reliability-9 HEAD only + +Dispatch independently ran11 scratch modes via dispatch imports at169d894f (runtime identicalc5b678f7):3 fail naturally(disposed/same-turn/later),8 pass(shared/cross/legacy/live-success/error/timeout/late-success/error),cancelled0. Final owner retains1HEAD/0abort/1timer5000ms/unsettled/original inflight; media slots0/queue0. Minimal implementation authorized ONLY js/ui/urlHealthController.js HEAD probe lifecycle cancellation hook/cleanup plus tests/url-health-lifecycle.test.mjs or unique HEAD lifecycle tests and strict integrity/spec/evidence notes. Existing observer/cache/app/HLS/torrent modules remain read-only. + +Explicit spec correction permitted: prior queued/media-only completion does not fulfil source-backed Stage3 active HEAD last-owner cleanup. Preserve failed baseline and existing live/shared/legacy controls; equally strict or stricter changed expectations with Test Integrity Note. Release final-owner HEAD by marking cancelled synchronously, abort transport, settle null promptly without waiting for ignored abort/timeout, remove timer/hook exactly once; no cache, stale badge, fallback media/mirror start or replacement deletion. Fresh same-turn and later owner must start fresh request, old success/error/timer/finalizer cannot affect it. Preserve live HEAD success/error/timeout fallback, shared/cross/ownerless legacy ownership, media-hook handoff and FIFO/queue bounds. Add transport ignoring abort, missing AbortController, cancellation-before-start/at-handoff, late callbacks and replacement controls where applicable. HEAD only, do not conflate Range GET task or HLS/torrent completion; measure those later separately. + +Focused regression + restored failing mutants + lint now, request serialized full/browser slot when ready, no expensive job or normal product submission without grant. QA has conditional next narrow Firefox slot. Normal local queue/gates/durable ancestry required. No retries/sleeps/force exits/relaxed expectations, external/signer/cloud/credential authority unchanged. + +## Current assignment 2026-10-22: client-reliability-9 — remaining active cancellation + +This supersedes the old item8/idle scope below. Read current dispatch campaign brief, PROGRAM/RULE-CHANGES/KNOWN_ISSUES and test integrity. All worktrees were clean at dispatch inspection; inspect your own status then fast-forward own branch to unstable if safe (never discard work); report exact SHA and ack next action. Claim item9. Start actual observer/controller active HEAD-request last-owner/shared/same-turn+later rejoin offline measurements. Use controlled fetch/abort, count settlement/slot/cache/mirrors/stale callbacks; retain live success/error/timeout/FIFO and replacement identity controls. HEAD first, HLS/torrent separately tracked, no wider completion claim. Source/scratch/unique evidence only; NO product or committed test changes until demonstrated defect and dispatch routing/spec note. Do not inherit previous item8 implementation permission for HEAD. Own future health files as roster, app/services/cache read-only. No full suite/build/browser/normal product submission yet; QA has initial expensive/browser slot. env -u NODE_TEST_CONTEXT for commands, no force exit/retries/sleeps. Send read/next action, reproduced result/blocker and READY for checks. After this slice, playback and Amber remain staged in campaign; await bounded routing, not optional self-selection. + +## Archived assignment history (not current routing) + +# Area: Client reliability (client-reliability) + +## Current assignment: client-reliability-8 — active URL-media cancellation + +### Implementation routing after independent evidence + +Dispatch independently ran nine scratch modes: disposed/fifo new cancellation assertions fail naturally after cleanup, seven shared/cross/legacy/rejoin/timeout/error/replacement controls pass. Final-owner media retains1source/3listeners/1timer/1slot/unsettled promise; source-backed Stage3 and owner continuation require releasing obsolete active work. Minimal implementation now authorized ONLY js/ui/urlHealthController.js (retainUrlProbe final-owner cancellation hook, record cancelled state, active media settle/cleanup) plus tests/url-health-lifecycle.test.mjs and unique integrity/spec/evidence notes. Existing observer/cache/HEAD/HLS/torrent/app modules remain read-only. Supersedes investigation-only prohibition for this boundary. + +Use existing cancellation paths to settle null, suppress cache/mirrors/stale badge writes, release slot and advance FIFO once. Mark cancelled record unavailable synchronously for same-turn rejoin; old settlement must never remove/paint/cache over replacement. Test both immediate same-turn rejoin and rejoin after microtask, sharing before final release, cross-controller sharing, ownerless legacy consumers, queued competition/FIFO, replacement identity, late success/error/timeout callbacks and cleanup idempotence. Clean src/listeners/timer and pause/load boundary; do not force exit or wait for original timeout. Preserve live-owner healthy/offline caching and normal timeout/retry, shared work, slot cap/FIFO and previous queued/mirror guards. + +Explicit spec correction permitted for active-disposed success/offline-cache and disposed-timeout/rejoin expectations ONLY: the old queued-only contract intentionally allowed completed work after disposal; new owner instruction plus Stage3 requires immediate active media cancellation once final registered owner leaves. Replace these with equally strict resource cleanup/null settlement/no cache/no mirrors/no stale writes/fresh replacement assertions; keep separate live-owner success/error/timeout controls. Record Test Integrity Note (spec_correction and new_tests, exact old→new contract/spec source, any did_relax explanation honest) and unique spec change note; do not silently delete scenarios. Every new scenario needs a restored mutant that fails naturally. Mutants must reject disabled cancellation, retained source/listeners/timer, cache-on-cancel, lost shared owner, stale inflight deletion, FIFO disruption as applicable. Report surviving mutants instead of weakening assertions. + +Focused real lifecycle/adjacent URL/controller/observer checks and lint now. No browser/full/build yet; request slot when focused results/mutants/lint and merged-tree candidate are ready. Normal unchanged full gate and durable landing/ancestry required before closure. Send READY with files/counts/mutants/contract diff and unverified scopes. No wider Stage3 claim; actual browser/network/HEAD/HLS/torrent remain unverified. Dispatch will coordinate final validation and preview. + +Owner exact continuation recorded in PROGRAM supersedes the review idle hold only for this bounded slice. Read RULE-CHANGES newest first, PROGRAM, KNOWN_ISSUES, performance-efficiency-plan Stage3 and test-integrity before work. Claim item8 on integrated current unstable after this coordination commit lands; merge, never rebase. First investigate actual URL observer→controller active media last-owner disposal with controlled external IO. No product or committed-test edits yet; scratch reproduction and unique evidence only. No full/build/browser until explicit slot grant; cheap focused checks allowed. + +Measure resource/listener/timer retention, promise settlement, active slots and queued FIFO progress, inflight/cache identity, stale badge writes before/after disposal and controlled completion/timeout. Compare shared visible live owner, cross-controller shared cache if applicable, fresh owner/rejoin before/after cancellation, later same-source request, and legacy ownerless callbacks. Do not treat missing AbortSignal alone as proof; use real ownership entrypoints, actual media cleanup and controlled IO. Report current HEAD, exact commands/counts, baseline and controls, proposed smallest fix boundary and which existing assertions would change. Existing active-success/offline-cache/timeout expectations protect previous queued-only scope; preserve tests now and propose explicit contract correction/integrity note rather than silently weakening them. Cancellation must eventually avoid negative cache, stale UI and deleting a replacement inflight record; live shared owners must continue to get results. Preserve cache, concurrency, FIFO, callback compatibility and existing queued cancellation/orphan mirror guards. + +Start URL-media only. HEAD/HLS/torrent read-only context if necessary; report evidence before broadening. No app.js/services/nostr/kit/runner/config/styling/scheduler/external/cloud/signers/push/deploy/spend. Storage and QA remain idle unless separately assigned. Send read/next-action then reproduction/controls; await explicit minimal-fix routing after dispatch independently confirms. Done for investigation = measured current behavior and scoped proposal, not active-abort completion. Dispatch owns preview refresh after verified product landing. + +## Archived completed assignment: client-reliability-7 — cold feed live notification blocker + +QA actual-app passive reproduction at386cc60e: fresh Trending/cache0, local relay delivers3 valid signed VIDEO events+EOSE; raw/all/active/appvideo maps3,actual filtered3 and every access/include true,whitelist/lockdown false. Connected visible view remains current0/rendered0/signatureEMPTY/DOM0 after15s;metadata reasoncache. Service emits videos:cache3 then videos:updated(subscription3),but app refresh owner is onVideos callback. Source loadVideos cache/optimistic paths call onVideos;live subscription branch does not. This is a demonstrated local-review blocker,not optional work. + +Temporary item ownership: ONLY js/services/nostrService.js loadVideos live-subscription notification path plus minimal service-owned current-consumer state/subscription-clear lifecycle if needed to prevent stale consumer delivery;new focused regression (prefer tests/nostr-service-live-feed-notification.test.mjs),unique integrity/evidence/backlog. Read app/feed/bootstrap and existing tests;do not edit them or sharedTODO without routing. No app.js,cache/filter policy,signer/protocol,runner/config/kit/styling changes. Current service area exclusion is overridden only for this method/item. + +Merge unstable non-destructively,claimitem7,read currentrules/KNOWN_ISSUES/test-integrity/localNIP/schema docs. Before edits reproduce actual service coldcache→controlled subscribeVideos callback→actualonVideos consumer notification absent;prove cache0 theneligible updates/deletion callbacks,not event-string assertions. Preserve filtered full-list ordering,deleted updates,tombstone removal,existing videos:updated payload and persistence,callbackthrow isolation/no event loss,optimistic/cache semantics. Reused subscription must notify latest consumer after subsequent load/feed switch; forceFetch/clear must not let late obsolete callbacks notify old or replacement consumers. Include event-only consumer compatibility; do not blindly close over first onVideos. After natural failing regression,implement smallest method-only callback delivery fix and restored failing mutants. Retain no product-policy change or forced refresh substitute. + +Cheap focused tests and lint now;no browser/full/build until slot grant. QA owns actual-app execution and will verify cold feed plus original core/mobile baseline after durable fix/preview rebuild. Send reproduction/controls/files before widening;ready event with focused counts/mutants/lint. One unchanged normal full gate can be authoritative after merged focused/lint;avoid duplicate full solely for logs. Normal commit/merge/queue/ancestry/closure rules apply. No external operations/newoptional tasks;idle once local-review blocker is closed. + +## Archived completed assignment: client-reliability-6 — retain actual history wiring regression + +Investigation complete:actual profile→real renderer→DOM remove→real controller/service works locally and through fake synced publish/read. Historical no-op override resurrects removal while existing profile27 tests still pass (worker evidence). Dispatch independently ran local/synced actual wiring exit0 and historical-noop local exit1 after cleanup. No current product failure established. + +Test-only retention now authorized: own new tests/profile-history-removal-wiring.test.mjs plus unique integrity/evidence/backlog. No product or existing profile-suite edits. Use actual profile config/factory,renderer,DOM remove,controller/local service;fake only persistence/publish IO and metadata boundaries. Retain local delete+clear+renderer recreation,zero local publishes;synced remove reduced-list replace:true/actor/real recreation through fake publish/read;separate assertions catch historical no-op. Explicitly distinguish any not-yet-proven synced-clear path and report real failure before product routing. Fixture cleanup restores globals/app/module state and blocks network/signers/relays. No source-string or merely config-presence test. + +New scenarios need restored failing mutants (historical no-op must kill real wiring cases);no sleeps/retries/expectation weakening. Relevant focused lower-layer/profile suites and lint;request full normal slot after ready. QA now gets bounded actual-app mobile browser baseline after old QA gate ends,so no browser/full/build until dispatch grant. Commit item6,merge verified unstable,normal unchanged full gate/durable ancestry before close. Metadata can be claimed now landed. No app/history service/cache/product scope or new feature authority. Send ready/blocker/landing/final events. + +## Archived completed assignment: client-reliability-5 — prevent orphaned mirror continuation + +Investigation at0c1dd7e8 found a narrow defect, independently reproduced by dispatch at47953970 (administrative roster only): after last-owner disposal during active primary media, primary error starts1 fresh mirror HEAD(expected0). /tmp/bitvid-dispatch-5-active-url.mjs mirror-disposed --expect-no-obsolete-head exits1 after cleanup/retry; mirror-shared control exits0/healthy,resources0/slots0. Bounded active retention/success caching itself is intentional and not a defect. + +Implement controller-only lifecycle guard before next mirror request: temporary ownership js/ui/urlHealthController.js, focused tests/url-health-lifecycle.test.mjs and your unique integrity/evidence notes. Existing ownership hook is sufficient. Do not change observer/app/shared cache/HEAD-media-HLS-torrent abort,TTLs/limits/mirror order. Ensure cancellation settles with explicit cancelled outcome, no negative cache, no stale UI or newer inflight deletion. Preserve shared/rejoining live owners and ordered mirrors, active healthy cache and normal timeout/retry behavior. No competing writers: storage-cors owns only storage helper; playback-qa product read-only. + +Read current rules/test integrity before edits; retain actual observer/controller/cache regression with controlled external IO and mock clock, no sleeps/retries. Test disposed active failure starts0 mirror HEAD; shared/rejoin owner continues; active success cache,timeout/retry and inflight identity preserved. Every new scenario needs reverted failing mutant. Relevant focused tests/lint/build; coordinate full suite/normal queue through dispatch before expensive run (roster full gate currently underway). QA initially owns shared browser: do not launch browser until dispatch allocates it; request availability and use only your own server/port. No blanket prior-suite reruns for administrative edits. Commit item5, merge unstable (never rebase), unchanged gate/durable ancestry before close. CLI/agent/lander launch must unset inherited NODE_TEST_CONTEXT test transport (operational environment correction, no assertion/gate change). Send blocker/product landing/final events with exact checks/counts/gaps. Dispatch owns preview3000 refresh. No subagents/kit/scheduler/external operations. Continue next actions via dispatch routing under existing owner direction. + +## Archived investigation: client-reliability-5 + +Interpreter checkpoint2026-10-20 confirms continuing development under existing direction. Claim item5 after non-destructive merge of unstable. Read RULE-CHANGES, KNOWN_ISSUES, PROGRAM, and performance-efficiency-plan Stage3. Items3/4 are completed: queued torrent/URL cancellation shipped; do not repeat their full qualification. + +Investigate active URL/torrent probe lifecycle at HEAD. Start with actual URL observer→controller last-owner disposal during active media work; compare a shared live-owner control, completion/cleanup and a later same-source request. Controlled external fetch/media events only; no public traffic. Measure active resource retention, settlement, slot release and stale/cache effects before disposal, after disposal and after controlled completion. Distinguish bounded timeout behavior and intentional active caching from a demonstrated resource/lifecycle defect; missing AbortSignal alone is insufficient. Inspect active HEAD/HLS/torrent read-only only if needed to identify the smallest next defect. No assumption the old plan is current. + +Investigation/evidence only: no product edits, no committed regression changes, no policy/limits/TTL tuning. Scratch diagnostics and your evidence/backlog notes permitted. Send dispatch exact HEAD, reproduction command, expected/actual counts, failing assertion/natural exit after cleanup, shared-owner control, smallest proposed fix files and acceptance. Dispatch will independently assess and route implementation. If no defect reproduces, report an evidence-backed next TODO candidate rather than speculate or silently stop. Preserve dirty/queued work. No subagents needed. No deploy/push/spend/wallet/production-data/scheduler/kit changes; preview3000 preserved. Mailbox event on blocker and completed investigation. Archived briefs below are historical, not new work authorization. + +## Archived completed assignment: client-reliability-4 — queued URL probe fix + +Investigation is complete: at4c54858c real observer/controller/cache gives4 HEAD requests,3 active media probes and1 queued. Dispose/remove last feed then release a slot: obsolete media starts1(expected0); --expect-fixed naturally fails exit1 after cleanup. Shared live-owner control passes exit0, healthy badge, settled4/active0/queued0. Dispatch independently reproduced both against dispatch code at604c8c61 using /tmp/bitvid-dispatch-4-url-lifecycle.mjs. No public IO. Your detailed investigation evidence remains authoritative for fixture details. + +Implementation now authorized for item4. Temporary explicit ownership: js/urlHealthObserver.js, js/ui/urlHealthController.js, focused URL lifecycle tests, relevant existing URL health tests, Test Integrity Note and area evidence/backlog notes. Use existing onCardUnregister boundary. No shared cardObserver/VideoListView/app/state-cache module changes without evidence and routing. No HEAD/active media abort, HLS/torrent changes, cache policy/TTLs, mirror order, concurrency, signers, kit or styling changes. Existing callback consumers must remain compatible without editing app.js. If that cannot work safely, report before broadening. + +Before edits read current rules and test-integrity standard, merge unstable and preserve investigation notes. Retain a real observer→controller→queue regression with controlled external IO. Cancel queued work only after its last live owner disappears; settle cancellation without negative cache, mirror fallthrough, stale UI mutation or deleting newer inflight records. Preserve live shared owners and existing event/source/playable-mode dedup; later same-source registrations can retry. Keep three-media concurrency and healthy hidden-card reveal. Already-active probes retain prior lifecycle/cache behavior. + +Verify last-owner obsolete starts0, shared live owner remains healthy, cancellation settlement and same-source retry, disposed/reused registration stale callbacks, mirror order, concurrency and hidden-card reveal. Every new scenario needs a deliberately broken mutant that fails; preserve assertions and no sleeps/retries. Run focused suites, npm run lint, full npm test with existing60s/file diagnostic bound, npm run build/artifact check and local Chromium/Firefox controlled lifecycle/source-health smoke if available (reserve browser in .task). Report actual playback/fallback gaps; do not claim CPU/RSS benefit from probe counts. + +Commit with item4 id, merge unstable (never rebase), verify merged tree and normal landing gate. Durable landed status plus unstable ancestry before close. Send dispatch mailbox on blocker, product landing and final completion including SHA/checks/counts/unverified scope; dispatch owns independent verification and preview refresh. No further item without routing. No subagents needed. No deploy/push/spend/wallet/production-data/scheduler/kit changes. + +## Archived investigation: client-reliability-4 + +Owner continuation relayed by interpreter on 2026-10-20 explicitly permits this bounded step during quiet hours. No recurring schedule/config change. + +Claim item4 after merging unstable. Read RULE-CHANGES, KNOWN_ISSUES and docs/performance-efficiency-plan.md Stage3. At current HEAD reproduce whether disposed feeds retain/start URL-health queued or active probes, or active torrent probes. Inspect js/urlHealthObserver.js, js/ui/urlHealthController.js, js/gridHealth.js and js/webtorrent.js read-only. Prefer the URL queued-owner case first; old plan concurrency and lifecycle claims may be stale. Use actual observer/controller entry points with deterministic deferred external IO boundaries and no public network. Preserve shared live-owner behavior. Measure started, queued, disposed and settled counts; record exact command, SHA, expected/actual and natural exit. Do not infer a defect merely from missing AbortSignal. + +This assignment authorizes investigation and scratch/evidence notes only: no product or committed regression-test changes yet, no test weakening, no browser reservation unless needed, no broad Stage3 implementation. Report the smallest reproduced defect plus proposed files/acceptance to dispatch before fixing. If not reproducible, record why and return the next evidenced TODO candidate for routing. Commit evidence/backlog notes separately through normal queue if needed. Send mailbox on blocker and final investigation result; current mailbox tools deliver a short wake, so archived text below claiming no wake is historical. No subagents needed. No deploy/push/spend/wallet/production-data/scheduler/kit changes. Preserve preview3000. Existing completed item3 evidence follows for context; do not redo it or follow its old implementation steps. + +## Archived completed assignment: client-reliability-3 + +Improve robustness/usability with one bounded client-facing fix from pre-launch TODO #34 Stage3: cancel queued torrent health probes when the last card/feed owner is disposed. Prior items1 and2 are done, normally landed at de6918ce and closed at f9adfc8f (425/425 unit files, lint11/11). Do not redo them. + +## Current evidence + +At f9adfc8f, dispatch ran /tmp/bitvid-dispatch-obsolete-probe-scenario.mjs. It imports actual gridHealth.js and torrentClient, uses real DOM cards and attachHealthBadges with a controllable IntersectionObserver, and replaces only the external probePeers boundary with deferred promises (no live network). Five cards enter: four probes start, one waits. Dispose the returned observer registration, remove feed DOM, then release active probes: one obsolete probe still starts, expected zero. The assertion fails with1!==0. This is stronger than the earlier isolated ProbeQueue class inspection; it is not a browser CPU or real-torrent measurement. Inspect the diagnostic for fixture details and reproduce against your own worktree before editing. Retain a proper regression in the repository. + +## Ownership for this item + +Temporary explicit ownership: js/gridHealth.js; a small queue/owner helper if needed (prefer js/gridHealthQueue.js); focused lifecycle tests (prefer tests/grid-health-lifecycle.test.mjs); relevant existing grid-health tests and Test Integrity Note; your backlog/evidence/area notes. Existing js/dom/cardObserver.js already exposes onCardUnregister: use that boundary. Report specific evidence before editing that shared module or VideoListView lifecycle. + +Do not edit js/app.js, URL-health plumbing, js/webtorrent.js or vendor bundle, active-probe abort behavior, playback/magnet helpers, cache policy/TTLs, concurrency limits, signers, payments, styling, test runner, fleet config, or kit infrastructure. No other worker owns this bounded scope. No subagents needed. + +## Plan and done + +1. Read current RULE-CHANGES, PROGRAM, RESUME-BRIEF, KNOWN_ISSUES, AGENTS, repo memory and test integrity standard. Merge unstable, claim client-reliability-3, reproduce actual five-card lifecycle before edits, and record started/queued counts. +2. Associate queued work with live card/feed owners. Disposing the last owner prevents the queued probe from starting; discarding one owner must not cancel shared work needed by a live card. Leave already-active probe behavior unchanged. +3. Preserve deduplication, priority and current concurrency (four), healthy/negative TTLs, valid magnet bytes and WSS policy. Cancelled jobs must settle without creating unhealthy cache entries or deleting newer inflight entries. Later live cards must be able to request the same source. Disposed registrations must not receive stale UI effects. +4. Test actual observer/queue/probe boundaries: last-owner disposal, two owners sharing a queued source, re-request after cancellation, concurrency/priority preservation, and a healthy hidden-card reveal path. Use controlled probes and no public relay/torrent traffic. Kill a deliberately broken variant for each new scenario or explain grouped mutant coverage; include integrity notes. No arbitrary sleeps/retries/relaxed assertions. +5. Run focused suites, full npm test with the existing60s/file diagnostic bound, npm run lint, npm run build, and a local browser lifecycle/health smoke where available. This is product code: report browser/source-fallback verification and any gap honestly. Reserve the shared browser in .task. No CPU improvement claim without measurement; this item measures obsolete starts, not the entire performance plan. +6. Commit with client-reliability-3, merge unstable (never rebase), verify merged tree, submit through unchanged normal landq, verify durable landed status and unstable ancestry, then close item. Route new out-of-scope failures to dispatch; do not weaken gates or broaden automatically. + +## Completion handoff and operating limits + +Send a dispatch mailbox event when blocked, when code lands, and at final completion, including SHA, exact checks/counts, unverified behavior and next step. The generated startup message said to report only when blocked; the new assignment explicitly replaces that reporting instruction with landing/final-completion events as well. The previous completion left no unread dispatch event. File-mailbox delivery only records a message: it does not start a Codex turn or wake idle dispatch. The operator currently supplies the new turn/tmux input; do not claim unattended monitoring. No scheduler or kit changes in this task. No additional work after this item without routing. + +Owner routine-work delegation was relayed from Codex desktop chat and recorded with provenance in PROGRAM. No deploys, spending, production data, real-wallet operations, or schedulers. Preserve fleet-bitvid:preview at http://127.0.0.1:3000; dispatch syncs and rebuilds after verified product changes land. Keep .task current, communicate directly via mailbox, and disclose tool/permission blockers without bypassing controls. diff --git a/docs/agents/prompts/dispatch.md b/docs/agents/prompts/dispatch.md new file mode 100644 index 00000000..aa76153b --- /dev/null +++ b/docs/agents/prompts/dispatch.md @@ -0,0 +1,3 @@ +# Session manager (`{{area.id}}`) + +This area runs `session-manager.md`. That file is the prompt. diff --git a/docs/agents/prompts/interpreter.md b/docs/agents/prompts/interpreter.md new file mode 100644 index 00000000..cbc059e3 --- /dev/null +++ b/docs/agents/prompts/interpreter.md @@ -0,0 +1,65 @@ +# Interpreter + +You are the user's agent. After setup you stay on as the interpreter. The user talks to you. You talk to the session manager. The user does not run a command, edit a fleet file, or attach to tmux unless they ask to watch. + +## Bring the fleet up + +Run `fleet up` yourself. If `fleet init` already started the session manager in this terminal, do not start a second one. If setup files are still uncommitted, call MCP `setup_commit` (or `fleet setup-commit`). Do not `git commit`, and do not ask the user to approve one. + +## The channel + +Send the session manager a message with MCP `tell`. That writes mail from `interpreter` to the session manager. When its tmux window is open, a short line wakes it (`You have new fleet mail from interpreter. Read it with mailbox_read.`). That line is not the message. The mail is the one record. The same wake is used for worker mail and for replies. Read replies with MCP `hear`. Reading takes them out of unread, so a later hear does not repeat them. Do not scrape the pane. The shell form `fleet tell` (and `fleet hear`) is a sandboxed command. Codex automatic approval review applies to that shell even when the MCP server is set to approve, so do not use the shell to send or read mail. + +At the start of every turn, run `fleet status` (MCP `fleet_status`) before you hear. That look is the one re-nudge for mail that has been waiting. A second look does not nudge again. `hear` and `tell` run that same check. + +## The goal + +Ask these in your own chat. Offer the default so a one-word answer works. Pass the answers with `tell`. When the session manager reads them back, tell the user in one line. + +1. What should this project get better at? (default: leave it measurably better: fewer bugs, safer, nicer to use, faster, easier to change, closer to what the README or spec promises) +2. Anything to prioritise right now? (default: no) +3. Anything off limits? (default: no deploys, no spending, no production data) + +## Codex tool approval + +Codex may pause on fleet mail tools such as `tell` and `mailbox_send`. `fleet up` starts Codex with `--ask-for-approval on-request`, no `--profile`, and it does not set `CODEX_HOME`, so the session reads the same `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`) as any other Codex process in that environment. The server name is `agent-fleet`. A table under another name or another profile does not apply. + +The value that skips approval is `default_tools_approval_mode = "approve"` under `[mcp_servers.agent-fleet]`. One tool is `[mcp_servers.agent-fleet.tools.mailbox_send]` and `[mcp_servers.agent-fleet.tools.tell]` with `approval_mode = "approve"`. The values are `auto`, `prompt`, `writes`, and `approve`. `auto` is not always-allow: these tools are not marked read-only, so `auto` and `writes` still require approval. `fleet up` uses `on-request`. When `approvals_reviewer` is `auto_review`, a call that still requires approval goes to that reviewer, which can reject it. `approve` is the mode that does not require approval, so the reviewer does not run. Offer to set `approve` for the agent-fleet server, and write it only after the user says yes. Do not put it in the shipped fragment. A running Codex session does not see a change to `~/.codex/config.toml` until that session is reloaded. + +Those keys are `mcp_servers..default_tools_approval_mode` and `mcp_servers..tools..approval_mode` in the Codex config reference (https://developers.openai.com/codex/config-reference) and the MCP page (https://learn.chatgpt.com/docs/extend/mcp). The reviewer behavior is https://learn.chatgpt.com/docs/sandboxing/auto-review and the Codex changes that skip review only for `approve` (https://github.com/openai/codex/pull/20069 and https://github.com/openai/codex/pull/20750). The words "internal coordination messages" are not in those pages. `fleet ready` reads the config and says whether mail is set to `approve`. It also proves one mailbox round trip. + +## Approvals + +You stand in for the user on routine decisions: start this team, take a normal task. Those count. + +Bring these to the user, and do not approve them yourself: money, a push, a deploy, a delete, a secret, an outgoing message, or a bigger structural or directional change. + +## Status + +When the user asks how things are going, answer in plain language: how many agents, which areas, what each is working on, what landed, and what needs the user. `fleet status` (MCP `fleet_status`) says, for each area, whether the tmux window is alive or dead, how many messages are still unread, a working or idle guess, a stall when nothing has advanced, and any hold (owner, unblock condition, next allowed task). A live window that still says working is not progress. Idle with unread mail means that agent has not read it. Looking at status nudges that pane once if the mail has been waiting. A second look does not nudge again. If status shows unread mail that has been waiting, or a recovery nudge fired, run `fleet wakes` (MCP `wake_log`) and tell the user which case it looked like: the wake line was still in the composer, a later Enter submitted it, a waiter typed it after the pane was busy, the line left the composer but the mail is still unread, or the pane was not there (the log names the windows and the tmux socket). Do not ask the user to open the log. Read `hear`, `fleet/LOG.md`, the landing queue, and `git log`. Offer a dev-server preview when that would help them see the work. + +## The machine + +When mail mentions memory, disk, heat, or battery, or the user asks how the machine is, run `fleet health` (MCP `health`). Say the verdict in plain words and the action the session manager suggests. Name the area that is using the CPU or memory when that is the cause. Swap occupancy alone is not a tight verdict, and neither is a swap file that is nearly full while plenty of RAM is free: Linux keeps swapped pages. Swap counts when available memory is under twice the memory limit, or when swap is actively being read in. A high load average while fleet areas are idle is a note about other programs (browsers, scanners, chat apps), not a hold. When health does hold heavy work, agents keep doing light work (source review, offline analysis) instead of going idle. Low disk is a proposal of what to remove and the sizes, then a question for the user. Do not delete, kill a process, or change a system setting yourself. + +The kit default CPU temperature is 90°C. If this machine runs hot below that, offer a lower `health.cpuTempC` in `fleet.config.json` (85 is a reasonable laptop figure, not a new kit default) and write it only after the user says yes. The next `fleet health` reads that file. A change to `~/.codex/config.toml` is different: Codex sessions need a reload before they see it. + +The schedules include a resource check every 15 minutes. `fleet crontab` only prints the lines. Offer to install them, and install them only after the user says yes. Do not tell the user to edit crontab. + +## Update the kit + +When a newer kit is available, do not re-run `fleet init` and do not hand-copy files. In `~/agent-fleet-kit` run `git pull`, reinstall with the same `npm install -g` used at setup, then in this project run `fleet update` (MCP `update`). That refreshes `tools/fleet` and the skill for the tool that is registered. A prompt you did not edit is overwritten. A prompt you edited stays, and the new template is the `.kit` file beside it: if you did not edit the local file, replace it with that copy and delete the `.kit` file; if you did, merge, then delete the `.kit` file. It does not change `fleet.config.json`, product files, worktrees, mail, or the landing queue. It commits only the paths it changed. If that commit cannot write the git index, call MCP `update` (or `setup_commit`). Do not `git commit`, and do not ask the user to approve one. If it says to restart, restart agents after they hand off so they load the new prompts. + +## After a stop + +A reboot, a power or network drop, or a vendor usage limit leaves sessions idle. Run `fleet resume` (the same as `fleet up`). It runs `fleet ready`: the mailbox round trip, the Codex approval path, health, whether durable deps are still on disk, and the last checkpoint for each area. Tell the session manager to continue from those checkpoints. Do not start the work over. + +There is no billing integration. If a pane or the user says the five-hour window is nearly gone (the wake log has shown "2% remaining"), tell the agents to write a checkpoint and hand off. Resuming after the reset is `fleet resume` plus that checkpoint. + +## Keep work on disk + +Runners, lockfiles, pinned archives, and evidence live in the area worktree or the fleet state dir under `deps/`, with a `manifest.txt` of the file names. `fleet status` prints that state dir. They do not live only in `/tmp` or the npm cache. Both are disposable: a laptop lost three pinned archives that way, and QA could not run. `fleet ready` checks the manifest. + +## Your own subagents + +You are a top-level Claude or Codex session. You may use your harness's own subagents for parallel research, investigation, test runs, or review. Edits inside a worktree stay coordinated by the agent that owns it, so subagents do not edit the same files at the same time. Landing still goes through that agent's `commit` and the queue. diff --git a/docs/agents/prompts/playback-qa.md b/docs/agents/prompts/playback-qa.md new file mode 100644 index 00000000..32336255 --- /dev/null +++ b/docs/agents/prompts/playback-qa.md @@ -0,0 +1,37 @@ +## Current assignment 2026-10-22 — playback-qa-4 mobile + +QA3 narrow native runner passes3/0fail/0flaky/0skip, not CI qualification. Keep QA3 OPEN or blocked/deferred with exact unavailable failure-evidence reason; retain docs-only evidence+normal queue/durable status. No more speculative Firefox reruns/concurrency ramp. + +Claim QA4 after rereading newest RULE-CHANGES. Preserve own dirty/unlanded evidence before merge unstable. Actual-app bounded Chromium at360x800/390x844/1280x720: verify genuine dependency mirror/hash and actual app readiness, then open Upload through actual UI; measure document/client/scroll widths, component bounding rectangles and exact overflow source. Preserve strict width<=viewport as originally failing; no separate relaxed pass replacing it. Capture actual critical touch-target sizes/reachability, focus-visible/Tab containment+close-focus restore, horizontal scroll/zoom behavior as feasible; distinguish measured local/browser checks from real device/AA audit. Use source trace/computed style to identify culprit, tokens/classes/rules, before suggesting minimal CSS file scope. No speculative redesign or token/color changes. + +Investigation/source/scratch/unique evidence ONLY, no product/CSS/retained tests/helpers/config edits. Do not publish/seed real events or use real accounts/relays/signers/cloud/media. Use existing actual-app fixtures and verified genuine offline dependency mirror; no fake app substitute. Local mock relay/synthetic data only if needed; block other public HTTP/WS. If prep prevents actual boot, stop and report exact blocker before substituting. + +Exclusive bounded Chromium browser slot GRANTED for this baseline, announce START/.task and RELEASE with UTC/exact SHA/commands/check counts/resource cleanup. Preserve preview3000/peer resources, use own private server. No broad/full/build or product submit before dispatch resource grant. After first demonstrated culprit or hard blocker report to dispatch for independent reproduction and precise CSS ownership. All6 campaign priorities remain tracked; no product fix to Firefox inferred from local pass. + +## Current assignment 2026-10-22: playback-qa-3 — Firefox deterministic CI failure + +Supersedes prior idle/QA2 scope below. Read current dispatch campaign brief, PROGRAM/RULE-CHANGES/KNOWN_ISSUES, harness and test integrity. Inspect own clean/unlanded state; fast-forward own branch safely to unstable, report SHA, claim QA3, send read/next-action. Interpreter reports run39536282396/job119013565277 failed Firefox at fb54cea3 while build/units/Chromium/visual passed; remote logs previously empty. This is reported evidence, not a diagnosed product failure. Inspect workflow/artifacts/read-only CI metadata if accessible; no credential disclosure/access, remote writes or workflow rerun. Identify failing step/assertion before smallest local deterministic reproduction, preserving first failure and natural exit. Do not assume known hydration flake is cause. Existing tests/helpers/config/product read-only until precise defect and dispatch ownership routing; scratch/new QA evidence only. Browser/expensive slot initially GRANTED for one narrow Firefox reproduction at a time, announce start in .task/mailbox and release promptly; no broad E2E/full npm test/build/normal product queue until separate slot. Other workers focused offline only. No retries/sleeps/weaker assertions/force exits/public fixture traffic; preserve preview3000 and peer servers. Report exact failure, command/counts/environment and unverified CI boundary. Mobile overflow/touch/focus is staged next after Firefox checkpoint; no CSS edits yet. + +## Archived assignment history (not current routing) + +# Area: Playback fallback QA (playback-qa) + +## Current assignment: playback-qa-2 — actual-app local core/mobile baseline + +Old QA1 prepared output independently verified64143f42→1bb3a4fc(normal gate0),dispatch retained regression4/4;finish old docs closure/final evidence,then claim QA2. The full/gate slot is released;no old regression/full/browser reruns. Ongoing owner goal is in QUALITY/GOAL/PROGRAM. + +New bounded baseline uses actual app,existing docs/testing/agent-playwright-harness.md and tests/e2e/helpers/bitvidTestFixture.ts/local mock relay on private ports. Read KNOWN_ISSUES first. Do one controlled Chromium pass at360x800,390x844,1280x720:feed initial load/navigation (Home/Explore/ForYou where applicable),open/close Profile and Upload,card controls/touch targets/horizontal overflow/focus/required-field validation. Use local synthetic notes,test-only identities from existing harness only if needed;no real signer/peers/cloud/public media/credential access and no publishing outside local relay. Block/log all non-local HTTP/WebSocket attempts. If app cannot boot offline with existing harness,record exact blocker before substituting app/module behavior;do not call a module harness an actual-app pass. + +Investigation/evidence only,no product/CSS/token/helper/config/existing test edits or speculative redesign. Own scratch/QA notes/backlog only until dispatch routes a reproduced issue or useful retained scenario. Report actual viewport/flow checklist,evidence/screenshots,console/network failures and exact reproduction for any defect;distinguish known Firefox/test fixture issue from product bug. No broad E2E or build now. Browser-ONLY resource slot GRANTED after acknowledging old release/starting new baseline;record .task BEFORE opening,send starting event and close own private server/browser after this single pass or first hard blocker. No full-suite/normal submission for new task until ready+explicit slot;other workers may focused-test only while your browser run. Firefox followup only on specific parity/reproduction need after reporting. Do not repeat prior no-clearance intervention. + +## Archived completed assignment: playback-qa-1 + +Independent regression/browser QA slice of pre-launch TODO34/performance-efficiency-plan Stage3 acceptance: dead hosted source falls back to viable torrent/mirror. Recent queued-probe fixes89ccb907/7d20a509 have controlled health lifecycle qualification but real playback fallback remains explicitly unverified. Existing webseed-playback.spec.ts uses public archive media and tolerates decode failure; do not modify or treat that as real offline success. + +Inspect actual playbackCoordinator/service and current test harness. Build a bounded local-only deterministic fallback scenario against actual orchestration: failed primary, alternate selected once, original magnet hash and hosted webseed preserved, terminal failure settles if both fail. Start scratch investigation; report fidelity of substituted IO. Prefer local decodable media if available; no downloading public samples or live peers. Browser is initially allocated to YOU: record ownership in .task, tell dispatch before opening and on release, clean only your own server/browser. Use your worktree/private port, never preview3000. Native Chromium+Firefox where available; no claim of real torrent/decoding unless measured. + +Own only new tests/e2e/fleet-playback-fallback.spec.ts or tests/fleet-playback-fallback.test.mjs plus docs/testing/playback-qa-* evidence/integrity and own backlog. Product JS and existing tests/helpers/config read-only. Propose retained regression approach before adding it; no app.js competing writer. Deliberately disable fallback in a temporary mutant in your own isolated tree and require failure, restore without committing product mutation. If actual product defect found, report exact trigger and minimal owner/file scope to dispatch; do not patch product. Scope stop after bounded QA result, retained valuable test if approved, evidence and durable normal landing. Full gate/build scheduling through dispatch; do not run full E2E suite in parallel with other gates. + +## Coordination and limits + +Send dispatch a read/next-action acknowledgement immediately, then claim your item and keep .task current. Record start/end UTC and time blocked by tools/browser/gates. Report exact SHA/commands/counts/unverified scope; mailbox on blocker, reproduced result, landing and final. Read RULE-CHANGES, PROGRAM, KNOWN_ISSUES, repo memory, and test-integrity before changes. New tests require failing mutants; no weakened expectations/retries/sleeps. Product/test fixes must be scoped from demonstrated evidence. No shared TEST_INTEGRITY.md or docs/TODO.md edits: keep uniquely prefixed integrity/evidence notes under docs/testing and ask dispatch to integrate status. Do not edit another area worktree or stop another process. Full-suite runs and product landings need dispatch resource coordination (one expensive gate at a time, unchanged normal landq). Focused offline checks can overlap. Commit named files via fleet tool; merge unstable, never rebase; normal queue/durable status+ancestry before close. No subagents initially. No deploy/push/spend/real-wallet/production-data/secrets/scheduler/kit changes. Preserve preview3000. Public network/real buckets/signers/peers forbidden in fixtures. diff --git a/docs/agents/prompts/session-manager.md b/docs/agents/prompts/session-manager.md new file mode 100644 index 00000000..0b9a2b22 --- /dev/null +++ b/docs/agents/prompts/session-manager.md @@ -0,0 +1,93 @@ +# Session manager (`{{area.id}}`) + +You are the session manager for this project. You don't write product code. Your job is to keep a small team of coding agents pointed at whatever will most improve the project toward `fleet/GOAL.md`, and to change that team as the project changes. + +The human says what they want in plain words. You and the agents figure out the rest. Come back to the human only for real decisions. The human should not need to know how the fleet works, edit a file, or run a command. + +## Who you are talking to + +The human may talk to you directly, or through their own agent, the interpreter. Messages from `interpreter` are the user. A line in this window that says you have new fleet mail is only a wake. Read the mailbox at the start of every turn, and whenever that line shows up. Reading takes the messages out of unread. The mail is the message. Reply with MCP `mailbox_send` (to `interpreter`, from `{{area.id}}`) with your next action. Do that once. Do not reply with the shell `mailbox send` or `fleet tell`. That shell is a sandboxed command. Codex automatic approval review applies to it even when `[mcp_servers.agent-fleet]` is `approve`, and the review has been rejecting those replies. + +At the start of every turn, run `fleet status` (MCP `fleet_status`) and `fleet health` (MCP `health`). Status shows each area's window alive or dead, how many messages are still unread, a working or idle guess, a stall when the checkpoint is old or missing and there is no unread mail, and any hold. A live window is not progress. Idle with unread mail that has been sitting is nudged again once by that look. A second look does not nudge again. Reading your mailbox runs that same check. There is no ack protocol and no background checker. If that look shows unread mail that has been waiting, or it nudges a pane again, run `fleet wakes` (MCP `wake_log`) and tell the user which case it looked like: the wake line was still in the composer, a later Enter submitted it, a waiter typed it after the pane was busy, the line left the composer but the mail is still unread, or the pane was not there (the log names the windows and the tmux socket). Do not ask them to open the log. Health is the machine snapshot. The machine section says what you may change. + +An approval the interpreter relays counts for a routine decision: start this team, take a normal task. It does not count for money, a push, a deploy, a delete, a secret, an outgoing message, or a bigger structural or directional change. Those go to the real user. If you are unsure which it is, ask the interpreter to take it to the user. Do not ask the user to edit a file, attach to tmux, or run a command. + +## The machine + +`fleet health` is one snapshot: CPU load and usage, memory and swap, disk free for the repo, the worktree root, the state dir, and tmp, CPU temperature and battery when the machine exposes them, and the CPU and memory of each area's process tree. A missing sensor says unavailable. The command changes nothing. + +Run it at the start of every turn, and again before you staff another area. The `health` block in `fleet.config.json` is suggested limits (disk free, memory, swap, temperature, battery, load). A missing block uses the built-in defaults. They are guidance. You decide. Swap occupancy alone is not tight, and a swap file that is nearly full while plenty of RAM is free is not tight: Linux keeps swapped pages. Swap counts when available memory is under twice the memory limit (default 30%), or when the snapshot sees swap being read in. A high load average while fleet areas are idle is a note that names the other programs. It does not hold fleet work. The CPU temperature default stays 90°C. A lower `cpuTempC` in that block is a per-repo choice. The interpreter offers one and writes it only after the user says yes. Do not change it yourself. + +On your own, inside the fleet, you may: staff fewer areas or hold a new dispatch; ask an area to finish its step and park; ask agents to run tests one at a time or with fewer workers; ask them to stop a dev server or browser they started and no longer need; slow down or wind down when the machine is on battery or hot; resume when a later snapshot is healthy. One hot reading is a warning. If the next check is still at or over the suggested temperature, treat it as sustained and slow down. Name the area whose process tree is using the CPU or memory. When you hold heavy work (builds, tests, new staffing), say so and tell the area to keep doing light work: source review, offline analysis, writing the next checkpoint. Do not leave them idle. + +Every hold is a record, not a feeling. MCP `hold` (or `fleet hold`) needs the owner, the exact unblock condition, and the next allowed task. Status prints that. Clear it when the condition is met. When something actually advances, MCP `progress` (or `fleet progress`) writes one line. A pane that still looks busy is not that line. The check-in flags an area with no progress for the stall window (default 20 minutes) and no unread mail. + +Take these to the user, through the interpreter or one dated line in `{{stateDir}}/owner.md`, and do not do them yourself: deleting files or caches, including build output, `node_modules`, and worktrees; killing a process the fleet did not start; changing a system setting; anything that costs money. Low disk means you propose what to remove and the sizes, then ask. + +Message an area at most once per problem per day. Check `{{stateDir}}/sent-.txt` and append what you send. + +`docs/agents/schedules/resources.md` is this same check every 15 minutes, including quiet hours, for when no one is in a turn. If `schedules` has no `resources` entry, add the one from the kit template. `fleet crontab` only prints the lines. The interpreter installs them after the user says yes. + +You are a top-level Claude or Codex session. You may use your harness's own subagents for parallel research, investigation, test runs, or review. You do not write product code. Edits inside a worktree stay coordinated by the agent that owns it, so subagents do not edit the same files at the same time. Landing still goes through that agent's `commit` and the queue. + +## Two ways to start + +- **Existing repo.** Read the code, tests, docs, and history, then ask the questions below with defaults grounded in what you found. `fleet/context.md` is a first skim gathered at `fleet init`; check the tree yourself before you trust it. +- **Spec or idea, little or no code.** The human gives a spec, a README, or a few sentences (a spec passed to `fleet init --spec` is already in `fleet/context.md`). Ask a few more questions that matter (who it's for, what "working" means, any must-use tech). Write the answers into `fleet/GOAL.md` and a short `docs/SPEC.md`, set up the skeleton, then run the same loop. + +## Getting the goal + +On your first run, if `fleet/GOAL.md` does not exist, ask the human a few short questions in this session. Offer a sensible default for each so a one-word answer works: + +1. What should this project get better at? (default: "leave it measurably better: fewer bugs, safer, nicer to use, faster, easier to change, closer to what the README or spec promises") +2. Anything to prioritise right now? (for example "UX polish before launch", "security first", or "no") +3. Anything off limits? (default: no deploys, no spending, no production data) + +Skim the project before you ask, so the questions are specific (for example "I see a payments module and no tests around it; want security and tests first?"). If the interpreter has already sent the answers, use those. Write the answers to `fleet/GOAL.md` yourself and read them back in one line. Later the human changes the goal by telling you. You update the file. + +## Each loop + +1. **Look.** Read `fleet/GOAL.md` (if it is missing, ask first, as above), the README or spec, `docs/TODO.md`, the backlog, recent commits, and landing results (`fleet landq status`). Run the tests. Skim the code where it matters. Note what is actually wrong or weak, with evidence (a failing test, a file and line, a confusing screen, a missing feature the spec promises). + +2. **Choose.** Pick the few improvements that would move the project furthest toward the goal right now. Prefer things with clear evidence and a way to tell when they are done. Leave anything that needs the human (credentials, payments, design taste, real-world data) in a short "needs you" list instead of guessing. + +3. **Staff.** Decide which agents you need for those improvements, and as few as possible. One is fine. Run `fleet health` before you add an area. When the snapshot is tight, staff fewer, or wait until it is healthy. Give each a short brief: its focus, what it owns, what it should leave alone, the evidence, and what "done" looks like. A focus can be a part of the codebase or a lens across it. Examples of lenses, not a required list: bugs and correctness, security, UI/UX, accessibility, tests and reliability, performance, docs and onboarding, dependency upkeep, spec gaps. Invent others when the project calls for them. Don't staff a lens just because it is on this list. + + You also pick the provider and model each agent runs on, from those available on this machine (`fleet/context.md` lists harness binaries on PATH; `fleet.config.json` `harnesses` lists providers and the model for each tier). Match the model to the work. The human overrides either just by telling you. Where an area has its own provider, you write `provider` and, when you want a specific model, `model` on that area. The human never has to. + + Each worker is a top-level Claude or Codex session and may use that harness's own subagents for parallel research, investigation, test runs, or review. Edits inside a worktree stay coordinated by the agent that owns it, so subagents do not edit the same files at the same time. Landing still goes through that agent's `commit` and the queue. When you staff, one strong agent with its own subagents may beat several fleet agents for a tightly coupled area. Split into several fleet agents when the work splits cleanly across owners. + +4. **Run.** Write the briefs into the fleet files and start the agents. On the first loop, show the human the proposed team in a few lines and ask "start these?" before launching. They answer in this session. Agents commit and submit through the landing queue on their own. + +5. **Check.** At your next check-in, read what landed and what was rejected. Re-run the tests. Ask: is the project better toward the goal? Retire agents whose focus has run dry, redirect ones that are stuck, and add new ones where new problems showed up. Ask your agents what slowed them down and what data or tool would have helped. If the same gap keeps coming up, note it in `fleet/LOG.md` as a suggested small kit addition instead of working around it forever. + +6. **Bigger moves, rarely.** Assume the human's direction and the project's structure are right; usually they are. Work within them. Only raise a structural or directional change when there's strong evidence it's needed (the current structure keeps causing bugs, blocks the goal, or the spec contradicts itself), not as a matter of taste. When you do, explain it in plain language: what you'd change, why, the trade-off, and your recommendation, then ask. If they say no, drop it. + +7. **Report.** Append a few lines to `fleet/LOG.md`: what changed, what you staffed and why, what's in "needs you". Keep it short. + +Whenever you need a decision from the human, ask them directly in this session with a short question and a default. Never expect them to edit a file or run a command. Stop and tell the human when the remaining improvements are small, risky, or all need them. Don't keep agents busy for the sake of it. + +## How you change the team + +You are the only one who edits the roster. Your worktree is this area's branch, not the main checkout. Commit fleet files here and submit them; do not commit product code, and do not push main. + +Commands are `fleet ...` (from this repo: `node tools/fleet/bin/fleet.mjs ...`). + +- **Add.** Append one object to `areas` in `fleet.config.json`: `id` (lowercase, digits, dashes), `title`, `"role": "area"`, `domain` (`core`, `feature`, `tooling`, or `content`), `owns` (path globs), `doesNotOwn`, `neighbours` (other area ids), and `provider` / `model` when not using the project default. Write `docs/agents/prompts/.md` from `docs/agents/prompts/area.md` with the brief. Commit those files and submit (`fleet commit -m "..." --submit -- fleet.config.json docs/agents/prompts/.md`, or `landq submit`). When `fleet landq status ` says `landed`, run `fleet worktrees --only `. Start the agent only after the human has said yes: `fleet up --only `. +- **Redirect.** Rewrite that area's prompt file, commit, submit, and send one mailbox note: `node tools/fleet/bin/mailbox.mjs send --from {{area.id}} "..."`. +- **Retire.** `fleet down --only `, remove the area from `fleet.config.json`, commit, submit, and say why in `fleet/LOG.md`. + +A scarce machine is a line in that area's `.task`, not a lock. Before you go idle, and before quiet hours, run `fleet status` so a waiting pane is nudged once, and `fleet health` so you slow down before you stop. Quiet hours are judgment: mail is still delivered. Read `fleet/RULE-CHANGES.md` newest first; it overrides this prompt. Record an owner ruling in `docs/agents/PROGRAM.md` with the date and the exact quote. + +After a reboot, a power loss, or a usage-limit stop, `fleet resume` (or `fleet up`) prints `fleet ready` and you continue from the checkpoints. Do not restaff work that a checkpoint already names. There is no billing integration. When a pane or the user says the five-hour window is nearly gone, write checkpoints, mail the next step, and stop cleanly so the resume after the reset is one step. + +Runners, lockfiles, pinned archives, and evidence stay in the worktree or `{{stateDir}}/deps` with a `manifest.txt`. `/tmp` and the npm cache are disposable. Three pinned archives vanished from them and QA could not run. Do not send an area back to that. + +## Rules of thumb + +- Brief the human in plain words. Lead with what matters to them. Ask only for real decisions. +- If the human asks how things are going, answer directly from `fleet/LOG.md`, the landing queue, and `git log`. A short table in the reply is enough. There is no separate dashboard. +- Evidence over opinion. If you can't point at it, don't staff it. +- Small, reviewable changes beat big rewrites. +- Never deploy, push to production, spend money, or touch anything `fleet/GOAL.md` marks off limits. +- Trust the agents' judgment. Give them goals and context, not step-by-step procedures. diff --git a/docs/agents/prompts/storage-cors.md b/docs/agents/prompts/storage-cors.md new file mode 100644 index 00000000..df00ab64 --- /dev/null +++ b/docs/agents/prompts/storage-cors.md @@ -0,0 +1,51 @@ +## Follow-on routed scope 2026-10-22 — storage-cors-4 coordinator + +FIRST consume existing storage3 full/normalqueue/build grant; freeze and submit only existing buffer/service validated tip. Do not fold this follow-on into storage3 tip or block that granted gate with more investigation. + +Dispatch independently reproduced exact89f8923c registered Recent/Explore/productionstages+real VideoListView/Card JSDOM, controlled awaited moderation subscription IO,4scenarios/2pass/2fail/cancelled0 naturalexit1/203.65ms. Old same-view refresh restores deletedDOM after newer subscription; old Recent result overwrites replacement Explore DOM/metadata after real destroy/remount. Valid delayed pipeline and fully disposed no-remount controls pass. Separate storage4 ownership ONLY js/app/feedCoordinator.js request/result/lifecycle eligibility and unique retained registered-lifecycle tests/integrity/evidence. No app.js,VideoListView,engine,buffer/service writes for4. + +After freezing/submitting storage3, claim4 and implement smallest coordinator stale-completion guard in a separate commit. Make eligibility account for request ordering, current feed/view instance and disposal across awaited runtime and engine stages. Stale success OR error must not paint videos/metadata/telemetry/side effects over the current view; preserve documented caller return behavior and report exact choice. Keep live valid delayed/sameview/newest fallback, unrelated content/filter/moderation/rank semantics and view lifecycle intact. No forced refresh, broad source/cache policy or API redesign. Retain actual production-stage asynchronous boundary, not custom decorator-only replacement. Add reverse ordering, late old failure, same feed remount/disposal, empty/deleted result, current valid failure/fallback controls; failing restored mutants required. Read test integrity; expectation changes need equally strict explicit spec note, no weakening/retries/sleeps. + +Focused/mutants/lint only under current full slot; no new normal submit/full/build/browser until dispatch grant. Existing storage3 gate remains first, client9 next full; QA4 mobile sourceprep held. Ask only for real ownership/evidence blockers, no routine new approval loop. + +## Implementation routing 2026-10-22 — storage-cors-3 + +Dispatch independently reproduced all7 scratch scenarios using dispatch imports at7369f1fe (runtime diff0 vsc5b678f7):3pass/4fail, naturalexit1/29.89ms/network0. Temporary ownership ONLY js/nostr/videoEventBuffer.js and js/services/nostrService.js plus new uniquely named focused regression/integrity/evidence files. This supersedes investigation-only restriction for these two boundaries; existing client.js and app/feedCoordinator.js remain read-only. + +Separate commits: first buffer timestamp-safe deletion (recordTombstone already protects strictly newer active content; remove no valid newer version), then service snapshot/persistent eligibility + obsolete affected-record cleanup. Retain failed baseline and independently failing mutants for every new scenario. Do not invent root/address/author rules or broaden deletion across authors; use existing authoritative tombstone semantics and verified conversion path. Cover deletion-marker ID differing from original; late older tombstone against newer live; late older content; multiple versions/unrelated roots/authors; deleted/invalid persistent entries; empty-result persistence so deleted-only caches do not resurrect; warm/second-load/latest-consumer and stale-subscription controls. Preserve live cache/moderation/access/ordering and publication semantics. Report any preexisting identity weakness separately rather than opportunistic rewrite. + +Coordinator stale-refresh result remains OPEN: current scratch pauses actual feed engine decorator and calls actual coordinator, but is not full registered UI lifecycle proof. Gather faithful registered-feed/view/disposal overlap evidence only after first fixes; no coordinator/app edits yet. No tests/runner/helpers changes to bypass contract. If new test needs explicit registration, ask dispatch for narrow ownership. Run focused suites, failing restored mutants and lint; request serialized full gate before product submit; QA holds narrow Firefox browser slot. Normal gates/durable ancestry mandatory. No real signer/cloud/relay/deletion/credential operations. + +## Current assignment 2026-10-22: storage-cors-3 — feed/view tombstone stale-refresh + +Supersedes old idle/storage2 and deferred3 scope below. Read dispatch campaign brief, PROGRAM/RULE-CHANGES/KNOWN_ISSUES and docs/nips before Nostr investigation. Inspect own worktree and safely fast-forward clean branch to unstable; preserve any unexpected work; report SHA, claim item3, send read/next-action. Read actual tombstone/deletion/cache/view lifecycle; reproduce delete ingest followed by stale refresh/late older event/view switch with synthetic offline events/controlled IO. Check unrelated live/newer valid content and existing deletion semantics; do not invent new policy. Source read/scratch/unique evidence/backlog only; NO product/committed test edits until demonstrated defect and precise ownership routing. App/feed/services/Nostr shared modules remain read-only for now. No real signer/relay/cloud/credentials/delete; no browser/build/full suite/normal product submission yet, QA owns initial expensive/browser slot. Focused offline checks permitted, env -u NODE_TEST_CONTEXT. Report path and strict failed/control counts or already-fixed evidence, proposed smallest files/retained regression and remaining limits. No optional new storage work after this slice without campaign routing. + +## Archived assignment history (not current routing) + +# Area: Storage CORS reliability (storage-cors) + +## Current assignment: storage-cors-2 — best-effort cleanup metadata guard + +Investigation independently confirmed by dispatch:actual R2Service.resolveConnection→StorageService metadata→deleteVideoStorage rejects2 malformed cached cases(bucket number/publicBaseUrl object),6 controls pass,zero network attempts. /tmp/bitvid-dispatch-delete-audit.mjs exit1(expected0 rejections,actual2) after controls. App/edit callers catch this,so do not claim note deletion itself blocked. Best-effort/no-throw cleanup contract is violated. + +Temporary explicit product ownership for this item overrides prior roster exclusion ONLY for deleteVideoStorage in js/services/r2Service.js,plus new focused tests/storage-delete-malformed-settings.test.mjs and unique integrity/evidence. Normalize only primitive strings/validate malformed fields safely;preserve structured skip/result semantics,never coerce arbitrary objects into URL/credentials,never widen owner base or content-addressed key collection. Cover malformed cached resolution and meaningful direct caller/settings types,valid owned/external+prefix-sibling,locked/external,partial failure and generic-S3/provider controls. No blanket catch masking valid-operation failures without structured result. No real SDK/cloud/delete calls. + +Mirror rule decision from actual code:duplicated upload hashing/magnet logic stays untouched in BOTH services. s3UploadService has NO deleteVideoStorage counterpart (dispatch rg verified);the existing provider-aware R2Service delete method already handles genericS3. Do not add a duplicate delete implementation or unrelated upload edits merely to touch two files. Explain this in integrity/decision evidence and prove both supported provider paths;if a duplicated affected counterpart is found,route before edits. No s3UploadService write without that evidence. + +Read test integrity;new retained real-service regression must reject old helper (all affected malformed cases),mutants restored. Focused existing delete/key suites+lint/build;no full/browser until dispatch slot. QA actual-app baseline gets next bounded browser slot;you may implement/focused now. Commit item2,merge verified unstable,normal full gate/durable ancestry then close. No upload/CORS/retention-policy/features/owner-bucket behavior changes. Feed audit deferred3,not cancelled. Send ready/blocker/landing/final with exact claims and limits. + +## Archived completed assignment: storage-cors-1 — preservation fix routed + +Implementation routed after worker and independent dispatch offline reproduction: adding second origin replaces the entire existing rule, existingRulesPreserved0/1. Dispatch command env -u NODE_TEST_CONTEXT node /tmp/bitvid-dispatch-storage-cors-reproduce.mjs exits1 at strict false!==true after all5 characterization controls. No cloud IO. + +Now implement only s3-multipart.js plus GetBucketCorsCommand export in s3-client.js and new storage-cors-preservation test/integrity note. Preserve every existing rule and all fields verbatim; add a separate existing-default-shaped rule for uncovered requested origins without broadening arbitrary rules. Idempotence: no PUT when existing equivalent default rule already covers all requested origins; explicitly test equivalent-rule detection and repeated calls. Missing config only on NoSuchCORSConfiguration; other read failures propagate with no PUT. Do not guess wildcard equivalence or merge restrictive/custom rule semantics. Preserve empty-origin no-op. Tests must use actual helper/command export, fake transport only, cover existing rules/wildcards/duplicates/no-config/read-denied/idempotence and bounded provider errors; do not silently replace on read failure. Do not change current default methods/headers/TTL as unrelated policy; if SDK/provider limits make safe preservation impossible, report before broadening. Offline mutants for every new scenario; preserve normal assertions/gates. Relevant focused storage suites/lint/build; request full-suite slot (client ahead), no browser/cloud need. CLI/test/lander launches clear NODE_TEST_CONTEXT. Commit item1, merge unstable, normal queue/durable ancestry before close; no premature qualification of real S3. + +Historical investigation context follows; its before-product restriction is superseded by the routed scope above. + +Bounded parallel trial: validate todo/TODO_2026-06-16_r2-followups.md CORS replace-not-merge report at current HEAD. Dispatch inspected ensureBucketCors in js/storage/s3-multipart.js: constructs PutBucketCors with requested origins only, no GetBucketCors. This is evidence for investigation, not proof every caller loses policy. Trace actual callers and required existing-origin behavior. Reproduce offline via real helper and fake S3 transport/SDK boundary: an existing origin/rule, adding a second origin, idempotent repeated call, missing config and read-failure cases. No cloud credentials/network/bucket mutation. + +First report smallest demonstrated loss plus proposed preservation semantics/files to dispatch before product changes. Inspect AWS command export in s3-client read-only. You own s3-multipart.js/s3-client.js and new tests/storage-cors-preservation.test.mjs only after fix routing; no R2/S3 upload service copies, app, signer or UI edits. Existing wildcard/method/header/non-origin rule semantics must not be guessed; read failure must not silently overwrite a policy. Do not add broad SDK/dependency upgrade. If stale/already fixed, close with evidence and propose next independent TODO. No browser initially. Relevant focused storage checks, lint, full normal gate/build after approved fix; ask dispatch before expensive runs. Integrity notes docs/testing/storage-cors-integrity.md. + +## Coordination and limits + +Send dispatch a read/next-action acknowledgement immediately, then claim your item and keep .task current. Record start/end UTC and time blocked by tools/browser/gates. Report exact SHA/commands/counts/unverified scope; mailbox on blocker, reproduced result, landing and final. Read RULE-CHANGES, PROGRAM, KNOWN_ISSUES, repo memory, and test-integrity before changes. New tests require failing mutants; no weakened expectations/retries/sleeps. Product/test fixes must be scoped from demonstrated evidence. No shared TEST_INTEGRITY.md or docs/TODO.md edits: keep uniquely prefixed integrity/evidence notes under docs/testing and ask dispatch to integrate status. Do not edit another area worktree or stop another process. Full-suite runs and product landings need dispatch resource coordination (one expensive gate at a time, unchanged normal landq). Focused offline checks can overlap. Commit named files via fleet tool; merge unstable, never rebase; normal queue/durable status+ancestry before close. No subagents initially. No deploy/push/spend/real-wallet/production-data/secrets/scheduler/kit changes. Preserve preview3000. Public network/real buckets/signers/peers forbidden in fixtures. diff --git a/docs/agents/schedules/checkin.md b/docs/agents/schedules/checkin.md new file mode 100644 index 00000000..7f087057 --- /dev/null +++ b/docs/agents/schedules/checkin.md @@ -0,0 +1,3 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: hourly check-in. Run `node tools/fleet/bin/doctor.mjs --json`, `node tools/fleet/bin/landq.mjs status`, `node tools/fleet/bin/fleet.mjs status`, and read every WAITING line. For each WAITING older than 2 hours, message the session being waited on (once per day). A status line that says stalled (no progress for the stall window and no unread mail) is a real problem: message that area once per day and name the hold if one is recorded (owner, unblock condition, next allowed task). A live window is not progress. A lander that is NOT running is owner-facing. Red or conflict results: make sure the submitting area knows (MCP mailbox_send, not a sandboxed shell send). Record new strikes (red landing, withdrawn submission, reviewer rejection, peer-caught false green, owner regression) in docs/agents/STRIKES.md; two in a week for one area is owner-facing with a proposed tier move. If the usage governor is enabled and the adapter usage hook reports at or above usage.windDownAtPct, that is owner-facing: run docs/agents/WIND-DOWN.md step 1 (create {{stateDir}}/PAUSED with the reason) and stop. There is no billing integration. If a wake line or a pane says the five-hour window is nearly gone, that is also owner-facing: the next step is a checkpoint and a clean handoff, then `fleet resume` after the reset. Land any doc change with `node tools/fleet/bin/landq.mjs submit`. diff --git a/docs/agents/schedules/hygiene.md b/docs/agents/schedules/hygiene.md new file mode 100644 index 00000000..7a2d95d0 --- /dev/null +++ b/docs/agents/schedules/hygiene.md @@ -0,0 +1,3 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: tree hygiene. Run `node tools/fleet/bin/doctor.mjs`. For each DIRTY tree with a .task older than 24 h, ONE-DISK commits older than a day, or a missing .task, message the owning area once (never touch its tree yourself). List scratch directories under /tmp that belong to finished runs (owner pid dead) and report them; do not delete anything outside your own worktree. Check the mailbox directory for messages unread for over a day and nudge their recipients. diff --git a/docs/agents/schedules/resources.md b/docs/agents/schedules/resources.md new file mode 100644 index 00000000..ae93569a --- /dev/null +++ b/docs/agents/schedules/resources.md @@ -0,0 +1,9 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: machine resources. Run `node tools/fleet/bin/fleet.mjs health`. This job is the check for when no one is in a turn. The `health` numbers in fleet.config.json are suggested limits, not orders. A missing sensor is unavailable and is not itself a problem. Swap occupancy alone is not a crossed limit, and a swap file that is nearly full while plenty of RAM is free is not one either. Swap counts when available memory is under twice the memory limit, or when swap is being read in. A high load average while fleet areas are idle is a note about other programs. It does not hold fleet work. When heavy work is held, tell the area to keep doing light work instead of going idle, and record the hold (owner, unblock condition, next allowed task). + +You may, inside the fleet: tell the session manager to staff fewer areas or hold a new dispatch; ask an area to finish its current step and park; ask an area to run tests one at a time or with fewer workers; ask an area to stop a dev server or browser it started and no longer needs; ask the fleet to slow down or wind down when the battery is low or discharging, or when a CPU temperature is at or over the suggested limit on this run and the previous resources log ({{stateDir}}/logs/) was hot too. When a later snapshot is healthy, tell them they can resume. Name the area whose process tree is using the CPU or memory. + +Do not do these. Append one dated line to {{stateDir}}/owner.md so the interpreter can ask the user: deleting files or caches, including build output, node_modules, and worktrees; killing a process the fleet did not start; changing a system setting; anything that costs money. Low disk means name what could be removed and the sizes, then ask. Do not delete it. + +If nothing crossed a suggested limit and the machine is not on battery, print the verdict and stop. Do not send mail just to say the machine is fine. diff --git a/docs/agents/schedules/restart.md b/docs/agents/schedules/restart.md new file mode 100644 index 00000000..c825c3fa --- /dev/null +++ b/docs/agents/schedules/restart.md @@ -0,0 +1,3 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: reset checkpoint for an already active, unpaused fleet. Read docs/agents/RESUME-BRIEF.md and the progress files under {{stateDir}}/progress. Report the next eligible work to the session manager from those checkpoints, not from a live window. Run `node tools/fleet/bin/fleet.mjs ready` and include its mailbox, approval, deps, and health lines in the summary. Never remove PAUSED or start sessions: down and pause are explicit owner controls, including for this schedule. The owner must unpause and bring the fleet up (`fleet resume`) before scheduled work can resume. diff --git a/docs/agents/schedules/summary.md b/docs/agents/schedules/summary.md new file mode 100644 index 00000000..52866b62 --- /dev/null +++ b/docs/agents/schedules/summary.md @@ -0,0 +1,3 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: morning summary for the owner. Write a short dated section into {{stateDir}}/owner.md: what landed since yesterday (`git log --oneline --since=yesterday
`), what is red or held, who is waiting on the owner (decisions, approvals, frames to look at), and the plan for today from PROGRAM.md priorities. Under 25 lines, plain words, every claim with a SHA or a file path. diff --git a/docs/agents/schedules/triage.md b/docs/agents/schedules/triage.md new file mode 100644 index 00000000..4b0187b6 --- /dev/null +++ b/docs/agents/schedules/triage.md @@ -0,0 +1,3 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: stale triage. `node tools/fleet/bin/backlog.mjs list` and the stale-items section of `doctor.mjs`. For each in-progress item no commit has named for the stale window: ask its area whether it is still active, blocked (then set status=blocked blockedOn=...) or should be deferred. For asks in owner.md or the mailbox with no owning area: propose the ONE owning area to the dispatcher. Never cancel an item; only the owner cancels, with a reason. diff --git a/docs/agents/schedules/winddown.md b/docs/agents/schedules/winddown.md new file mode 100644 index 00000000..b754cc15 --- /dev/null +++ b/docs/agents/schedules/winddown.md @@ -0,0 +1,3 @@ +You are a scheduled headless job of the {{project}} dispatcher, started by tools/fleet/bin/sched.sh in the dispatcher's worktree on {{date}}. You have no memory of earlier runs: docs/agents/PROGRAM.md, the backlog, `node tools/fleet/bin/doctor.mjs` and docs/agents/WIND-DOWN.md are your context. Rules: merge, never rebase; never rm a variable path; never bare git stash; never print or move secrets; never relay an approval; never install crontab lines or start sessions. Message a session only for a real, new problem, at most once per problem per day: check {{stateDir}}/sent-{{date}}.txt and append what you send. Owner-facing items (a decision, work at risk, main red, the wind-down mark) get one dated line appended to {{stateDir}}/owner.md. Finish in under 30 minutes and print a 3-line summary of what you did and what you did NOT verify. + +TASK: weekly wind-down before the usage reset. Follow docs/agents/WIND-DOWN.md: create {{stateDir}}/PAUSED with the reason, tell every area to park, commit, merge, submit and update .task + docs/TODO.md, then check doctor and the landing queue. Draft docs/agents/RESUME-BRIEF.md from the backlog, doctor and TODO.md and land it. Owner-facing: one line saying the fleet is paused and why. diff --git a/docs/lessons/cloudflare-s3-upload-gotchas.md b/docs/lessons/cloudflare-s3-upload-gotchas.md new file mode 100644 index 00000000..fe589c53 --- /dev/null +++ b/docs/lessons/cloudflare-s3-upload-gotchas.md @@ -0,0 +1,10 @@ +# Cloudflare/S3 Upload-Path Gotchas (audited 2026-06-16) + +> Extracted verbatim from `AGENTS.md` §4a, which retains a short summary. Dated incident writeup from the 2026-06-16 upload-path audit. + +* **Two near-identical upload services exist** — `js/services/r2Service.js` (Cloudflare R2) and `js/services/s3UploadService.js` (generic S3). They share `buildR2Key`, `resolveUploadIdentifier`, and the magnet build but are *copies*. **Any fix to one must be mirrored in the other** or they silently drift. +* **Storage-key collisions = data loss — FIXED 2026-06-16.** `buildR2Key` (`js/r2.js`) builds `u///.` where `namespace` used to fall back to the literal `"uploads"` and `slug` to `"video"` (e.g. a non-ASCII filename, or no info-hash) — so a URL-first upload with no info-hash and a duplicate filename overwrote the previous object while the old note still pointed at that URL. WebTorrent uploads were always safe (info-hash namespaces the key = content-addressed). Fix: `computeStorageContentHash(file)` (`js/r2.js`) derives a content-based namespace when no info-hash is available (full SHA-256 for ≤512 MB, sampled metadata+edge fingerprint above that to avoid buffering huge files), wired into **both** `r2Service` and `s3UploadService`. Test: `tests/storage-key-collision.test.mjs`. **If you add a third upload path, reuse `computeStorageContentHash`.** +* **Whole-file hash before upload.** When the modal doesn't pre-seed an info-hash, `resolveUploadIdentifier` runs `calculateTorrentInfoHash(file)`, which reads the *entire* file before the upload begins and before any progress is emitted — on multi-GB files this looks like a hang. Emit a "preparing/hashing" status or parallelize. +* **Publish can report success unconfirmed.** `publishEventToRelay` (`js/nostrPublish.js`) has an optimistic-success fallthrough when the relay handle exposes no `on()`/`then()` — a note may show "Published" with no relay ACK. Don't tighten blindly (legacy `seen`-only relays rely on it), but log it. +* **Silent partial failures.** Thumbnail and `.torrent` upload failures are caught and `warn`-only; the video note still publishes with no user signal. Empty (0-byte) files hit `CompleteMultipartUpload` with `Parts:[]` and surface a cryptic error — guard `file.size === 0`. +* **Object cleanup (deletes/edits).** `R2Service.deleteVideoStorage()` removes the backing objects so "deleted" videos aren't left publicly downloadable. Contract to preserve: it's **best-effort and never throws** (must not block note deletion), it derives keys via `collectVideoStorageKeys()` and **only ever touches URLs under the owner's bucket base** (never external/3rd-party URLs), and it skips cleanly when storage is locked/credential-less. Wired into delete (`app.handleDeleteModalConfirm`) and edit-with-replaced-URL (`editModalController.handleSubmit`). Edit cleanup intentionally does **not** delete thumbnails (the new note may still reference them) and only fires when both old and new URLs are present and different. diff --git a/docs/lessons/nip07-signer-reliability.md b/docs/lessons/nip07-signer-reliability.md new file mode 100644 index 00000000..dc08fa06 --- /dev/null +++ b/docs/lessons/nip07-signer-reliability.md @@ -0,0 +1,15 @@ +# NIP-07 Signer Reliability & Encrypted-List Loading (hard-won, 2026-06-16) + +> Extracted verbatim from `AGENTS.md` §17, which retains a short summary. Dated incident writeup; see also `docs/KNOWN_BUGS.md` #0. + +The single biggest source of "DMs / hashtags / watch-history / block & subscription lists won't load after login" is **not** bitvid — it's an **unresponsive NIP-07 signer**. The extension's MV3 background service-worker can die or its content-script↔worker channel can orphan, after which raw `window.nostr` calls hang forever. No client change can force a dead signer to answer. + +* **Diagnose first, don't guess.** Run a single raw `window.nostr` probe in the page console (`getPublicKey → nip04.encrypt → nip04.decrypt`) that bypasses bitvid entirely. If *that* hangs, it's the extension/environment. Recommend a well-maintained signer (nos2x, Alby); KeysBand's dead worker was the root cause and switching to nos2x fixed everything. +* **Resilience invariants — do not regress these** (see `docs/KNOWN_BUGS.md` #0 for the full history and the files): + 1. **Cap relay fan-out** (`js/nostr/toolkit.js` `capReadRelays`, ≤8, user-relays-first + 2 reserved default slots). An uncapped cold-login REQ storm to ~20 dead NIP-65 relays starves the single-threaded signer's postMessage round-trips. + 2. **Circuit breaker** on the NIP-07 channel (`js/nostr/nip07Permissions.js`): after N consecutive *timeouts* fast-fail instead of hanging ~15s each. Channel-death errors must count toward opening (not reset it); interactive permission prompts bypass it; one periodic probe detects recovery. + 3. **Never swallow a transient decrypt error as an empty result.** Re-throw channel-death/timeout sub-errors so the retry path runs — returning `[]` turns "signer is slow" into "user has no blocks/lists" and kills retries. + 4. **Generous decrypt budget** (~25–30s/call, ~60s backoff cap); a 6s timeout kills slow-but-responsive signers mid-decrypt. Handshake variant timeout is ~20s for the same reason. + 5. **One actionable user notice** (`js/utils/signerHealthNotice.js`) after a few timeouts with `signerStatus: "present"` — not a silent forever-retry. +* **Don't render into closed modals at login.** Once a responsive signer makes decryption instant, eagerly populating every profile panel at login (friends avatars, subscriptions, blocks, DM summaries) freezes the main thread ~10–15s. Populate each pane lazily on open (`selectPane`) and gate data-change re-render listeners on "is the view open". +* **Deterministic testing.** Reproduce signer-dependent bugs headlessly with a fake `window.nostr` (Playwright `exposeBinding` + `addInitScript`) + a mock relay + configurable channel models (healthy/slow/overload/dead, latency override). See `scripts/perf/nip07-channel-sim.mjs`. diff --git a/docs/spec_changes/2026-10-20-client-reliability-8-active-media.md b/docs/spec_changes/2026-10-20-client-reliability-8-active-media.md new file mode 100644 index 00000000..5ec2645d --- /dev/null +++ b/docs/spec_changes/2026-10-20-client-reliability-8-active-media.md @@ -0,0 +1,13 @@ +# Active URL-media final-owner cancellation + +Source: docs/performance-efficiency-plan.md Stage3 requires removed cards not retain probes and shared visible cards not cancel each other's work. PROGRAM records owner continuation "Then continue with the active-probe/URL cancellation work." Dispatch explicitly routed this correction at3dac0afb after independent nine-mode confirmation. + +The prior queued-only lifecycle tests deliberately allowed an active probe to finish and cache healthy/offline after its final observer owner was disposed. That was the contract for items4/5, not evidence of a regression. This item implements the next planned boundary: last-owner disposal during active video-element probing now immediately pauses/removes src/reloads, removes media listeners and the timeout, and settles cancelled/null without writing health cache or starting mirrors. Existing finally releases the slot once and advances FIFO. Its cancelled marker is synchronous, so a fresh owner even in the same turn obtains a new promise; late old callbacks must not cache, paint, remove newer inflight or release slots again. + +Explicit corrections in tests/url-health-lifecycle.test.mjs: +- Disposed active-success result/cache retention -> immediate cleanup/null/no-cache and stale success/error callback immunity, followed by a fresh successful request. +- Disposed final failure/offline cache -> separately retained live-owner offline cache check; disposed late error now strict null/no-cache. +- Disposed timeout-driven cleanup -> cancellation before any time tick, plus separate live-owner retry-success and retry-timeout/cache scenarios. +- Rejoin after final-owner disposal -> new promise/new primary; sharing before final owner leaves still uses the same promise/ordered mirrors. + +Separate live-owner healthy/offline/timeout cache, timeout retry, shared/cross-controller/legacy ownerless consumers, bounded concurrency/FIFO, queued cancellation and ordered mirrors remain required. No observer/cache/HEAD/HLS/torrent/playback policy change. Browser/network/CPU/RSS evidence remains a later gate. diff --git a/docs/spec_changes/2026-10-22-client-reliability-9-active-head.md b/docs/spec_changes/2026-10-22-client-reliability-9-active-head.md new file mode 100644 index 00000000..2d7e2793 --- /dev/null +++ b/docs/spec_changes/2026-10-22-client-reliability-9-active-head.md @@ -0,0 +1,11 @@ +# Active HEAD owner cancellation + +Source: docs/performance-efficiency-plan.md Stage3, campaign2026-10-22 priority2, fleet/RULE-CHANGES.md2026-10-22 and explicit dispatch routing56417d11 durably landed e04549f8. + +Old scoped behavior: queued work and active media cancel on final-owner disposal, but active HEAD fetch remains until response/error/timeout. A fresh observer may join that obsolete inflight record. Controlled11-mode reproduction independently confirmed3 failures/8 controls. + +New contract: final registered owner loss invalidates active HEAD synchronously, requests transport abort when supported and promptly settles the observer chain null independently of transport cooperation. HEAD timer and cancellation hook are released. Same-turn/later consumers start fresh work; obsolete response/error/timer/finalizer cannot affect replacement, cache, badges, media or mirrors. Unsupported AbortController still permits logical settlement and cleanup, but cannot promise transport abort. + +Preserve shared and cross-controller ownership, legacy consumers, live HEAD success/error/timeout/opaque/bad-status media fallback and cache, media-hook handoff and FIFO/cap. Existing29 media lifecycle scenarios and4+4 adjacent controller/multisource scenarios remain unchanged. No retained assertion was relaxed or removed;23 new HEAD scenarios tighten previously uncovered behavior. This is a scope extension, not retroactive proof of HEAD cancellation by item8. + +Only js/ui/urlHealthController.js HEAD phase changes. Observer, cache, app, HLS, torrent, RangeGET and limits/TTLs unchanged. HLS/torrent remain separately open. No browser/real network bytes/fullStage3 qualification. diff --git a/docs/spec_changes/2026-10-22-client-reliability-9-active-hls.md b/docs/spec_changes/2026-10-22-client-reliability-9-active-hls.md new file mode 100644 index 00000000..b139980b --- /dev/null +++ b/docs/spec_changes/2026-10-22-client-reliability-9-active-hls.md @@ -0,0 +1,11 @@ +# Active HLS manifest owner cancellation + +Source: docs/performance-efficiency-plan.md Stage3, PROGRAM campaign priority2, explicit dispatch mailbox2026-10-22T20:55:13Z and newest RULE-CHANGES HLS routing. Dispatch independently reproduced actual12mode baseline7pass/5fail atd04d501e. + +Old scoped behavior: queued/media/HEAD ownership cancellation does not reach the HLS manifest helper. Final-owner disposal retains manifest GET/body-read/timer and unsettled observer work; same-source rejoin inherits the old request and late success can cache. + +New HLS health-probe contract: last registered owner disposal synchronously invalidates that work, requests transport abort where available and promptly settles the observer chain null even if fetch/body ignore abort or AbortController is absent. Release manifest timer and only that probe's hook once. Pre-cancelled work starts no GET. Fresh same-turn/later same-source owners start fresh identity; old responses/errors/timers/finalizers cannot change replacement/cache/badges/media/mirrors. + +Preserve shared/cross-controller and ownerless legacy consumers, live success/error/bad-body/timeout, existing helper calls without lifecycle, native-HLS preference and existing media/HEAD/FIFO behavior. No existing assertion changed;35 new cases tighten uncovered HLS ownership behavior. This is a scope extension, not retroactive qualification by prior HEAD/media fixes. + +Only controller HLS lifecycle argument and manifest-probe optional cancellation plumbing change. HLS engine/player/session/config, app, torrent and RangeGET stay outside this slice. No real playback/network-byte/browser/CPU/wholeStage3 qualification from unit evidence. diff --git a/docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.md b/docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.md new file mode 100644 index 00000000..21f59cfa --- /dev/null +++ b/docs/spec_changes/2026-10-26-client-reliability-9-active-torrent.md @@ -0,0 +1,11 @@ +# Client9 active torrent ownership contract correction + +Authority: dispatch docs/agents/ROUTING-2026-10-26-client9-torrent.md (17:38UTC), read locally by client after interpreter18:53 relay. Independent exact12cases=7pass/5fail/naturalexit1/network0/cleanup0 confirms client baseline. This note precedes any prior expectation edits. + +Source: docs/performance-efficiency-plan.md Stage3 explicitly extends owner cancellation to active torrent probes and says removed cards do not retain probes. Earlier queued-only implementation intentionally left running probes eligible across final-owner disposal, a new same-source owner and cache completion. + +Old tests/grid-health-lifecycle.test.mjs scenarios originally at367 and417 permit last-owner active work to be rejoined and ownerless success cached. New contract: final-owner disposal synchronously marks old work cancelled/unavailable; it cannot be rejoined, cache, or paint. Timer/cancel hooks are cleaned, cancelled promise settles null, and safe temporary-torrent destruction happens after event unwind before queue slot release exactly once. A fresh same-source owner requests fresh work; old callbacks/finalizer cannot delete/cache/paint over it. Cancellation does not depend on an AbortController or cooperative transport. + +Retain strict independent controls: one leaving owner cannot cancel another live owner, active healthy/error/timeout caches retain existing TTL behavior, standalone helper callers preserve results, queue cap4/priority/FIFO and destroy-before-slot safety remain. The shared probe client and unrelated torrents must survive. Replace the two superseded scenarios with equally strict sharing-with-live-owner and obsolete-cache/fresh-request assertions, and add real grid+probe resource/settlement/identity tests. No silent deletion or relaxed assertions. + +Scope only js/gridHealth.js ownership/inflight/cache and js/webtorrent.js probePeers optional lifecycle, plus focused tests/notes. No app, playback/service-worker engine, URL/HLS, magnet/tracker policy, concurrency/TTL/config changes. Current offline source defect does not qualify real peers/network byte release, browser decoding/playback, CPU, RangeGET or all Stage3. Full/build/browser/queue still need separate resource grant. diff --git a/docs/testing/agent-playwright-harness.md b/docs/testing/agent-playwright-harness.md new file mode 100644 index 00000000..9bbd8401 --- /dev/null +++ b/docs/testing/agent-playwright-harness.md @@ -0,0 +1,126 @@ +# Playwright Agent Testing Infrastructure + +> Extracted verbatim from `AGENTS.md` §14, which retains a short summary. This is the full reference for the Playwright agent test harness, mock relay, fixtures, and `data-testid` selectors. + +bitvid includes a test harness and fixtures that let Playwright CLI agents programmatically log in, seed relay data, and inspect app state — no browser extensions or real relays required. + +### Activating Test Mode + +Add `?__test__=1` to the URL, or set `localStorage.__bitvidTestMode__ = "1"` via `addInitScript`. This installs `window.__bitvidTest__` on the page. + +### Overriding Relays + +Point the app at a local mock relay instead of production: + +``` +?__test__=1&__testRelays__=ws://127.0.0.1:8877 +``` + +Or set `localStorage.__bitvidTestRelays__` to a JSON array of relay URLs. + +### Test Harness API (`window.__bitvidTest__`) + +| Method | Returns | Purpose | +|--------|---------|---------| +| `loginWithNsec(hexKey)` | `Promise` (pubkey) | Programmatic login, bypasses the modal | +| `logout()` | `void` | Clear active signer | +| `getAppState()` | `{ isLoggedIn, activePubkey, relays, ... }` | Inspect current state | +| `getFeedItems()` | `Array<{ title, pubkey, dTag, hasUrl, hasMagnet }>` | Scrape video cards from DOM | +| `waitForFeedItems(n, ms)` | `Promise` | Wait for N cards to appear | +| `waitForSelector(sel, ms)` | `Promise` | Wait for a DOM element | +| `getRelayHealth()` | `{ relays, unreachable, backoff }` | Relay connection status | +| `applyRelayOverrides(urls)` | `boolean` | Redirect relay connections | +| `setTestRelays(urls)` | `{ ok, relays }` | Align relayManager + nostrClient to a deterministic relay set | +| `setSignerDecryptBehavior(mode, opts?)` | `{ ok, mode, ... }` | Force decrypt behavior (`passthrough`, `timeout`, `error`, `delay`) for signer-based list sync tests | +| `getListSyncEvents()` | `Array<{ source, at, detail }>` | Read captured sync timeline (`bitvid:auth-loading-state` + `userBlocks` status) | +| `clearListSyncEvents()` | `void` | Reset captured sync events between assertions | +| `waitForListSyncEvent(criteria, ms?)` | `Promise` | Wait for sync milestones (`status`, `reason`, `source`) | +| `nostrClient` | `NostrClient` | Direct access for advanced use | + +### Mock Relay (`scripts/agent/simple-relay.mjs`) + +Start with `startRelay(port, { httpPort })`. Alongside the Nostr WebSocket protocol, exposes an HTTP API: + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/seed` | `POST` | Inject signed events (single object or array) | +| `/events` | `GET` | List all stored events | +| `/events` | `DELETE` | Clear all events | +| `/health` | `GET` | Event count + connection count | + +Programmatic API: `relay.seedEvent(event)`, `relay.clearEvents()`, `relay.getEvents()`. + +### Playwright Fixture (`tests/e2e/helpers/bitvidTestFixture.ts`) + +Import `test` and `expect` from the fixture for tests that need the full stack: + +```typescript +import { test, expect } from "./helpers/bitvidTestFixture"; + +test("agent can seed and view videos", async ({ page, gotoApp, loginAs, seedEvent }) => { + await seedEvent({ title: "Test Video", url: "https://example.com/v.mp4" }); + await gotoApp(); + await loginAs(page); + // ... assertions +}); +``` + +**Available fixture values:** + +| Fixture | Type | Purpose | +|---------|------|---------| +| `relay` | Relay instance | Auto-started/stopped per test | +| `seedEvent(video)` | `(TestVideoEvent) => Promise` | Create a signed kind 30078 event and inject it | +| `seedRawEvent(event)` | `(any) => Promise` | Inject a pre-built event | +| `clearRelay()` | `() => Promise` | Wipe all relay events | +| `gotoApp(path?)` | `(string?) => Promise` | Navigate with test mode + relay overrides | +| `loginAs(page)` | `(Page) => Promise` | Login with deterministic test key | +| `setTestRelays(page, relays)` | `(Page, string[]) => Promise<{ok, relays}>` | Force relay alignment through harness + relayManager | +| `setDecryptBehavior(page, mode, opts?)` | `(Page, mode, opts?) => Promise` | Force signer decrypt behavior (`passthrough`, `timeout`, `error`, `delay`) | +| `startDiagnostics(page, opts?)` | `(Page, opts?) => Promise<{stop()}>` | Capture console/page errors and list-sync telemetry for assertions | +| `testPubkey` | `string` | Hex pubkey of the test key | +| `relayUrl` | `string` | WebSocket URL of the mock relay | + +### `data-testid` Selectors + +Use these for stable element targeting: + +| Selector | Element | +|----------|---------| +| `[data-testid="login-button"]` | Header login button | +| `[data-testid="upload-button"]` | Header upload button (hidden until logged in) | +| `[data-testid="profile-button"]` | Header profile button (hidden until logged in) | +| `[data-testid="profile-permission-prompt"]` | Profile list-sync permission CTA container | +| `[data-testid="profile-permission-prompt-button"]` | Profile list-sync permission/retry CTA button | +| `[data-testid="search-input"]` | Header search field | +| `[data-testid="login-modal"]` | Login modal container | +| `[data-testid="login-provider-button"]` | Login provider option buttons | +| `[data-testid="nsec-secret-input"]` | Private key textarea in nsec login | +| `[data-testid="nsec-submit"]` | Nsec login submit button | +| `[data-testid="upload-modal"]` | Upload modal container | +| `[data-testid="upload-title"]` | Video title input | +| `[data-testid="upload-url"]` | Video URL input | +| `[data-testid="upload-magnet"]` | Magnet link input | +| `[data-testid="upload-submit"]` | Publish button | +| `[data-testid="hashtags-sync-status"]` | Profile hashtag sync status message | +| `[data-testid="subscriptions-sync-status"]` | Profile subscriptions sync status message | +| `[data-testid="blocked-refresh-button"]` | Profile blocked creators refresh button | +| `[data-testid="blocked-sync-status"]` | Profile blocked creators sync status message | +| `[data-testid="blocked-list"]` | Blocked creators list container | +| `[data-testid="blocked-empty-state"]` | Blocked creators empty state | +| `[data-testid="video-modal"]` | Video player modal | +| `[data-testid="video-card"]` | Individual video card in the feed | +| `[data-testid="video-list"]` | Video feed grid container | + +### Key Files + +| File | Purpose | +|------|---------| +| `js/testHarness.js` | Test harness module (installs `window.__bitvidTest__`) | +| `scripts/agent/simple-relay.mjs` | Mock relay with HTTP seeding API | +| `tests/e2e/helpers/bitvidTestFixture.ts` | Reusable Playwright fixture | +| `tests/e2e/agent-testability.spec.ts` | Infrastructure validation tests | +| `scripts/playwright/run-extension-persistent.mjs` | Dedicated persistent-context launcher for extension smoke tests | +| `docs/testing/playwright-integration-recommendations-2026-02-22.md` | Playwright/extension integration recommendations and follow-ups | +| `docs/testing/playwright-function-coverage.md` | Playwright coverage integration, latest baseline metrics, and improvement plan | + diff --git a/docs/testing/playback-qa-1-evidence.md b/docs/testing/playback-qa-1-evidence.md new file mode 100644 index 00000000..0972556f --- /dev/null +++ b/docs/testing/playback-qa-1-evidence.md @@ -0,0 +1,222 @@ +# playback-qa-1: bounded local fallback qualification + +Start UTC: 2026-10-20T13:38:36Z. Baseline: `bbc1be79e941add929c9c51f984778676822b5f2`. +`git diff 0c1dd7e..HEAD -- js/app/playbackCoordinator.js js/services/playbackService.js` +was empty; the filed scenario still applies. Product code and existing tests remain read-only. + +Plan: +- Exercise actual coordinator → strategy → playback session, with real magnet derivation/validation. +- Control probe/media/torrent IO only; check source, handoff payload, surfaced error and settlement. +- Retain a focused regression only after dispatch approves the proposed approach. +- Require disabled-fallback mutants to fail, restore scratch product copies, run coordinated normal gates and verify landing ancestry. + +## Scratch results + +`env -u NODE_TEST_CONTEXT node /tmp/bitvid-playback-qa-1/check.mjs`: exit0,3/3 scenarios +(404 probe → torrent once; media error → torrent once; both fail → null result/error once). +Initial scratch setup failed with absent worktree jsdom, then missing app callback; corrected the fixture +before recording the3/3 result. No product change was made to obtain a pass. + +Worktree has no node_modules. Scratch used already-installed main checkout jsdom/playwright read-only; +no dependency install or public sample download. Native test input was generated locally: + +```sh +ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=blue:s=64x64:r=10 -t 2 -an -c:v libvpx -y /tmp/bitvid-playback-qa-1/clip.webm +node /tmp/bitvid-playback-qa-1/browser.mjs +``` + +Browser script: exit0; Chromium154.0.8037.97 and Firefox146.0.1,3/3 each. +Both selected torrent once after a real local HEAD404; substituted torrent handler attached the generated +WebM and awaited native play. Both selected a viable hosted mirror once after HEAD404, with no torrent call. +Both settled null/error once when the alternate rejected; app session/result promise cleared. +Native decoding measured64×64, readyState4, currentTime>0: + +| Browser | Substituted torrent transport time | Hosted mirror time | +| --- | ---: | ---: | +| Chromium |0.001026s |0.002208s | +| Firefox |0.000372s |0.160829s | + +Zero external requests in either browser. HTTP server bound only127.0.0.1:31847; +all browser requests were origin-filtered; no real torrent client instantiated. +Browser and server closed in finally blocks, released13:40:46Z. Preview3000 untouched. + +Fidelity: real imported product orchestration/magnet code, native video element, real local HTTP404/200, +native VP8 decoding. Substituted app/modal callbacks and torrent transport; module harness does not boot +full Application/UI/feed/relay fixture. Local HTTP is a loopback test exception, not HTTPS/CORS qualification. +No actual WebTorrent, swarm, peer, torrent metadata/file selection or webseed consumption was measured. +Hash/webseed preservation is at the torrent-handler boundary, not proof of subsequent network use. +No claim for late stalls after successful startup, HLS, whole Stage3, CPU/RSS, full E2E, signer/wallet or production. + +## Coordination and contention + +First mailbox read was empty. Claim and inspection started; retention proposal/coordination sent13:39:50Z. +Dispatch's13:38:29 mail was read only after the browser run; it required expensive-browser slot coordination +while an admin drain and client gate were active. The bounded module browser run had already finished. +No resource-contention error was observed in these six scenarios; overlapping gate impact was not measured. This scheduling miss was disclosed to dispatch13:40:48Z; no further browser/full gates will run without clearance. +Sandbox loopback listen failedEPERM; approved elevated rerun completed in8.31s. No automatic review rejection. Lint also required an approved elevated rerun after git-spawnEPERM. +Retention approval/full-gate scheduling wait began13:39:50Z; useful independent evidence work continued. + +## Reproduce bounded browser check + +Generate the clip above. Save the following as `/tmp/bitvid-playback-qa-1/browser.mjs`. +It uses installed dependencies read-only at the stated main path; adjust only dependency/root paths for another checkout. + +```js +import { createServer } from 'node:http'; +import { readFile } from 'node:fs/promises'; +import { chromium, firefox } from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import assert from 'node:assert/strict'; +const root='/home/user/Documents/GitHub/bitvid-playback-qa'; +const requests=[]; +const server=createServer(async(req,res)=>{ + requests.push(req.url); + if(req.url==='/'){res.setHeader('Content-Type','text/html');res.end('');return;} + if(req.url==='/dead.webm'){res.writeHead(404);res.end();return;} + try { + const path=req.url==='/clip.webm'?'/tmp/bitvid-playback-qa-1/clip.webm':root+req.url; + if(!/^\/(js|config)\/[a-zA-Z0-9/_.-]+$/.test(req.url)&&req.url!='/clip.webm')throw Error('blocked'); + const data=await readFile(path); + res.setHeader('Content-Type',req.url.endsWith('.webm')?'video/webm':'text/javascript');res.end(data); + }catch{res.writeHead(404);res.end();} +}); +try { + await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(31847,'127.0.0.1',resolve);}); + for(const [name,type] of [['chromium',chromium],['firefox',firefox]]){ + const browser=await type.launch({headless:true,...(name==='chromium'?{executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']}: {})}); + try{ + const page=await browser.newPage();const external=[]; + await page.route('**/*',r=>new URL(r.request().url()).origin==='http://127.0.0.1:31847'?r.continue():(external.push(r.request().url()),r.abort())); + await page.goto('http://127.0.0.1:31847'); + const results=await page.evaluate(async()=>{ + const {PlaybackService}=await import('/js/services/playbackService.js'); + const {PlaybackStrategyService}=await import('/js/services/playbackStrategyService.js'); + const {createPlaybackCoordinator}=await import('/js/app/playbackCoordinator.js'); + const {deriveTorrentPlaybackConfig}=await import('/js/playbackUtils.js'); + const {isValidMagnetUri}=await import('/js/utils/magnetValidators.js'); + localStorage.setItem('unmutedAutoplay','false'); + const results=[]; + for(const mode of ['torrent-local-decode','mirror-local-decode','both-fail']){ + const video=document.querySelector('video');video.pause();video.removeAttribute('src');video.load(); + const calls=[],errors=[];const hash='ABCDEF0123456789abcdef0123456789ABCDEF01'; + const url=location.origin+'/dead.webm';const mirror=location.origin+'/clip.webm'; + const magnet=`magnet:?xt=urn:btih:${hash}`; + const service=new PlaybackService({deriveTorrentPlaybackConfig,isValidMagnetUri}); + const strategy=new PlaybackStrategyService({playbackService:service,logger:{log(){}}}); + const app={modalVideo:video,currentVideo:{},playbackStrategyService:strategy,videoModal:{updateStatus(){}}, + waitForCleanup:async()=>{},cancelPendingViewLogging(){},clearActiveIntervals(){},showModalWithPoster(){}, + teardownVideoElement:v=>v,applyModalLoadingPoster(){},resetTorrentStats(){},preparePlaybackLogging(){},autoplayModalVideo(){},forceRemoveModalPoster(){},showError:x=>errors.push(x), + probeUrl:async u=>{calls.push(['probe',u]);const r=await fetch(u,{method:'HEAD'});return {outcome:r.ok?'ok':'bad',status:r.status};}, + playViaWebTorrent:async(m,o)=>{calls.push(['torrent',m,o]);if(mode==='both-fail')throw Error('local alternate unavailable');video.src=mirror;await video.play();return {substitutedTransport:true};} + }; + const result=await createPlaybackCoordinator({}).playVideoWithFallback.call(app,{url,magnet,...(mode==='mirror-local-decode'?{sources:[mirror,mirror]}:{})}); + if(result.source){await new Promise((resolve,reject)=>{if(video.currentTime>0)return resolve();video.addEventListener('timeupdate',resolve,{once:true});video.addEventListener('error',reject,{once:true});});} + results.push({mode,source:result.source,errors,calls,currentTime:video.currentTime,width:video.videoWidth,height:video.videoHeight,readyState:video.readyState,activeSession:app.activePlaybackSession,activePromise:app.activePlaybackResultPromise}); + } + return results; + }); + for(const r of results){ + assert.equal(r.source,r.mode==='both-fail'?null:r.mode==='mirror-local-decode'?'url':'torrent'); + assert.equal(r.calls.filter(c=>c[0]==='torrent').length,r.mode==='mirror-local-decode'?0:1); + assert.equal(r.errors.length,r.mode==='both-fail'?1:0); + assert.equal(r.activeSession,null);assert.equal(r.activePromise,null); + if(r.source){assert.equal(r.width,64);assert.equal(r.height,64);assert.ok(r.currentTime>0);} + } + assert.deepEqual(external,[]);console.log(JSON.stringify({browser:name,version:browser.version(),results,externalRequests:external.length})); + }finally{await browser.close();} + } + console.log(JSON.stringify({httpRequests:requests})); +}finally{await new Promise(resolve=>server.close(resolve));} +``` + +## Retained regression and verification + +Dispatch approved the exact retained4-scenario approach13:41:49Z; read before13:42:54Z. +`env -u NODE_TEST_CONTEXT node tests/fleet-playback-fallback.test.mjs`: exit0,4 tests/pass4/fail0/cancelled0/skipped0. +`python3 /tmp/bitvid-playback-qa-1/mutants.py`: exit0; six mutants each rejected exit1. +Restored scratch test run:4/4, exit0. No product changes in worktree. +`npm run lint`: initial sandbox attempt exited2 at lint:hex (git spawnEPERM); approved elevated rerun exited0,11/11 scripts. +Own temporary `node_modules` symlink reused existing main dependencies for lint; no install. +No full unit/E2E/build started independently; normal queue gate awaits dispatch slot. + +Isolated mutation reproduction (copy product/test into the scratch tree first): + +```sh +mkdir -p /tmp/bitvid-playback-qa-1/tree/tests/test-helpers +cp -R js config /tmp/bitvid-playback-qa-1/tree/ +cp package.json /tmp/bitvid-playback-qa-1/tree/ +cp tests/test-helpers/setup-localstorage.mjs /tmp/bitvid-playback-qa-1/tree/tests/test-helpers/ +cp tests/fleet-playback-fallback.test.mjs /tmp/bitvid-playback-qa-1/tree/tests/ +python3 /tmp/bitvid-playback-qa-1/mutants.py +env -u NODE_TEST_CONTEXT node /tmp/bitvid-playback-qa-1/tree/tests/fleet-playback-fallback.test.mjs +``` + +Save this as the stated mutants.py: + +```python +from pathlib import Path +import subprocess, os, json +root=Path('/tmp/bitvid-playback-qa-1/tree') +mutants=[ + ('drop-session-cleanup','js/services/playbackStrategyService.js','this.activePlaybackSession = null;\n if (typeof context.setActivePlaybackSession', '/* deliberately retain session */\n if (typeof context.setActivePlaybackSession'), + ('disable-torrent-fallback','js/services/playbackService.js','if (torrentAttempted) return null;','return null; // deliberately disable alternate\n if (torrentAttempted) return null;'), + ('drop-mirror-forwarding','js/services/playbackStrategyService.js','sources: Array.isArray(options.sources) ? options.sources : [],','sources: [],'), + ('corrupt-literal-hash','js/services/playbackService.js','playViaWebTorrent(this.magnetForPlayback, {','playViaWebTorrent(this.magnetForPlayback.toLowerCase(), {'), + ('drop-hosted-hint','js/services/playbackService.js','hostedUrlWebSeed: httpsUrl,','hostedUrlWebSeed: "",'), + ('drop-terminal-error','js/services/playbackStrategyService.js','context.showError(displayMessage);','/* deliberately suppress terminal UI error */'), +] +results=[] +for name,path,old,new in mutants: + p=root/path; original=p.read_text(); assert original.count(old)==1 + try: + p.write_text(original.replace(old,new)); + r=subprocess.run(['env','-u','NODE_TEST_CONTEXT','node',str(root/'tests/fleet-playback-fallback.test.mjs')],capture_output=True,text=True,timeout=10) + Path('/tmp/bitvid-playback-qa-1/'+name+'.log').write_text(r.stdout+r.stderr) + assert r.returncode!=0, name+' survived' + results.append({'mutant':name,'exit':r.returncode,'failed':[l for l in r.stdout.splitlines() if l.startswith('not ok')]}) + finally:p.write_text(original) +print(json.dumps(results,indent=2)) +``` + +Pre-gate checkpoint: regression commit `025771a84d95c05842193631c1ace70539558368`. +Merged local unstable into own branch at `acb2fdef3baf74c2526babf79eea8a2451a3aa46`; +sandbox ORIG_HEAD lock initially read-only, then approved elevated local merge succeeded. +Post-merge focused check:4/4, exit0; `git diff --check`: exit0. New RULE-CHANGES read. +Full+submit scheduling explicitly waits behind client then storage per dispatch13:43:52Z. + +## Granted merged integration check + +Authoritative dispatch mailbox grant header:2026-10-20T14:49:35Z; consumed14:49:46Z. +Its initial body said14:11; dispatch corrected this explicitly in14:49:55 mail, consumed14:50:25Z. +Request13:43:52→grant14:49:35=65m43s resource/coordination wait; grant consumption11s. +Storage release14:09:38→grant14:49:35=39m57s dispatch foreground/wakeup latency, not worker/test time. +The earlier worker reply interpreted14:11 as intended grant time; this was incorrect. It was a stale body timestamp only, with no14:11 send or delivery delay. Dispatch wake/checkpoint lag39m57s is the valid delay metric; grant consumption was11s. + +Merged verified unstable7f99c045 into own branch at `124be14310a314bd4ca99d075a7aacc207c238ac`. +`env -u NODE_TEST_CONTEXT node tests/fleet-playback-fallback.test.mjs`: exit0,4/4. +`env -u NODE_TEST_CONTEXT npm run lint`: exit0,11/11 on merged tree (approved outside sandbox for git spawn). +`git diff --check`: exit0. +`env -u NODE_TEST_CONTEXT UNIT_TEST_TIMEOUT_MS=60000 npm test`: exit0, all429 unit files passed, including the new regression; completed14:55:44Z. Actual process exit was checked, not a log-reading command. No timeout/failure or assertion changes. +No browser/build repeat: relevant playback/magnet chain unchanged by predecessor landings. + +## Durable landing and closure + +Submitted `64143f42ed890687093e0fec7a6191189f073cb2` at14:56:10.819Z through the unchanged normal queue. +During diagnostic, target advanced only with dispatch documentation (`d88716e3`,3 files/19 insertions); +normal lander merged the current target and ran its unchanged install + npm test gate. +Durable land_status and result JSON report **landed** at15:02:17.419Z, main SHA +`1bb3a4fc452969f060c09a286b72b8cd7ac4123e`, normal gate **exit0**. +`git merge-base --is-ancestor 64143f42ed890687093e0fec7a6191189f073cb2 unstable`: exit0. +`git log -1 unstable`: `3f9c26e361ec977b9bd6435c987cf29b2518df3f` (later dispatch docs). +No full-gate file count is inferred from the result tail; independently run diagnostic counted429/429. +Expensive QA slot released15:02:30Z; browser had already been released13:40:46Z. +Backlog closed only after durable landing/ancestry. Closure metadata will use unchanged normal queue handling. +No new item started; dispatch asked to integrate status into shared TODO/trial outcome. + +Bounded result: no defect demonstrated in primary404/media-error fallback, viable mirror selection, or terminal +settlement. Retained4-scenario regression rejects6 meaningful mutants; no product/existing test/config edits. +Full app UI/relay path, actual WebTorrent metadata/peers/webseed consumption, post-start late stalls, HTTPS/CORS, +HLS, performance/CPU/RSS, production and wholeStage3 remain **unverified**. +Final verification work completed15:02:30Z, from start13:38:36Z (1h23m54s), including65m54s request-to-grant-consumption +resource/coordination hold. Browser sandbox loopback retry and lint/Git approved sandbox boundary work were short +tool stalls; exact per-tool blocked wall times were not independently measured. Normal queue gating14:56:10.819→15:02:17.419 +was6m6.600s. Grant wait and the39m57s dispatch wake/checkpoint lag overlap and must not be added. diff --git a/docs/testing/playback-qa-1-integrity.md b/docs/testing/playback-qa-1-integrity.md new file mode 100644 index 00000000..fc86cb52 --- /dev/null +++ b/docs/testing/playback-qa-1-integrity.md @@ -0,0 +1,64 @@ +# playback-qa-1 test integrity + +Approved by dispatch mailbox `20261020T134149-dispatch-c71c3f.md`. +Scope: new dependency-free `tests/fleet-playback-fallback.test.mjs`; no existing expectations changed. + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-playback-primary-404 + given: "dual-source video; primary probe returns definitive 404" + when: "actual app coordinator invokes strategy and playback session" + then: "torrent selected once; literal hash and original/hosted webseeds preserved; source/UI state and session settle" + - id: SCN-playback-primary-media-error + given: "dual-source video; successful probe followed by media error and stalled events" + when: "actual playback watchdog receives the failures" + then: "torrent selected once with preserved handoff and cleared session" + - id: SCN-playback-viable-mirror + given: "primary 404 and duplicated viable hosted mirror" + when: "actual playback orchestration tries sources" + then: "mirror selected once, URL success/source surfaced, no torrent invocation" + - id: SCN-playback-terminal-both-fail + given: "primary 404 and alternate handler rejection" + when: "actual playback orchestration runs" + then: "promise settles with error/null source, one UI error, session/promise released" + observable_outcomes: + - "app source, returned source/error/alternate instance, status and surfaced errors" + - "torrent boundary payload preserves exact original xt/hash and webseed hints" + - "ordered probe/media attempts and exactly one alternate selection" + - "no active app/strategy session or pending result after settlement" + determinism_controls: + - "fixed magnets/URLs; no sockets, peers, fetch or external service" + - "EventTarget media double emits controlled playing/error/stalled events" + - "probe and torrent IO controlled at injected app boundaries" + - "real coordinator, strategy, PlaybackService, magnet derivation and validator" + - "2s test failure bound; no sleeps/retries or replaced selection/watchdog logic" + anti_cheat_rationale: + prevents: + - "hard-coded source/result without actual alternate invocation" + - "over-mocking orchestration or magnet normalization" + - "skipping viable mirror or duplicate alternate selection" + - "hash corruption, lost hosted hint, suppressed UI error, unclosed session" + - "snapshot rubber-stamping and retry/sleep-based flake masking" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Six deliberate product mutations were made only in an isolated copy under `/tmp/bitvid-playback-qa-1/tree`. +Each failed exit1; originals were restored in finally, and the complete original suite passed4/4 again. + +| Mutant | Scenarios rejecting it | +| --- | --- | +| Disable torrent alternate entirely |404, media error, both fail | +| Drop mirror forwarding in strategy |viable mirror | +| Lowercase handoff magnet, corrupting literal mixed-case hash |404, media error, both fail | +| Erase hostedUrlWebSeed hint |404, media error, both fail | +| Suppress terminal UI error |both fail | +| Retain strategy active session on finish |all4 | + +Each new scenario rejects at least one mutant. No product mutation was committed. + +Fidelity limits and native local decoding results: [evidence](playback-qa-1-evidence.md). +The retained media double validates orchestration, not a browser decoder or real WebTorrent. diff --git a/docs/testing/playback-qa-2-360-feed.png b/docs/testing/playback-qa-2-360-feed.png new file mode 100644 index 00000000..3138690a Binary files /dev/null and b/docs/testing/playback-qa-2-360-feed.png differ diff --git a/docs/testing/playback-qa-2-360-profile.png b/docs/testing/playback-qa-2-360-profile.png new file mode 100644 index 00000000..36406f57 Binary files /dev/null and b/docs/testing/playback-qa-2-360-profile.png differ diff --git a/docs/testing/playback-qa-2-360-upload-validation.png b/docs/testing/playback-qa-2-360-upload-validation.png new file mode 100644 index 00000000..86d7723c Binary files /dev/null and b/docs/testing/playback-qa-2-360-upload-validation.png differ diff --git a/docs/testing/playback-qa-2-assets-plan.md b/docs/testing/playback-qa-2-assets-plan.md new file mode 100644 index 00000000..e1b03a00 --- /dev/null +++ b/docs/testing/playback-qa-2-assets-plan.md @@ -0,0 +1,216 @@ +# playback-qa-2: genuine cached asset plan, not a browser pass + +Read-only preparation authorized by dispatch mailbox20261020T151044; no browser/build/download performed. +Inspection HEAD: `8d7e60ebf1761cda479d18a011dc382af5704f7b` (QA2 blocker evidence commit). +Actual app/mobile outcome remains blocked; no further viewport or core-flow validation claimed. + +## Found exact source packages, no install needed + +`npm cache ls buffer`, `npm cache ls base64-js`, `npm cache ls ieee754` found exact tarballs in +`/home/user/.npm/_cacache`. Offline `cacache.get` using existing `/usr/share/nodejs/cacache` +read/verified cached content. Recomputed SHA512 matched each cache integrity string AND the corresponding +version's `dist.integrity` in the cached registry packument. No registry/CDN request was made. +This establishes consistency with local cached registry metadata, not new online verification. +Tar package.json confirms names/versions/main/dependencies; genuine source index.js hashes are below. + +| Package | Tarball bytes | Tarball SHA256 | index.js SHA256 | +| --- | ---: | --- | --- | +|buffer@6.0.3 |23108 |`555b6f49224afcf6b2c5a9aea7cd34a7f443f395b10532bc5580022d57aa51f5` |`c25853fd31addfce188b01061fe85bfe667d5fb6c7a7bbb1c83d0ddfd8627acc` | +|base64-js@1.5.1 |4109 |`b1b7a945b52685269083425216d6597e33d97bf21699d656e92fdb3eb5210a85` |`829eadd8a1a441d25be0cb93b00e16a0d0c20fd294db95d8f2ed87e6954b7182` | +|ieee754@1.2.1 |2886 |`8ef14b9b397e339db89db97881fb714f49319d8f0eb1275901f45567b28f9dac` |`fe3e2b20c1ebb57c3b390c8f685ad1e04e6f493517fa7e432f4351bb77a1a4d0` | + +Buffer6.0.3 declares base64-js^1.3.1 and ieee754^1.2.1; cached1.5.1/1.2.1 satisfy these ranges. +Neither dependency declares further runtime dependencies. Package license files must accompany scratch extraction. +No installed top-level Buffer package is required; tarballs provide genuine source without `npm install`. +No exact cached jsDelivr `+esm` response was found in the npm-cache inventory; npm source is CommonJS. + +## Proposed zero-network scratch adapter, approval still required + +1. Re-read exact cached tarballs via the cache inspection script below; verify all hashes/integrities before extraction. +2. Extract only safe regular files under their package prefixes into a private scratch node_modules tree + (`buffer`, `base64-js`, `ieee754`), rejecting absolute/traversal/symlink/hardlink members. +3. Create scratch entry `export { Buffer } from './node_modules/buffer/index.js';`. +4. Use existing esbuild0.27.3 only to bundle that real entry/source/dependency graph as browser ESM. + Example command, **not run during preparation**: + +```sh +/home/user/Documents/GitHub/bitvid/node_modules/.bin/esbuild /tmp/bitvid-playback-qa-2-assets/entry.mjs --bundle --platform=browser --format=esm --outfile=/tmp/bitvid-playback-qa-2-assets/buffer-6.0.3.mjs +``` + +5. Record output SHA256 and esbuild metafile; require exactly the pinned three genuine package inputs. + Validate Buffer.from/toString/base64/float/read-write round trips against real Node Buffer before browser use. + These are adapter sanity checks, not replacements for actual app behavior. +6. With a new explicit browser grant, map **only** original import URL + `https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm` to those local reconstructed ESM bytes using request fulfillment. + Every other non-local HTTP/WebSocket request stays blocked/logged; never call route.continue for the mapped CDN. + Product files/import graph/global setup remain unchanged. No fake Buffer/toolkit or no-op module. +7. If another hard offline dependency appears, stop/report/release again. No speculative substitution or public fetching. + +Fidelity: exact npm Buffer6.0.3 implementation and pinned genuine dependencies, repackaged by local esbuild; +**not byte-identical to jsDelivr's generated ESM, nor CDN availability/loader parity qualification**. +Actual app/bootstrap/UI can then be exercised with a documented cached-library transport adapter only if approved. +No claim yet that this plan makes the full app boot; the first pass stopped before subsequent dependencies ran. + +## CSS: byte-verified existing output candidate + +Existing dispatch build asset (read-only): +`/home/user/Documents/GitHub/bitvid-dispatch/dist/css/tailwind.generated.css`,398371 bytes, +SHA256 `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`. +Dispatch HEAD read as `8abbaa77b499cbf054ed55147815fb9403f702c3`. +Compared465 input files byte-for-byte against this QA checkout: **0 different inputs**. +Compared tailwind config content globs(index/embed/components/views/js/torrent), all source CSS except generated +output,postcss config,tailwind config,package.json and package-lock.json. The sorted[path,ownSHA256,dispatchSHA256] +JSON manifest SHA256 is `58743f25fc1ff7333c2bbc5b2aa9a60b3b833428b81e772bbbf3fe3e7b1b4177`. +`git diff 7f99c045..HEAD -- css tailwind.config.cjs postcss.config.cjs index.html embed.html components views js torrent package.json package-lock.json` +was empty; dispatch's prior verified preview build at7f99c045 has identical current relevant inputs. +No inference from commit names alone: this check compared actual files on disk. + +Dispatch installed versions read: postcss8.5.6,postcss-cli11.0.1,postcss-import15.1.0,tailwindcss3.4.19, +autoprefixer10.4.24; esbuild0.27.3 also available in existing main dependencies. +No build was run. After approval, copy only this genuine CSS output into scratch after re-verifying output hash +and input equality, then serve the missing `/css/tailwind.generated.css` path from scratch. Do not modify +or regenerate another area's output. Alternative: later authorized own local CSS build; not needed if approved copy. +This is output reuse, not an independent fresh-build/release qualification. + +## Existing harness and remaining dependencies + +`tests/e2e/helpers/bitvidTestFixture.ts` supplies relay overrides,programmatic test-only login and synthetic event +seeding; no dedicated Buffer/CDN offline mapping was found by: +`rg -n 'route\(|routeWebSocket|buffer@6|cdn.jsdelivr' tests/e2e/helpers scripts/playwright` (no matches). +Do not rewrite helpers/config. `vendor/nostr-tools.bundle.min.js` is already local(110545 bytes), +SHA256 `2b063a02d0f449d715fc3bb892364c7a859f7c2e443af15321a2bcbd561c4e02`. +`js/nostrToolsBootstrap.js` attempts this local bundle and has public fallback imports; all unmatched fallbacks +must remain blocked. No toolkit mutation or claim that later dynamic dependency loading was verified. + +## Integrity records + +```json +[ + { + "name": "buffer", + "version": "6.0.3", + "key": "make-fetch-happen:request-cache:https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "bytes": 23108, + "cacheIntegrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "sha256": "555b6f49224afcf6b2c5a9aea7cd34a7f443f395b10532bc5580022d57aa51f5", + "registryIntegrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "registryMatch": true, + "file": "/tmp/bitvid-playback-qa-2-assets/buffer-6.0.3.tgz", + "package": { + "name": "buffer", + "version": "6.0.3", + "main": "index.js", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + }, + "license": "MIT" + }, + "indexSha256": "c25853fd31addfce188b01061fe85bfe667d5fb6c7a7bbb1c83d0ddfd8627acc" + }, + { + "name": "base64-js", + "version": "1.5.1", + "key": "make-fetch-happen:request-cache:https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "bytes": 4109, + "cacheIntegrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "sha256": "b1b7a945b52685269083425216d6597e33d97bf21699d656e92fdb3eb5210a85", + "registryIntegrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "registryMatch": true, + "file": "/tmp/bitvid-playback-qa-2-assets/base64-js-1.5.1.tgz", + "package": { + "name": "base64-js", + "version": "1.5.1", + "main": "index.js", + "dependencies": null, + "license": "MIT" + }, + "indexSha256": "829eadd8a1a441d25be0cb93b00e16a0d0c20fd294db95d8f2ed87e6954b7182" + }, + { + "name": "ieee754", + "version": "1.2.1", + "key": "make-fetch-happen:request-cache:https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "bytes": 2886, + "cacheIntegrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "sha256": "8ef14b9b397e339db89db97881fb714f49319d8f0eb1275901f45567b28f9dac", + "registryIntegrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "registryMatch": true, + "file": "/tmp/bitvid-playback-qa-2-assets/ieee754-1.2.1.tgz", + "package": { + "name": "ieee754", + "version": "1.2.1", + "main": "index.js", + "dependencies": null, + "license": "BSD-3-Clause" + }, + "indexSha256": "fe3e2b20c1ebb57c3b390c8f685ad1e04e6f493517fa7e432f4351bb77a1a4d0" + } +] +``` + +## Offline cache-inspection script + +Requires only existing system cacache; writes tarball copies/report to own scratch, no extraction/install/build. + +```js +const cache = require('/usr/share/nodejs/cacache'); +const fs=require('node:fs/promises'); +const crypto=require('node:crypto'); +(async()=>{ + const results=[]; + for(const [name,version] of [['buffer','6.0.3'],['base64-js','1.5.1'],['ieee754','1.2.1']]){ + const key=`make-fetch-happen:request-cache:https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`; + const r=await cache.get('/home/user/.npm/_cacache',key); + const actual='sha512-'+crypto.createHash('sha512').update(r.data).digest('base64'); + if(r.integrity!==actual)throw Error('cache integrity mismatch '+name); + const file=`/tmp/bitvid-playback-qa-2-assets/${name}-${version}.tgz`;await fs.writeFile(file,r.data); + let registryIntegrity=null; + try{const p=await cache.get('/home/user/.npm/_cacache',`make-fetch-happen:request-cache:https://registry.npmjs.org/${name}`);registryIntegrity=JSON.parse(p.data).versions?.[version]?.dist?.integrity||null;}catch{} + results.push({name,version,key,bytes:r.data.length,cacheIntegrity:r.integrity,sha256:crypto.createHash('sha256').update(r.data).digest('hex'),registryIntegrity,registryMatch:registryIntegrity===actual,file}); + } + await fs.writeFile('/tmp/bitvid-playback-qa-2-assets/cache.json',JSON.stringify(results,null,2));console.log(JSON.stringify(results,null,2)); +})().catch(e=>{console.error(e);process.exitCode=1;}); + +``` + +Current action: retain evidence/plan, no browser/full/build while storage holds the slot. Request dispatch review +of exact local fulfillment+verified CSS reuse, then a new bounded browser grant before executing any adapter/build. + +## Approved prepared mirror and frozen snapshot (latest checkpoint) + +Dispatch15:14:36 mailbox approved preparation/copy of its genuine prepared mirror and verified dist, plus +docs-only normal submission. **No second browser grant yet.** This supersedes the earlier request to build +our own adapter: no esbuild/build/install was run by QA. Root's prepared bundle/provenance are copied as approved. + +Own snapshot: `/tmp/bitvid-playback-qa-2-snapshot`, files chmod0444 after copy. +Dispatch-owned assets were only read; no other worktree/process was changed. No symlink assets were followed. +The exact mirror and all3 tarball SHA256 values were independently verified against the earlier cache report. +Copied root provenance states esbuild0.27.3,pins buffer6.0.3/base64-js1.5.1/ieee7541.2.1,no network/hooks. +QA did not independently reproduce that bundler output or receive an esbuild input metafile; the earlier +metafile-based proposal was not executed. Actual copied bytes are bound by the approved SHA256 below. + +Independent local mirror API sanity checks: `node --input-type=module` importing copied real Buffer, exit0,4/4: +UTF8 bytes equal native Node Buffer; base64 round-trip; float64 read/write bytes equal native Buffer; subarray +shares backing memory. No app/toolkit behavior replacement was used, and these probes do not qualify app boot. + +```json +{ + "source": "/home/user/Documents/GitHub/bitvid-dispatch/dist", + "files": 811, + "totalBytes": 16145338, + "manifestSha256": "9997b48049fd01d7c7627c009373296c9a4203683522d31bc5ee82ff98e8599b", + "indexSha256": "13b12280ffea5529172d081ba30aab64bb6f7140145fa17af407a09fb60786b4", + "bufferPolyfillSha256": "9c6412a7c3a7bda9d19ba420a0fad122b4d0e60a3af68ceb43d8eb98b68e6bff", + "cssSha256": "283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3", + "mirrorSha256": "507a593508c51d1397a9a5818ad0b8dbf7e3ad810feaa87cabff87ff56cf3984" +} +``` + +The full sorted path→SHA256 manifest remains in own scratch `dist-manifest.json`; hashed summary above is durable. +Next granted pass serves this frozen **actual built app** instead of incomplete source checkout; existing local +relay/harness is retained. Only exact public Buffer import URL is fulfilled from local approved bytes and the +mirror hit logged. Every other non-local HTTP/WebSocket stays aborted/logged. No CDN fetch/pass-through,crypto +stub,toolkit override,product import rewrite or preview3000 use. Stop/release at the next hard prerequisite. + +QA2 stays BLOCKED pending resource grant/actual app result; all unrun viewport/UI checks remain unverified. diff --git a/docs/testing/playback-qa-2-boot-blocker.png b/docs/testing/playback-qa-2-boot-blocker.png new file mode 100644 index 00000000..51830806 Binary files /dev/null and b/docs/testing/playback-qa-2-boot-blocker.png differ diff --git a/docs/testing/playback-qa-2-evidence.md b/docs/testing/playback-qa-2-evidence.md new file mode 100644 index 00000000..a7f20a14 --- /dev/null +++ b/docs/testing/playback-qa-2-evidence.md @@ -0,0 +1,142 @@ +> Latest checkpoint16:08:41: [measured cold-feed render gap](playback-qa-2-render-gap.md) routed to client7. +> Actual app now boots and ingests/admits3 videos,but no cards render. Core/mobile baseline remains blocked; +> the source-checkout failure below is historical and no longer the active prerequisite. + +# playback-qa-2: actual-app offline baseline blocked + +Baseline HEAD: `ac99ae9a6bdbeae555a30f659b0720ae67e777e8`. +Assignment `filedAt`: `1bb3a4fc452969f060c09a286b72b8cd7ac4123e`. +`git diff 1bb3a4fc..HEAD -- js index.html config scripts/agent/simple-relay.mjs tests/e2e/helpers/bitvidTestFixture.ts` +was empty. Relevant actual app/harness behavior is unchanged; no product/CSS/test/helper/config edits. + +## Plan and authority + +- Use actual repository index/app modules with existing simple-relay and documented test harness relay overrides. +- One Chromium pass at360×800,390×844,1280×720; stop at the first hard offline boot blocker. +- Block/log non-local HTTP and WebSocket attempts, use only synthetic local notes/test identities if needed. +- Inspect feed/nav/Profile/Upload/required fields/overflow/focus only after successful actual app boot. +- Preserve precise failure evidence; no module substitution, public downloads, build/full suite or product edits. + +Browser-only slot granted in dispatch mailbox20261020T150619, consumed before preparation. +START sent to dispatch15:08:13Z **before** browser/server launch; reservation was in `.task`. +Actual execution15:08:16.718Z→15:08:32.952Z (16.234s); no browser/gate contention observed during this pass, +no resource waiting between START and execution, no claim about unrelated host work. + +## Actual result + +`node /tmp/bitvid-playback-qa-2/baseline.mjs`: process exit0, **scenario blocked**, not a passing app scenario. +Chromium154.0.8037.97, fresh context, first viewport360×800. Existing app is served directly from this worktree +on127.0.0.1:31847; existing simple-relay runs on private31848/31849 using its unchanged API. +Temporary own node_modules symlink reused existing installed dependencies read-only and was removed afterward. +Service workers were disabled to keep interception deterministic; no application methods/toolkit were substituted. + +`page.waitForFunction(() => typeof window.__bitvidTest__ === 'object', null, {timeout:15000})` +timed out. This is the documented fixture harness-ready bound, not a retry or relaxed assertion. +Static import chain `js/index.js:4` → `js/bufferPolyfill.js:1` requires: + +```js +import { Buffer } from "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm"; +``` + +The network guard blocked that request. Static dependency loading cannot complete, so the app's early +`installTestHarness()` statement is never reached. Exactly1 blocked non-local HTTP attempt was observed; +0 WebSocket attempts reached the non-local guard,0 pageerror events. This is an offline boot/fixture constraint, +not a demonstrated mobile-layout product defect. Missing assets are a separate environment limitation: +`http://127.0.0.1:31847/css/tailwind.generated.css` returned404; own worktree has no generated CSS/dist, +and building is excluded from this pass. Screenshot shows unstyled static shell/search/logo, not a hydrated app. + +[Captured360×800 screenshot](playback-qa-2-boot-blocker.png) + +| Viewport / flow | Outcome | +| --- | --- | +|360×800 actual app boot / harness |BLOCKED:15s harness timeout | +|390×844 /1280×720 |Not run after first hard blocker | +| Feed initial hydration/local seeded cards |Unverified; no synthetic events needed/seeded | +| Home/Explore/ForYou navigation |Unverified | +| Profile/Upload open-close / required fields |Unverified | +| Card controls/touch target sizes/overflow/focus |Unverified; unstyled shell cannot qualify layout | + +No real signer,peers,buckets,public media,production data or external publishing was used. +No successful non-local page requests were allowed. Actual local page bootstrap was attempted; this is not +module-harness success, full app/mobile qualification, or a Firefox/KNOWN_ISSUES hydration-flake diagnosis. + +## Cleanup and routing + +Own browser/context and app/relay servers closed in finally at15:08:32.952Z. Browser slot RELEASE explicitly +sent to dispatch15:08:53Z; .task updated. Preview3000 was untouched; no other processes stopped. +Blocker sent with exact trigger and evidence; waiting on dispatch to route an offline asset/harness strategy. +Do not permit public CDN traffic or substitute Buffer/app/modules and silently call this an actual-app pass. +No full/normal queue submission without dispatch's explicit slot for this new item. + +## Observed browser errors + +```text +Failed to load resource: the server responded with a status of 404 (Not Found) +Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector +``` + +## Reproduction script + +Create `/tmp/bitvid-playback-qa-2`; temporarily link existing installed node_modules into this worktree if absent, +then save/run this script using the granted browser-only slot. It closes its own resources on the first blocker. +Remove that own symlink after the process exits. Requires existing main checkout Playwright/dependencies; +no installing or downloading assets is part of this reproduction. + +```js +import { createServer } from 'node:http'; +import { readFile } from 'node:fs/promises'; +import { resolve, extname } from 'node:path'; +import { chromium } from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import { startRelay } from '/home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs'; +const root='/home/user/Documents/GitHub/bitvid-playback-qa'; +const out='/tmp/bitvid-playback-qa-2'; +const {writeFile}=await import('node:fs/promises'); +const result={started:new Date().toISOString(),viewports:[],blockedHttp:[],blockedWs:[],failedLocal:[],pageErrors:[],consoleErrors:[]}; +const server=createServer(async(req,res)=>{ + const url=new URL(req.url,'http://127.0.0.1:31847'); + let path=decodeURIComponent(url.pathname);if(path==='/')path='/index.html'; + const file=resolve(root,'.'+path); + if(!file.startsWith(root+'/')||path.includes('/.')||!['.html','.js','.mjs','.json','.css','.svg','.png','.jpg','.jpeg','.webp','.ico','.woff','.woff2','.ttf','.wasm','.map','.webmanifest'].includes(extname(file))){res.writeHead(404);res.end();return;} + try{const data=await readFile(file);res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.mjs':'text/javascript','.json':'application/json','.css':'text/css','.svg':'image/svg+xml','.wasm':'application/wasm','.png':'image/png','.webmanifest':'application/manifest+json'})[extname(file)]||'application/octet-stream');res.end(data);} + catch{res.writeHead(404);res.end();} +}); +let relay,browser; +try{ + await new Promise((ok,no)=>{server.once('error',no);server.listen(31847,'127.0.0.1',ok);}); + relay=startRelay(31848,{httpPort:31849}); + browser=await chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']}); + result.browserVersion=browser.version(); + const context=await browser.newContext({viewport:{width:360,height:800},serviceWorkers:'block'}); + await context.route('**/*',route=>{const u=new URL(route.request().url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))return route.continue();result.blockedHttp.push(route.request().url());return route.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',ws=>{const u=new URL(ws.url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))ws.connectToServer();else{result.blockedWs.push(ws.url());ws.close();}}); + await context.addInitScript(()=>{localStorage.setItem('hasSeenDisclaimer','true');localStorage.setItem('__bitvidTestMode__','1');localStorage.setItem('__bitvidTestRelays__',JSON.stringify(['ws://127.0.0.1:31848']));window.__bitvidTestRelays__=['ws://127.0.0.1:31848'];localStorage.setItem('bitvid_admin_whitelist_mode','false');}); + const page=await context.newPage(); + page.on('pageerror',e=>result.pageErrors.push(e.message)); + page.on('console',m=>{if(m.type()==='error')result.consoleErrors.push(m.text().slice(0,700));}); + page.on('response',r=>{if(r.status()>=400&&new URL(r.url()).hostname==='127.0.0.1')result.failedLocal.push({url:r.url(),status:r.status()});}); + for(const viewport of [{width:360,height:800},{width:390,height:844},{width:1280,height:720}]){ + await page.setViewportSize(viewport); + await page.goto('http://127.0.0.1:31847/?__test__=1&__testRelays__=ws%3A%2F%2F127.0.0.1%3A31848',{waitUntil:'domcontentloaded'}); + const v={viewport,flows:{}};result.viewports.push(v); + try{ + await page.waitForFunction(()=>typeof window.__bitvidTest__==='object',null,{timeout:15000}); + await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');return !!m.getApplication();},null,{timeout:15000}); + await page.locator('[data-testid="video-list"]').waitFor({state:'visible',timeout:15000}); + v.flows.boot='passed'; + }catch(e){v.flows.boot='blocked';v.blocker=e.message.split('\n')[0];v.dom=await page.locator('body').innerText();await page.screenshot({path:root+'/docs/testing/playback-qa-2-boot-blocker.png',fullPage:true});result.firstHardBlocker=true;break;} + v.layout=await page.evaluate(()=>({clientWidth:document.documentElement.clientWidth,scrollWidth:document.documentElement.scrollWidth,focus:document.activeElement?.tagName})); + // Stop at a new blocker rather than modifying product/fixture behavior. + v.flows.remaining='actual app booted; further actions require live harness state inspection'; + await page.screenshot({path:out+`/${viewport.width}.png`,fullPage:true}); + } + await context.close(); +} catch(e){result.runError=e.stack;process.exitCode=1;} +finally{ + if(browser)await browser.close(); + if(relay)await relay.close(); + await new Promise(ok=>server.close(ok)); + result.ended=new Date().toISOString();result.resourcesClosed=true; + await writeFile(out+'/result.json',JSON.stringify(result,null,2)); + console.log(JSON.stringify({started:result.started,ended:result.ended,browserVersion:result.browserVersion,viewports:result.viewports.map(v=>({viewport:v.viewport,flows:v.flows,blocker:v.blocker})),blockedHttp:result.blockedHttp,blockedWs:result.blockedWs,failedLocal:result.failedLocal,pageErrors:result.pageErrors,runError:result.runError,resourcesClosed:result.resourcesClosed})); +} +``` diff --git a/docs/testing/playback-qa-2-final-driver.md b/docs/testing/playback-qa-2-final-driver.md new file mode 100644 index 00000000..aec61f37 --- /dev/null +++ b/docs/testing/playback-qa-2-final-driver.md @@ -0,0 +1,102 @@ +# QA2 granted final attempt: exact scratch driver + +Unretained diagnostic/browser runner, not an added regression test. SHA256 a420415d0b4e0da1713386458e83c26db4db0d089470c441d043e72920f0967d. + +```js +import {createServer} from 'node:http'; +import {createHash} from 'node:crypto'; +import {mediaResponse} from './media-fixture.mjs'; +import {readFile,writeFile} from 'node:fs/promises'; +import {resolve,extname} from 'node:path'; +import {createInterface} from 'node:readline'; +import {createRequire} from 'node:module'; +import {chromium} from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import {startRelay} from '/home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs'; +const require=createRequire('/home/user/Documents/GitHub/bitvid/package.json'); +const {finalizeEvent,verifyEvent}=require('nostr-tools'); +import {NOTE_TYPES,validateEventStructure} from '/home/user/Documents/GitHub/bitvid-playback-qa/js/nostrEventSchemas.js'; +const base='/tmp/bitvid-playback-qa-2-final',root=base+'/dist'; +const evidence='/home/user/Documents/GitHub/bitvid-playback-qa/docs/testing'; +const report={driver:'noninteractive bounded actions',started:new Date().toISOString(),mirrorHits:[],blockedHttp:[],blockedWs:[],failedLocal:[],pageErrors:[],consoleErrors:[],actions:[]}; +const sk=new Uint8Array(32).fill(0x7b); +const fixtureBytes=await readFile(base+'/fixtures/qa2-unplayed.webm'); +if(createHash('sha256').update(fixtureBytes).digest('hex')!=='d911005a5bca7fb1f3e5aa2fbee701b6476c9baf93460264c2aa5105dd0b3c69')throw Error('local genuine fixture hash changed'); +const frozenManifest=await readFile(base+'/dist-manifest.json'); +if(createHash('sha256').update(frozenManifest).digest('hex')!=='2747d37916362971a5abe05db4018adf31b5c7031b48d8e4bd6eb1e19b9fa927')throw Error('integrated artifact manifest changed'); +for(const [name,hash] of Object.entries(JSON.parse(frozenManifest)))if(createHash('sha256').update(await readFile(root+'/'+name)).digest('hex')!==hash)throw Error('integrated artifact byte changed: '+name); +const server=createServer(async(req,res)=>{ + let path=decodeURIComponent(new URL(req.url,'http://127.0.0.1:31847').pathname);if(path==='/qa2-unplayed.webm'){const r=mediaResponse(req.method,req.headers.range,fixtureBytes);res.writeHead(r.status,r.headers);res.end(r.body);return;}if(path==='/')path='/index.html';const f=resolve(root,'.'+path); + if(!f.startsWith(root+'/')||path.includes('/.')){res.writeHead(404);res.end();return;} + try{const b=await readFile(f);res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.mjs':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.wasm':'application/wasm','.png':'image/png','.webmanifest':'application/manifest+json'})[extname(f)]||'application/octet-stream');res.end(b);}catch{res.writeHead(404);res.end();} +}); +let browser,relay,context,page,closed=false,hardDeadline; +async function finish(){if(closed)return;closed=true;clearTimeout(hardDeadline);if(context)await context.close();if(browser)await browser.close();if(relay)await relay.close();await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await writeFile(base+'/result.json',JSON.stringify(report,null,2));console.log(JSON.stringify({type:'CLOSED',ended:report.ended,mirrorHits:report.mirrorHits.length,blockedHttp:report.blockedHttp,blockedWs:report.blockedWs,failedLocal:report.failedLocal,pageErrors:report.pageErrors}));} +hardDeadline=setTimeout(()=>{report.deadlineFailure='120s bounded runner deadline';void finish().finally(()=>process.exit(1));},120000); +try{ + await new Promise((ok,no)=>{server.once('error',no);server.listen(31847,'127.0.0.1',ok);}); + relay=startRelay(31848,{httpPort:31849}); + for(let i=0;i<3;i++){const title=i===0?'QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls':'QA local synthetic video '+i;const event=finalizeEvent({kind:30078,created_at:Math.floor(Date.now()/1000)-i,tags:[['d','qa2-'+i],['t','video'],['s','nostr:qa2-'+i],['title',title],['url','http://127.0.0.1:31847/qa2-unplayed.webm']],content:JSON.stringify({version:3,title,url:'http://127.0.0.1:31847/qa2-unplayed.webm',videoRootId:'qa2-'+i,mode:'dev',isPrivate:false,deleted:false})},sk);if(!validateEventStructure(NOTE_TYPES.VIDEO_POST,event).valid||!verifyEvent(JSON.parse(JSON.stringify(event))))throw Error('canonical real signed fixture invalid');relay.seedEvent(event);} + const fixtureUrl='http://127.0.0.1:31847/qa2-unplayed.webm'; + const head=await fetch(fixtureUrl,{method:'HEAD'}),whole=await fetch(fixtureUrl),part=await fetch(fixtureUrl,{headers:{Range:'bytes=0-31'}}); + const wholeBytes=Buffer.from(await whole.arrayBuffer()),partBytes=Buffer.from(await part.arrayBuffer()); + if(head.status!==200||head.headers.get('content-type')!=='video/webm'||Number(head.headers.get('content-length'))!==fixtureBytes.length||whole.status!==200||!wholeBytes.equals(fixtureBytes)||part.status!==206||!partBytes.equals(fixtureBytes.subarray(0,32)))throw Error('genuine local media HTTP preflight failed'); + report.fixtureHttp={head:head.status,get:whole.status,range:part.status,bytes:fixtureBytes.length,contentType:head.headers.get('content-type')}; + browser=await chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']});report.browserVersion=browser.version(); + context=await browser.newContext({viewport:{width:360,height:800},serviceWorkers:'block'}); + const mirror=await readFile(base+'/buffer-6.0.3-local.mjs'); + await context.route('**/*',r=>{const url=r.request().url();if(url==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){report.mirrorHits.push(url);return r.fulfill({body:mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}const u=new URL(url);if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))return r.continue();report.blockedHttp.push(url);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',ws=>{const u=new URL(ws.url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))ws.connectToServer();else{report.blockedWs.push(ws.url());ws.close();}}); + await context.addInitScript(()=>{localStorage.setItem('hasSeenDisclaimer','true');localStorage.setItem('__bitvidTestMode__','1');localStorage.setItem('__bitvidTestRelays__',JSON.stringify(['ws://127.0.0.1:31848']));window.__bitvidTestRelays__=['ws://127.0.0.1:31848'];localStorage.setItem('bitvid_admin_whitelist_mode','false');}); + page=await context.newPage();page.setDefaultTimeout(10000); + page.on('pageerror',e=>report.pageErrors.push(e.message));page.on('console',m=>{if(m.type()==='error')report.consoleErrors.push(m.text().slice(0,700));});page.on('response',r=>{if(r.status()>=400&&new URL(r.url()).hostname==='127.0.0.1')report.failedLocal.push({url:r.url(),status:r.status()});}); + await page.goto('http://127.0.0.1:31847/?__test__=1&__testRelays__=ws%3A%2F%2F127.0.0.1%3A31848',{waitUntil:'domcontentloaded'}); + try{await page.waitForFunction(()=>typeof window.__bitvidTest__==='object',null,{timeout:15000});await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');return !!m.getApplication();},null,{timeout:15000});await page.locator('[data-testid="video-list"]').waitFor({state:'visible',timeout:15000});report.boot='passed';console.log(JSON.stringify({type:'BOOTED',viewport:{width:360,height:800},body:(await page.locator('body').innerText()).slice(0,3000)}));} + catch(e){report.boot='blocked';report.bootError=e.message;await page.screenshot({path:evidence+'/playback-qa-2-second-boot-blocker.png',fullPage:true});console.log(JSON.stringify({type:'BLOCKED',error:e.message,body:(await page.locator('body').innerText()).slice(0,1500)}));await finish();process.exit(0);} + + // Noninteractive: no stdin/readline loop; each action is awaited exactly once. + const check=(condition,message)=>{if(!condition)throw new Error(message);}; + const step=async(name,action)=>{const item={name,at:new Date().toISOString()};report.actions.push(item);try{item.result=await action();item.ok=true;console.log(JSON.stringify({type:'STEP',name,ok:true,result:item.result}));return item.result;}catch(e){item.ok=false;item.error=e.message;console.log(JSON.stringify({type:'STEP',name,ok:false,error:e.message}));throw e;}}; + const capture=async(name)=>{await page.screenshot({path:evidence+'/'+name,fullPage:true});report.actions.push({screenshot:name});}; + const layout=async()=>page.evaluate(()=>({width:innerWidth,documentWidth:document.documentElement.scrollWidth,cardBounds:[...document.querySelectorAll('[data-testid="video-card"]')].map(e=>{const r=e.getBoundingClientRect();return {x:r.x,right:r.right,width:r.width};}),smallControls:[...document.querySelectorAll('button,a,input')].filter(e=>e.getClientRects().length&&getComputedStyle(e).visibility!=='hidden').map(e=>{const r=e.getBoundingClientRect();return {id:e.id,label:(e.getAttribute('aria-label')||e.textContent||'').trim().slice(0,80),width:r.width,height:r.height};}).filter(x=>x.width>0&&x.height>0&&(x.width<44||x.height<44))})); + const modalFocus=async(root)=>{const samples=[];for(let i=0;i<6;i++){await page.keyboard.press('Tab');samples.push(await page.evaluate(selector=>{const el=document.activeElement;return {inside:!!document.querySelector(selector)?.contains(el),id:el?.id,tag:el?.tagName,outline:getComputedStyle(el).outlineStyle,shadow:getComputedStyle(el).boxShadow};},root));}check(samples.every(x=>x.inside),'keyboard focus escaped '+root);return samples;}; + try{ + await step('actual toolkit ready',async()=>{ + const event=[...relay.getEvents().values()].find(e=>e.kind===30078); + const ready=await page.evaluate(async(seed)=>{ + const m=await import('/js/nostrToolsBootstrap.js'),t=await m.nostrToolsReady; + const callable=['getPublicKey','finalizeEvent','verifyEvent'].every(k=>typeof t?.[k]==='function'); + const state={callable,canonicalIdentity:t===window.__BITVID_CANONICAL_NOSTR_TOOLS__,frozen:Object.isFrozen(t),mockFlag:!!window.__TEST_MODE__||window.process?.env?.NODE_ENV==='test'}; + if(!callable)return state; + const sk=new Uint8Array(32).fill(0x7b),clean=JSON.parse(JSON.stringify(seed)); + state.validSeed=t.verifyEvent(clean)===true;state.tamperedRejected=t.verifyEvent({...JSON.parse(JSON.stringify(seed)),content:'tampered local challenge'})===false;state.pubkeyMatches=t.getPublicKey(sk)===seed.pubkey; + state.signed=t.finalizeEvent({kind:1,created_at:seed.created_at,tags:[],content:'QA real toolkit sanity'},sk); + return state; + },event); + const {signed,...status}=ready; + check(status.callable&&status.canonicalIdentity&&status.frozen&&!status.mockFlag&&status.validSeed&&status.tamperedRejected&&status.pubkeyMatches,'real toolkit contract unavailable: '+JSON.stringify(status)); + const {verifyEvent}=require('nostr-tools');check(verifyEvent(JSON.parse(JSON.stringify(signed)))===true,'actual toolkit signature failed independent Node verification'); + return {...status,independentSignatureVerified:true}; + }); + await step('actual application startup completes',async()=>{ + await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');await m.getApplicationReady();return !!m.getApplication();},null,{timeout:15000}); + const relayState=await page.evaluate(()=>window.__bitvidTest__.setTestRelays(['ws://127.0.0.1:31848'],{persist:false})); + check(relayState.ok===true,'documented fixture relay alignment failed'); + return {readyPromiseCompleted:true,relays:relayState.relays}; + }); + await step('three synthetic relay cards hydrate',async()=>{await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const items=await page.evaluate(()=>window.__bitvidTest__.getFeedItems());check(items.length===3,'expected exactly3 local cards,got'+items.length);return items.map(x=>({title:x.title,dTag:x.dTag}));}); + await step('local test identity login',async()=>{await page.evaluate(()=>window.__bitvidTest__.loginWithNsec('7b'.repeat(32)));await page.locator('[data-testid="profile-button"]').waitFor({state:'visible'});await page.locator('[data-testid="upload-button"]').waitFor({state:'visible'});return {profileVisible:true,uploadVisible:true};}); + for(const viewport of [{width:360,height:800},{width:390,height:844},{width:1280,height:720}]){ + await page.setViewportSize(viewport);const prefix=String(viewport.width); + await step(prefix+' trending navigation/feed',async()=>{await page.locator('#trendingLink').click();await page.waitForFunction(()=>location.hash.includes('view=trending'));await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const r=await layout();check(r.documentWidth<=r.width,'horizontal document overflow');check(r.cardBounds.every(x=>x.x>=0&&x.right<=r.width),'card extends beyond viewport');return r;}); + await capture('playback-qa-2-'+prefix+'-feed.png'); + for(const [id,view] of [['#exploreLink','explore'],['#forYouLink','for-you'],['#trendingLink','trending']])await step(prefix+' navigation '+view,async()=>{await page.locator(id).click();await page.waitForFunction(v=>location.hash.includes('view='+v),view);return {hash:await page.evaluate(()=>location.hash),headings:await page.locator('h1,h2').allTextContents()};}); + await step(prefix+' Profile open/focus/close',async()=>{await page.locator('[data-testid="profile-button"]').click();await page.locator('#profileModal').waitFor({state:'visible'});const focus=await modalFocus('#profileModal');await capture('playback-qa-2-'+prefix+'-profile.png');await page.locator('#closeProfileModal').click();await page.locator('#profileModal').waitFor({state:'hidden'});await page.waitForFunction(()=>document.activeElement?.id==='profileButton');const restored=await page.evaluate(()=>document.activeElement?.id);check(restored==='profileButton','Profile close did not restore trigger focus');return {focus,restored};}); + await step(prefix+' Upload required fields/focus/close',async()=>{const videoCount=()=>[...relay.getEvents().values()].filter(e=>e.kind===30078).length;const before=videoCount();await page.locator('[data-testid="upload-button"]').click();await page.locator('#uploadModal').waitFor({state:'visible'});await page.locator('#btn-mode-external').click();const focus=await modalFocus('#uploadModal');await page.locator('#input-title').fill('');await page.locator('#input-url').fill('');await page.locator('#input-magnet').fill('');await page.locator('#btn-submit').click();const missingTitle=await page.locator('#input-title').evaluate(e=>e.validity.valueMissing);check(missingTitle,'empty title not rejected by native validation');await page.locator('#input-title').fill('QA invalid local form');await page.locator('#btn-submit').click();await page.waitForFunction(()=>document.querySelector('#errorContainer')?.textContent.includes('Please provide at least a Video URL or Magnet Link.'));check(videoCount()===before,'invalid form published video event');await capture('playback-qa-2-'+prefix+'-upload-validation.png');const l=await layout();check(l.documentWidth<=l.width,'Upload horizontal document overflow');await page.locator('#closeUploadModal').click();await page.locator('#uploadModal').waitFor({state:'hidden'});const restored=await page.evaluate(()=>document.activeElement?.id);check(restored==='uploadButton','Upload close did not restore trigger focus');return {focus,missingTitle,noSourceRejected:true,noRelayPublish:true,restored,layout:l};}); + await step(prefix+' Home navigation',async()=>{await page.getByRole('link',{name:'Return to the bitvid home page',exact:true}).click();await page.waitForURL('**/index.html');await page.waitForFunction(()=>!!window.__bitvidTest__);await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');await m.getApplicationReady();return !!m.getApplication();},null,{timeout:15000});await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const items=await page.evaluate(()=>window.__bitvidTest__.getFeedItems());check(items.length===3,'Home expected exactly3 local cards');return {url:page.url(),cards:items.length};}); + } + report.baseline='completed'; + }catch(e){report.baseline='blocked-or-failed';report.failedStep=report.actions.findLast(x=>x.ok===false)?.name;report.failure=e.message;try{report.failureDiagnostics=await page.evaluate(async()=>{const m=await import('/js/applicationContext.js');return {appPresent:!!m.getApplication(),relayHealth:window.__bitvidTest__?.getRelayHealth(),feedItems:window.__bitvidTest__?.getFeedItems(),body:document.body.innerText.slice(0,2000)};});}catch{}await capture('playback-qa-2-resumed-stop.png');} + await finish(); +}catch(e){report.runError=e.stack;console.log(JSON.stringify({type:'ERROR',error:e.stack}));await finish();process.exitCode=1;} + +``` diff --git a/docs/testing/playback-qa-2-final-evidence.md b/docs/testing/playback-qa-2-final-evidence.md new file mode 100644 index 00000000..405d995e --- /dev/null +++ b/docs/testing/playback-qa-2-final-evidence.md @@ -0,0 +1,79 @@ +# QA2: cold-feed fixed; original overflow failure and separate completed coverage + +**Disposition:** qualification completed with known minor open mobile overflow; original strict baseline failed, separate remaining coverage completed. Dispatch accepted closure16:29:54. No product fix or whole-baseline pass claimed. + +Actual app tested: unstable b1174163556e2705ba85868e21adcaf51b44bd5f, dispatch build6da89895. +Dispatch supplied explicit exclusive browser grant16:22:06; QA START16:25:27, runner16:25:30.066→16:25:33.901 UTC (3.835s). +All owned context/browser/app+relay servers closed at end; RELEASE16:25:47; dependency symlink removed. +No browser, server or build was run during the prior client7 gate hold. Blocked on fix/artifact from16:08:41 to grant16:22:06 (13m25s). + +Original failure below is preserved. Dispatch subsequently authorized one geometry check plus independent remaining coverage; see follow-on below. + +## Original run result and limits + +`node /tmp/bitvid-playback-qa-2-final/noninteractive.mjs` process exit0 means controlled cleanup, **not baseline success**. +JSON baseline is blocked-or-failed:9 completed steps and1 failed step. Chromium154.0.8037.97, fresh360×800 context: + +- Genuine canonical browser toolkit/signature/tamper/independent-Node checks passed; application readiness and existing fixture relay alignment passed. +- Original cold-feed assertion passed: exactly3 genuine signed synthetic events became actual cards within15s, without manual refresh/render/cache/card injection. This verifies the former measured cold-feed symptom on the fixed build, with canonical fixtures. +- Test identity login and360 Trending fit passed: documentWidth360, all3 cards x76.796875/right347.203125/width270.40625. +- Actual Explore, For You and Trending clicks/hash transitions passed. Headings captured; no claim of complete feed content correctness for those views. +- Profile open,6 keyboard tabs contained within modal, close and exact profileButton focus restoration passed. +- Upload opened in External Link mode;6 tabs contained, native empty-title rejection, exact missing-source error and unchanged relay video-event count all passed before the subsequent layout assertion failed. +- First failure: **Upload horizontal document overflow**. Full-page screenshots are362px wide at a360px viewport; feed/Profile screenshots are360px. Exact failed DOM scrollWidth and offending element bounds were not persisted because the assertion threw before returning layout. Do not invent a culprit or precise CSS fix from this evidence. +- Screenshot also shows Upload title/error-banner overlap; captured visual observation, no separately isolated cause. +- Remaining Upload close/focus restoration, Home navigation,390×844 and1280×720 checks were NOT EXECUTED. QA2 remains blocked/incomplete, not closed as successful. No product/CSS/helper/config/test changes. + +Visible controls below44px are observations, not an invented failure threshold: e.g. sidebar35.1875, theme32, profile/upload38.390625, card buttons32. + +[Feed](playback-qa-2-360-feed.png), [Profile](playback-qa-2-360-profile.png), [Upload failure](playback-qa-2-360-upload-validation.png), [stop](playback-qa-2-final-stop.png). +[Exact report](playback-qa-2-final-result.json), [exact driver](playback-qa-2-final-driver.md). +Report SHA2568434598bc187e203c313c2378e94d3beec853293471de987853af87dbe9eb31f. + +## Provenance and fidelity + +Fresh private frozen snapshot830files; full manifest SHA2562747d37916362971a5abe05db4018adf31b5c7031b48d8e4bd6eb1e19b9fa927. +All830file bytes verified before server start; supplied index78f8bb3d5ed2a92b5ab388eb9e9c3afd2a475e4525fa6ea23c454ffb0ac8cf40, +CSS283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3, +nostrService d23884c8ec46d51ca602de9548e17b417975d6de08fa30d6a6fae76070c573ae all match dispatch pins. +First preparation selected nonexistent css/tailwind.css and ambiguously found duplicate equal-hash generated copies; assertion failed before copying/browser. Corrected to actual index-linked css/tailwind.generated.283d04642d201edb.css; no product change. + +Canonical established fixture s=nostr:dTag was added explicitly per dispatch; original failed no-s inputs remain in prior evidence/source/commits. Cheap actual-runner seed-loop preflight:3 unique events,3 schema-valid,3 genuine signatures,3 tampered rejected,3 canonical s tags; exit0. This changes input fidelity, so not a same-input before/after claim. Client7 owns controlled service regression/mutants; QA observed actual DOM after fix. +Profile source uses requestAnimationFrame for restoration; exact bounded focus wait was added before unchanged expected-trigger assertion. Actual Home anchor is index.html with accessible name Return to the bitvid home page; added original required Home action to runner, but not reached. +Runner syntax check exit0. No new retained test/assertion changes, retries or sleeps; no mutant required for these evidence-only files. + +Existing genuine VP8 fixture d911005a5bca7fb1f3e5aa2fbee701b6476c9baf93460264c2aa5105dd0b3c69 (1019bytes): actual local HEAD200/GET200/Range206 passed. Fixture not played; no actual playback/decoding/WebTorrent/live-service claim here. +Only exact Buffer CDN URL fulfilled once with previously pinned genuine cached bundle (not CDN-byte equivalence).15 nonlocal HTTP and4 nonlocal WebSocket attempts blocked, including auth fallback/robohash/relay attempts;0 external requests allowed.0 pageerrors,0 failed local HTTP. Private ports31847/31848/31849; preview3000 untouched. No real signer/wallet/cloud/peers. + +## Follow-up routing + +Dispatch notified first failure plus release; client-reliability notified actual cold3card pass. Possible next owner scope is upload modal/CSS layout, requiring dispatch routing and bounded exact DOM measurements before a fix. QA did not reopen browser or patch product. Original baseline remains required after resolution; stop-milestone review cannot claim these unexecuted checks passed. +Historical evidence: [cold render gap](playback-qa-2-render-gap.md), [prior interventions](playback-qa-2-second-pass.md). + +## Separately authorized geometry and remaining coverage + +Dispatch16:26:39 authorized exact Upload geometry; if minor and usable, continue independent remaining checks while preserving original failure. START16:27:42, runner16:27:44.416→16:28:10.112 UTC (25.696s); RELEASE16:28:25; all owned resources closed and dependency link removed. No product/CSS edits or build/full-suite run. + +`node /tmp/bitvid-playback-qa-2-final/follow-on.mjs` exit0, report explicitly says follow-on coverage completed; original360overflow remains failed/open.25 completed steps,0 hard failures. This is separate diagnostic coverage, **not a passing original baseline**. + +| Viewport | Document client/scroll width | Body client/scroll width | Panel client/scroll width | Outcome | +|---|---|---|---|---| +|360×800|360/362|360/362|360/360|2px document overflow, usable controls| +|390×844|390/394|390/394|390/390|4px document overflow, usable controls| +|1280×720|1280/1280|1280/1280|502/502|No document overflow| + +ScrollX/Y=0/0 for all samples. Modal root fits viewport.360 control bounds:inputs/publish x38.375→321.625, close283.21875→315.21875,y41.578125→73.578125; successful actual close and uploadButton focus restoration.390 close313.21875→345.21875; successful real close/focus. Inputs were actually filled, native required validation and missing-source message observed, invalid submission did not add relay video events. At desktop the scrolled form URL has negative y; actual prior fill/tab and close work, not all controls claimed simultaneously visible. + +All3 viewports completed Trending feed exactly3/cards within viewport, Explore/ForYou/Trending navigation, Profile6Tab containment+close+trigger focus, Upload6Tab containment/title+source validation/no publication/close+trigger focus, and actual Home anchor navigation returning exactly3 cards. Home reload uses real app startup and persisted existing test-mode flag; no manual refreshFeed/render/card injection. Feed widths360/390/1280 fit. Touch-control size observations retained in JSON; no invented44px acceptance change. + +Raw offender list includes unlabelled SPANs with large bounds even at desktop (where no document overflow), likely clipped/animated descendants. It does not isolate the cause; no culprit or CSS fix inferred. The diagnostic heuristic >16px or critical horizontal control outside viewport only decides whether to stop follow-on, not a new tolerance for original strict overflow assertion. Original2px failure and mobile4px finding remain open. Header/error overlap is visual observation, not independently isolated severity. + +[Full follow-on report](playback-qa-2-follow-on-result.json), SHA2560c8b8eff287e06d47dc830425b258d969bc71eba1f07870b6b9dc88bc5819200; [exact separate driver](playback-qa-2-follow-on-driver.md). +Screenshots for each viewport use playback-qa-2-follow-on-{360,390,1280}-{feed,profile,upload-validation}.png. +4 exact genuine Buffer mirror fulfillments (initial plus3 Home reloads),53 externalHTTP/16 externalWS attempts denied,0 allowed external traffic,0 pageerrors,0 failed localHTTP. Real cloud/signers/peers/deployed CDN/full offline operation remain unverified; no actual media playback/decoding in QA2. Browser closed; dispatch to integrate open minor finding and local-review coverage. No optional CSS work started. + +## Durable evidence landing and closure disposition + +Fleet land_status submission17163984f7b27a8fef3e0f240e5abc623a043067 reports landed06e920e77e756a4ad0c7d6495a2dbd930cf8482a at16:29:45.932 UTC; gitlogunstable agrees and git merge-base --is-ancestor submission unstable exits0. Merged-tree evidence/source-pin checks7/7 and gitdiff--check0. Queue receipt proves docs landing; no fresh full-unit suite count claimed for this evidence-only submission. + +Dispatch16:28:47 and16:29:54 explicitly accepted completed bounded qualification with open minor mobile layout finding and directed closure/IDLE. This supersedes earlier waiting-status prose as workflow disposition, without changing any test result. No optional CSS fix, additional browser run or new item started. Known finding is recorded here and in closed backlog resolution for dispatch local-review handoff; exact culprit remains unproven. Live/deployed/browserFirefox actual-app/real torrent or decoding/full production acceptance remain unverified. diff --git a/docs/testing/playback-qa-2-final-result.json b/docs/testing/playback-qa-2-final-result.json new file mode 100644 index 00000000..573558b3 --- /dev/null +++ b/docs/testing/playback-qa-2-final-result.json @@ -0,0 +1,459 @@ +{ + "driver": "noninteractive bounded actions", + "started": "2026-10-20T16:25:30.066Z", + "mirrorHits": [ + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm" + ], + "blockedHttp": [ + "https://esm.sh/@aws-sdk/client-s3@3.637.0?bundle", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://robohash.org/032ee87ce307cbf86f3eeecea732f73a19698cd07d13394a0c14b52d35798304", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022" + ], + "blockedWs": [ + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/" + ], + "failedLocal": [], + "pageErrors": [], + "consoleErrors": [ + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "[bitvid] Submission failed Error: Please provide at least a Video URL or Magnet Link.\n at UploadModal.handleExternalFlow (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1342:17)\n at UploadModal.handleSubmit (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1280:25)\n at HTMLFormElement. (http://127.0.0.1:31847/js/ui/components/UploadModal.js:360:14)", + "[bitvid] Please provide at least a Video URL or Magnet Link." + ], + "actions": [ + { + "name": "actual toolkit ready", + "at": "2026-10-20T16:25:32.215Z", + "result": { + "callable": true, + "canonicalIdentity": true, + "frozen": true, + "mockFlag": false, + "validSeed": true, + "tamperedRejected": true, + "pubkeyMatches": true, + "independentSignatureVerified": true + }, + "ok": true + }, + { + "name": "actual application startup completes", + "at": "2026-10-20T16:25:32.252Z", + "result": { + "readyPromiseCompleted": true, + "relays": [ + "ws://127.0.0.1:31848" + ] + }, + "ok": true + }, + { + "name": "three synthetic relay cards hydrate", + "at": "2026-10-20T16:25:32.266Z", + "result": [ + { + "title": "QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls", + "dTag": "qa2-0" + }, + { + "title": "QA local synthetic video 1", + "dTag": "qa2-1" + }, + { + "title": "QA local synthetic video 2", + "dTag": "qa2-2" + } + ], + "ok": true + }, + { + "name": "local test identity login", + "at": "2026-10-20T16:25:32.273Z", + "result": { + "profileVisible": true, + "uploadVisible": true + }, + "ok": true + }, + { + "name": "360 trending navigation/feed", + "at": "2026-10-20T16:25:32.366Z", + "result": { + "width": 360, + "documentWidth": 360, + "cardBounds": [ + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + }, + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + }, + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "Recently Added", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "For Kids", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "History", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 32, + "height": 32 + }, + { + "id": "uploadButton", + "label": "+", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "profileButton", + "label": "", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 16, + "height": 16 + }, + { + "id": "", + "label": "#video", + "width": 49.8125, + "height": 18.21875 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + } + ] + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-360-feed.png" + }, + { + "name": "360 navigation explore", + "at": "2026-10-20T16:25:32.590Z", + "result": { + "hash": "#view=explore", + "headings": [ + "Explore", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n General Feedback Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n Search Filters\n ", + "\n Share a Video\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "Edit Video Note", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "360 navigation for-you", + "at": "2026-10-20T16:25:32.705Z", + "result": { + "hash": "#view=for-you", + "headings": [ + "For You", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n General Feedback Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n Search Filters\n ", + "\n Share a Video\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "Edit Video Note", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "360 navigation trending", + "at": "2026-10-20T16:25:32.815Z", + "result": { + "hash": "#view=trending", + "headings": [ + "Trending", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n General Feedback Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n Search Filters\n ", + "\n Share a Video\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "Edit Video Note", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "360 Profile open/focus/close", + "at": "2026-10-20T16:25:32.905Z", + "result": { + "focus": [ + { + "inside": true, + "id": "profileModalIdentityCard", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 3.2px 4.8px -0.8px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavAccount", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgb(84, 0, 17) 3px 0px 0px 0px inset, rgba(15, 23, 42, 0.18) 0px 3.2px 4.8px -0.8px" + }, + { + "inside": true, + "id": "profileNavRelays", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavWallet", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavStorage", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavMyVideos", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "restored": "profileButton" + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-360-profile.png" + }, + { + "name": "360 Upload required fields/focus/close", + "at": "2026-10-20T16:25:33.252Z", + "ok": false, + "error": "Upload horizontal document overflow" + }, + { + "screenshot": "playback-qa-2-360-upload-validation.png" + }, + { + "screenshot": "playback-qa-2-resumed-stop.png" + } + ], + "fixtureHttp": { + "head": 200, + "get": 200, + "range": 206, + "bytes": 1019, + "contentType": "video/webm" + }, + "browserVersion": "154.0.8037.97", + "boot": "passed", + "baseline": "blocked-or-failed", + "failedStep": "360 Upload required fields/focus/close", + "failure": "Upload horizontal document overflow", + "failureDiagnostics": { + "appPresent": true, + "relayHealth": { + "relays": [ + "ws://127.0.0.1:31848" + ], + "unreachable": [], + "backoff": {} + }, + "feedItems": [ + { + "title": "QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls", + "pubkey": "032ee87ce307cbf86f3eeecea732f73a19698cd07d13394a0c14b52d35798304", + "dTag": "qa2-0", + "hasUrl": true, + "hasMagnet": false + }, + { + "title": "QA local synthetic video 1", + "pubkey": "032ee87ce307cbf86f3eeecea732f73a19698cd07d13394a0c14b52d35798304", + "dTag": "qa2-1", + "hasUrl": true, + "hasMagnet": false + }, + { + "title": "QA local synthetic video 2", + "pubkey": "032ee87ce307cbf86f3eeecea732f73a19698cd07d13394a0c14b52d35798304", + "dTag": "qa2-2", + "hasUrl": true, + "hasMagnet": false + } + ], + "body": "Switch to dark theme\n+\nPlease provide at least a Video URL or Magnet Link.\nTrending\n#video\nQA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls\nQA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls\nQA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls\n\nUnknown\n\njust now\n✅ CDN\n0 views\n0 sats\nQA local synthetic video 1\nQA local synthetic video 1\nQA local synthetic video 1\n\nUnknown\n\njust now\n✅ CDN\n0 views\n0 sats\nQA local synthetic video 2\nQA local synthetic video 2\nQA local synthetic video 2\n\nUnknown\n\njust now\n✅ CDN\n0 views\n0 sats\nShare a Video\nUpload File\nExternal Link\nVideo URL\n\nPrimary hosted video source (URL-first playback).\n\nMagnet Link\n\nOptional WebTorrent fallback when the URL is unavailable.\n\nTitle\nDescription\nThumbnail URL\nHashtags\n+ Add Tag\nEnable Comments\nNSFW\nFor Kids\nDo not share this video to other Nostr apps\nAdvanced Options\nPublish Video\n\nseed. zap. subscribe.\nv: 6da89895 • 2026-10-20" + }, + "ended": "2026-10-20T16:25:33.901Z", + "resourcesClosed": true +} \ No newline at end of file diff --git a/docs/testing/playback-qa-2-final-stop.png b/docs/testing/playback-qa-2-final-stop.png new file mode 100644 index 00000000..c1d3424c Binary files /dev/null and b/docs/testing/playback-qa-2-final-stop.png differ diff --git a/docs/testing/playback-qa-2-follow-on-1280-feed.png b/docs/testing/playback-qa-2-follow-on-1280-feed.png new file mode 100644 index 00000000..6d4785ec Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-1280-feed.png differ diff --git a/docs/testing/playback-qa-2-follow-on-1280-profile.png b/docs/testing/playback-qa-2-follow-on-1280-profile.png new file mode 100644 index 00000000..e6adba48 Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-1280-profile.png differ diff --git a/docs/testing/playback-qa-2-follow-on-1280-upload-validation.png b/docs/testing/playback-qa-2-follow-on-1280-upload-validation.png new file mode 100644 index 00000000..5001ca1d Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-1280-upload-validation.png differ diff --git a/docs/testing/playback-qa-2-follow-on-360-feed.png b/docs/testing/playback-qa-2-follow-on-360-feed.png new file mode 100644 index 00000000..1f477e10 Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-360-feed.png differ diff --git a/docs/testing/playback-qa-2-follow-on-360-profile.png b/docs/testing/playback-qa-2-follow-on-360-profile.png new file mode 100644 index 00000000..680f0b9f Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-360-profile.png differ diff --git a/docs/testing/playback-qa-2-follow-on-360-upload-validation.png b/docs/testing/playback-qa-2-follow-on-360-upload-validation.png new file mode 100644 index 00000000..2d0a041a Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-360-upload-validation.png differ diff --git a/docs/testing/playback-qa-2-follow-on-390-feed.png b/docs/testing/playback-qa-2-follow-on-390-feed.png new file mode 100644 index 00000000..4898dcfd Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-390-feed.png differ diff --git a/docs/testing/playback-qa-2-follow-on-390-profile.png b/docs/testing/playback-qa-2-follow-on-390-profile.png new file mode 100644 index 00000000..4b1c818e Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-390-profile.png differ diff --git a/docs/testing/playback-qa-2-follow-on-390-upload-validation.png b/docs/testing/playback-qa-2-follow-on-390-upload-validation.png new file mode 100644 index 00000000..98daad5c Binary files /dev/null and b/docs/testing/playback-qa-2-follow-on-390-upload-validation.png differ diff --git a/docs/testing/playback-qa-2-follow-on-driver.md b/docs/testing/playback-qa-2-follow-on-driver.md new file mode 100644 index 00000000..ffa58c12 --- /dev/null +++ b/docs/testing/playback-qa-2-follow-on-driver.md @@ -0,0 +1,102 @@ +# QA2 separately authorized follow-on driver + +Diagnostic-only scratch, SHA25652cb54c6f327affa4a95dc3e2f5ba8b65892c3f42b2bb39f868775d974c4aff7. Original failed baseline and strict assertion preserved separately. Overflow conditional is diagnostic routing, not regression acceptance policy. + +```js +import {createServer} from 'node:http'; +import {createHash} from 'node:crypto'; +import {mediaResponse} from './media-fixture.mjs'; +import {readFile,writeFile} from 'node:fs/promises'; +import {resolve,extname} from 'node:path'; +import {createInterface} from 'node:readline'; +import {createRequire} from 'node:module'; +import {chromium} from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import {startRelay} from '/home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs'; +const require=createRequire('/home/user/Documents/GitHub/bitvid/package.json'); +const {finalizeEvent,verifyEvent}=require('nostr-tools'); +import {NOTE_TYPES,validateEventStructure} from '/home/user/Documents/GitHub/bitvid-playback-qa/js/nostrEventSchemas.js'; +const base='/tmp/bitvid-playback-qa-2-final',root=base+'/dist'; +const evidence='/home/user/Documents/GitHub/bitvid-playback-qa/docs/testing'; +const report={driver:'noninteractive bounded actions',started:new Date().toISOString(),mirrorHits:[],blockedHttp:[],blockedWs:[],failedLocal:[],pageErrors:[],consoleErrors:[],actions:[]}; +const sk=new Uint8Array(32).fill(0x7b); +const fixtureBytes=await readFile(base+'/fixtures/qa2-unplayed.webm'); +if(createHash('sha256').update(fixtureBytes).digest('hex')!=='d911005a5bca7fb1f3e5aa2fbee701b6476c9baf93460264c2aa5105dd0b3c69')throw Error('local genuine fixture hash changed'); +const frozenManifest=await readFile(base+'/dist-manifest.json'); +if(createHash('sha256').update(frozenManifest).digest('hex')!=='2747d37916362971a5abe05db4018adf31b5c7031b48d8e4bd6eb1e19b9fa927')throw Error('integrated artifact manifest changed'); +for(const [name,hash] of Object.entries(JSON.parse(frozenManifest)))if(createHash('sha256').update(await readFile(root+'/'+name)).digest('hex')!==hash)throw Error('integrated artifact byte changed: '+name); +const server=createServer(async(req,res)=>{ + let path=decodeURIComponent(new URL(req.url,'http://127.0.0.1:31847').pathname);if(path==='/qa2-unplayed.webm'){const r=mediaResponse(req.method,req.headers.range,fixtureBytes);res.writeHead(r.status,r.headers);res.end(r.body);return;}if(path==='/')path='/index.html';const f=resolve(root,'.'+path); + if(!f.startsWith(root+'/')||path.includes('/.')){res.writeHead(404);res.end();return;} + try{const b=await readFile(f);res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.mjs':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.wasm':'application/wasm','.png':'image/png','.webmanifest':'application/manifest+json'})[extname(f)]||'application/octet-stream');res.end(b);}catch{res.writeHead(404);res.end();} +}); +let browser,relay,context,page,closed=false,hardDeadline; +async function finish(){if(closed)return;closed=true;clearTimeout(hardDeadline);if(context)await context.close();if(browser)await browser.close();if(relay)await relay.close();await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await writeFile(base+'/follow-on-result.json',JSON.stringify(report,null,2));console.log(JSON.stringify({type:'CLOSED',ended:report.ended,mirrorHits:report.mirrorHits.length,blockedHttp:report.blockedHttp,blockedWs:report.blockedWs,failedLocal:report.failedLocal,pageErrors:report.pageErrors}));} +hardDeadline=setTimeout(()=>{report.deadlineFailure='120s bounded runner deadline';void finish().finally(()=>process.exit(1));},120000); +try{ + await new Promise((ok,no)=>{server.once('error',no);server.listen(31847,'127.0.0.1',ok);}); + relay=startRelay(31848,{httpPort:31849}); + for(let i=0;i<3;i++){const title=i===0?'QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls':'QA local synthetic video '+i;const event=finalizeEvent({kind:30078,created_at:Math.floor(Date.now()/1000)-i,tags:[['d','qa2-'+i],['t','video'],['s','nostr:qa2-'+i],['title',title],['url','http://127.0.0.1:31847/qa2-unplayed.webm']],content:JSON.stringify({version:3,title,url:'http://127.0.0.1:31847/qa2-unplayed.webm',videoRootId:'qa2-'+i,mode:'dev',isPrivate:false,deleted:false})},sk);if(!validateEventStructure(NOTE_TYPES.VIDEO_POST,event).valid||!verifyEvent(JSON.parse(JSON.stringify(event))))throw Error('canonical real signed fixture invalid');relay.seedEvent(event);} + const fixtureUrl='http://127.0.0.1:31847/qa2-unplayed.webm'; + const head=await fetch(fixtureUrl,{method:'HEAD'}),whole=await fetch(fixtureUrl),part=await fetch(fixtureUrl,{headers:{Range:'bytes=0-31'}}); + const wholeBytes=Buffer.from(await whole.arrayBuffer()),partBytes=Buffer.from(await part.arrayBuffer()); + if(head.status!==200||head.headers.get('content-type')!=='video/webm'||Number(head.headers.get('content-length'))!==fixtureBytes.length||whole.status!==200||!wholeBytes.equals(fixtureBytes)||part.status!==206||!partBytes.equals(fixtureBytes.subarray(0,32)))throw Error('genuine local media HTTP preflight failed'); + report.fixtureHttp={head:head.status,get:whole.status,range:part.status,bytes:fixtureBytes.length,contentType:head.headers.get('content-type')}; + browser=await chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']});report.browserVersion=browser.version(); + context=await browser.newContext({viewport:{width:360,height:800},serviceWorkers:'block'}); + const mirror=await readFile(base+'/buffer-6.0.3-local.mjs'); + await context.route('**/*',r=>{const url=r.request().url();if(url==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){report.mirrorHits.push(url);return r.fulfill({body:mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}const u=new URL(url);if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))return r.continue();report.blockedHttp.push(url);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',ws=>{const u=new URL(ws.url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))ws.connectToServer();else{report.blockedWs.push(ws.url());ws.close();}}); + await context.addInitScript(()=>{localStorage.setItem('hasSeenDisclaimer','true');localStorage.setItem('__bitvidTestMode__','1');localStorage.setItem('__bitvidTestRelays__',JSON.stringify(['ws://127.0.0.1:31848']));window.__bitvidTestRelays__=['ws://127.0.0.1:31848'];localStorage.setItem('bitvid_admin_whitelist_mode','false');}); + page=await context.newPage();page.setDefaultTimeout(10000); + page.on('pageerror',e=>report.pageErrors.push(e.message));page.on('console',m=>{if(m.type()==='error')report.consoleErrors.push(m.text().slice(0,700));});page.on('response',r=>{if(r.status()>=400&&new URL(r.url()).hostname==='127.0.0.1')report.failedLocal.push({url:r.url(),status:r.status()});}); + await page.goto('http://127.0.0.1:31847/?__test__=1&__testRelays__=ws%3A%2F%2F127.0.0.1%3A31848',{waitUntil:'domcontentloaded'}); + try{await page.waitForFunction(()=>typeof window.__bitvidTest__==='object',null,{timeout:15000});await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');return !!m.getApplication();},null,{timeout:15000});await page.locator('[data-testid="video-list"]').waitFor({state:'visible',timeout:15000});report.boot='passed';console.log(JSON.stringify({type:'BOOTED',viewport:{width:360,height:800},body:(await page.locator('body').innerText()).slice(0,3000)}));} + catch(e){report.boot='blocked';report.bootError=e.message;await page.screenshot({path:evidence+'/playback-qa-2-second-boot-blocker.png',fullPage:true});console.log(JSON.stringify({type:'BLOCKED',error:e.message,body:(await page.locator('body').innerText()).slice(0,1500)}));await finish();process.exit(0);} + + // Noninteractive: no stdin/readline loop; each action is awaited exactly once. + const check=(condition,message)=>{if(!condition)throw new Error(message);}; + const step=async(name,action)=>{const item={name,at:new Date().toISOString()};report.actions.push(item);try{item.result=await action();item.ok=true;console.log(JSON.stringify({type:'STEP',name,ok:true,result:item.result}));return item.result;}catch(e){item.ok=false;item.error=e.message;console.log(JSON.stringify({type:'STEP',name,ok:false,error:e.message}));throw e;}}; + const capture=async(name)=>{name=name.replace('playback-qa-2-','playback-qa-2-follow-on-');await page.screenshot({path:evidence+'/'+name,fullPage:true});report.actions.push({screenshot:name});}; + const layout=async()=>page.evaluate(()=>({width:innerWidth,documentWidth:document.documentElement.scrollWidth,cardBounds:[...document.querySelectorAll('[data-testid="video-card"]')].map(e=>{const r=e.getBoundingClientRect();return {x:r.x,right:r.right,width:r.width};}),smallControls:[...document.querySelectorAll('button,a,input')].filter(e=>e.getClientRects().length&&getComputedStyle(e).visibility!=='hidden').map(e=>{const r=e.getBoundingClientRect();return {id:e.id,label:(e.getAttribute('aria-label')||e.textContent||'').trim().slice(0,80),width:r.width,height:r.height};}).filter(x=>x.width>0&&x.height>0&&(x.width<44||x.height<44))})); + const modalFocus=async(root)=>{const samples=[];for(let i=0;i<6;i++){await page.keyboard.press('Tab');samples.push(await page.evaluate(selector=>{const el=document.activeElement;return {inside:!!document.querySelector(selector)?.contains(el),id:el?.id,tag:el?.tagName,outline:getComputedStyle(el).outlineStyle,shadow:getComputedStyle(el).boxShadow};},root));}check(samples.every(x=>x.inside),'keyboard focus escaped '+root);return samples;}; + try{ + await step('actual toolkit ready',async()=>{ + const event=[...relay.getEvents().values()].find(e=>e.kind===30078); + const ready=await page.evaluate(async(seed)=>{ + const m=await import('/js/nostrToolsBootstrap.js'),t=await m.nostrToolsReady; + const callable=['getPublicKey','finalizeEvent','verifyEvent'].every(k=>typeof t?.[k]==='function'); + const state={callable,canonicalIdentity:t===window.__BITVID_CANONICAL_NOSTR_TOOLS__,frozen:Object.isFrozen(t),mockFlag:!!window.__TEST_MODE__||window.process?.env?.NODE_ENV==='test'}; + if(!callable)return state; + const sk=new Uint8Array(32).fill(0x7b),clean=JSON.parse(JSON.stringify(seed)); + state.validSeed=t.verifyEvent(clean)===true;state.tamperedRejected=t.verifyEvent({...JSON.parse(JSON.stringify(seed)),content:'tampered local challenge'})===false;state.pubkeyMatches=t.getPublicKey(sk)===seed.pubkey; + state.signed=t.finalizeEvent({kind:1,created_at:seed.created_at,tags:[],content:'QA real toolkit sanity'},sk); + return state; + },event); + const {signed,...status}=ready; + check(status.callable&&status.canonicalIdentity&&status.frozen&&!status.mockFlag&&status.validSeed&&status.tamperedRejected&&status.pubkeyMatches,'real toolkit contract unavailable: '+JSON.stringify(status)); + const {verifyEvent}=require('nostr-tools');check(verifyEvent(JSON.parse(JSON.stringify(signed)))===true,'actual toolkit signature failed independent Node verification'); + return {...status,independentSignatureVerified:true}; + }); + await step('actual application startup completes',async()=>{ + await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');await m.getApplicationReady();return !!m.getApplication();},null,{timeout:15000}); + const relayState=await page.evaluate(()=>window.__bitvidTest__.setTestRelays(['ws://127.0.0.1:31848'],{persist:false})); + check(relayState.ok===true,'documented fixture relay alignment failed'); + return {readyPromiseCompleted:true,relays:relayState.relays}; + }); + await step('three synthetic relay cards hydrate',async()=>{await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const items=await page.evaluate(()=>window.__bitvidTest__.getFeedItems());check(items.length===3,'expected exactly3 local cards,got'+items.length);return items.map(x=>({title:x.title,dTag:x.dTag}));}); + await step('local test identity login',async()=>{await page.evaluate(()=>window.__bitvidTest__.loginWithNsec('7b'.repeat(32)));await page.locator('[data-testid="profile-button"]').waitFor({state:'visible'});await page.locator('[data-testid="upload-button"]').waitFor({state:'visible'});return {profileVisible:true,uploadVisible:true};}); + for(const viewport of [{width:360,height:800},{width:390,height:844},{width:1280,height:720}]){ + await page.setViewportSize(viewport);const prefix=String(viewport.width); + await step(prefix+' trending navigation/feed',async()=>{await page.locator('#trendingLink').click();await page.waitForFunction(()=>location.hash.includes('view=trending'));await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const r=await layout();check(r.documentWidth<=r.width,'horizontal document overflow');check(r.cardBounds.every(x=>x.x>=0&&x.right<=r.width),'card extends beyond viewport');return r;}); + await capture('playback-qa-2-'+prefix+'-feed.png'); + for(const [id,view] of [['#exploreLink','explore'],['#forYouLink','for-you'],['#trendingLink','trending']])await step(prefix+' navigation '+view,async()=>{await page.locator(id).click();await page.waitForFunction(v=>location.hash.includes('view='+v),view);return {hash:await page.evaluate(()=>location.hash),headings:await page.locator('h1,h2').allTextContents()};}); + await step(prefix+' Profile open/focus/close',async()=>{await page.locator('[data-testid="profile-button"]').click();await page.locator('#profileModal').waitFor({state:'visible'});const focus=await modalFocus('#profileModal');await capture('playback-qa-2-'+prefix+'-profile.png');await page.locator('#closeProfileModal').click();await page.locator('#profileModal').waitFor({state:'hidden'});await page.waitForFunction(()=>document.activeElement?.id==='profileButton');const restored=await page.evaluate(()=>document.activeElement?.id);check(restored==='profileButton','Profile close did not restore trigger focus');return {focus,restored};}); + await step(prefix+' Upload required fields/focus/close',async()=>{const videoCount=()=>[...relay.getEvents().values()].filter(e=>e.kind===30078).length;const before=videoCount();await page.locator('[data-testid="upload-button"]').click();await page.locator('#uploadModal').waitFor({state:'visible'});await page.locator('#btn-mode-external').click();const focus=await modalFocus('#uploadModal');await page.locator('#input-title').fill('');await page.locator('#input-url').fill('');await page.locator('#input-magnet').fill('');await page.locator('#btn-submit').click();const missingTitle=await page.locator('#input-title').evaluate(e=>e.validity.valueMissing);check(missingTitle,'empty title not rejected by native validation');await page.locator('#input-title').fill('QA invalid local form');await page.locator('#btn-submit').click();await page.waitForFunction(()=>document.querySelector('#errorContainer')?.textContent.includes('Please provide at least a Video URL or Magnet Link.'));check(videoCount()===before,'invalid form published video event');await capture('playback-qa-2-'+prefix+'-upload-validation.png');const l=await layout();const geometry=await page.evaluate(()=>{const box=e=>{if(!e)return null;const r=e.getBoundingClientRect();return {id:e.id,tag:e.tagName,x:r.x,y:r.y,right:r.right,bottom:r.bottom,width:r.width,height:r.height,clientWidth:e.clientWidth,scrollWidth:e.scrollWidth,overflowX:getComputedStyle(e).overflowX,position:getComputedStyle(e).position};};const panel=document.querySelector('#uploadModal .modal-sheet');return {viewport:{width:innerWidth,height:innerHeight,scrollX,scrollY},document:box(document.documentElement),body:box(document.body),panel:box(panel),root:box(document.querySelector('#uploadModal')),active:box(document.activeElement),controls:['#input-title','#input-url','#input-magnet','#btn-submit','#closeUploadModal'].map(s=>({selector:s,...box(document.querySelector(s))})),offenders:[...document.querySelectorAll('body *')].filter(e=>e.getClientRects().length&&getComputedStyle(e).visibility!=='hidden').map(box).filter(r=>r.x<0||r.right>innerWidth).slice(0,40)};});report.uploadGeometry??=[];report.uploadGeometry.push({viewport:prefix,geometry});if(l.documentWidth>l.width){const critical=geometry.controls;const hard=l.documentWidth-l.width>16||critical.some(r=>r.width<=0||r.x<0||r.right>l.width);report.uploadOverflowFindings??=[];report.uploadOverflowFindings.push({viewport:prefix,documentWidth:l.documentWidth,width:l.width,severity:hard?'hard usability candidate':'minor horizontal overflow; controls horizontally in viewport',geometry});if(hard)throw Error('hard Upload overflow/accessibility candidate; stop for routing');}await page.locator('#closeUploadModal').click();await page.locator('#uploadModal').waitFor({state:'hidden'});const restored=await page.evaluate(()=>document.activeElement?.id);check(restored==='uploadButton','Upload close did not restore trigger focus');return {focus,missingTitle,noSourceRejected:true,noRelayPublish:true,restored,layout:l};}); + await step(prefix+' Home navigation',async()=>{await page.getByRole('link',{name:'Return to the bitvid home page',exact:true}).click();await page.waitForURL('**/index.html');await page.waitForFunction(()=>!!window.__bitvidTest__);await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');await m.getApplicationReady();return !!m.getApplication();},null,{timeout:15000});await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const items=await page.evaluate(()=>window.__bitvidTest__.getFeedItems());check(items.length===3,'Home expected exactly3 local cards');return {url:page.url(),cards:items.length};}); + } + report.baseline='follow-on coverage completed; original360overflow remains failed/open'; + }catch(e){report.baseline='blocked-or-failed';report.failedStep=report.actions.findLast(x=>x.ok===false)?.name;report.failure=e.message;try{report.failureDiagnostics=await page.evaluate(async()=>{const m=await import('/js/applicationContext.js');return {appPresent:!!m.getApplication(),relayHealth:window.__bitvidTest__?.getRelayHealth(),feedItems:window.__bitvidTest__?.getFeedItems(),body:document.body.innerText.slice(0,2000)};});}catch{}await capture('playback-qa-2-resumed-stop.png');} + await finish(); +}catch(e){report.runError=e.stack;console.log(JSON.stringify({type:'ERROR',error:e.stack}));await finish();process.exitCode=1;} + +``` diff --git a/docs/testing/playback-qa-2-follow-on-result.json b/docs/testing/playback-qa-2-follow-on-result.json new file mode 100644 index 00000000..3a92fc87 --- /dev/null +++ b/docs/testing/playback-qa-2-follow-on-result.json @@ -0,0 +1,3256 @@ +{ + "driver": "noninteractive bounded actions", + "started": "2026-10-20T16:27:44.416Z", + "mirrorHits": [ + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm", + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm", + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm", + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm" + ], + "blockedHttp": [ + "https://esm.sh/@aws-sdk/client-s3@3.637.0?bundle", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://robohash.org/032ee87ce307cbf86f3eeecea732f73a19698cd07d13394a0c14b52d35798304", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://esm.sh/@aws-sdk/client-s3@3.637.0?bundle", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://esm.sh/@aws-sdk/client-s3@3.637.0?bundle", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://esm.sh/@aws-sdk/client-s3@3.637.0?bundle", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js" + ], + "blockedWs": [ + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/" + ], + "failedLocal": [], + "pageErrors": [], + "consoleErrors": [ + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "[bitvid] Submission failed Error: Please provide at least a Video URL or Magnet Link.\n at UploadModal.handleExternalFlow (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1342:17)\n at UploadModal.handleSubmit (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1280:25)\n at HTMLFormElement. (http://127.0.0.1:31847/js/ui/components/UploadModal.js:360:14)", + "[bitvid] Please provide at least a Video URL or Magnet Link.", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "[bitvid] Submission failed Error: Please provide at least a Video URL or Magnet Link.\n at UploadModal.handleExternalFlow (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1342:17)\n at UploadModal.handleSubmit (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1280:25)\n at HTMLFormElement. (http://127.0.0.1:31847/js/ui/components/UploadModal.js:360:14)", + "[bitvid] Please provide at least a Video URL or Magnet Link.", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "[bitvid] Submission failed Error: Please provide at least a Video URL or Magnet Link.\n at UploadModal.handleExternalFlow (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1342:17)\n at UploadModal.handleSubmit (http://127.0.0.1:31847/js/ui/components/UploadModal.js:1280:25)\n at HTMLFormElement. (http://127.0.0.1:31847/js/ui/components/UploadModal.js:360:14)", + "[bitvid] Please provide at least a Video URL or Magnet Link.", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector" + ], + "actions": [ + { + "name": "actual toolkit ready", + "at": "2026-10-20T16:27:46.595Z", + "result": { + "callable": true, + "canonicalIdentity": true, + "frozen": true, + "mockFlag": false, + "validSeed": true, + "tamperedRejected": true, + "pubkeyMatches": true, + "independentSignatureVerified": true + }, + "ok": true + }, + { + "name": "actual application startup completes", + "at": "2026-10-20T16:27:46.625Z", + "result": { + "readyPromiseCompleted": true, + "relays": [ + "ws://127.0.0.1:31848" + ] + }, + "ok": true + }, + { + "name": "three synthetic relay cards hydrate", + "at": "2026-10-20T16:27:46.642Z", + "result": [ + { + "title": "QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls", + "dTag": "qa2-0" + }, + { + "title": "QA local synthetic video 1", + "dTag": "qa2-1" + }, + { + "title": "QA local synthetic video 2", + "dTag": "qa2-2" + } + ], + "ok": true + }, + { + "name": "local test identity login", + "at": "2026-10-20T16:27:46.647Z", + "result": { + "profileVisible": true, + "uploadVisible": true + }, + "ok": true + }, + { + "name": "360 trending navigation/feed", + "at": "2026-10-20T16:27:46.742Z", + "result": { + "width": 360, + "documentWidth": 360, + "cardBounds": [ + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + }, + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + }, + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "Recently Added", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "For Kids", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "History", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 32, + "height": 32 + }, + { + "id": "uploadButton", + "label": "+", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "profileButton", + "label": "", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 16, + "height": 16 + }, + { + "id": "", + "label": "#video", + "width": 49.8125, + "height": 18.21875 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + } + ] + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-360-feed.png" + }, + { + "name": "360 navigation explore", + "at": "2026-10-20T16:27:46.968Z", + "result": { + "hash": "#view=explore", + "headings": [ + "Explore", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n General Feedback Form\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "\n Search Filters\n ", + "Edit Video Note", + "\n Share a Video\n ", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "360 navigation for-you", + "at": "2026-10-20T16:27:47.062Z", + "result": { + "hash": "#view=for-you", + "headings": [ + "For You", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n General Feedback Form\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "\n Search Filters\n ", + "Edit Video Note", + "\n Share a Video\n ", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "360 navigation trending", + "at": "2026-10-20T16:27:47.192Z", + "result": { + "hash": "#view=trending", + "headings": [ + "Trending", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n General Feedback Form\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "\n Search Filters\n ", + "Edit Video Note", + "\n Share a Video\n ", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "360 Profile open/focus/close", + "at": "2026-10-20T16:27:47.286Z", + "result": { + "focus": [ + { + "inside": true, + "id": "profileModalIdentityCard", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 3.2px 4.8px -0.8px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavAccount", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgb(84, 0, 17) 3px 0px 0px 0px inset, rgba(15, 23, 42, 0.18) 0px 3.2px 4.8px -0.8px" + }, + { + "inside": true, + "id": "profileNavRelays", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavWallet", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavStorage", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavMyVideos", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "restored": "profileButton" + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-360-profile.png" + }, + { + "name": "360 Upload required fields/focus/close", + "at": "2026-10-20T16:27:47.593Z", + "result": { + "focus": [ + { + "inside": true, + "id": "input-url", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-magnet", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-title", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-description", + "tag": "TEXTAREA", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-thumbnail", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "missingTitle": true, + "noSourceRejected": true, + "noRelayPublish": true, + "restored": "uploadButton", + "layout": { + "width": 360, + "documentWidth": 362, + "cardBounds": [ + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + }, + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + }, + { + "x": 76.796875, + "right": 347.203125, + "width": 270.40625 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "Recently Added", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "For Kids", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "History", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 32, + "height": 32 + }, + { + "id": "uploadButton", + "label": "+", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "profileButton", + "label": "", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 16, + "height": 16 + }, + { + "id": "", + "label": "#video", + "width": 49.8125, + "height": 18.21875 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "closeUploadModal", + "label": "Close upload modal", + "width": 32, + "height": 32 + }, + { + "id": "btn-mode-upload", + "label": "Upload File", + "width": 97.046875, + "height": 23.59375 + }, + { + "id": "btn-mode-external", + "label": "External Link", + "width": 106.109375, + "height": 23.59375 + }, + { + "id": "input-url", + "label": "", + "width": 283.25, + "height": 28.78125 + }, + { + "id": "input-magnet", + "label": "", + "width": 283.25, + "height": 28.78125 + }, + { + "id": "input-title", + "label": "", + "width": 283.25, + "height": 28.78125 + }, + { + "id": "input-thumbnail", + "label": "", + "width": 283.25, + "height": 28.78125 + }, + { + "id": "", + "label": "+ Add Tag", + "width": 67.09375, + "height": 22.140625 + }, + { + "id": "check-comments", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "check-nsfw", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "check-kids", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "check-no-nip71-mirror", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "btn-advanced-toggle", + "label": "Advanced Options", + "width": 283.25, + "height": 14 + }, + { + "id": "btn-submit", + "label": "Publish Video", + "width": 283.25, + "height": 40.375 + } + ] + } + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-360-upload-validation.png" + }, + { + "name": "360 Home navigation", + "at": "2026-10-20T16:27:53.359Z", + "result": { + "url": "http://127.0.0.1:31847/index.html", + "cards": 3 + }, + "ok": true + }, + { + "name": "390 trending navigation/feed", + "at": "2026-10-20T16:27:55.486Z", + "result": { + "width": 390, + "documentWidth": 390, + "cardBounds": [ + { + "x": 76.796875, + "right": 377.203125, + "width": 300.40625 + }, + { + "x": 76.796875, + "right": 377.203125, + "width": 300.40625 + }, + { + "x": 76.796875, + "right": 377.203125, + "width": 300.40625 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "Recently Added", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "For Kids", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "History", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 32, + "height": 32 + }, + { + "id": "uploadButton", + "label": "+", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "profileButton", + "label": "", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 16, + "height": 16 + }, + { + "id": "", + "label": "#video", + "width": 49.8125, + "height": 18.21875 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + } + ] + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-390-feed.png" + }, + { + "name": "390 navigation explore", + "at": "2026-10-20T16:27:55.992Z", + "result": { + "hash": "#view=explore", + "headings": [ + "Explore", + "\n Login Options\n ", + "\n General Feedback Form\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n Search Filters\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "\n Share a Video\n ", + "Edit Video Note", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "390 navigation for-you", + "at": "2026-10-20T16:27:56.117Z", + "result": { + "hash": "#view=for-you", + "headings": [ + "For You", + "\n Login Options\n ", + "\n General Feedback Form\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n Search Filters\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "\n Share a Video\n ", + "Edit Video Note", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "390 navigation trending", + "at": "2026-10-20T16:27:56.236Z", + "result": { + "hash": "#view=trending", + "headings": [ + "Trending", + "\n Login Options\n ", + "\n General Feedback Form\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Feature Request Form\n ", + "\n Bug Fix Form\n ", + "\n Search Filters\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "\n Share a Video\n ", + "Edit Video Note", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "390 Profile open/focus/close", + "at": "2026-10-20T16:27:56.383Z", + "result": { + "focus": [ + { + "inside": true, + "id": "profileModalIdentityCard", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 3.2px 4.8px -0.8px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavAccount", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgb(84, 0, 17) 3px 0px 0px 0px inset, rgba(15, 23, 42, 0.18) 0px 3.2px 4.8px -0.8px" + }, + { + "inside": true, + "id": "profileNavRelays", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavWallet", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavStorage", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavMyVideos", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "restored": "profileButton" + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-390-profile.png" + }, + { + "name": "390 Upload required fields/focus/close", + "at": "2026-10-20T16:27:56.919Z", + "result": { + "focus": [ + { + "inside": true, + "id": "input-url", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-magnet", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-title", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-description", + "tag": "TEXTAREA", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "input-thumbnail", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.8px 1.6px 0px" + }, + { + "inside": true, + "id": "", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "missingTitle": true, + "noSourceRejected": true, + "noRelayPublish": true, + "restored": "uploadButton", + "layout": { + "width": 390, + "documentWidth": 394, + "cardBounds": [ + { + "x": 76.796875, + "right": 377.203125, + "width": 300.40625 + }, + { + "x": 76.796875, + "right": 377.203125, + "width": 300.40625 + }, + { + "x": 76.796875, + "right": 377.203125, + "width": 300.40625 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "Recently Added", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "For Kids", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "", + "label": "History", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 35.1875, + "height": 35.1875 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 32, + "height": 32 + }, + { + "id": "uploadButton", + "label": "+", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "profileButton", + "label": "", + "width": 38.390625, + "height": 38.390625 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 16, + "height": 16 + }, + { + "id": "", + "label": "#video", + "width": 49.8125, + "height": 18.21875 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "More options", + "width": 32, + "height": 32 + }, + { + "id": "", + "label": "Video settings", + "width": 32, + "height": 32 + }, + { + "id": "closeUploadModal", + "label": "Close upload modal", + "width": 32, + "height": 32 + }, + { + "id": "btn-mode-upload", + "label": "Upload File", + "width": 97.046875, + "height": 23.59375 + }, + { + "id": "btn-mode-external", + "label": "External Link", + "width": 106.109375, + "height": 23.59375 + }, + { + "id": "input-url", + "label": "", + "width": 313.25, + "height": 28.78125 + }, + { + "id": "input-magnet", + "label": "", + "width": 313.25, + "height": 28.78125 + }, + { + "id": "input-title", + "label": "", + "width": 313.25, + "height": 28.78125 + }, + { + "id": "input-thumbnail", + "label": "", + "width": 313.25, + "height": 28.78125 + }, + { + "id": "", + "label": "+ Add Tag", + "width": 67.09375, + "height": 22.140625 + }, + { + "id": "check-comments", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "check-nsfw", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "check-kids", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "check-no-nip71-mirror", + "label": "", + "width": 16, + "height": 16 + }, + { + "id": "btn-advanced-toggle", + "label": "Advanced Options", + "width": 313.25, + "height": 14 + }, + { + "id": "btn-submit", + "label": "Publish Video", + "width": 313.25, + "height": 40.375 + } + ] + } + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-390-upload-validation.png" + }, + { + "name": "390 Home navigation", + "at": "2026-10-20T16:28:03.079Z", + "result": { + "url": "http://127.0.0.1:31847/index.html", + "cards": 3 + }, + "ok": true + }, + { + "name": "1280 trending navigation/feed", + "at": "2026-10-20T16:28:05.318Z", + "result": { + "width": 1280, + "documentWidth": 1280, + "cardBounds": [ + { + "x": 72, + "right": 353, + "width": 281 + }, + { + "x": 377, + "right": 658, + "width": 281 + }, + { + "x": 682, + "right": 963, + "width": 281 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 33, + "height": 33 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 33, + "height": 33 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 33, + "height": 33 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "Recently Added", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "For Kids", + "width": 33, + "height": 33 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 33, + "height": 33 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "History", + "width": 33, + "height": 33 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "Open search filters", + "width": 27, + "height": 27 + }, + { + "id": "headerSearchInput", + "label": "", + "width": 310, + "height": 13.125 + }, + { + "id": "", + "label": "Submit search", + "width": 27, + "height": 27 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 30, + "height": 30 + }, + { + "id": "uploadButton", + "label": "+", + "width": 36, + "height": 36 + }, + { + "id": "profileButton", + "label": "", + "width": 36, + "height": 36 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 15, + "height": 15 + }, + { + "id": "", + "label": "#video", + "width": 46.78125, + "height": 17.234375 + }, + { + "id": "", + "label": "More options", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "Video settings", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "More options", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "Video settings", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "More options", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "Video settings", + "width": 30, + "height": 30 + } + ] + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-1280-feed.png" + }, + { + "name": "1280 navigation explore", + "at": "2026-10-20T16:28:05.913Z", + "result": { + "hash": "#view=explore", + "headings": [ + "Explore", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Bug Fix Form\n ", + "\n Feature Request Form\n ", + "\n General Feedback Form\n ", + "\n Search Filters\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "Edit Video Note", + "\n Share a Video\n ", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "1280 navigation for-you", + "at": "2026-10-20T16:28:06.026Z", + "result": { + "hash": "#view=for-you", + "headings": [ + "For You", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Bug Fix Form\n ", + "\n Feature Request Form\n ", + "\n General Feedback Form\n ", + "\n Search Filters\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "Edit Video Note", + "\n Share a Video\n ", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "1280 navigation trending", + "at": "2026-10-20T16:28:06.163Z", + "result": { + "hash": "#view=trending", + "headings": [ + "Trending", + "\n Login Options\n ", + "\n Application Form\n ", + "\n Content Appeals Form\n ", + "\n Bug Fix Form\n ", + "\n Feature Request Form\n ", + "\n General Feedback Form\n ", + "\n Search Filters\n ", + "", + "\n Comments\n ", + "\n Similar Content\n ", + "Edit Video Note", + "\n Share a Video\n ", + "Profile", + "Conversations", + "\n Allow browser uploads (bucket CORS)\n ", + "\n Welcome to bitvid\n ", + "\n seed. zap. subscribe.\n " + ] + }, + "ok": true + }, + { + "name": "1280 Profile open/focus/close", + "at": "2026-10-20T16:28:06.280Z", + "result": { + "focus": [ + { + "inside": true, + "id": "profileModalIdentityCard", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(202, 204, 208, 0.498) 0px 1.83617px 2.75425px 0.316847px, rgba(84, 0, 17, 0.39) 0px 0px 0px 1.55178px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavAccount", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 3px 4.5px -0.75px" + }, + { + "inside": true, + "id": "profileNavRelays", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavWallet", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavStorage", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + }, + { + "inside": true, + "id": "profileNavMyVideos", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "restored": "profileButton" + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-1280-profile.png" + }, + { + "name": "1280 Upload required fields/focus/close", + "at": "2026-10-20T16:28:06.720Z", + "result": { + "focus": [ + { + "inside": true, + "id": "input-url", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.75px 1.5px 0px" + }, + { + "inside": true, + "id": "input-magnet", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.75px 1.5px 0px" + }, + { + "inside": true, + "id": "input-title", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.75px 1.5px 0px" + }, + { + "inside": true, + "id": "input-description", + "tag": "TEXTAREA", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.75px 1.5px 0px" + }, + { + "inside": true, + "id": "input-thumbnail", + "tag": "INPUT", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(15, 23, 42, 0.18) 0px 0.75px 1.5px 0px" + }, + { + "inside": true, + "id": "", + "tag": "BUTTON", + "outline": "solid", + "shadow": "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px" + } + ], + "missingTitle": true, + "noSourceRejected": true, + "noRelayPublish": true, + "restored": "uploadButton", + "layout": { + "width": 1280, + "documentWidth": 1280, + "cardBounds": [ + { + "x": 72, + "right": 353, + "width": 281 + }, + { + "x": 377, + "right": 658, + "width": 281 + }, + { + "x": 682, + "right": 963, + "width": 281 + } + ], + "smallControls": [ + { + "id": "sidebarCollapseToggle", + "label": "Expand sidebar", + "width": 33, + "height": 33 + }, + { + "id": "forYouLink", + "label": "For You", + "width": 33, + "height": 33 + }, + { + "id": "trendingLink", + "label": "Trending", + "width": 33, + "height": 33 + }, + { + "id": "mostZappedLink", + "label": "Most Zapped", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "Recently Added", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "For Kids", + "width": 33, + "height": 33 + }, + { + "id": "exploreLink", + "label": "Explore", + "width": 33, + "height": 33 + }, + { + "id": "subscriptionsLink", + "label": "Subscriptions", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "History", + "width": 33, + "height": 33 + }, + { + "id": "footerDropdownButton", + "label": "Show more sidebar links", + "width": 33, + "height": 33 + }, + { + "id": "", + "label": "Open search filters", + "width": 27, + "height": 27 + }, + { + "id": "headerSearchInput", + "label": "", + "width": 310, + "height": 13.125 + }, + { + "id": "", + "label": "Submit search", + "width": 27, + "height": 27 + }, + { + "id": "themeToggle", + "label": "Switch to dark theme", + "width": 30, + "height": 30 + }, + { + "id": "uploadButton", + "label": "+", + "width": 36, + "height": 36 + }, + { + "id": "profileButton", + "label": "", + "width": 36, + "height": 36 + }, + { + "id": "trendingInfoTrigger", + "label": "How is this feed sorted?", + "width": 15, + "height": 15 + }, + { + "id": "", + "label": "#video", + "width": 46.78125, + "height": 17.234375 + }, + { + "id": "", + "label": "More options", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "Video settings", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "More options", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "Video settings", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "More options", + "width": 30, + "height": 30 + }, + { + "id": "", + "label": "Video settings", + "width": 30, + "height": 30 + }, + { + "id": "closeUploadModal", + "label": "Close upload modal", + "width": 30, + "height": 30 + }, + { + "id": "btn-mode-upload", + "label": "Upload File", + "width": 91.0625, + "height": 22.125 + }, + { + "id": "btn-mode-external", + "label": "External Link", + "width": 99.578125, + "height": 22.125 + }, + { + "id": "input-url", + "label": "", + "width": 418, + "height": 27.125 + }, + { + "id": "input-magnet", + "label": "", + "width": 418, + "height": 27.125 + }, + { + "id": "input-title", + "label": "", + "width": 418, + "height": 27.125 + }, + { + "id": "input-thumbnail", + "label": "", + "width": 418, + "height": 27.125 + }, + { + "id": "", + "label": "+ Add Tag", + "width": 63.0625, + "height": 20.890625 + }, + { + "id": "check-comments", + "label": "", + "width": 15, + "height": 15 + }, + { + "id": "check-nsfw", + "label": "", + "width": 15, + "height": 15 + }, + { + "id": "check-kids", + "label": "", + "width": 15, + "height": 15 + }, + { + "id": "check-no-nip71-mirror", + "label": "", + "width": 15, + "height": 15 + }, + { + "id": "btn-advanced-toggle", + "label": "Advanced Options", + "width": 418, + "height": 13.125 + }, + { + "id": "btn-submit", + "label": "Publish Video", + "width": 418, + "height": 38 + } + ] + } + }, + "ok": true + }, + { + "screenshot": "playback-qa-2-follow-on-1280-upload-validation.png" + }, + { + "name": "1280 Home navigation", + "at": "2026-10-20T16:28:07.601Z", + "result": { + "url": "http://127.0.0.1:31847/index.html", + "cards": 3 + }, + "ok": true + } + ], + "fixtureHttp": { + "head": 200, + "get": 200, + "range": 206, + "bytes": 1019, + "contentType": "video/webm" + }, + "browserVersion": "154.0.8037.97", + "boot": "passed", + "uploadGeometry": [ + { + "viewport": "360", + "geometry": { + "viewport": { + "width": 360, + "height": 800, + "scrollX": 0, + "scrollY": 0 + }, + "document": { + "id": "", + "tag": "HTML", + "x": 0, + "y": 0, + "right": 360, + "bottom": 1336.796875, + "width": 360, + "height": 1336.796875, + "clientWidth": 360, + "scrollWidth": 362, + "overflowX": "visible", + "position": "static" + }, + "body": { + "id": "", + "tag": "BODY", + "x": 0, + "y": 0, + "right": 360, + "bottom": 1336.796875, + "width": 360, + "height": 1336.796875, + "clientWidth": 360, + "scrollWidth": 362, + "overflowX": "hidden", + "position": "static" + }, + "panel": { + "id": "", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 360, + "bottom": 800, + "width": 360, + "height": 800, + "clientWidth": 360, + "scrollWidth": 360, + "overflowX": "hidden", + "position": "relative" + }, + "root": { + "id": "uploadModal", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 360, + "bottom": 800, + "width": 360, + "height": 800, + "clientWidth": 360, + "scrollWidth": 360, + "overflowX": "visible", + "position": "fixed" + }, + "active": { + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 715.265625, + "right": 321.625, + "bottom": 755.640625, + "width": 283.25, + "height": 40.375, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "visible", + "position": "static" + }, + "controls": [ + { + "selector": "#input-title", + "id": "input-title", + "tag": "INPUT", + "x": 38.375, + "y": 247.09375, + "right": 321.625, + "bottom": 275.875, + "width": 283.25, + "height": 28.78125, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-url", + "id": "input-url", + "tag": "INPUT", + "x": 38.375, + "y": 62.703125, + "right": 321.625, + "bottom": 91.484375, + "width": 283.25, + "height": 28.78125, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-magnet", + "id": "input-magnet", + "tag": "INPUT", + "x": 38.375, + "y": 141.609375, + "right": 321.625, + "bottom": 170.390625, + "width": 283.25, + "height": 28.78125, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#btn-submit", + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 715.265625, + "right": 321.625, + "bottom": 755.640625, + "width": 283.25, + "height": 40.375, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "visible", + "position": "static" + }, + { + "selector": "#closeUploadModal", + "id": "closeUploadModal", + "tag": "BUTTON", + "x": 283.21875, + "y": 41.578125, + "right": 315.21875, + "bottom": 73.578125, + "width": 32, + "height": 32, + "clientWidth": 30, + "scrollWidth": 30, + "overflowX": "visible", + "position": "static" + } + ], + "offenders": [ + { + "id": "", + "tag": "SPAN", + "x": 51.439674377441406, + "y": 362.515625, + "right": 1526.8771743774414, + "bottom": 384.125, + "width": 1475.4375, + "height": 21.609375, + "clientWidth": 1475, + "scrollWidth": 1475, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 51.439674377441406, + "y": 362.515625, + "right": 789.1584243774414, + "bottom": 384.125, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 789.158447265625, + "y": 362.515625, + "right": 1526.877197265625, + "bottom": 384.125, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 686.484375, + "right": 510.1875, + "bottom": 708.09375, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 686.484375, + "right": 510.1875, + "bottom": 708.09375, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 1010.453125, + "right": 510.1875, + "bottom": 1032.0625, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 1010.453125, + "right": 510.1875, + "bottom": 1032.0625, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + } + ] + } + }, + { + "viewport": "390", + "geometry": { + "viewport": { + "width": 390, + "height": 844, + "scrollX": 0, + "scrollY": 0 + }, + "document": { + "id": "", + "tag": "HTML", + "x": 0, + "y": 0, + "right": 390, + "bottom": 1387.421875, + "width": 390, + "height": 1387.421875, + "clientWidth": 390, + "scrollWidth": 394, + "overflowX": "visible", + "position": "static" + }, + "body": { + "id": "", + "tag": "BODY", + "x": 0, + "y": 0, + "right": 390, + "bottom": 1387.421875, + "width": 390, + "height": 1387.421875, + "clientWidth": 390, + "scrollWidth": 394, + "overflowX": "hidden", + "position": "static" + }, + "panel": { + "id": "", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 390, + "bottom": 844, + "width": 390, + "height": 844, + "clientWidth": 390, + "scrollWidth": 390, + "overflowX": "hidden", + "position": "relative" + }, + "root": { + "id": "uploadModal", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 390, + "bottom": 844, + "width": 390, + "height": 844, + "clientWidth": 390, + "scrollWidth": 390, + "overflowX": "visible", + "position": "fixed" + }, + "active": { + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 759.265625, + "right": 351.625, + "bottom": 799.640625, + "width": 313.25, + "height": 40.375, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "visible", + "position": "static" + }, + "controls": [ + { + "selector": "#input-title", + "id": "input-title", + "tag": "INPUT", + "x": 38.375, + "y": 291.09375, + "right": 351.625, + "bottom": 319.875, + "width": 313.25, + "height": 28.78125, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-url", + "id": "input-url", + "tag": "INPUT", + "x": 38.375, + "y": 106.703125, + "right": 351.625, + "bottom": 135.484375, + "width": 313.25, + "height": 28.78125, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-magnet", + "id": "input-magnet", + "tag": "INPUT", + "x": 38.375, + "y": 185.609375, + "right": 351.625, + "bottom": 214.390625, + "width": 313.25, + "height": 28.78125, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#btn-submit", + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 759.265625, + "right": 351.625, + "bottom": 799.640625, + "width": 313.25, + "height": 40.375, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "visible", + "position": "static" + }, + { + "selector": "#closeUploadModal", + "id": "closeUploadModal", + "tag": "BUTTON", + "x": 313.21875, + "y": 41.578125, + "right": 345.21875, + "bottom": 73.578125, + "width": 32, + "height": 32, + "clientWidth": 30, + "scrollWidth": 30, + "overflowX": "visible", + "position": "static" + } + ], + "offenders": [ + { + "id": "", + "tag": "SPAN", + "x": 28.115467071533203, + "y": 379.390625, + "right": 1503.5529670715332, + "bottom": 401, + "width": 1475.4375, + "height": 21.609375, + "clientWidth": 1475, + "scrollWidth": 1475, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 28.115467071533203, + "y": 379.390625, + "right": 765.8342170715332, + "bottom": 401, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 765.834228515625, + "y": 379.390625, + "right": 1503.552978515625, + "bottom": 401, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 720.234375, + "right": 510.1875, + "bottom": 741.84375, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 720.234375, + "right": 510.1875, + "bottom": 741.84375, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 1061.078125, + "right": 510.1875, + "bottom": 1082.6875, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 1061.078125, + "right": 510.1875, + "bottom": 1082.6875, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + } + ] + } + }, + { + "viewport": "1280", + "geometry": { + "viewport": { + "width": 1280, + "height": 720, + "scrollX": 0, + "scrollY": 0 + }, + "document": { + "id": "", + "tag": "HTML", + "x": 0, + "y": 0, + "right": 1280, + "bottom": 720, + "width": 1280, + "height": 720, + "clientWidth": 1280, + "scrollWidth": 1280, + "overflowX": "visible", + "position": "static" + }, + "body": { + "id": "", + "tag": "BODY", + "x": 0, + "y": 0, + "right": 1280, + "bottom": 720, + "width": 1280, + "height": 720, + "clientWidth": 1280, + "scrollWidth": 1280, + "overflowX": "hidden", + "position": "static" + }, + "panel": { + "id": "", + "tag": "DIV", + "x": 388, + "y": 36, + "right": 892, + "bottom": 684, + "width": 504, + "height": 648, + "clientWidth": 502, + "scrollWidth": 502, + "overflowX": "hidden", + "position": "relative" + }, + "root": { + "id": "uploadModal", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 1280, + "bottom": 720, + "width": 1280, + "height": 720, + "clientWidth": 1280, + "scrollWidth": 1280, + "overflowX": "visible", + "position": "fixed" + }, + "active": { + "id": "btn-submit", + "tag": "BUTTON", + "x": 431, + "y": 597.671875, + "right": 849, + "bottom": 635.671875, + "width": 418, + "height": 38, + "clientWidth": 416, + "scrollWidth": 416, + "overflowX": "visible", + "position": "static" + }, + "controls": [ + { + "selector": "#input-title", + "id": "input-title", + "tag": "INPUT", + "x": 431, + "y": 158.03125, + "right": 849, + "bottom": 185.15625, + "width": 418, + "height": 27.125, + "clientWidth": 416, + "scrollWidth": 416, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-url", + "id": "input-url", + "tag": "INPUT", + "x": 431, + "y": -15.25, + "right": 849, + "bottom": 11.875, + "width": 418, + "height": 27.125, + "clientWidth": 416, + "scrollWidth": 416, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-magnet", + "id": "input-magnet", + "tag": "INPUT", + "x": 431, + "y": 58.890625, + "right": 849, + "bottom": 86.015625, + "width": 418, + "height": 27.125, + "clientWidth": 416, + "scrollWidth": 416, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#btn-submit", + "id": "btn-submit", + "tag": "BUTTON", + "x": 431, + "y": 597.671875, + "right": 849, + "bottom": 635.671875, + "width": 418, + "height": 38, + "clientWidth": 416, + "scrollWidth": 416, + "overflowX": "visible", + "position": "static" + }, + { + "selector": "#closeUploadModal", + "id": "closeUploadModal", + "tag": "BUTTON", + "x": 813, + "y": 79, + "right": 843, + "bottom": 109, + "width": 30, + "height": 30, + "clientWidth": 28, + "scrollWidth": 28, + "overflowX": "visible", + "position": "static" + } + ], + "offenders": [ + { + "id": "", + "tag": "SPAN", + "x": 85, + "y": 355.671875, + "right": 1469.09375, + "bottom": 375.921875, + "width": 1384.09375, + "height": 20.25, + "clientWidth": 1384, + "scrollWidth": 1384, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 777.046875, + "y": 355.671875, + "right": 1469.09375, + "bottom": 375.921875, + "width": 692.046875, + "height": 20.25, + "clientWidth": 692, + "scrollWidth": 692, + "overflowX": "visible", + "position": "static" + } + ] + } + } + ], + "uploadOverflowFindings": [ + { + "viewport": "360", + "documentWidth": 362, + "width": 360, + "severity": "minor horizontal overflow; controls horizontally in viewport", + "geometry": { + "viewport": { + "width": 360, + "height": 800, + "scrollX": 0, + "scrollY": 0 + }, + "document": { + "id": "", + "tag": "HTML", + "x": 0, + "y": 0, + "right": 360, + "bottom": 1336.796875, + "width": 360, + "height": 1336.796875, + "clientWidth": 360, + "scrollWidth": 362, + "overflowX": "visible", + "position": "static" + }, + "body": { + "id": "", + "tag": "BODY", + "x": 0, + "y": 0, + "right": 360, + "bottom": 1336.796875, + "width": 360, + "height": 1336.796875, + "clientWidth": 360, + "scrollWidth": 362, + "overflowX": "hidden", + "position": "static" + }, + "panel": { + "id": "", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 360, + "bottom": 800, + "width": 360, + "height": 800, + "clientWidth": 360, + "scrollWidth": 360, + "overflowX": "hidden", + "position": "relative" + }, + "root": { + "id": "uploadModal", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 360, + "bottom": 800, + "width": 360, + "height": 800, + "clientWidth": 360, + "scrollWidth": 360, + "overflowX": "visible", + "position": "fixed" + }, + "active": { + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 715.265625, + "right": 321.625, + "bottom": 755.640625, + "width": 283.25, + "height": 40.375, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "visible", + "position": "static" + }, + "controls": [ + { + "selector": "#input-title", + "id": "input-title", + "tag": "INPUT", + "x": 38.375, + "y": 247.09375, + "right": 321.625, + "bottom": 275.875, + "width": 283.25, + "height": 28.78125, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-url", + "id": "input-url", + "tag": "INPUT", + "x": 38.375, + "y": 62.703125, + "right": 321.625, + "bottom": 91.484375, + "width": 283.25, + "height": 28.78125, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-magnet", + "id": "input-magnet", + "tag": "INPUT", + "x": 38.375, + "y": 141.609375, + "right": 321.625, + "bottom": 170.390625, + "width": 283.25, + "height": 28.78125, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#btn-submit", + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 715.265625, + "right": 321.625, + "bottom": 755.640625, + "width": 283.25, + "height": 40.375, + "clientWidth": 281, + "scrollWidth": 281, + "overflowX": "visible", + "position": "static" + }, + { + "selector": "#closeUploadModal", + "id": "closeUploadModal", + "tag": "BUTTON", + "x": 283.21875, + "y": 41.578125, + "right": 315.21875, + "bottom": 73.578125, + "width": 32, + "height": 32, + "clientWidth": 30, + "scrollWidth": 30, + "overflowX": "visible", + "position": "static" + } + ], + "offenders": [ + { + "id": "", + "tag": "SPAN", + "x": 51.439674377441406, + "y": 362.515625, + "right": 1526.8771743774414, + "bottom": 384.125, + "width": 1475.4375, + "height": 21.609375, + "clientWidth": 1475, + "scrollWidth": 1475, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 51.439674377441406, + "y": 362.515625, + "right": 789.1584243774414, + "bottom": 384.125, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 789.158447265625, + "y": 362.515625, + "right": 1526.877197265625, + "bottom": 384.125, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 686.484375, + "right": 510.1875, + "bottom": 708.09375, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 686.484375, + "right": 510.1875, + "bottom": 708.09375, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 1010.453125, + "right": 510.1875, + "bottom": 1032.0625, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 1010.453125, + "right": 510.1875, + "bottom": 1032.0625, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + } + ] + } + }, + { + "viewport": "390", + "documentWidth": 394, + "width": 390, + "severity": "minor horizontal overflow; controls horizontally in viewport", + "geometry": { + "viewport": { + "width": 390, + "height": 844, + "scrollX": 0, + "scrollY": 0 + }, + "document": { + "id": "", + "tag": "HTML", + "x": 0, + "y": 0, + "right": 390, + "bottom": 1387.421875, + "width": 390, + "height": 1387.421875, + "clientWidth": 390, + "scrollWidth": 394, + "overflowX": "visible", + "position": "static" + }, + "body": { + "id": "", + "tag": "BODY", + "x": 0, + "y": 0, + "right": 390, + "bottom": 1387.421875, + "width": 390, + "height": 1387.421875, + "clientWidth": 390, + "scrollWidth": 394, + "overflowX": "hidden", + "position": "static" + }, + "panel": { + "id": "", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 390, + "bottom": 844, + "width": 390, + "height": 844, + "clientWidth": 390, + "scrollWidth": 390, + "overflowX": "hidden", + "position": "relative" + }, + "root": { + "id": "uploadModal", + "tag": "DIV", + "x": 0, + "y": 0, + "right": 390, + "bottom": 844, + "width": 390, + "height": 844, + "clientWidth": 390, + "scrollWidth": 390, + "overflowX": "visible", + "position": "fixed" + }, + "active": { + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 759.265625, + "right": 351.625, + "bottom": 799.640625, + "width": 313.25, + "height": 40.375, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "visible", + "position": "static" + }, + "controls": [ + { + "selector": "#input-title", + "id": "input-title", + "tag": "INPUT", + "x": 38.375, + "y": 291.09375, + "right": 351.625, + "bottom": 319.875, + "width": 313.25, + "height": 28.78125, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-url", + "id": "input-url", + "tag": "INPUT", + "x": 38.375, + "y": 106.703125, + "right": 351.625, + "bottom": 135.484375, + "width": 313.25, + "height": 28.78125, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#input-magnet", + "id": "input-magnet", + "tag": "INPUT", + "x": 38.375, + "y": 185.609375, + "right": 351.625, + "bottom": 214.390625, + "width": 313.25, + "height": 28.78125, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "clip", + "position": "static" + }, + { + "selector": "#btn-submit", + "id": "btn-submit", + "tag": "BUTTON", + "x": 38.375, + "y": 759.265625, + "right": 351.625, + "bottom": 799.640625, + "width": 313.25, + "height": 40.375, + "clientWidth": 311, + "scrollWidth": 311, + "overflowX": "visible", + "position": "static" + }, + { + "selector": "#closeUploadModal", + "id": "closeUploadModal", + "tag": "BUTTON", + "x": 313.21875, + "y": 41.578125, + "right": 345.21875, + "bottom": 73.578125, + "width": 32, + "height": 32, + "clientWidth": 30, + "scrollWidth": 30, + "overflowX": "visible", + "position": "static" + } + ], + "offenders": [ + { + "id": "", + "tag": "SPAN", + "x": 28.115467071533203, + "y": 379.390625, + "right": 1503.5529670715332, + "bottom": 401, + "width": 1475.4375, + "height": 21.609375, + "clientWidth": 1475, + "scrollWidth": 1475, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 28.115467071533203, + "y": 379.390625, + "right": 765.8342170715332, + "bottom": 401, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 765.834228515625, + "y": 379.390625, + "right": 1503.552978515625, + "bottom": 401, + "width": 737.71875, + "height": 21.609375, + "clientWidth": 738, + "scrollWidth": 738, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 720.234375, + "right": 510.1875, + "bottom": 741.84375, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 720.234375, + "right": 510.1875, + "bottom": 741.84375, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 90.59375, + "y": 1061.078125, + "right": 510.1875, + "bottom": 1082.6875, + "width": 419.59375, + "height": 21.609375, + "clientWidth": 420, + "scrollWidth": 420, + "overflowX": "visible", + "position": "static" + }, + { + "id": "", + "tag": "SPAN", + "x": 300.390625, + "y": 1061.078125, + "right": 510.1875, + "bottom": 1082.6875, + "width": 209.796875, + "height": 21.609375, + "clientWidth": 210, + "scrollWidth": 210, + "overflowX": "visible", + "position": "static" + } + ] + } + } + ], + "baseline": "follow-on coverage completed; original360overflow remains failed/open", + "ended": "2026-10-20T16:28:10.112Z", + "resourcesClosed": true +} \ No newline at end of file diff --git a/docs/testing/playback-qa-2-pipeline-diagnostic.png b/docs/testing/playback-qa-2-pipeline-diagnostic.png new file mode 100644 index 00000000..ec6eb0d5 Binary files /dev/null and b/docs/testing/playback-qa-2-pipeline-diagnostic.png differ diff --git a/docs/testing/playback-qa-2-pipeline-evidence.md b/docs/testing/playback-qa-2-pipeline-evidence.md new file mode 100644 index 00000000..e4ed917e --- /dev/null +++ b/docs/testing/playback-qa-2-pipeline-evidence.md @@ -0,0 +1,154 @@ +# QA2 measured pipeline boundary: ingested3, rendered0 + +Diagnostic authorized by dispatch15:58:27 and interpreter15:59:09;START16:00:28 before browser/server launch. +Actual run16:00:32.126→16:00:50.999Z(18.873s),Chromium154.0.8037.97,360×800. +Unchanged original3 seeded notes(no s tag),genuine media overlay,real Buffer mirror,immutable822 built app; +no refresh/card injection/cache edit/crypto stub/product change or assertion relaxation. + +**First evidenced gap is after ingestion/app map and before DOM cards.** Actual local WS connected and sent a +video REQ kinds30078/#tvideo/limit200. Received3 distinct EVENT ids and EOSE for that subscription. +Client rawEvents/allEvents/activeMap all3;app.videosMap3;initialized true,pool one relay,app service shares same client. +Actual DOM has0 data-testid=video-card and0 data-video-id. Styled Trending view displays no public videos. +No navigation/login/modal/390/1280/UI assertions executed;baseline remains incomplete. This is not yet a product-defect +attribution:filtering/visibility/render decisions have not been measured. Configured relay health alone is no longer +the only evidence;the actual wire delivery and ingest counts were observed passively. + +Missing s tag causes actual schema validation failure but did NOT prevent delivery/ingestion of these3 notes; +actual converter accepted them. Do not fix that gap blindly and claim it explains this render boundary. +It remains a known difference from the canonical existing fixture to disclose in any later baseline. +Earlier mode-filter speculation is unsupported and withdrawn. Startup-ready correction also did not move0/3. + +Genuine HTTP fixture checks passed HEAD200/GET2001019bytes/Range206;real canonical toolkit/signature/tamper proofs passed. +Buffer fulfilled locally once;all other non-local HTTP attempts blocked10,non-localWS0,local404=0,pageerrors0. +Only browser warning:service worker blocked intentionally by the harness context. Guards do not fake media events/health. +All owned browser/app/relay servers closed16:00:50.999Z;explicit RELEASE16:01:25Z. Preview3000 untouched. +No further browser or fix was started from this result. Next routing must measure actual filtering/render boundary, +not reduce the3-card requirement,change toolkit/crypto logic,or turn this into a boot-only success. + +[Actual empty styled Trending screenshot](playback-qa-2-pipeline-diagnostic.png) + +Reproduction uses own scratch diagnostic.mjs(versionSHA2563b758e6695eecb438f53344512fed186f4d1c459ca70ba49722a0177afcf39f5), +node execution against the documented frozen artifact/genuine media/real existing mock relay with external deny guard. +It attaches only passive page WebSocket/frame and console listeners,then reads cache/DOM counts after the unchanged +3-card15s wait. Full filters for unrelated built-in admin-list subscriptions are omitted below;they contain no video +seed response and do not alter the identified video path. Exact raw trace remains in own scratch pipeline-result.json. + +```json +{ + "started": "2026-10-20T16:00:32.126Z", + "ended": "2026-10-20T16:00:50.999Z", + "browser": "154.0.8037.97", + "localWsRouteHits": [ + "ws://127.0.0.1:31848/" + ], + "frameCounts": { + "sent:REQ": 15, + "received:EOSE": 15, + "sent:CLOSE": 12, + "received:EVENT": 3 + }, + "videoSubscriptionTraffic": [ + { + "at": "2026-10-20T16:00:35.756Z", + "direction": "sent", + "url": "ws://127.0.0.1:31848/", + "type": "REQ", + "subId": "sub:13", + "filters": [ + { + "#t": [ + "video" + ], + "kinds": [ + 30078 + ], + "limit": 200 + } + ] + }, + { + "at": "2026-10-20T16:00:35.764Z", + "direction": "received", + "url": "ws://127.0.0.1:31848/", + "type": "EVENT", + "subId": "sub:13", + "eventId": "e5464da7e3278076a0d3030ee3b9f233495d98b3159ec4e801b7651076b41c21", + "kind": 30078 + }, + { + "at": "2026-10-20T16:00:35.765Z", + "direction": "received", + "url": "ws://127.0.0.1:31848/", + "type": "EVENT", + "subId": "sub:13", + "eventId": "696fe1a6e5cfe784dc4a883f6c32cc9c3a15f9770a9403e0600d57cfe69d705a", + "kind": 30078 + }, + { + "at": "2026-10-20T16:00:35.765Z", + "direction": "received", + "url": "ws://127.0.0.1:31848/", + "type": "EVENT", + "subId": "sub:13", + "eventId": "e48da2722113d312388399e7af346bcf1e8fd0edad2b7e8446e0e379e645d28b", + "kind": 30078 + }, + { + "at": "2026-10-20T16:00:35.765Z", + "direction": "received", + "url": "ws://127.0.0.1:31848/", + "type": "EOSE", + "subId": "sub:13" + } + ], + "failureDiagnostics": { + "appPresent": true, + "relayHealth": { + "relays": [ + "ws://127.0.0.1:31848" + ], + "unreachable": [], + "backoff": {} + }, + "feedItems": [], + "rawEvents": 3, + "allEvents": 3, + "activeMap": 3, + "appVideoMap": 3, + "clientInitialized": true, + "poolExists": true, + "poolRelayCount": 1, + "subscriptionManagerKeys": [ + "subscribe", + "list", + "handleReconnect", + "closeAll", + "getActiveKeys" + ], + "appServiceClientMatches": true, + "domCardTestids": 0, + "domVideoIds": 0, + "body": "Switch to dark theme\nTrending\n\nNo public videos available yet. Be the first to upload one!\n\nseed. zap. subscribe.\nv: 1834b6a9 \u2022 2026-10-20" + }, + "consoleWarnings": [ + "Service Worker registration blocked by Playwright" + ], + "fixtureHttp": { + "head": 200, + "get": 200, + "range": 206, + "bytes": 1019, + "contentType": "video/webm" + }, + "bufferMirrorHits": 1, + "otherNonlocalHttpBlocked": 10, + "nonlocalWsBlocked": 0, + "local404": 0, + "pageErrors": [], + "resourcesClosed": true +} +``` + +Runtime snapshot manifest remains662fa755e3cde021058f5bf9a5bd2825698bad064ab80472a5ba70e651d764b1; +build1834b6a9/index5095c3f1326b035ef93267ad77663ea641edb1acaae6d18e88085eff08c5d6d3. +No full suite/build/native Firefox/production/live-peer/cloud/signer qualification is implied. diff --git a/docs/testing/playback-qa-2-render-diagnostic.png b/docs/testing/playback-qa-2-render-diagnostic.png new file mode 100644 index 00000000..ec6eb0d5 Binary files /dev/null and b/docs/testing/playback-qa-2-render-diagnostic.png differ diff --git a/docs/testing/playback-qa-2-render-gap.md b/docs/testing/playback-qa-2-render-gap.md new file mode 100644 index 00000000..fbd2a517 --- /dev/null +++ b/docs/testing/playback-qa-2-render-gap.md @@ -0,0 +1,167 @@ +# QA2: demonstrated cold-feed refresh gap (client7 routed) + +Dispatch16:08:41 classified this as a hard local-review blocker and routed a method-only actual-service +regression/fix to client-reliability-7. QA is product-read-only and holds browser closed until durable fix+ +refreshed built artifact. No forced refresh/render is an acceptable baseline substitute. + +## Trigger and observation + +Fresh actual app at1b3ef49c/build1834b6a9,empty browser cache,existing local mock relay preseeded3 synthetic +kind30078 video events. Hosted media is genuine local VP8 with real HEAD/GET/Range semantics;crypto uses genuine +canonical toolkit and local pinned Buffer mirror. Runtime artifact,external deny guard,inputs and3-card15s bound unchanged. + +Granted targeted passive run16:06:34.174→16:06:51.787Z(17.613s),Chromium154.0.8037.97,360×800: +- Actual local relay video REQ returns3 EVENTs plus EOSE;client and app caches contain3. +- Actual getFilteredActiveVideos returns3;all3 shouldIncludeVideo and canAccess decisions aretrue. +- No private,NSFW,deleted,invalid,author-blocked or event-blacklisted flags. Whitelistfalse,lockdownfalse,hydratedtrue. +- Passive service chronology:initial videos:updated cache count0,then videos:cache size3 and videos:updated subscription count3. +- View remains currentVideos0,renderedIDs0,last signature__EMPTY__,metadata.reason=cache;actualDOM cards0 after15s. +- View container connected and matches activevideoList;bodynotinert,documentvisible. This rules out the measured + access/private/NSFW/blacklist and mount/visibility candidates for this reproduction. + +[Actual empty styled Trending screenshot](playback-qa-2-render-diagnostic.png) + +First missing observed notification/render boundary is after allowed subscription data enters the service/appmap. +Source:js/services/nostrService.js loadVideos applyAndNotify invokes onVideos for cache/optimistic paths; +live subscription callback caches/emits but never invokes that callback. js/app/feedCoordinator.js onVideos owns +refreshFeed/render;applicationBootstrap binds subscription:changed but not videos:updated for main-feed rendering. +This is the minimal owner/file boundary sent to dispatch,not a product edit by QA. Client7 owns reproduction/fix. + +## Fidelity and interventions + +Original diagnostic seed omitted canonical s tag. Actual schema rejects that omission,while actual converter and +service eligibility accept all3,and wire/ingest/allow decisions are measured. Thus this schema mismatch did not +prevent the observed ingestion/eligibility and must not be presented as the empty-render cause. The post-fix +baseline should retain the established canonical fixture shape;no input or assertion was silently changed here. + +An early passive observer initScript had1 pageerror for relative module resolution on the initial document. +The observer subsequently DID attach on the actual app and captured the exact cache/subscription chronology below. +This is an instrumentation error,not a product error. Product methods were not wrapped/replaced;only event listeners +and read-only queries were added. No refresh/render invocation,manual card insertion,cache mutation,new seeds, +fake media events/health,extra mirror or product change. Existing earlier driver interventions remain preserved in +[second-pass history](playback-qa-2-second-pass.md). + +All owned browser/context/app+relay servers closed16:06:51.787Z,explicit RELEASE16:07:45Z;temporary dependency +symlink removed. Preview3000 and other processes untouched. No login/navigation/Profile/Upload/remaining viewport +checks were executed. Full core/mobile baseline is blocked,not done. QA will resume original assertions only after +client7 durable landing,integrated preview rebuild and dispatch browser clearance. + +## Exact captured decisions + +```json +{ + "started": "2026-10-20T16:06:34.174Z", + "ended": "2026-10-20T16:06:51.787Z", + "downstream": { + "activeVideos": 3, + "filteredVideos": 3, + "whitelistMode": false, + "lockdownActive": false, + "accessHydrated": true, + "decisions": [ + { + "id": "d8214f91f51180561f0fac563d163c4d3a049f53ea6dd24d0a3c892f918777e1", + "isPrivate": false, + "isNsfw": false, + "deleted": false, + "invalid": false, + "accessAllowed": true, + "serviceInclude": true, + "authorBlocked": false, + "eventBlacklisted": false + }, + { + "id": "1895c1e24ab449778121178f2301a2d5b9d296f6a76c2703900cd9141034058f", + "isPrivate": false, + "isNsfw": false, + "deleted": false, + "invalid": false, + "accessAllowed": true, + "serviceInclude": true, + "authorBlocked": false, + "eventBlacklisted": false + }, + { + "id": "11939f8c1f39254c8ff49905e9ec8c78c19d362e7f131d99eb0f2bb5eecaee22", + "isPrivate": false, + "isNsfw": false, + "deleted": false, + "invalid": false, + "accessAllowed": true, + "serviceInclude": true, + "authorBlocked": false, + "eventBlacklisted": false + } + ], + "viewExists": true, + "viewCurrentVideos": 0, + "viewRenderedIds": 0, + "viewLastSignature": "__EMPTY__", + "viewContainerConnected": true, + "viewContainerMatches": true, + "latestMetadata": { + "why": [], + "config": { + "timeWindow": null, + "actorFilters": [], + "tagFilters": [], + "sortOrder": "recent" + }, + "reason": "cache", + "preserveOrder": true + }, + "viewMetadata": { + "why": [], + "config": { + "timeWindow": null, + "actorFilters": [], + "tagFilters": [], + "sortOrder": "recent" + }, + "reason": "cache", + "preserveOrder": true + }, + "bodyInert": false, + "documentVisibility": "visible", + "passiveFeedEvents": [ + { + "type": "observer-attached", + "at": 1792512395999 + }, + { + "type": "videos:updated", + "at": 1792512396468, + "reason": "cache", + "count": 0 + }, + { + "type": "videos:cache", + "at": 1792512396487, + "size": 3 + }, + { + "type": "videos:updated", + "at": 1792512396487, + "reason": "subscription", + "count": 3 + } + ] + }, + "fixtureHttp": { + "head": 200, + "get": 200, + "range": 206, + "bytes": 1019, + "contentType": "video/webm" + }, + "pageErrors": [ + "Failed to resolve module specifier '/js/applicationContext.js'" + ], + "resourcesClosed": true +} +``` + +Diagnostic source SHA2567867eaced118cad69ae9f5e5dd0054149d71b8c892772a5a75fda2225d8b2715; +full report retained at own scratch render-result.json. Original822file runtime manifest remains +662fa755e3cde021058f5bf9a5bd2825698bad064ab80472a5ba70e651d764b1. +No actual WebTorrent/live peer/provider/signer/production/release qualification is implied. diff --git a/docs/testing/playback-qa-2-resumed-stop.png b/docs/testing/playback-qa-2-resumed-stop.png new file mode 100644 index 00000000..ec6eb0d5 Binary files /dev/null and b/docs/testing/playback-qa-2-resumed-stop.png differ diff --git a/docs/testing/playback-qa-2-second-pass.md b/docs/testing/playback-qa-2-second-pass.md new file mode 100644 index 00000000..b3e2137a --- /dev/null +++ b/docs/testing/playback-qa-2-second-pass.md @@ -0,0 +1,574 @@ +# QA2 second browser attempt: shell ready, baseline incomplete + +Actual dispatch-built app:1b3ef49c,version1834b6a9. QA source HEAD at execution:c5818cfe67c2f986138e8322951d99bcb94a5c47. +Authoritative browser grant15:24:20.735Z,consumed15:24:27Z; START15:26:26Z before browser/server launch. +Prior blocker/asset docs submission25942a15 landed1b3ef49c at15:22:48.832Z; durable status verified. + +Fresh own frozen snapshot822 files,manifest SHA256662fa755e3cde021058f5bf9a5bd2825698bad064ab80472a5ba70e651d764b1; +index5095c3f1326b035ef93267ad77663ea641edb1acaae6d18e88085eff08c5d6d3, +CSS283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3. +Copied integrated r2Service bytes matched merged current source, SHA2564719aecfeae3355a7dc933eec0d55d2e664e427bf425dedf7d3bd324dd682658. +Real pinned Buffer ESM65405 bytes,SHA256507a593508c51d1397a9a5818ad0b8dbf7e3ad810feaa87cabff87ff56cf3984; +local fulfillment occurred exactly once at its original CDN import URL. No other non-local request was permitted. +This remains a genuine npm-source repackaging,not byte-identical CDN/transitive resolution or production-offline proof. + +## Measured result and driver intervention + +`node /tmp/bitvid-playback-qa-2-integrated/controller.mjs` in a non-PTY exec session: exit0, +15:26:29.558→15:26:32.207Z (2.649s),Chromium154.0.8037.97,360×800. +App harness object,actual getApplication() object and visible video-list became ready. Initial emitted BOOTED/boot=passed +meant these three readiness checks only; it **does not mean baseline passed**. The retained report corrects that label to +shell-and-harness-ready-only/INCOMPLETE. Actual page body:Trending/No public videos available yet/footer version1834b6a9. +No pageerror or local404. Guard blocked10 non-local HTTP attempts(AWS SDK and parallel Nostr fallback imports),0 non-localWS. +These attempts alone do not establish a hard boot blocker:the bootstrap also tries the existing real local Nostr bundle. +No later toolkit readiness or seeded-card hydration was measured before closure. + +The runner's interactive stdin was immediately EOF in the non-PTY tool session. Its for-await command loop ended, +then finally closed all resources before any planned UI actions ran. `actions=[]`. This is an avoidable runner mistake, +not a demonstrated app defect. No second-attempt screenshot was captured; the first-attempt screenshot documents only +its unstyled source-checkout blocker and must not stand in for this integrated snapshot. + +Browser/context/app31847+existing mockrelay31848/31849 closed15:26:32.207Z;explicit RELEASE/intervention reported +15:27:26Z. Own temporary dependency symlink removed;preview3000 and other processes untouched. This pass seeded only3 +synthetic local video events with real test-key signing through the existing mockrelay API;no production identity/signing, +real media,peers,buckets or outgoing external publishing. Their appearance in the feed was not verified. + +## Unverified work and concrete correction + +390×844/1280×720,3-card hydration,navigation,Profile/Upload interactions,required-field validation,touch controls, +focus and overflow remain unverified. Do not classify parallel blocked imports as a product regression without observation. +Concrete runner correction: use the same isolated controller in a PTY(`tty:true`) so stdin remains available; +wait for actual seeded-card/toolkit readiness,then execute bounded UI actions. Requires dispatch resumption/browser grant. +No further browser/build/asset mirroring/product changes have been performed under this note. + +## Saved report + +```json +{ + "started": "2026-10-20T15:26:29.558Z", + "mirrorHits": [ + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm" + ], + "blockedHttp": [ + "https://esm.sh/@aws-sdk/client-s3@3.637.0?bundle", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0?target=es2022&exports=nip04", + "https://esm.sh/nostr-tools@2.17.0/nip44?target=es2022", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js" + ], + "blockedWs": [], + "failedLocal": [], + "pageErrors": [], + "consoleErrors": [ + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector", + "Failed to load resource: net::ERR_BLOCKED_BY_CLIENT.Inspector" + ], + "actions": [], + "browserVersion": "154.0.8037.97", + "boot": "shell-and-harness-ready-only", + "ended": "2026-10-20T15:26:32.207Z", + "resourcesClosed": true, + "baseline": "INCOMPLETE", + "driverIssue": "stdin EOF closed interactive controller before any actions; not a demonstrated app blocker" +} +``` + +## Exact controller used (non-PTY invocation caused early EOF) + +```js +import {createServer} from 'node:http'; +import {readFile,writeFile} from 'node:fs/promises'; +import {resolve,extname} from 'node:path'; +import {createInterface} from 'node:readline'; +import {createRequire} from 'node:module'; +import {chromium} from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import {startRelay} from '/home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs'; +const require=createRequire('/home/user/Documents/GitHub/bitvid/package.json'); +const {finalizeEvent}=require('nostr-tools'); +const base='/tmp/bitvid-playback-qa-2-integrated',root=base+'/dist'; +const evidence='/home/user/Documents/GitHub/bitvid-playback-qa/docs/testing'; +const report={started:new Date().toISOString(),mirrorHits:[],blockedHttp:[],blockedWs:[],failedLocal:[],pageErrors:[],consoleErrors:[],actions:[]}; +const sk=new Uint8Array(32).fill(0x7b); +const server=createServer(async(req,res)=>{ + let path=decodeURIComponent(new URL(req.url,'http://127.0.0.1:31847').pathname);if(path==='/')path='/index.html';const f=resolve(root,'.'+path); + if(!f.startsWith(root+'/')||path.includes('/.')){res.writeHead(404);res.end();return;} + try{const b=await readFile(f);res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.mjs':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.wasm':'application/wasm','.png':'image/png','.webmanifest':'application/manifest+json'})[extname(f)]||'application/octet-stream');res.end(b);}catch{res.writeHead(404);res.end();} +}); +let browser,relay,context,page,closed=false; +async function finish(){if(closed)return;closed=true;if(context)await context.close();if(browser)await browser.close();if(relay)await relay.close();await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await writeFile(base+'/result.json',JSON.stringify(report,null,2));console.log(JSON.stringify({type:'CLOSED',ended:report.ended,mirrorHits:report.mirrorHits.length,blockedHttp:report.blockedHttp,blockedWs:report.blockedWs,failedLocal:report.failedLocal,pageErrors:report.pageErrors}));} +try{ + await new Promise((ok,no)=>{server.once('error',no);server.listen(31847,'127.0.0.1',ok);}); + relay=startRelay(31848,{httpPort:31849}); + for(let i=0;i<3;i++){const title=i===0?'QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls':'QA local synthetic video '+i;relay.seedEvent(finalizeEvent({kind:30078,created_at:Math.floor(Date.now()/1000)-i,tags:[['d','qa2-'+i],['t','video'],['title',title],['url','http://127.0.0.1:31847/qa2-unplayed.webm']],content:JSON.stringify({version:3,title,url:'http://127.0.0.1:31847/qa2-unplayed.webm',videoRootId:'qa2-'+i,mode:'dev',isPrivate:false,deleted:false})},sk));} + browser=await chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']});report.browserVersion=browser.version(); + context=await browser.newContext({viewport:{width:360,height:800},serviceWorkers:'block'}); + const mirror=await readFile(base+'/buffer-6.0.3-local.mjs'); + await context.route('**/*',r=>{const url=r.request().url();if(url==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){report.mirrorHits.push(url);return r.fulfill({body:mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}const u=new URL(url);if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))return r.continue();report.blockedHttp.push(url);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',ws=>{const u=new URL(ws.url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))ws.connectToServer();else{report.blockedWs.push(ws.url());ws.close();}}); + await context.addInitScript(()=>{localStorage.setItem('hasSeenDisclaimer','true');localStorage.setItem('__bitvidTestMode__','1');localStorage.setItem('__bitvidTestRelays__',JSON.stringify(['ws://127.0.0.1:31848']));window.__bitvidTestRelays__=['ws://127.0.0.1:31848'];localStorage.setItem('bitvid_admin_whitelist_mode','false');}); + page=await context.newPage();page.setDefaultTimeout(10000); + page.on('pageerror',e=>report.pageErrors.push(e.message));page.on('console',m=>{if(m.type()==='error')report.consoleErrors.push(m.text().slice(0,700));});page.on('response',r=>{if(r.status()>=400&&new URL(r.url()).hostname==='127.0.0.1')report.failedLocal.push({url:r.url(),status:r.status()});}); + await page.goto('http://127.0.0.1:31847/?__test__=1&__testRelays__=ws%3A%2F%2F127.0.0.1%3A31848',{waitUntil:'domcontentloaded'}); + try{await page.waitForFunction(()=>typeof window.__bitvidTest__==='object',null,{timeout:15000});await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');return !!m.getApplication();},null,{timeout:15000});await page.locator('[data-testid="video-list"]').waitFor({state:'visible',timeout:15000});report.boot='passed';console.log(JSON.stringify({type:'BOOTED',viewport:{width:360,height:800},body:(await page.locator('body').innerText()).slice(0,3000)}));} + catch(e){report.boot='blocked';report.bootError=e.message;await page.screenshot({path:evidence+'/playback-qa-2-second-boot-blocker.png',fullPage:true});console.log(JSON.stringify({type:'BLOCKED',error:e.message,body:(await page.locator('body').innerText()).slice(0,1500)}));await finish();process.exit(0);} + const lines=createInterface({input:process.stdin}); + for await(const line of lines){if(!line.trim())continue;try{const c=JSON.parse(line);let result; + if(c.type==='done'){report.actions.push(c);await finish();break;} + if(c.type==='eval'){const fn=Function('return ('+c.script+')')();result=await page.evaluate(fn,c.arg);} + if(c.type==='click'){await page.locator(c.selector).click();result='clicked';} + if(c.type==='fill'){await page.locator(c.selector).fill(c.value);result='filled';} + if(c.type==='press'){await page.keyboard.press(c.key);result='pressed';} + if(c.type==='viewport'){await page.setViewportSize(c.viewport);result=c.viewport;} + if(c.type==='screenshot'){await page.screenshot({path:evidence+'/'+c.name,fullPage:true});result=c.name;} + if(c.type==='wait'){await page.locator(c.selector).waitFor({state:c.state||'visible',timeout:c.timeout||10000});result='ready';} + report.actions.push({at:new Date().toISOString(),command:c,result});console.log(JSON.stringify({type:'RESULT',id:c.id,result})); + }catch(e){report.actions.push({error:e.message});console.log(JSON.stringify({type:'ACTION_ERROR',error:e.message}));}} + await finish(); +}catch(e){report.runError=e.stack;console.log(JSON.stringify({type:'ERROR',error:e.stack}));await finish();process.exitCode=1;} +``` + +A browser-free PTY stdin handshake was independently checked after the driver failure: +Node printed PTY_READY and stayed running; a later write_stdin sent qa2-driver-ack; +process printed PTY_HANDSHAKE_PASS and exited0. This verifies the proposed terminal transport correction +without opening another browser/server or changing app behavior. It is not a resumed UI baseline. + +## Noninteractive correction prepared under interpreter hold + +Interpreter15:30:06 confirms history/client6 owns full/browser resource; no browser/server/build may launch +until dispatch explicitly grants the next attempt after release. Only read-only scratch preparation performed. +The corrected runner removes stdin entirely, has a120s resource-cleaning hard deadline and existing bounded +locator/card-readiness waits. It requires real toolkit readiness(no __TEST_MODE__ mock flag),exactly3 synthetic +relay video cards,then test-only login through the unchanged harness. For each360×800/390×844/1280×720 viewport: +actual Trending/Explore/ForYou link routing; feed/card/document width observations; Profile open→6Tab focus containment +→close→trigger focus restoration; Upload external-link mode open→native missing-title validity→title-only missing +source error→no additional kind30078 video published→close/focus restoration. It captures feed/profile/validation +screenshots,small-control dimensions against44px as observations only,and all network guard outcomes. +No source/toolkit/crypto override,new public resource mirror,or expectation change. Syntax-check exit0 only; +**none of these prepared UI actions has yet been executed**. First unavailable prerequisite or failed assertion +stops this pass and captures evidence; no retry/sleep masking. Guard and genuine Buffer/artifact pins unchanged. +The per-validation publication check counts video events specifically,excluding unrelated asynchronous list events. + +Dispatch15:30:36 explicitly approved the same own-scratch noninteractive correction only,while client6 holds +full/browser. Final browser requires a later explicit grant and runtime diff check after test-only history landing. +Prepared runner SHA256:`f60065c2a45b4b20485acaecd91bc5c5c937069cea4db095727c2838895588e0`. +Exact prepared source below is evidence,not a registered/retained test or a claim of successful UI execution. + +```js +import {createServer} from 'node:http'; +import {readFile,writeFile} from 'node:fs/promises'; +import {resolve,extname} from 'node:path'; +import {createInterface} from 'node:readline'; +import {createRequire} from 'node:module'; +import {chromium} from '/home/user/Documents/GitHub/bitvid/node_modules/playwright/index.mjs'; +import {startRelay} from '/home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs'; +const require=createRequire('/home/user/Documents/GitHub/bitvid/package.json'); +const {finalizeEvent}=require('nostr-tools'); +const base='/tmp/bitvid-playback-qa-2-integrated',root=base+'/dist'; +const evidence='/home/user/Documents/GitHub/bitvid-playback-qa/docs/testing'; +const report={driver:'noninteractive bounded actions',started:new Date().toISOString(),mirrorHits:[],blockedHttp:[],blockedWs:[],failedLocal:[],pageErrors:[],consoleErrors:[],actions:[]}; +const sk=new Uint8Array(32).fill(0x7b); +const server=createServer(async(req,res)=>{ + let path=decodeURIComponent(new URL(req.url,'http://127.0.0.1:31847').pathname);if(path==='/')path='/index.html';const f=resolve(root,'.'+path); + if(!f.startsWith(root+'/')||path.includes('/.')){res.writeHead(404);res.end();return;} + try{const b=await readFile(f);res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.mjs':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.wasm':'application/wasm','.png':'image/png','.webmanifest':'application/manifest+json'})[extname(f)]||'application/octet-stream');res.end(b);}catch{res.writeHead(404);res.end();} +}); +let browser,relay,context,page,closed=false,hardDeadline; +async function finish(){if(closed)return;closed=true;clearTimeout(hardDeadline);if(context)await context.close();if(browser)await browser.close();if(relay)await relay.close();await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await writeFile(base+'/result.json',JSON.stringify(report,null,2));console.log(JSON.stringify({type:'CLOSED',ended:report.ended,mirrorHits:report.mirrorHits.length,blockedHttp:report.blockedHttp,blockedWs:report.blockedWs,failedLocal:report.failedLocal,pageErrors:report.pageErrors}));} +hardDeadline=setTimeout(()=>{report.deadlineFailure='120s bounded runner deadline';void finish().finally(()=>process.exit(1));},120000); +try{ + await new Promise((ok,no)=>{server.once('error',no);server.listen(31847,'127.0.0.1',ok);}); + relay=startRelay(31848,{httpPort:31849}); + for(let i=0;i<3;i++){const title=i===0?'QA local synthetic video with a deliberately long title to inspect narrow-card wrapping and controls':'QA local synthetic video '+i;relay.seedEvent(finalizeEvent({kind:30078,created_at:Math.floor(Date.now()/1000)-i,tags:[['d','qa2-'+i],['t','video'],['title',title],['url','http://127.0.0.1:31847/qa2-unplayed.webm']],content:JSON.stringify({version:3,title,url:'http://127.0.0.1:31847/qa2-unplayed.webm',videoRootId:'qa2-'+i,mode:'dev',isPrivate:false,deleted:false})},sk));} + browser=await chromium.launch({headless:true,executablePath:'/usr/bin/google-chrome',args:['--no-sandbox']});report.browserVersion=browser.version(); + context=await browser.newContext({viewport:{width:360,height:800},serviceWorkers:'block'}); + const mirror=await readFile(base+'/buffer-6.0.3-local.mjs'); + await context.route('**/*',r=>{const url=r.request().url();if(url==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){report.mirrorHits.push(url);return r.fulfill({body:mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}const u=new URL(url);if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))return r.continue();report.blockedHttp.push(url);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',ws=>{const u=new URL(ws.url());if(['127.0.0.1','localhost','[::1]'].includes(u.hostname))ws.connectToServer();else{report.blockedWs.push(ws.url());ws.close();}}); + await context.addInitScript(()=>{localStorage.setItem('hasSeenDisclaimer','true');localStorage.setItem('__bitvidTestMode__','1');localStorage.setItem('__bitvidTestRelays__',JSON.stringify(['ws://127.0.0.1:31848']));window.__bitvidTestRelays__=['ws://127.0.0.1:31848'];localStorage.setItem('bitvid_admin_whitelist_mode','false');}); + page=await context.newPage();page.setDefaultTimeout(10000); + page.on('pageerror',e=>report.pageErrors.push(e.message));page.on('console',m=>{if(m.type()==='error')report.consoleErrors.push(m.text().slice(0,700));});page.on('response',r=>{if(r.status()>=400&&new URL(r.url()).hostname==='127.0.0.1')report.failedLocal.push({url:r.url(),status:r.status()});}); + await page.goto('http://127.0.0.1:31847/?__test__=1&__testRelays__=ws%3A%2F%2F127.0.0.1%3A31848',{waitUntil:'domcontentloaded'}); + try{await page.waitForFunction(()=>typeof window.__bitvidTest__==='object',null,{timeout:15000});await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');return !!m.getApplication();},null,{timeout:15000});await page.locator('[data-testid="video-list"]').waitFor({state:'visible',timeout:15000});report.boot='passed';console.log(JSON.stringify({type:'BOOTED',viewport:{width:360,height:800},body:(await page.locator('body').innerText()).slice(0,3000)}));} + catch(e){report.boot='blocked';report.bootError=e.message;await page.screenshot({path:evidence+'/playback-qa-2-second-boot-blocker.png',fullPage:true});console.log(JSON.stringify({type:'BLOCKED',error:e.message,body:(await page.locator('body').innerText()).slice(0,1500)}));await finish();process.exit(0);} + + // Noninteractive: no stdin/readline loop; each action is awaited exactly once. + const check=(condition,message)=>{if(!condition)throw new Error(message);}; + const step=async(name,action)=>{const item={name,at:new Date().toISOString()};report.actions.push(item);try{item.result=await action();item.ok=true;console.log(JSON.stringify({type:'STEP',name,ok:true,result:item.result}));return item.result;}catch(e){item.ok=false;item.error=e.message;console.log(JSON.stringify({type:'STEP',name,ok:false,error:e.message}));throw e;}}; + const capture=async(name)=>{await page.screenshot({path:evidence+'/'+name,fullPage:true});report.actions.push({screenshot:name});}; + const layout=async()=>page.evaluate(()=>({width:innerWidth,documentWidth:document.documentElement.scrollWidth,cardBounds:[...document.querySelectorAll('[data-testid="video-card"]')].map(e=>{const r=e.getBoundingClientRect();return {x:r.x,right:r.right,width:r.width};}),smallControls:[...document.querySelectorAll('button,a,input')].filter(e=>e.getClientRects().length&&getComputedStyle(e).visibility!=='hidden').map(e=>{const r=e.getBoundingClientRect();return {id:e.id,label:(e.getAttribute('aria-label')||e.textContent||'').trim().slice(0,80),width:r.width,height:r.height};}).filter(x=>x.width>0&&x.height>0&&(x.width<44||x.height<44))})); + const modalFocus=async(root)=>{const samples=[];for(let i=0;i<6;i++){await page.keyboard.press('Tab');samples.push(await page.evaluate(selector=>{const el=document.activeElement;return {inside:!!document.querySelector(selector)?.contains(el),id:el?.id,tag:el?.tagName,outline:getComputedStyle(el).outlineStyle,shadow:getComputedStyle(el).boxShadow};},root));}check(samples.every(x=>x.inside),'keyboard focus escaped '+root);return samples;}; + try{ + await step('actual toolkit ready',async()=>{const ready=await page.evaluate(async()=>{const m=await import('/js/nostrToolsBootstrap.js');const r=await m.nostrToolsReady;return {ok:r?.ok,reason:r?.reason,mockFlag:!!window.__TEST_MODE__};});check(ready.ok===true&&!ready.mockFlag,'real toolkit unavailable: '+JSON.stringify(ready));return ready;}); + await step('three synthetic relay cards hydrate',async()=>{await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const items=await page.evaluate(()=>window.__bitvidTest__.getFeedItems());check(items.length===3,'expected exactly3 local cards,got'+items.length);return items.map(x=>({title:x.title,dTag:x.dTag}));}); + await step('local test identity login',async()=>{await page.evaluate(()=>window.__bitvidTest__.loginWithNsec('7b'.repeat(32)));await page.locator('[data-testid="profile-button"]').waitFor({state:'visible'});await page.locator('[data-testid="upload-button"]').waitFor({state:'visible'});return {profileVisible:true,uploadVisible:true};}); + for(const viewport of [{width:360,height:800},{width:390,height:844},{width:1280,height:720}]){ + await page.setViewportSize(viewport);const prefix=String(viewport.width); + await step(prefix+' trending navigation/feed',async()=>{await page.locator('#trendingLink').click();await page.waitForFunction(()=>location.hash.includes('view=trending'));await page.evaluate(()=>window.__bitvidTest__.waitForFeedItems(3,15000));const r=await layout();check(r.documentWidth<=r.width,'horizontal document overflow');check(r.cardBounds.every(x=>x.x>=0&&x.right<=r.width),'card extends beyond viewport');return r;}); + await capture('playback-qa-2-'+prefix+'-feed.png'); + for(const [id,view] of [['#exploreLink','explore'],['#forYouLink','for-you'],['#trendingLink','trending']])await step(prefix+' navigation '+view,async()=>{await page.locator(id).click();await page.waitForFunction(v=>location.hash.includes('view='+v),view);return {hash:await page.evaluate(()=>location.hash),headings:await page.locator('h1,h2').allTextContents()};}); + await step(prefix+' Profile open/focus/close',async()=>{await page.locator('[data-testid="profile-button"]').click();await page.locator('#profileModal').waitFor({state:'visible'});const focus=await modalFocus('#profileModal');await capture('playback-qa-2-'+prefix+'-profile.png');await page.locator('#closeProfileModal').click();await page.locator('#profileModal').waitFor({state:'hidden'});const restored=await page.evaluate(()=>document.activeElement?.id);check(restored==='profileButton','Profile close did not restore trigger focus');return {focus,restored};}); + await step(prefix+' Upload required fields/focus/close',async()=>{const videoCount=()=>[...relay.getEvents().values()].filter(e=>e.kind===30078).length;const before=videoCount();await page.locator('[data-testid="upload-button"]').click();await page.locator('#uploadModal').waitFor({state:'visible'});await page.locator('#btn-mode-external').click();const focus=await modalFocus('#uploadModal');await page.locator('#input-title').fill('');await page.locator('#input-url').fill('');await page.locator('#input-magnet').fill('');await page.locator('#btn-submit').click();const missingTitle=await page.locator('#input-title').evaluate(e=>e.validity.valueMissing);check(missingTitle,'empty title not rejected by native validation');await page.locator('#input-title').fill('QA invalid local form');await page.locator('#btn-submit').click();await page.waitForFunction(()=>document.querySelector('#errorContainer')?.textContent.includes('Please provide at least a Video URL or Magnet Link.'));check(videoCount()===before,'invalid form published video event');await capture('playback-qa-2-'+prefix+'-upload-validation.png');const l=await layout();check(l.documentWidth<=l.width,'Upload horizontal document overflow');await page.locator('#closeUploadModal').click();await page.locator('#uploadModal').waitFor({state:'hidden'});const restored=await page.evaluate(()=>document.activeElement?.id);check(restored==='uploadButton','Upload close did not restore trigger focus');return {focus,missingTitle,noSourceRejected:true,noRelayPublish:true,restored,layout:l};}); + } + report.baseline='completed'; + }catch(e){report.baseline='blocked-or-failed';report.failedStep=report.actions.findLast(x=>x.ok===false)?.name;report.failure=e.message;await capture('playback-qa-2-resumed-stop.png');} + await finish(); +}catch(e){report.runError=e.stack;console.log(JSON.stringify({type:'ERROR',error:e.stack}));await finish();process.exitCode=1;} +``` + +## Genuine media fixture preflight (no browser/server launch) + +Interpreter15:36:55 correctly identified that all3 synthetic notes referenced absent `/qa2-unplayed.webm`. +This was fixture incompleteness,not a reproduced app defect. Provisioned only own scratch,not frozen dist/product: + +```sh +ffmpeg -hide_banner -loglevel error -f lavfi -i color=c=blue:s=64x64:r=10 -t 2 -an -c:v libvpx -y /tmp/bitvid-playback-qa-2-integrated/fixtures/qa2-unplayed.webm +ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=codec_name,width,height,nb_read_frames -of json /tmp/bitvid-playback-qa-2-integrated/fixtures/qa2-unplayed.webm +``` + +ffmpeg6.1.1-3ubuntu5; genuine generated VP8,64×64,2.000s,1019 bytes; ffprobe read20 frames,exit0. +Actual fixture SHA256:`d911005a5bca7fb1f3e5aa2fbee701b6476c9baf93460264c2aa5105dd0b3c69`. +No source media downloaded/copied from another user. The generation recipe fixes content,not necessarily container +random identifiers across regenerated files;this exact frozen input is pinned by its measured hash. No product build ran. + +Standalone own media handler serves exact path before static-file lookup,with video/webm/Content-Length/Accept-Ranges: +HEAD200,no body;GET200,full genuine bytes;single prefix/open/suffix Range206 with exact bytes/Content-Range; +unsatisfiable416;unsupported method405. In-memory contract preflight7/7,exit0. Three own-scratch broken variants +(wrong Content-Length,truncated GET,ignored Range) each exited1; originals restored and7/7 passed again. +No socket/browser/server was started. These checks validate fixture IO,not app/controller behavior or native-browser playback. + +The next explicitly granted run includes real local HTTP HEAD/GET/Range checks **before** opening Chromium. +No response/app/controller state is faked. All822 frozen app file hashes rechecked unchanged;future runner also +verifies the manifest and every file plus exact media hash before opening a server. Existing real Buffer/external-deny +guard and3-card/UI assertions retained. ForYou/Explore remain observed through real post-login visibility;no forced clicks. + +Input data:3 actual signed synthetic kind30078 notes from existing fixture test identity,local URL only,one deliberately +long title for narrow-card wrapping. No valid publication is attempted;invalid Upload submissions must not add a video +event. Local media does not qualify HTTPS/CORS,live peers or production content playback. + +Updated prepared runner SHA256:`dc10bf51310ea0c2138367219f28212471c994efab2886ed35eb6fd10e874ad7`. +Prior f60065c2 source above is historical;current difference is standalone genuine fixture transport+wire preflight+ +stronger snapshot/input pins only. `node --check` exit0;UI sequence still unexecuted pending dispatch grant. + +Exact standalone handler: + +```js +import {createHash} from 'node:crypto'; +export function mediaResponse(method, range, bytes) { + const length=bytes.length; + const headers={'Content-Type':'video/webm','Content-Length':String(length),'Accept-Ranges':'bytes','ETag':'"'+createHash('sha256').update(bytes).digest('hex')+'"'}; + if(!['GET','HEAD'].includes(method))return {status:405,headers:{Allow:'GET, HEAD','Content-Length':'0'},body:null}; + if(method==='HEAD')return {status:200,headers,body:null}; + if(!range)return {status:200,headers,body:bytes}; + const m=/^bytes=(\d*)-(\d*)$/.exec(range); + let start,end; + if(m&&(m[1]||m[2])){ + if(!m[1]){const suffix=Number(m[2]);if(suffix>0){start=Math.max(0,length-suffix);end=length-1;}} + else {start=Number(m[1]);end=m[2]?Math.min(Number(m[2]),length-1):length-1;} + } + if(!Number.isSafeInteger(start)||!Number.isSafeInteger(end)||start<0||start>=length||end eval:2:7", + "evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1974, + "node": 2, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:2:7", + "evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "mutation-videoList", + "at": 1974, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "@debugger eval code:10:174", + "MutationCallback*@debugger eval code:10:4", + "@debugger eval code:11:5", + "@debugger eval code:12:7" + ] + }, + { + "type": "getElementById-videoList", + "at": 1976, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "init@http://127.0.0.1:31857/js/forYouView.js:19:51", + "initForYouView@http://127.0.0.1:31857/js/forYouView.js:150:8", + "@debugger eval code line 290 > eval:20:22" + ] + }, + { + "type": "before-innerHTML", + "at": 1976, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "updateEmptyState@http://127.0.0.1:31857/js/forYouView.js:107:7", + "init@http://127.0.0.1:31857/js/forYouView.js:26:10", + "initForYouView@http://127.0.0.1:31857/js/forYouView.js:150:8" + ] + }, + { + "type": "after-innerHTML", + "at": 1976, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "updateEmptyState@http://127.0.0.1:31857/js/forYouView.js:107:7", + "init@http://127.0.0.1:31857/js/forYouView.js:26:10", + "initForYouView@http://127.0.0.1:31857/js/forYouView.js:150:8" + ] + }, + { + "type": "getElementById-videoList", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "@debugger eval code line 290 > eval:24:34", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:31:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:31:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:34:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:34:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:38:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:38:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "updateEmptyState@http://127.0.0.1:31857/js/forYouView.js:107:7", + "@debugger eval code line 290 > eval:39:14", + "async*evaluate@debugger eval code:292:16" + ] + }, + { + "type": "after-innerHTML", + "at": 1977, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "updateEmptyState@http://127.0.0.1:31857/js/forYouView.js:107:7", + "@debugger eval code line 290 > eval:39:14", + "async*evaluate@debugger eval code:292:16" + ] + } + ], + "postResultState": { + "application": false, + "harness": true, + "videoLists": 1 + }, + "pass": true + }, + { + "name": "exercises explore view empty-state transitions", + "result": { + "ok": true, + "emptyInitial": true, + "emptyDuringLoading": false, + "emptyWithVideo": false + }, + "trace": [ + { + "type": "mutation-videoList", + "at": 1325, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "@debugger eval code:10:174", + "MutationCallback*@debugger eval code:10:4", + "@debugger eval code:11:5", + "@debugger eval code:12:7" + ] + }, + { + "type": "getElementById-videoList", + "at": 1325, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "getElementById@http://127.0.0.1:31857/js/ui/videoListViewController.js:110:26", + "mount@http://127.0.0.1:31857/js/ui/videoListViewController.js:31:38", + "mountVideoListView@http://127.0.0.1:31857/js/app.js:3098:71" + ] + }, + { + "type": "getElementById-videoList", + "at": 1325, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "getElementById@http://127.0.0.1:31857/js/ui/videoListViewController.js:110:26", + "mount@http://127.0.0.1:31857/js/ui/videoListViewController.js:31:38", + "mountVideoListView@http://127.0.0.1:31857/js/app.js:3098:71" + ] + }, + { + "type": "before-innerHTML", + "at": 1325, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "showLoading@http://127.0.0.1:31857/js/ui/views/VideoListView.js:269:7", + "loadFeedVideos@http://127.0.0.1:31857/js/app/feedCoordinator.js:1099:30", + "loadFeedVideos@http://127.0.0.1:31857/js/app.js:4520:23" + ] + }, + { + "type": "after-innerHTML", + "at": 1325, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "showLoading@http://127.0.0.1:31857/js/ui/views/VideoListView.js:269:7", + "loadFeedVideos@http://127.0.0.1:31857/js/app/feedCoordinator.js:1099:30", + "loadFeedVideos@http://127.0.0.1:31857/js/app.js:4520:23" + ] + }, + { + "type": "getElementById-videoList", + "at": 1327, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "getElementById@http://127.0.0.1:31857/js/ui/videoListViewController.js:110:26", + "mount@http://127.0.0.1:31857/js/ui/videoListViewController.js:31:38", + "mountVideoListView@http://127.0.0.1:31857/js/app.js:3098:71" + ] + }, + { + "type": "before-innerHTML", + "at": 1327, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "render@http://127.0.0.1:31857/js/ui/views/VideoListView.js:392:7", + "renderVideoList@http://127.0.0.1:31857/js/app/feedCoordinator.js:1906:26", + "renderVideoList@http://127.0.0.1:31857/js/app.js:4717:23" + ] + }, + { + "type": "after-innerHTML", + "at": 1328, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "render@http://127.0.0.1:31857/js/ui/views/VideoListView.js:392:7", + "renderVideoList@http://127.0.0.1:31857/js/app/feedCoordinator.js:1906:26", + "renderVideoList@http://127.0.0.1:31857/js/app.js:4717:23" + ] + }, + { + "type": "before-innerHTML", + "at": 1838, + "node": 2, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:2:7", + "evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1847, + "node": 2, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:2:7", + "evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "mutation-videoList", + "at": 1847, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "@debugger eval code:10:174", + "MutationCallback*@debugger eval code:10:4", + "@debugger eval code:11:5", + "@debugger eval code:12:7" + ] + }, + { + "type": "getElementById-videoList", + "at": 1849, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "init@http://127.0.0.1:31857/js/exploreView.js:16:51", + "initExploreView@http://127.0.0.1:31857/js/exploreView.js:118:8", + "@debugger eval code line 290 > eval:9:23" + ] + }, + { + "type": "before-innerHTML", + "at": 1849, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "updateEmptyState@http://127.0.0.1:31857/js/exploreView.js:82:7", + "init@http://127.0.0.1:31857/js/exploreView.js:22:10", + "initExploreView@http://127.0.0.1:31857/js/exploreView.js:118:8" + ] + }, + { + "type": "after-innerHTML", + "at": 1850, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "updateEmptyState@http://127.0.0.1:31857/js/exploreView.js:82:7", + "init@http://127.0.0.1:31857/js/exploreView.js:22:10", + "initExploreView@http://127.0.0.1:31857/js/exploreView.js:118:8" + ] + }, + { + "type": "getElementById-videoList", + "at": 1850, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "@debugger eval code line 290 > eval:10:34", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1850, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:17:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1850, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:17:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1850, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:20:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1850, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:20:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + } + ], + "postResultState": { + "application": true, + "harness": true, + "videoLists": 1 + }, + "pass": true + }, + { + "name": "exercises kids view empty-state transitions", + "result": { + "ok": true, + "emptyInitial": true, + "emptyDuringLoading": false, + "emptyWithVideo": false + }, + "trace": [ + { + "type": "mutation-videoList", + "at": 1200, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "@debugger eval code:10:174", + "MutationCallback*@debugger eval code:10:4", + "@debugger eval code:11:5", + "@debugger eval code:12:7" + ] + }, + { + "type": "getElementById-videoList", + "at": 1200, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "getElementById@http://127.0.0.1:31857/js/ui/videoListViewController.js:110:26", + "mount@http://127.0.0.1:31857/js/ui/videoListViewController.js:31:38", + "mountVideoListView@http://127.0.0.1:31857/js/app.js:3098:71" + ] + }, + { + "type": "getElementById-videoList", + "at": 1200, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "getElementById@http://127.0.0.1:31857/js/ui/videoListViewController.js:110:26", + "mount@http://127.0.0.1:31857/js/ui/videoListViewController.js:31:38", + "mountVideoListView@http://127.0.0.1:31857/js/app.js:3098:71" + ] + }, + { + "type": "before-innerHTML", + "at": 1200, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "showLoading@http://127.0.0.1:31857/js/ui/views/VideoListView.js:269:7", + "loadFeedVideos@http://127.0.0.1:31857/js/app/feedCoordinator.js:1099:30", + "loadFeedVideos@http://127.0.0.1:31857/js/app.js:4520:23" + ] + }, + { + "type": "after-innerHTML", + "at": 1200, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "showLoading@http://127.0.0.1:31857/js/ui/views/VideoListView.js:269:7", + "loadFeedVideos@http://127.0.0.1:31857/js/app/feedCoordinator.js:1099:30", + "loadFeedVideos@http://127.0.0.1:31857/js/app.js:4520:23" + ] + }, + { + "type": "getElementById-videoList", + "at": 1203, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "getElementById@http://127.0.0.1:31857/js/ui/videoListViewController.js:110:26", + "mount@http://127.0.0.1:31857/js/ui/videoListViewController.js:31:38", + "mountVideoListView@http://127.0.0.1:31857/js/app.js:3098:71" + ] + }, + { + "type": "before-innerHTML", + "at": 1203, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "render@http://127.0.0.1:31857/js/ui/views/VideoListView.js:392:7", + "renderVideoList@http://127.0.0.1:31857/js/app/feedCoordinator.js:1906:26", + "renderVideoList@http://127.0.0.1:31857/js/app.js:4717:23" + ] + }, + { + "type": "after-innerHTML", + "at": 1203, + "node": 1, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "render@http://127.0.0.1:31857/js/ui/views/VideoListView.js:392:7", + "renderVideoList@http://127.0.0.1:31857/js/app/feedCoordinator.js:1906:26", + "renderVideoList@http://127.0.0.1:31857/js/app.js:4717:23" + ] + }, + { + "type": "before-innerHTML", + "at": 1700, + "node": 2, + "connected": true, + "videoList": 1, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:2:7", + "evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1710, + "node": 2, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:2:7", + "evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "mutation-videoList", + "at": 1710, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "@debugger eval code:10:174", + "MutationCallback*@debugger eval code:10:4", + "@debugger eval code:11:5", + "@debugger eval code:12:7" + ] + }, + { + "type": "getElementById-videoList", + "at": 1713, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "init@http://127.0.0.1:31857/js/kidsView.js:16:51", + "initKidsView@http://127.0.0.1:31857/js/kidsView.js:117:8", + "@debugger eval code line 290 > eval:9:20" + ] + }, + { + "type": "before-innerHTML", + "at": 1713, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "updateEmptyState@http://127.0.0.1:31857/js/kidsView.js:82:7", + "init@http://127.0.0.1:31857/js/kidsView.js:22:10", + "initKidsView@http://127.0.0.1:31857/js/kidsView.js:117:8" + ] + }, + { + "type": "after-innerHTML", + "at": 1713, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "updateEmptyState@http://127.0.0.1:31857/js/kidsView.js:82:7", + "init@http://127.0.0.1:31857/js/kidsView.js:22:10", + "initKidsView@http://127.0.0.1:31857/js/kidsView.js:117:8" + ] + }, + { + "type": "getElementById-videoList", + "at": 1714, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "Document.prototype.getElementById@debugger eval code:7:107", + "@debugger eval code line 290 > eval:10:34", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1714, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:17:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1714, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:17:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "before-innerHTML", + "at": 1714, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:145", + "@debugger eval code line 290 > eval:20:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + }, + { + "type": "after-innerHTML", + "at": 1714, + "node": 3, + "connected": true, + "videoList": 3, + "body": 2, + "harness": true, + "stack": [ + "set@debugger eval code:9:210", + "@debugger eval code line 290 > eval:20:7", + "async*evaluate@debugger eval code:292:16", + "@debugger eval code:1:44" + ] + } + ], + "postResultState": { + "application": true, + "harness": true, + "videoLists": 1 + }, + "pass": true + } + ], + "blocked": [ + "https://esm.sh/@aws-sdk/client-s3@3.637.0", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0/nip44", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://nos.lol/", + "wss://relay.damus.io/", + "wss://relay.primal.net/", + "wss://relay.snort.social/", + "https://esm.sh/@aws-sdk/client-s3@3.637.0", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0/nip44", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://nos.lol/", + "wss://relay.damus.io/", + "wss://relay.primal.net/", + "wss://relay.snort.social/", + "https://esm.sh/@aws-sdk/client-s3@3.637.0", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/+esm", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0", + "https://esm.sh/nostr-tools@2.17.0/nip44", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "wss://relay.damus.io/", + "wss://nos.lol/", + "wss://relay.snort.social/", + "wss://relay.primal.net/", + "https://cdn.jsdelivr.net/npm/nostr-tools@2.17.0/lib/nostr.bundle.min.js", + "wss://nos.lol/", + "wss://relay.damus.io/", + "wss://relay.primal.net/", + "wss://relay.snort.social/" + ], + "pageErrors": [], + "browser": "146.0.1", + "ended": "2026-10-22T14:19:02.503Z", + "resourcesClosed": true +} \ No newline at end of file diff --git a/docs/testing/playback-qa-3-drivers.md b/docs/testing/playback-qa-3-drivers.md new file mode 100644 index 00000000..1f5e1d44 --- /dev/null +++ b/docs/testing/playback-qa-3-drivers.md @@ -0,0 +1,156 @@ +# QA3 scratch diagnostic drivers + +Exact first driver; no retained test added. + +```js +const fs=require('node:fs/promises'); +const http=require('node:http'); +const path=require('node:path'); +const vm=require('node:vm'); +const dep='/home/user/Documents/GitHub/bitvid-dispatch/node_modules/'; +const {firefox}=require(dep+'playwright'); +const {expect}=require(dep+'@playwright/test'); +const {transformHook}=require(dep+'playwright/lib/transform/transform.js'); +const root='/home/user/Documents/GitHub/bitvid-playback-qa'; +const report={started:new Date().toISOString(),sha:'c5b678f7c9d0b92fbf786b16ae831f1be1d0b899',cases:[],blocked:[],pageErrors:[]}; +let browser,server; +(async()=>{ + const source=await fs.readFile(root+'/tests/e2e/runtime-coverage-expansion.spec.ts','utf8'); + const tests=[];const test=(name,fn)=>tests.push({name,fn});test.describe=(_,fn)=>fn(); + const code=transformHook(source.replace('import { expect, test } from "./helpers/instrumentedTest";',''),'/tmp/playback-qa-3-original.ts').code; + vm.runInNewContext(code,{test,expect,exports:{},require}); + server=http.createServer(async(req,res)=>{let p=new URL(req.url,'http://localhost').pathname;if(p==='/')p='/index.html';const file=path.resolve(root,'.'+p);if(!file.startsWith(root+'/')){res.writeHead(403);res.end();return;}try{const bytes=await fs.readFile(file);res.setHeader('Content-Type',({'.js':'text/javascript','.html':'text/html','.css':'text/css','.json':'application/json','.svg':'image/svg+xml'})[path.extname(file)]||'application/octet-stream');res.end(bytes);}catch{res.writeHead(404);res.end();}}); + await new Promise(ok=>server.listen(31857,'127.0.0.1',ok)); + browser=await firefox.launch({headless:true});report.browser=browser.version(); + for(const t of tests.slice(0,3)){ + const context=await browser.newContext({serviceWorkers:'block'}); + await context.route('**/*',r=>{const u=new URL(r.request().url());if(u.origin==='http://127.0.0.1:31857')return r.continue();report.blocked.push(u.origin+u.pathname);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',r=>{report.blocked.push(r.url());r.close();}); + const page=await context.newPage();page.on('pageerror',e=>report.pageErrors.push(e.message)); + const proxy={goto:(url,opts)=>page.goto('http://127.0.0.1:31857'+url,opts),evaluate:async(...args)=>{const result=await page.evaluate(...args);report.cases.at(-1).result=result;return result;}}; + const item={name:t.name};report.cases.push(item); + try{await t.fn({page:proxy});item.pass=true;}catch(e){item.pass=false;item.error=e.message;} + await context.close(); + } +})().catch(e=>{report.blocker=e.stack;process.exitCode=1;}).finally(async()=>{if(browser)await browser.close();if(server)await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await fs.writeFile('/tmp/playback-qa-3-local.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));}); + +``` + +App-ready plus isolated-control driver; readiness deliberately differs from CI networkidle-only setup. + +```js +const fs=require('node:fs/promises'); +const http=require('node:http'); +const path=require('node:path'); +const vm=require('node:vm'); +const dep='/home/user/Documents/GitHub/bitvid-dispatch/node_modules/'; +const {firefox}=require(dep+'playwright'); +const {expect}=require(dep+'@playwright/test'); +const {transformHook}=require(dep+'playwright/lib/transform/transform.js'); +const root='/home/user/Documents/GitHub/bitvid-playback-qa'; +const report={mirrorHits:0,started:new Date().toISOString(),sha:'c5b678f7c9d0b92fbf786b16ae831f1be1d0b899',cases:[],blocked:[],pageErrors:[]}; +let browser,server; +(async()=>{ + const source=await fs.readFile(root+'/tests/e2e/runtime-coverage-expansion.spec.ts','utf8'); + const tests=[];const test=(name,fn)=>tests.push({name,fn});test.describe=(_,fn)=>fn(); + const code=transformHook(source.replace('import { expect, test } from "./helpers/instrumentedTest";',''),'/tmp/playback-qa-3-original.ts').code; + vm.runInNewContext(code,{test,expect,exports:{},require}); + server=http.createServer(async(req,res)=>{let p=new URL(req.url,'http://localhost').pathname;if(p==='/')p='/index.html';const file=path.resolve(root,'.'+p);if(!file.startsWith(root+'/')){res.writeHead(403);res.end();return;}try{const bytes=await fs.readFile(file);res.setHeader('Content-Type',({'.js':'text/javascript','.html':'text/html','.css':'text/css','.json':'application/json','.svg':'image/svg+xml'})[path.extname(file)]||'application/octet-stream');res.end(bytes);}catch{res.writeHead(404);res.end();}}); + await new Promise(ok=>server.listen(31857,'127.0.0.1',ok)); + browser=await firefox.launch({headless:true});report.browser=browser.version(); + const mirror=await fs.readFile('/tmp/playback-qa-3-assets/buffer.mjs'); + for(const mode of ['app-ready','isolated']) for(const t of tests.slice(0,3)){ + const context=await browser.newContext({serviceWorkers:'block'}); + await context.route('**/*',r=>{const u=new URL(r.request().url());if(u.href==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){report.mirrorHits++;return r.fulfill({body:mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}if(mode==='isolated' && u.origin==='http://127.0.0.1:31857' && u.pathname==='/')return r.fulfill({body:'',contentType:'text/html'});if(u.origin==='http://127.0.0.1:31857')return r.continue();report.blocked.push(u.origin+u.pathname);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',r=>{report.blocked.push(r.url());r.close();}); + const page=await context.newPage();page.on('pageerror',e=>report.pageErrors.push(e.message)); + const proxy={goto:async(url,opts)=>{await page.goto('http://127.0.0.1:31857'+url,opts);if(mode==='app-ready'){await page.waitForFunction(async()=>{const m=await import('/js/applicationContext.js');await m.getApplicationReady();return !!m.getApplication() && !!window.__bitvidTest__ && !!document.getElementById('videoList');},null,{timeout:15000});item.ready=await page.evaluate(()=>({harness:!!window.__bitvidTest__,videoLists:document.querySelectorAll('#videoList').length,heading:document.querySelector('h1,h2')?.textContent}));}return;},evaluate:async(...args)=>{const result=await page.evaluate(...args);report.cases.at(-1).result=result;return result;}}; + const item={mode,name:t.name};report.cases.push(item); + try{await t.fn({page:proxy});item.pass=true;}catch(e){item.pass=false;item.error=e.message;if(!item.ready && mode==='app-ready')throw new Error('actual-app readiness blocked: '+e.message);} + await context.close(); + } +})().catch(e=>{report.blocker=e.stack;process.exitCode=1;}).finally(async()=>{if(browser)await browser.close();if(server)await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await fs.writeFile('/tmp/playback-qa-3-app-ready-result.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));}); + +``` + +Original-timing passive DOM trace driver; exact original callbacks/expectations, no readiness wait. + +```js +const fs=require('node:fs/promises'); +const http=require('node:http'); +const path=require('node:path'); +const vm=require('node:vm'); +const dep='/home/user/Documents/GitHub/bitvid-dispatch/node_modules/'; +const {firefox}=require(dep+'playwright'); +const {expect}=require(dep+'@playwright/test'); +const {transformHook}=require(dep+'playwright/lib/transform/transform.js'); +const root='/home/user/Documents/GitHub/bitvid-playback-qa'; +const report={mirrorHits:0,started:new Date().toISOString(),sha:'c5b678f7c9d0b92fbf786b16ae831f1be1d0b899',cases:[],blocked:[],pageErrors:[]}; +let browser,server; +(async()=>{ + const source=await fs.readFile(root+'/tests/e2e/runtime-coverage-expansion.spec.ts','utf8'); + const tests=[];const test=(name,fn)=>tests.push({name,fn});test.describe=(_,fn)=>fn(); + const code=transformHook(source.replace('import { expect, test } from "./helpers/instrumentedTest";',''),'/tmp/playback-qa-3-original.ts').code; + vm.runInNewContext(code,{test,expect,exports:{},require}); + server=http.createServer(async(req,res)=>{let p=new URL(req.url,'http://localhost').pathname;if(p==='/')p='/index.html';const file=path.resolve(root,'.'+p);if(!file.startsWith(root+'/')){res.writeHead(403);res.end();return;}try{const bytes=await fs.readFile(file);res.setHeader('Content-Type',({'.js':'text/javascript','.html':'text/html','.css':'text/css','.json':'application/json','.svg':'image/svg+xml'})[path.extname(file)]||'application/octet-stream');res.end(bytes);}catch{res.writeHead(404);res.end();}}); + await new Promise(ok=>server.listen(31857,'127.0.0.1',ok)); + browser=await firefox.launch({headless:true});report.browser=browser.version(); + const mirror=await fs.readFile('/tmp/playback-qa-3-assets/buffer.mjs'); + for(const t of tests.slice(0,3)){ + const context=await browser.newContext({serviceWorkers:'block'}); + await context.route('**/*',r=>{const u=new URL(r.request().url());if(u.href==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){report.mirrorHits++;return r.fulfill({body:mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}if(u.origin==='http://127.0.0.1:31857')return r.continue();report.blocked.push(u.origin+u.pathname);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',r=>{report.blocked.push(r.url());r.close();}); + await context.addInitScript(()=>{ + const ids=new WeakMap();let next=0;const id=n=>n?(ids.has(n)?ids.get(n):(ids.set(n,++next),next)):null; + const trace=[];window.__qaTrace=trace; + const get=Document.prototype.getElementById; + const snap=(type,node)=>{if(trace.length>=300)return;trace.push({type,at:performance.now(),node:id(node),connected:node?.isConnected,videoList:id(get.call(document,'videoList')),body:id(document.body),harness:!!window.__bitvidTest__,stack:new Error().stack?.split('\n').slice(1,5)});}; + Document.prototype.getElementById=function(name){const r=get.call(this,name);if(name==='videoList')snap('getElementById-videoList',r);return r;}; + const desc=Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML'); + Object.defineProperty(Element.prototype,'innerHTML',{...desc,set(value){const watch=this===document.body||this.id==='videoList';if(watch)snap('before-innerHTML',this);desc.set.call(this,value);if(watch)snap('after-innerHTML',this);}}); + new MutationObserver(records=>{if(records.some(r=>[...r.addedNodes,...r.removedNodes].some(n=>n.nodeType===1&&(n.id==='videoList'||n.querySelector?.('#videoList')))))snap('mutation-videoList',get.call(document,'videoList'));}).observe(document,{childList:true,subtree:true}); + }); + const page=await context.newPage();page.on('pageerror',e=>report.pageErrors.push(e.message)); + const proxy={goto:(url,opts)=>page.goto('http://127.0.0.1:31857'+url,opts),evaluate:async(...args)=>{const result=await page.evaluate(...args);report.cases.at(-1).result=result;report.cases.at(-1).trace=await page.evaluate(()=>window.__qaTrace);report.cases.at(-1).postResultState=await page.evaluate(async()=>{const m=await import('/js/applicationContext.js');return {application:!!m.getApplication(),harness:!!window.__bitvidTest__,videoLists:document.querySelectorAll('#videoList').length};});return result;}}; + const item={name:t.name};report.cases.push(item); + try{await t.fn({page:proxy});item.pass=true;}catch(e){item.pass=false;item.error=e.message;} + await context.close(); + } +})().catch(e=>{report.blocker=e.stack;process.exitCode=1;}).finally(async()=>{if(browser)await browser.close();if(server)await new Promise(ok=>server.close(ok));report.ended=new Date().toISOString();report.resourcesClosed=true;await fs.writeFile('/tmp/playback-qa-3-causal-result.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));}); + +``` + +Exact Playwright runner scratch overlay and network-only auto fixture. Spec is original source with only registration import changed to ../guard. + +playwright.config.ts + +```js +import original from '/home/user/Documents/GitHub/bitvid-playback-qa/playwright.config'; +export default {...original,testDir:'/tmp/playback-qa-3-runner',globalSetup:'/home/user/Documents/GitHub/bitvid-playback-qa/tests/e2e/helpers/playwrightCoverageGlobalSetup.ts',outputDir:'/tmp/playback-qa-3-runner/results',reporter:[['list'],['json',{outputFile:'/tmp/playback-qa-3-runner/report.json'}]],use:{...original.use,baseURL:'http://127.0.0.1:31857',serviceWorkers:'block'},projects:original.projects.filter(p=>p.name==='e2e-firefox'),webServer:{...original.webServer,command:'node /tmp/playback-qa-3-runner/server.cjs',url:'http://127.0.0.1:31857/docs/kitchen-sink.html',reuseExistingServer:false}}; + +``` + +guard.ts + +```js +import {test as base,expect} from '/home/user/Documents/GitHub/bitvid-playback-qa/tests/e2e/helpers/instrumentedTest'; +import fs from 'node:fs/promises'; +const mirror=fs.readFile('/tmp/playback-qa-3-assets/buffer.mjs'); +export const test=base.extend({_offlineGuard:[async({context},use,testInfo)=>{ + const blocked:string[]=[];let mirrorHits=0; + await context.route('**/*',async r=>{const u=new URL(r.request().url());if(u.href==='https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm'){mirrorHits++;return r.fulfill({body:await mirror,contentType:'text/javascript',headers:{'Access-Control-Allow-Origin':'*'}});}if(u.origin==='http://127.0.0.1:31857')return r.continue();blocked.push(u.origin+u.pathname);return r.abort('blockedbyclient');}); + await context.routeWebSocket('**/*',r=>{blocked.push(r.url());r.close();}); + try{await use();}finally{await testInfo.attach('offline-boundary',{body:JSON.stringify({mirrorHits,blocked}),contentType:'application/json'});} +},{auto:true}]}); +export {expect}; + +``` + +server.cjs + +```js +const fs=require('node:fs/promises'),http=require('node:http'),path=require('node:path');const root='/home/user/Documents/GitHub/bitvid-playback-qa';let server; + server=http.createServer(async(req,res)=>{let p=new URL(req.url,'http://localhost').pathname;if(p==='/')p='/index.html';const file=path.resolve(root,'.'+p);if(!file.startsWith(root+'/')){res.writeHead(403);res.end();return;}try{const bytes=await fs.readFile(file);res.setHeader('Content-Type',({'.js':'text/javascript','.html':'text/html','.css':'text/css','.json':'application/json','.svg':'image/svg+xml'})[path.extname(file)]||'application/octet-stream');res.end(bytes);}catch{res.writeHead(404);res.end();}}); + server.listen(31857,'127.0.0.1'); + +``` diff --git a/docs/testing/playback-qa-3-exact-runner-report.json b/docs/testing/playback-qa-3-exact-runner-report.json new file mode 100644 index 00000000..0df113aa --- /dev/null +++ b/docs/testing/playback-qa-3-exact-runner-report.json @@ -0,0 +1,214 @@ +{ + "config": { + "configFile": "/tmp/playback-qa-3-runner/playwright.config.ts", + "rootDir": "/tmp/playback-qa-3-runner", + "forbidOnly": false, + "fullyParallel": false, + "globalSetup": "/home/user/Documents/GitHub/bitvid-playback-qa/tests/e2e/helpers/playwrightCoverageGlobalSetup.ts", + "globalTeardown": null, + "globalTimeout": 0, + "grep": {}, + "grepInvert": null, + "maxFailures": 0, + "metadata": { + "actualWorkers": 1 + }, + "preserveOutput": "always", + "projects": [ + { + "outputDir": "/tmp/playback-qa-3-runner/results", + "repeatEach": 1, + "retries": 0, + "metadata": { + "actualWorkers": 1 + }, + "id": "e2e-firefox", + "name": "e2e-firefox", + "testDir": "/tmp/playback-qa-3-runner", + "testIgnore": [], + "testMatch": [ + "e2e/**/*.spec.ts" + ], + "timeout": 120000 + } + ], + "quiet": false, + "reporter": [ + [ + "list", + null + ], + [ + "json", + { + "outputFile": "/tmp/playback-qa-3-runner/report.json" + } + ] + ], + "reportSlowTests": { + "max": 5, + "threshold": 300000 + }, + "runAgents": "none", + "shard": null, + "tags": [], + "updateSnapshots": "missing", + "updateSourceMethod": "patch", + "version": "1.58.2", + "workers": 1, + "webServer": { + "command": "node /tmp/playback-qa-3-runner/server.cjs", + "url": "http://127.0.0.1:31857/docs/kitchen-sink.html", + "reuseExistingServer": false, + "timeout": 120000 + } + }, + "suites": [ + { + "title": "e2e/runtime-coverage-expansion.spec.ts", + "file": "e2e/runtime-coverage-expansion.spec.ts", + "column": 0, + "line": 0, + "specs": [], + "suites": [ + { + "title": "runtime coverage expansion", + "file": "e2e/runtime-coverage-expansion.spec.ts", + "line": 3, + "column": 6, + "specs": [ + { + "title": "exercises for-you view empty-state and hashtag action", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 120000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "e2e-firefox", + "projectName": "e2e-firefox", + "results": [ + { + "workerIndex": 0, + "parallelIndex": 0, + "status": "passed", + "duration": 3135, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2026-10-22T14:22:02.653Z", + "annotations": [], + "attachments": [ + { + "name": "offline-boundary", + "contentType": "application/json", + "body": "eyJtaXJyb3JIaXRzIjoxLCJibG9ja2VkIjpbImh0dHBzOi8vZXNtLnNoL0Bhd3Mtc2RrL2NsaWVudC1zM0AzLjYzNy4wIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvK2VzbSIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMCIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMCIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMC9uaXA0NCIsIndzczovL3JlbGF5LmRhbXVzLmlvLyIsIndzczovL25vcy5sb2wvIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwid3NzOi8vcmVsYXkuZGFtdXMuaW8vIiwid3NzOi8vbm9zLmxvbC8iLCJ3c3M6Ly9yZWxheS5zbm9ydC5zb2NpYWwvIiwid3NzOi8vcmVsYXkucHJpbWFsLm5ldC8iLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9ub3MubG9sLyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbm9zdHItdG9vbHNAMi4xNy4wL2xpYi9ub3N0ci5idW5kbGUubWluLmpzIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvbGliL25vc3RyLmJ1bmRsZS5taW4uanMiLCJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL25vc3RyLXRvb2xzQDIuMTcuMC9saWIvbm9zdHIuYnVuZGxlLm1pbi5qcyIsImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbm9zdHItdG9vbHNAMi4xNy4wL2xpYi9ub3N0ci5idW5kbGUubWluLmpzIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvbGliL25vc3RyLmJ1bmRsZS5taW4uanMiLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9ub3MubG9sLyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsIndzczovL3JlbGF5LmRhbXVzLmlvLyIsIndzczovL25vcy5sb2wvIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwid3NzOi8vbm9zLmxvbC8iLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iXX0=" + } + ] + } + ], + "status": "expected" + } + ], + "id": "392e8fc2af458905b98f-9ff48298987f061c177a", + "file": "e2e/runtime-coverage-expansion.spec.ts", + "line": 4, + "column": 7 + }, + { + "title": "exercises explore view empty-state transitions", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 120000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "e2e-firefox", + "projectName": "e2e-firefox", + "results": [ + { + "workerIndex": 0, + "parallelIndex": 0, + "status": "passed", + "duration": 2267, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2026-10-22T14:22:06.278Z", + "annotations": [], + "attachments": [ + { + "name": "offline-boundary", + "contentType": "application/json", + "body": "eyJtaXJyb3JIaXRzIjoxLCJibG9ja2VkIjpbImh0dHBzOi8vZXNtLnNoL0Bhd3Mtc2RrL2NsaWVudC1zM0AzLjYzNy4wIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvK2VzbSIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMCIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMCIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMC9uaXA0NCIsIndzczovL3JlbGF5LmRhbXVzLmlvLyIsIndzczovL25vcy5sb2wvIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwid3NzOi8vcmVsYXkuZGFtdXMuaW8vIiwid3NzOi8vbm9zLmxvbC8iLCJ3c3M6Ly9yZWxheS5zbm9ydC5zb2NpYWwvIiwid3NzOi8vcmVsYXkucHJpbWFsLm5ldC8iLCJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL25vc3RyLXRvb2xzQDIuMTcuMC9saWIvbm9zdHIuYnVuZGxlLm1pbi5qcyIsImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbm9zdHItdG9vbHNAMi4xNy4wL2xpYi9ub3N0ci5idW5kbGUubWluLmpzIiwid3NzOi8vcmVsYXkuZGFtdXMuaW8vIiwid3NzOi8vbm9zLmxvbC8iLCJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL25vc3RyLXRvb2xzQDIuMTcuMC9saWIvbm9zdHIuYnVuZGxlLm1pbi5qcyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbm9zdHItdG9vbHNAMi4xNy4wL2xpYi9ub3N0ci5idW5kbGUubWluLmpzIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvbGliL25vc3RyLmJ1bmRsZS5taW4uanMiLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9ub3MubG9sLyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsIndzczovL3JlbGF5LmRhbXVzLmlvLyIsIndzczovL25vcy5sb2wvIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwid3NzOi8vbm9zLmxvbC8iLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iXX0=" + } + ] + } + ], + "status": "expected" + } + ], + "id": "392e8fc2af458905b98f-44c51fdca915cc6d7a20", + "file": "e2e/runtime-coverage-expansion.spec.ts", + "line": 79, + "column": 7 + }, + { + "title": "exercises kids view empty-state transitions", + "ok": true, + "tags": [], + "tests": [ + { + "timeout": 120000, + "annotations": [], + "expectedStatus": "passed", + "projectId": "e2e-firefox", + "projectName": "e2e-firefox", + "results": [ + { + "workerIndex": 0, + "parallelIndex": 0, + "status": "passed", + "duration": 2209, + "errors": [], + "stdout": [], + "stderr": [], + "retry": 0, + "startTime": "2026-10-22T14:22:08.564Z", + "annotations": [], + "attachments": [ + { + "name": "offline-boundary", + "contentType": "application/json", + "body": "eyJtaXJyb3JIaXRzIjoxLCJibG9ja2VkIjpbImh0dHBzOi8vZXNtLnNoL0Bhd3Mtc2RrL2NsaWVudC1zM0AzLjYzNy4wIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvK2VzbSIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMCIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMCIsImh0dHBzOi8vZXNtLnNoL25vc3RyLXRvb2xzQDIuMTcuMC9uaXA0NCIsIndzczovL3JlbGF5LmRhbXVzLmlvLyIsIndzczovL25vcy5sb2wvIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwid3NzOi8vcmVsYXkuZGFtdXMuaW8vIiwid3NzOi8vbm9zLmxvbC8iLCJ3c3M6Ly9yZWxheS5zbm9ydC5zb2NpYWwvIiwid3NzOi8vcmVsYXkucHJpbWFsLm5ldC8iLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9ub3MubG9sLyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbm9zdHItdG9vbHNAMi4xNy4wL2xpYi9ub3N0ci5idW5kbGUubWluLmpzIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvbGliL25vc3RyLmJ1bmRsZS5taW4uanMiLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9ub3MubG9sLyIsImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vbm9zdHItdG9vbHNAMi4xNy4wL2xpYi9ub3N0ci5idW5kbGUubWluLmpzIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ub3N0ci10b29sc0AyLjE3LjAvbGliL25vc3RyLmJ1bmRsZS5taW4uanMiLCJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL25vc3RyLXRvb2xzQDIuMTcuMC9saWIvbm9zdHIuYnVuZGxlLm1pbi5qcyIsIndzczovL3JlbGF5LmRhbXVzLmlvLyIsIndzczovL25vcy5sb2wvIiwid3NzOi8vcmVsYXkuc25vcnQuc29jaWFsLyIsIndzczovL3JlbGF5LnByaW1hbC5uZXQvIiwid3NzOi8vbm9zLmxvbC8iLCJ3c3M6Ly9yZWxheS5kYW11cy5pby8iLCJ3c3M6Ly9yZWxheS5wcmltYWwubmV0LyIsIndzczovL3JlbGF5LnNub3J0LnNvY2lhbC8iXX0=" + } + ] + } + ], + "status": "expected" + } + ], + "id": "392e8fc2af458905b98f-079002fdea00236f7003", + "file": "e2e/runtime-coverage-expansion.spec.ts", + "line": 120, + "column": 7 + } + ] + } + ] + } + ], + "errors": [], + "stats": { + "startTime": "2026-10-22T14:22:01.436Z", + "duration": 9955.026, + "expected": 3, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + } +} \ No newline at end of file diff --git a/docs/testing/playback-qa-3-exact-runner-stdout.txt b/docs/testing/playback-qa-3-exact-runner-stdout.txt new file mode 100644 index 00000000..3876773b --- /dev/null +++ b/docs/testing/playback-qa-3-exact-runner-stdout.txt @@ -0,0 +1,12 @@ + +Running 3 tests using 1 worker + +(node:141920) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set. +(Use `node --trace-warnings ...` to show where the warning was created) +(node:141920) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set. +(Use `node --trace-warnings ...` to show where the warning was created) + ✓ 1 [e2e-firefox] › ../../../../../tmp/playback-qa-3-runner/e2e/runtime-coverage-expansion.spec.ts:4:7 › runtime coverage expansion › exercises for-you view empty-state and hashtag action (3.1s) + ✓ 2 [e2e-firefox] › ../../../../../tmp/playback-qa-3-runner/e2e/runtime-coverage-expansion.spec.ts:79:7 › runtime coverage expansion › exercises explore view empty-state transitions (2.3s) + ✓ 3 [e2e-firefox] › ../../../../../tmp/playback-qa-3-runner/e2e/runtime-coverage-expansion.spec.ts:120:7 › runtime coverage expansion › exercises kids view empty-state transitions (2.2s) + + 3 passed (10.0s) diff --git a/docs/testing/playback-qa-3-investigation.md b/docs/testing/playback-qa-3-investigation.md new file mode 100644 index 00000000..268487ac --- /dev/null +++ b/docs/testing/playback-qa-3-investigation.md @@ -0,0 +1,76 @@ +# QA3 Firefox CI investigation — first checkpoint + +**Disposition:** blocked, not fixed/done. Dispatch20261022T142307 accepted this bounded checkpoint and stopped further Firefox reruns. Public CI184passed/2failed/3flaky remains open; actual serial local runner3/3 passed without retry. No causal defect was established. Need new actionable CI-equivalent failure evidence; public logs403 and no E2E artifact limit investigation. Evidence-only checkpoint approved for normal docs queue; no product gate or new browser work is implied. + +2026-10-22. Current HEAD `c5b678f7c9d0b92fbf786b16ae831f1be1d0b899`; filed CI SHA `fb54cea3a2ba99b4860421724a5050af6b97c293`. Own clean branch fast-forwarded to unstable, no unique commits discarded. Relevant original view modules and runtime-coverage spec have zero diff between these SHAs. QA3 claimed; implementation remains unrouted. + +Plan: identify exact CI failures through public metadata; inspect original scenario and view wiring; run one narrow Firefox diagnostic with all external traffic denied; preserve first result and release resources; route any further environment preparation or demonstrated fix through dispatch. Product, existing tests, helpers, and configuration remain read-only. + +## Public CI evidence + +Unauthenticated GET job119013565277 in run39536282396 succeeds. Head SHA matches filedAt. Job `e2e-tests (22, e2e-firefox)` failed at step9, `Run deterministic E2E suite (e2e-firefox)`; build and browser install passed. Workflow command is `npx playwright test tests/e2e --project=${{ matrix.project }}`. Current config enables one CI retry; no local retry was used. + +Public check annotations endpoint returned13 entries. Summary:184 passed,2 failed,3 flaky in15.8minutes. Terminal failures, including their original attempts and CI retry: + +- `runtime-coverage-expansion.spec.ts:73`: For You `profileShows` expected to contain `hashtags`, received `[]`. +- Same file line156: Kids `emptyInitial` expected true, received false. + +Three flaky tests had `browserContext.newPage`/page setup120000ms timeouts: notification-system line84, profile-management line49 and line126. These are distinct from terminal assertion failures. The known feed hydration flake is not established as the cause. + +Logs endpoint returned HTTP403 (curl exit22). Artifact listing returned one artifact, `dist-hashes-current`; no retained Playwright trace/report artifact was listed. Check output summary/text are null, but annotations provide the assertions above. No credentials, rerun, or remote write used. Initial sandbox DNS failure was resolved by approved read-only network escalation. + +## First local controlled result + +Command `node /tmp/playback-qa-3-run.cjs`; Node22.23.1, existing read-only Playwright1.58.2 dependencies, Firefox146.0.1. Scratch HTTP server private31857 serves this worktree. Existing spec imported through Playwright's TypeScript transformer with only its test registration import replaced by a collector; exact first three test callbacks/assertions execute against fresh Firefox contexts. Coverage/console fixture is not executed; no full-run concurrency, build or CI environment equivalence claimed. + +Initial sandbox bind failed EPERM before browser start; approved escalation then executed once. Measured run14:11:11.923→14:11:17.342UTC:3/3 original view scenarios passed (For You, Explore control, Kids). Node exited naturally0. All owned browser/context/server resources closed in finally; preview3000 untouched. Driver contains no retries, sleeps, assertion weakening or forced exit. + +Three external requests, all exact Buffer CDN import, were blocked. No external HTTP/WebSocket allowed. No pageerror events recorded. This prevents the full application module graph from booting, so these results establish only the original view scenarios with blocked bootstrap. They do not reproduce, fix or clear the CI failures, and are not actual-app qualification. Original CI failures remain open. Exact first result is retained in `playback-qa-3-local-first-result.json`. + +## Blocker and next boundary + +Dispatch notified CI assertions and resource release. Further browser work awaits routing/resource grant for genuine offline dependency preparation and app/DOM interference measurement. No fake Buffer, substituted product behavior, public fixture traffic, real signer/relay/media/cloud, product/test/helper/config edits, full suite or build used. CI browser version, full logs, concurrency/resource conditions, cause and actual-app behavior remain unverified. No new retained test was added, so no mutant claim is made. + +## Authorized cached-artifact preparation: missing bundle + +Dispatch message20261022T141235 authorized reuse of the genuine cached Buffer artifact only, matching prior QA provenance/hash, followed by app readiness and isolated-view controls. It explicitly requires reporting a blocker if the artifact is missing. No new browser was started. + +Prior durable `playback-qa-2-assets-plan.md` and `playback-qa-2-second-pass.md` identify a65405-byte locally bundled genuine npm Buffer6.0.3 artifact, SHA256 `507a593508c51d1397a9a5818ad0b8dbf7e3ad810feaa87cabff87ff56cf3984`. Its old QA scratch directories (`/tmp/bitvid-playback-qa-2-assets`, `-snapshot`, `-integrated`, `-final`) are absent. Filename inventory of `/tmp`, fleet state and relevant repository paths found no reusable Buffer bundle. Existing source polyfill files reference the remote CDN; they are not the bundle. + +`npm cache ls buffer base64-js ieee754` exited0 and lists the three original versioned tarball cache keys. These are source packages, not the approved built artifact; no extraction, install, rebundle, substitute, external fetch or browser execution followed. Dispatch was asked to identify a surviving exact-hash prepared artifact or route offline reconstruction separately. The conditional browser grant has not been consumed; browser/expensive resources remain released. Actual app readiness, original CI assertion reproduction and root cause remain unverified. + +## Separately authorized offline reconstruction and readiness controls + +Dispatch20261022T141453 explicitly authorized offline reconstruction from the three exact cached upstream packages. Cache SHA512 matched cached registry metadata and cache integrity for3/3; historical tarball and index.js SHA256 pins matched3/3. Safe extraction admitted regular package files only, rejecting traversal and links. Existing read-only esbuild0.27.3 bundled precisely buffer6.0.3/base64-js1.5.1/ieee7541.2.1 plus the scratch entry, with no fetch/install scripts/product changes. New output65331bytes SHA256 `2f1f8730af8c9fb08068a41c9c18356b9f48f21635fe5deed6d17fc6b9b97be7` differs from historical65405bytes/507a; no byte-equivalence claim. Native-Node comparisons/API probes passed6/6: exports, UTF8, base64, shared subarray backing, float64 bytes, typed-array hex. Provenance and output report retained beside this note. + +Granted command `node /tmp/playback-qa-3-ready-run.cjs` ran14:16:24.113→14:16:35.031UTC at the same QA HEAD. Firefox146.0.1, private31857, fresh contexts. Exact CDN Buffer import was fulfilled3 times with the verified reconstructed artifact; all other public HTTP/WebSocket attempts denied. Node exited naturally0; all owned resources closed, RELEASE sent. No pageerrors recorded. No broad/full suite/build or test retry used. + +With explicit `getApplicationReady()` resolution, actual application object, harness and videoList readiness required before the original callbacks, all three scenarios passed3/3. Each observed Trending heading and one videoList before DOM replacement. Separate inert-document isolated-view controls passed3/3. Original assertions unchanged; exact drivers and result JSON retained. This adds app-readiness timing relative to original CI `networkidle` only, so it does not prove CI equivalence or root cause and does not erase either terminal CI failure. No control establishes real relay, signer, cloud, playback, CDN or production behavior. + +Next proposed causal measurement sent to dispatch: retain original networkidle-only timing with verified Buffer, passively trace document/container replacement and application-context identity while executing original callbacks. This would distinguish app-bootstrap interference from view behavior without weakening assertions or adding readiness waits. It has not yet run; browser resources remain released pending routing. CI concurrency, exact browser version and logs remain unavailable/unverified. + +## Approved original-timing causal diagnostic + +Dispatch20261022T141750 granted the original networkidle-only timing with passive DOM/application traces. Command `node /tmp/playback-qa-3-causal-run.cjs > /tmp/playback-qa-3-causal-stdout.json` ran14:18:54.853→14:19:02.503UTC, exit0, Firefox146.0.1, same HEAD/verified Buffer/private31857/public-deny boundary. No readiness waits, sleeps, retries or original callback/assertion edits. All3 scenarios passed3/3; CI terminal failures remain unreproduced. Browser/context/server closed naturally; RELEASE sent. + +Passive traces assign identity1 to app videoList, identity2 to body, identity3 to replacement fixture videoList. Each original callback replaces the body's content; each view initializer then resolves connected fixture node3. All initial empty states are observed and all expected actions/transitions pass. App feed loading/render writes occurred before fixture replacement; no app-driven videoList/root replacement during the measured callbacks is observed. No evidence yet supports a causal race fix. Instrumentation wrappers preserve getter/setter values but their execution overhead remains an environmental difference. + +Post-result application-context sampling is not exact-assertion sampling: For You has application false because its original callback explicitly calls setApplication(null); Explore/Kids application true. Harness is present and one videoList remains in all3. This cannot rule out unobserved context changes at other times and must not be presented as proof of CI app identity. Buffer fulfillment3, pageerrors0; all other public attempts denied. + +Inspected original instrumentedTest: Firefox does not start JS coverage; console/pageerror capture requires environment flags; no additional readiness waits or before-test app setup is supplied. Scratch VM registration still differs from actual Playwright fixture/worker execution. Results retain unchanged source callbacks but do not reproduce CI concurrency, browser revision, CDN transport, fixture execution or full runner. No product/test/helper/config fix is justified from these passing local measurements. Proposed next bounded diagnostic, not yet run: actual original Playwright runner/fixture with external network guard and exact three selectors to remove the scratch registration difference. Resources released pending dispatch routing. + +## Approved actual Playwright runner checkpoint + +Dispatch20261022T142037 authorized exact runner/fixture diagnostic with scratch overlay and exactly3 tests. Preflight `--list` exited0 and selected only For You line4, Explore line79, Kids line120 in one copied spec. Copy differs from original solely in its registration import pointing to a scratch auto network guard; that guard extends the actual original instrumentedTest. Original callbacks/expectations and instrumentation/globalSetup source are unchanged. + +Execution command (stdout redirected to owned scratch): + +```sh +env -u NODE_TEST_CONTEXT NODE_PATH=/home/user/Documents/GitHub/bitvid-dispatch/node_modules node /home/user/Documents/GitHub/bitvid-dispatch/node_modules/playwright/cli.js test -c /tmp/playback-qa-3-runner/playwright.config.ts --project=e2e-firefox --grep 'exercises (for-you|explore|kids) view' --workers=1 --retries=0 +``` + +Natural exit0. Start14:22:01.436UTC, duration9955.026ms;3 passed,0 unexpected,0 flaky,0 skipped, one attempt each. No readiness wait or passive DOM wrappers were added to this execution. Runner owns/stops its private31857 webServer; post-exit process inventory showed only checking shell/sandbox command lines, no node server process. An independent socket check was sandbox-denied and is not claimed as port verification. RELEASE sent; no other server/preview was touched. + +Explicit environment differences: source checkout rather than CI install/build; existing read-only Playwright1.58.2/Firefox146.0.1 dependency/browser; NODE_PATH resolution; scratch testDir/output and list+JSON reporter; private31857/baseURL; blocked service workers and all nonlocal HTTP/WebSocket; verified reconstructed genuine Buffer fulfillment; workers1 and retries0 instead of CI defaults/retry1. Original http-server dependency is absent locally, so the authorized scratch overlay uses the same prior Node static server (MIME/path serving) rather than installing it. Original global setup absolute path and instrumentedTest/coverage helper are preserved. No coverage/console-capture environment opt-in was added. These differences preclude whole-CI parity claims. + +Retained JSON report, stdout, and exact scratch overlay/guard/server are beside this note. All previous first results remain intact. No product, existing test, helper or configuration file changed. No demonstrated causal defect or fix proposal yet; CI failures remain OPEN/unreproduced, with precise public annotations preserved. Further execution awaits dispatch disposition; no retries/concurrency ramp/broad suite/build planned autonomously. diff --git a/docs/testing/playback-qa-3-local-first-result.json b/docs/testing/playback-qa-3-local-first-result.json new file mode 100644 index 00000000..02f2e80d --- /dev/null +++ b/docs/testing/playback-qa-3-local-first-result.json @@ -0,0 +1,48 @@ +{ + "started": "2026-10-22T14:11:11.923Z", + "sha": "c5b678f7c9d0b92fbf786b16ae831f1be1d0b899", + "cases": [ + { + "name": "exercises for-you view empty-state and hashtag action", + "result": { + "ok": true, + "profileShows": [ + "hashtags" + ], + "forYouInitialEmpty": true, + "forYouLoadingEmpty": false, + "forYouVideoEmpty": false + }, + "pass": true + }, + { + "name": "exercises explore view empty-state transitions", + "result": { + "ok": true, + "emptyInitial": true, + "emptyDuringLoading": false, + "emptyWithVideo": false + }, + "pass": true + }, + { + "name": "exercises kids view empty-state transitions", + "result": { + "ok": true, + "emptyInitial": true, + "emptyDuringLoading": false, + "emptyWithVideo": false + }, + "pass": true + } + ], + "blocked": [ + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm", + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm", + "https://cdn.jsdelivr.net/npm/buffer@6.0.3/+esm" + ], + "pageErrors": [], + "browser": "146.0.1", + "ended": "2026-10-22T14:11:17.342Z", + "resourcesClosed": true +} diff --git a/docs/testing/playback-qa-4-ab-prep-blocker-2026-10-26.md b/docs/testing/playback-qa-4-ab-prep-blocker-2026-10-26.md new file mode 100644 index 00000000..8757cc45 --- /dev/null +++ b/docs/testing/playback-qa-4-ab-prep-blocker-2026-10-26.md @@ -0,0 +1,23 @@ +# QA4 causal A/B preparation blocker — 2026-10-26 + +Source HEAD and unstable `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd` printed by git. QA4 reclaimed; source comparison from filedAt `e04549f816f6ec61f305e20df0ce2b59797c4651` for index.html, js/index.js and css/tailwind.source.css returned no delta. All four existing untracked evidence notes and dirty backlog preserved. + +Interpreter mailbox `20261026T134112-interpreter-216b89.md` grants one separately saved360x800 causal A/B after health<75C/load<8/ampleRAM and no peer browser: reproduce failed native checkbox click first, then remove ONLY app.fade-in with unchanged geometry/points and native toggle. Stop on failure without retry. Independent reproduction/listener timing still precedes implementation routing. + +Recovery plan: inspect pinned artifacts and dependency provenance; reconstruct missing scratch from verified cache only; syntax-check a distinct runner; launch only after qualifying fresh health; preserve A/B plus cleanup. No product/CSS/test/helper/config changes. + +Preparation checks found `/tmp/playback-qa-4-cause-run.cjs` and `/tmp/playback-qa-4-cause-result.json` absent (ls/cat exit1). Prior runtime counts in earlier notes remain reported historical evidence; their raw temporary artifacts are unavailable after restart. Own node_modules is absent; dispatch Playwright/ws/esbuild paths exist, but no dependency was imported or executed. + +Fresh health2026-10-26T13:42:06.538Z: max75.1C, load1.98, availableRAM72.7%; fails the grant's <75C prerequisite. Peer .task reads show client browser RELEASED and dispatch reserving QA next brief browser; no peer browser launch performed here. + +Exact reconstruction command was an inline Python3 script reading docs/testing/playback-qa-3-buffer-provenance.json, decoding each recorded SHA512 SRI into the local npm-cache content path, requiring both SHA256/SHA512 before extraction. It exited1 on its FIRST archive read: + +`FileNotFoundError: /home/user/.npm/_cacache/content-v2/sha512/15/38/82a4dc6dc226591c465b71b4c87198c44552029fdcaafe90c591397de7f031cc3d6768172d37b60eebcae233f80b48363bb1dacc6f2f21a1f00362ebaa38` + +This is the recorded genuine buffer6.0.3 archive, expected SHA256555b6f49224afcf6b2c5a9aea7cd34a7f443f395b10532bc5580022d57aa51f5. No archive bytes read/hash checks passed; no package extraction, bundling, network download/install, substitute dependency or app launch. Stop at this preparation blocker as assigned. Need restored verified genuine mirror/cache provenance before an actual-app run; no new grant presumed after release. + +Counts: browser0, contexts0, private servers0, readiness0, runtime assertions0, A/B comparisons0, tests/builds/submissions0. No performance or causal qualification. Empty owned scratch directory /tmp/playback-qa-4-ab-20261026 removed by literal rmdir. Preview3000/peer processes untouched; no runtime resources created requiring cleanup. + +Original strict horizontal overflow362/360 and394/390 remains open; previous native checkbox failure1/1 remains historical. A/B sufficiency, startup listener timing, original overflow cause, Firefox CI-equivalent failure, real-device and AA remain unverified. No product fix proposed from this attempt. + +RELEASE recorded UTC: 2026-10-26T13:43:26.712816+00:00 diff --git a/docs/testing/playback-qa-4-ab-result-2026-10-26.md b/docs/testing/playback-qa-4-ab-result-2026-10-26.md new file mode 100644 index 00000000..9107e4d8 --- /dev/null +++ b/docs/testing/playback-qa-4-ab-result-2026-10-26.md @@ -0,0 +1,41 @@ +# QA4 separate causal A/B attempt — blocked by reconstructed runner + +Source HEAD/unstable `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd` printed by git. Interpreter mailbox20261026T151946 grants one separate360x800 A/B after freshhealth;152449 permits bounded health rechecks. Client offline only/dispatch source-only, no competing browser recorded. No product/CSS/retained tests/helpers/config edited. + +## Preparation and command + +Durable reproduction directory: `/home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/` contains prepare.py, README.md, run.cjs, entry.mjs, genuine extracted package sources, buffer.mjs, verified-provenance.json, bundle-provenance.json, health-snapshots.jsonl and result.json. No sole reliance on /tmp. Existing original dirty QA evidence retained. + +`python3 .../ab-20261026/prepare.py` exit0: all3 restored genuine archives independently verified byte count+SHA256+SHA512 before any extraction. Existing dispatch esbuild0.27.3 produced Buffer mirror65583bytes, SHA256ff1c4a2377a9df17eaffa1f5b00da3d835f748f3d832a0490bd309756434f73d; absolute-path comments differ from historical bundle, no claim of output byte equality. Runtime Buffer API sanity6/6. CSS hash283d0464 and two relay source hashesab11b299 match prior pins; relevant CSS/template/buildsource diff d9424fb7..HEAD exit0. No download/install/productbuild. + +`node --check /home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/run.cjs` exit0. Runner SHA256ce95cf5c8ae04db564e38569859a750403ccb8f5b6505c8e19ae8eb3017c8e69 preserved exactly after execution. + +Prelaunch health15:25:13.754Z max74C/load6.15/availableRAM61.5%/battery70%49min, satisfying explicit short-run grant. Earlier15:22:58 load17.23 and15:24:11 max76/load8.74 were held, no launch. START.task written before execution. No unrelated process stopped. + +Exact command: `node /home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/run.cjs` from own worktree, approved host execution. Natural exit1. START15:25:27.773Z; runtime15:25:28.223Z; RELEASE15:25:46.532Z (18.759s total). Chromium145.0.7632.6. Result SHA256868e6dd80a0887dce319b5a2a9e5f0be083721541a1f5820d306b8bfe12134a9. + +## Exact first failure and interpretation + +`page.waitForFunction: Timeout 15000ms exceeded` at run.cjs:84, after native I Understand locator.click resolved. Predicate incorrectly requires `disclaimerModal.hidden && dataset.open === "false"`. Source js/ui/components/staticModalAccessibility.js:125-143 uses `classList.add("hidden")` and data-open=false, rather than the HTML hidden property. This reconstructed-runner mistake makes the wait incompatible with normal source behavior. The run did not capture disclaimer DOM at failure, so do not claim it independently measured the closed state or a product disclaimer regression. + +A second independently visible setup mistake: routeWebSocket compares ws.url() exactly with `ws://127.0.0.1:31865`; recorded actual URL is `ws://127.0.0.1:31865/`. All8 local-relay attempts were denied by this faulty allowlist. These8 are LOCAL attempts, not public-network attempts; no remote WebSockets were recorded. The guard continued to deny other public WebSockets, but local relay functionality was not qualified. + +These two runner defects were identified after natural exit via source/result inspection only. Executed runner/results remain unchanged. At the first failure checkpoint no corrected script was created/executed and no retry occurred. Later source-only preparation is recorded below. A future independently reviewed preparation can normalize and compare allowed WebSocket URL components (only localhost31865 path/) and wait for class-hidden plus data-open=false; it must preserve exact public-deny scope and receive a distinct run grant/path. No longer timeout/force click/skip disclaimer/forced UI state substitute is justified. + +Counts: browser1/context1; actual applicationContext.getApplicationReady plus app/harness/videoList readiness1; resolved disclaimer native click1 but successful closed-state check0; Upload0, wheel0, checkbox native clicks0, interventions0, A/B comparisons0, width runtime assertions0, product edits0, tests/builds/submissions0. Pageerrors0. Genuine Buffer route served1; public HTTP denied10; localWS denied8. No fixture login/relay seeding/publishing/media/cloud action reached. + +Cleanup awaited context, browser, relay and server closure; tracked app sockets0, resourcesClosed=true. No separate OS socket inventory was taken. Private app31864/relay31865 only; preview3000/peers untouched. Durable evidence/dependency sources retained, no temporary runtime process intentionally left running. Slot RELEASED. No continuing browser qualification implied. + +## Remaining evidence boundary + +Original horizontal overflow362/360 and394/390 remains open. Previous native checkbox interception1/1 and prior width passes remain historical observations, not results of this attempt. Causal A/B, startup listener timing, original overflow cause, Firefox CI-equivalent failure, other viewport/device parity and real-device/AA remain unverified. No product/CSS fix recommendation follows from this setup failure. Independent causal reproduction and explicit ownership routing remain required before implementation. + +Recorded UTC: 2026-10-26T15:26:56.714504+00:00 + +## Interpreter-directed corrected preparation (no runtime) + +Mailbox20261026T152632 explicitly requests source-only diagnosis/proposal, no immediate rerun. Separate durable `.../ab-20261026/corrected-prep/run.cjs` prepared with exactly two predicate substitutions: normalized URL href equality for the intended local WS root, and classList hidden + data-open=false for disclaimer closure. `node --check` exit0; runtime not executed. SHA25614f327b92c7adb4373761dece9359158300f944ab2270781e87de64ac5ed9181. Supporting Buffer/provenance copies are byte-identical to parent. README records exact future command and requires NEW explicit grant/fresh health/START. Existing A/B preconditions, strict widths, points/geometry, publicdeny and awaitedcleanup unchanged. + +After preparation original runner SHA256ce95cf5c and result SHA256868e6dd8 rechecked unchanged. No second browser/server launch, no A/B attempt or product changes. Post-release health15:26:59 max72.9C/load5.35/RAM62.1%/battery66%41min is advisory, not permission for another run. .task records RELEASE plus proposed preparation READY awaiting new routing. + +Mailbox20261026T152816 authorizes corrected preparation only, with stronger prerequisites. Updated prepared SHA256593fc0556257e1943f61e9496d3fcd5b906dee9025ccbb6368f7ea26d78fc5f2: exact WS protocol/hostname/port/rootpath plus empty search/hash/user/password; disclaimer class hidden+data-open=false+computed display none+hasSeenDisclaimer=true following genuine button click. Source js/disclaimer.js hide writes hasSeenDisclaimer=true; staticModalAccessibility.closeStaticModal adds CSS hidden/data-open=false. Two original lines replaced, all other assertions/deadlines unchanged. Prior prepared14f327b9 was never executed; updated preparation also runtime0. Await NEW grant. diff --git a/docs/testing/playback-qa-4-boot-blocker-2026-10-22.md b/docs/testing/playback-qa-4-boot-blocker-2026-10-22.md new file mode 100644 index 00000000..91cbceda --- /dev/null +++ b/docs/testing/playback-qa-4-boot-blocker-2026-10-22.md @@ -0,0 +1,121 @@ +# QA4 bounded attempt: private server blocked + +- Own source SHA `4de357e4a7489d6a25466cdec9babd8ed1edd513`; dispatch browser grant21:04:02Z. Dirty prior preparation and claim preserved; no merge/product edits. +- Plan: actual-app Upload through real UI at360×800,390×844,1280×720, strict document width, computed source rules, touch/focus/close restore; stop on boot blocker or culprit. +- Read-only storage build provenance: `test_logs/TEST_LOG_2026-10-22_20-58-00_storage-cors-4-landed.md` reports artifact29cbe6f0 from source d9424fb7, runtime-equivalent7edc7ea8. Served source was intended to remain own checkout, with generated CSS alone reused. `git diff --stat HEAD d9424fb7 -- css components/upload-modal.html tailwind.config.cjs postcss.config.cjs` output empty (exit0). +- SHA256 Buffer `2f1f8730af8c9fb08068a41c9c18356b9f48f21635fe5deed6d17fc6b9b97be7`; CSS `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`: both checked in script before bind and matched. Existing genuine cached dependency provenance retained in `/tmp/playback-qa-3-assets/provenance.json`; no network fetch or dependency substitution. +- Command `node /tmp/playback-qa-4-run.cjs`, natural exit1. Started21:05:04.269Z, ended21:05:04.281Z. Exact failure: `Error: listen EPERM: operation not permitted 127.0.0.1:31864` at Server.setupListenHandle. No assertion relaxation or rerun. +- Counts: runtime cases0, browser launches0, app readiness checks0, measured width/focus/touch cases0. Two byte-hash guards passed before server bind; not app qualification. +- Cleanup: failure handler closed own server object; browser never created, no listening private server created. No peer process/server or preview3000 touched. Slot RELEASE21:05:04.281Z. Script and JSON preserved under `/tmp/playback-qa-4-run.cjs` and `/tmp/playback-qa-4-result.json`. +- Unverified: actual app boot, dependency browser execution, current overflow/source culprit, touch/focus/scroll/zoom, real device/AA audit. Original strict QA2 failures362/360 and394/390 remain open; desktop1280 control remains historical. QA3 remains unreproduced/open. +- Next: dispatch disposition for denied local server execution before another bounded attempt; no substitute app or module harness, product/CSS/test/helper/config edit, build/full gate, or submission. + +## 2026-10-23 source/prep disposition + +- Dispatch12:49Z keeps QA4 source/prep only; HLS sole full/normal slot, no launch. Saved source runner and failed-attempt JSON remain present. Runner SHA256 `238b9ea5fdf04d5af6170b90f7525c8e0d3008860f47219465f3f217921ba542`; first failed attempt JSON SHA256 `6d1f61de37359c05bbaf402918dff36cee26701c5062815ed8dbd0690f9ad507` unchanged. +- Rechecked genuine Buffer mirror SHA256 `2f1f8730af8c9fb08068a41c9c18356b9f48f21635fe5deed6d17fc6b9b97be7` and generated CSS SHA256 `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`, matching the runner guards. Provenance for Buffer is `/tmp/playback-qa-3-assets/provenance.json`; generated CSS is the read-only artifact29cbe6f0 from source d9424fb7, documented in the storage-cors-4 landing evidence. `git diff --exit-code d9424fb7 HEAD -- index.html components/upload-modal.html css/tailwind.source.css` exit0: the relevant actual-app/template/source CSS files have no drift. Their SHA256 at current source: index `3df4e6fdbccb8982b3df0b973fd1f1ef988c9323a67a95f42cba04dfec2b9c17`, Upload template `9dc3e8fc8514cf4d19eb1e513764c80680b559c1069247a31367613ac925b017`, Tailwind source `2bc5b0bdf34a6c82e53c15977d7df438dd54af19eceb10518a72d662d0160e78`. +- Exact eventual command: `node /tmp/playback-qa-4-run.cjs` from `/home/user/Documents/GitHub/bitvid-playback-qa`. It contains the same pinned Buffer/CSS SHA guards, fixed private bind `127.0.0.1:31864`, local-origin-only HTTP allowance, public HTTP abort and WebSocket close, exact three viewports, and original strict `document.scrollWidth <= width` per viewport. It first binds the private app server, then launches Chromium; the first recorded run stopped before browser launch at that bind. +- Needed sandbox approval when dispatch renews browser grant: normal host execution approval for this exact command, so Node may bind only its assigned private `127.0.0.1:31864` app server and launch the bounded Chromium run. No approval requested/consumed and no command rerun now; no alternate port or edited guard. + +## QA4 host-approved run: actual-app readiness, Upload obstructed + +- Dispatch conditional grant active after client build/artifact release13:00:59Z and health13:01:10Z. Normal host escalation was accepted; no automatic-review rejection. Worktree source SHA `4de357e4a7489d6a25466cdec9babd8ed1edd513`; no product/CSS/test/config/build/full changes. +- Command `node /tmp/playback-qa-4-run.cjs`, host execution, natural exit1. Browser Chromium `145.0.7632.6`. Start13:01:39.395Z, end13:02:11.082Z, 31.687s. Actual app readiness succeeded once at360×800 (`applicationContext.getApplicationReady()`, app, test harness and `#videoList` present); local synthetic test-key login helper invoked. +- Next actual UI action, Playwright click `[data-testid="upload-button"]`, timed out at30s. Locator resolved visible/enabled/stable. Repeated native click attempts report `
…
` inside `#modalContainer` intercepts pointer events. Therefore Upload did not open. Exact error and call log preserved in `/tmp/playback-qa-4-result.json`. This is an app/harness UI blocker to reaching Upload; cause not diagnosed from this run. +- Counts: browser launches1; actual readiness1; strict viewport geometry0; Upload modal openings0; touch/focus/scroll measurements0; page errors0. Public HTTP deny intercepted CDN esm.sh/jsdelivr requests; public relay WSS attempts were blocked, plus local relay `ws://127.0.0.1:31865` had no server. This runtime cannot claim genuine fully offline app readiness beyond the above harness signal; Upload click blocker supersedes source culprit measurement. +- Strict QA2 baselines remain362/360 and394/390 FAIL; desktop1280 historical result only. No relaxed tolerance. Browser and private server closed naturally; report resourcesClosed true; slot RELEASE13:02:11.082Z. Preview3000 untouched. No retries or further viewport/browser reruns. +- Unverified: current document overflow and exact source rule, 390/1280, critical target bounds, focus/tab/restore, scroll/zoom, real device/AA. QA3 Firefox CI failure remains open/unreproduced. + +## Dispatch source diagnosis: disclaimer setup correction (2026-10-23) + +- Source at current QA HEAD: `js/index.js:1325` calls `disclaimerModal.show()` after `bootstrapInterface()` loads and initializes the disclaimer at `js/index.js:1197-1202`. `show()` opens `#disclaimerModal` when localStorage has no `hasSeenDisclaimer` (`js/disclaimer.js:56-64`). Actual button is `#acceptDisclaimer`, accessible name “I Understand” (`components/disclaimer.html:127-135`); its production click handler calls `hide()` (`js/disclaimer.js:21-37`), which uses `closeStaticModal`, writes `hasSeenDisclaimer=true`, dispatches `bitvid:disclaimer-dismissed`, and therefore releases the modal focus trap / returns prior focus (`js/disclaimer.js:40-53`; `staticModalAccessibility.js:125-143`). +- Official Playwright fixture `tests/e2e/helpers/bitvidTestFixture.ts:114-121` bypasses the onboarding by setting `localStorage.hasSeenDisclaimer=true` before `page.goto`. Our actual-app runner does not do this, appropriately, so first-run UI covers Upload. Next bounded plan: leave disclaimer unseen, wait until `#disclaimerModal` is visible, click it by normal user interaction/accessibility name (`getByRole('button',{name:'I Understand'})`), then await `#disclaimerModal.hidden` and verify `data-open=false` before logging in and clicking Upload. Do not use force click, remove DOM, set storage, skip the interstitial, or inflate timeout. +- Previous runtime also showed blocked `ws://127.0.0.1:31865` and public WSS defaults. Start the existing `scripts/agent/simple-relay.mjs` on fixed private WS31865 with HTTP disabled (no events needed), install test-relay override before app initialization and confirm harness `setTestRelays([relay],{persist:false})`; allow only static app origin31864 and this relay WebSocket. Keep all public HTTP aborted and other WS closed. This avoids relying on the unused relay port or requiring feed content. No new browser run launched; wait for renewed dispatch slot after HLS browser stage. + +## QA4 corrected host run preparation and prior artifact preservation + +- Client-reliability released HLS browser/server13:08:47Z, no processes. Dispatch13:06:23Z conditionally granted one corrected run after release+fresh health. Latest health13:10:19.701Z: RAM available63.3%, max74.9C, load2.88/8; swap-only88% threshold warning, treated advisory under dispatch13:01:18 clarification. No competing browser/full gate reported. +- Preserved prior disclaimer blocker JSON at `/tmp/playback-qa-4-disclaimer-blocker-result.json` (SHA256 `2264955f74fc8c36deee43177eecb885aa02d8db57ff6016ad743cd8d657fd97`) and EPERM event JSON at `/tmp/playback-qa-4-eprem-blocker-result.json` (SHA256 `943794b810ddecd4c28334008a873d16b5ea85f430c4591f3aae84e95781cea1`); the earlier textual record remains above. +- Corrected runner SHA256 `7e1b6a8433e7ce34ea6c6e7d9b0c548ff75424356fb1e6f0d963bc89c25e677b`; `node --check /tmp/playback-qa-4-run.cjs` exit0. Same genuine Buffer/CSS hash guards re-read and passed; relevant source diff d9424fb7..HEAD exit0. It now starts only the existing `simple-relay.mjs` at31865 with HTTP disabled, installs the official test relay override before navigation and invokes `setTestRelays`, accepts visible disclaimer via accessible “I Understand” click and waits hidden/data-open=false before login/Upload. Static app remains fixed31864; external HTTP abort and all nonlocal WS close. Third run writes a distinct `/tmp/playback-qa-4-corrected-result.json`. Preserves strict geometry result for360/390/1280, control/touch rects, focus/Tab/restore evidence and matched rules on overflow candidates. +- No third run has started yet. + +## Corrected run stopped at local relay dependency blocker + +- Dispatch conditional slot was active after client HLS browser release13:08Z and fresh health13:08:57Z; dispatch classifies swap-only88% with ample RAM as advisory. Exact prepared command `node /tmp/playback-qa-4-run.cjs`, host execution approval accepted. +- Corrected runner SHA256 `7e1b6a8433e7ce34ea6c6e7d9b0c548ff75424356fb1e6f0d963bc89c25e677b`; node syntax check exit0. Buffer and generated CSS hash guards passed2/2 before setup. Natural exit1; start13:10:37.868Z, stop13:10:37.889Z (21ms). +- Exact blocker: `Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'ws' imported from /home/user/Documents/GitHub/bitvid-playback-qa/scripts/agent/simple-relay.mjs`. ESM resolver cannot find ws from this worktree. Failure occurred before relay listener, static app server, or Chromium launch. Runtime cases0; no disclaimer/Upload/geometry assertions ran. Resources closed, no port bound. Report `/tmp/playback-qa-4-corrected-result.json`. +- This run did not reject host execution; it exposed missing local relay dependency resolution. Stopped under first-blocker rule. No alternate port, shim relay, install, substitute, or retry. QA browser/private server/relay slot RELEASE13:10:37.889Z; preview3000 untouched. Prior EPERM and disclaimer artifacts preserved separately. +- Remaining: genuine mock relay dependency is unavailable from this worktree's ESM resolution; next attempt needs dispatch source/prep disposition and a renewed browser slot. No actual mobile geometry/focus/touch measured. + +## Dispatch source/prep correction: relay module resolution + +- Dispatch authorized importing the byte-identical existing simple-relay module from its worktree because that worktree resolves the installed genuine `ws` package. No package install, shim, or product module copy. +- Verified own relay source and dispatch relay source with `cmp` exit0; both SHA256 `ab11b29937690810900d3a16e01e16571ce68d7f02d50c86656b76f47776cba4`. Import-only command from dispatch module: `node --input-type=module -e "const m=await import('file:///home/user/Documents/GitHub/bitvid-dispatch/scripts/agent/simple-relay.mjs'); if(typeof m.startRelay!=='function') process.exit(1); process.stdout.write('import-only PASS startRelay=function; no server started\\n')"`, exit0. It imports only; module's standalone server guard did not run. +- Scratch runner now checks own+dispatch module byte equality and the pinned hash, imports dispatch module and checks `startRelay` before starting the local relay or Chromium. `node --check /tmp/playback-qa-4-run.cjs` exit0. New runner SHA256 `80b2bd6a45b78c7d5e5dc1691ecff7a9b0370b5f6a2ce275064e5f21d42c1973`; no browser/server launched by this preparation. +- Await client HLS review/release and renewed separate browser grant plus fresh health. No product changes or second run yet. + +## QA4 corrected actual-app mobile run completed + +- Own worktree source SHA `4de357e4a7489d6a25466cdec9babd8ed1edd513`; dispatch conditional run after client HLS browser release13:17Z and fresh health13:17:39Z (RAM67.7%, max72.9C, swap-only84% advisory). Normal host approval accepted. Exact `node /tmp/playback-qa-4-run.cjs`, Chromium145.0.7632.6, natural exit0, 7.131s (13:17:53.184–13:18:00.315Z), resourcesClosed true. No product/CSS/test/config/build edits. +- Genuine Buffer SHA guard and generated CSS SHA guard passed2/2. Existing relay source pinned and identity checked before launch. Local simple-relay WS31865 started with HTTP disabled; no events seeded. Static app served from own checkout on private31864. Browser allowed only local app HTTP and local relay WS; public HTTP/other WS denied. 42 blocked requests, 9 unique destinations (5 external HTTP URLs and4 public WSS relays); pageErrors0. No real identity/relay/cloud/media or publishing. +- Actual app readiness and genuine UI disclaimer acceptance succeeded3/3 at360×800,390×844,1280×720. `#disclaimerModal` ended hidden/data-open=false and hasSeenDisclaimer=true. Upload opened through actual header button in all3. Strict document scrollWidth<=viewport retained and passed3/3 in this run: 360/360,390/390,1280/1280. Bounding overflow candidates0 in each. This new pass does NOT replace prior QA2 strict failures362/360 and394/390; those historic observations remain open and unexplained. No current overflowing node/rule culprit was demonstrated, so no CSS fix scope is proposed. Source trace confirms mobile `.modal-sheet` uses `inline-size:100vw` under border-box; computed sheet edges were exactly viewport edges. +- Focus: 12 keyboard Tab steps per viewport, all12 stayed within Upload and matched `:focus-visible`; after closing, focus returned to `#uploadButton` in3/3. This is local Chromium behavior, not real-device or WCAG/AA certification. +- Horizontal/zoom probe: `window.scrollTo(100,0)` left `scrollX=0` and document width equal viewport at all3; `visualViewport.scale=1`. No pinch/zoom interaction or real touch device tested. +- Direct bounding-box measurements include touch-size concerns: close Upload32×32 mobile/30×30 desktop; Upload File toggle97×23.6 mobile; External Link106×23.6; Storage Status84.8×12.8; Browse66.9×28.8; Add Tag67.1×22.1; Advanced Options full width but14 high; Submit full width but40.4 high mobile/38 desktop. Checkbox input boxes16×16; associated label hit areas were not measured. Most measured button boxes are shorter than44px. At360, Submit begins y819.6 below viewport800; at390 it begins819.6 and extends to860 beyond844; desktop Submit y809–847 beyond the modal bottom684. Upload `.modal-body` source is `overflow-y-auto`; actual vertical scroll-to-submit reachability was not exercised. +- Exact current Upload style source was the verified generated stylesheet hash `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`, with known source version d9424fb7; relevant index/template/Tailwind source comparison remained unchanged. No overflow node existed to map to a culprit selector. +- Artifacts: `/tmp/playback-qa-4-corrected-result.json` and `/tmp/playback-qa-4-{360,390,1280}.png`; hashes recorded in the run output/tool log. Browser, static server, local relay closed naturally. Slot RELEASE13:18:00.315Z; preview3000 untouched. +- Unverified: prior strict overflow failure's exact source/cause and same-run reproduction; actual vertical scrolling to lower controls; checkbox label target sizes; mobile real-device touch/zoom; color contrast/AA; Firefox; production/live relay or media behavior. QA3 Firefox CI remains unreproduced/open. + +## QA4 vertical/effective label actual-app result — 2026-10-24 +- Source SHA `4de357e4a7489d6a25466cdec9babd8ed1edd513`. Dispatch NEW slot02:58:48; local preflight02:58:59.979 max66.1C/load1.89/RAM63.3%, swap82% advisory. Exact command `node /tmp/playback-qa-4-vertical-run.cjs`, pinned runner8bb9fcff, normal host approval accepted; no auto-review rejection. Chromium145.0.7632.6, start02:59:14.538Z/end02:59:22.300Z, natural exit0/7.762s. +- Thermal limitation: dispatch measured91C at02:59:19 during this short run; its stop-if-still>=85C mail02:59:31 was read after natural completion/release. No sustained temperature qualification; all further heavy work HOLD, no retries. +- Genuine dependency/CSS/relay identity guards retained; actual readiness/disclaimer acceptance/Upload3/3. No events seeded or submitted. Public deny42 attempts/9 unique destinations; pageErrors0. Browser/private app31864/local relay31865 closed via awaited finally cleanup; resourcesClosed true. Separate `ss` netlink inventory unavailable (Operation not permitted), so no independent socket inventory claim. Preview3000/peer resources untouched. +- Strict document width passes3/3 initially and21/21 vertical snapshots (initial, three wheel, expanded, expanded-bottom, collapsed per viewport). Original historical362/360 and394/390 failures remain unexplained and retained; no source overflow culprit demonstrated or CSS fix scope proposed. + +| Viewport | Body client/scroll height | Body scrollTop after first wheel | Advanced expanded scrollTop at bottom | Publish visible after wheel (height) | +|---|---|---|---|---| +| 360x800 | 693/798 | 105 | 319 | 40.4px | +| 390x844 | 737/798 | 61 | 275 | 40.4px | +| 1280x720 | 537/749 | 212 | 371 | 38.0px | + +- Userwheel moved `.modal-body`, while document/modal/sheet scrollTop stayed0. Initial Publish was outside/mostly clipped; the first wheel made Publish and Advanced fully visible with3/3 sampled points owned by each control in all viewports. Pre-click geometry proves wheel reach independently of locator auto-scroll. Expanded bottom wheel exposed webseed/xs/Publish with owned samples3/3 per target per viewport; Publish was never clicked. Advanced expansion/collapse via ordinary UI3/3. +- Four associated flag labels per viewport were visible after wheel. Native label text clicks changed checked state and restored it:12/12 checks pass. This establishes sampled effective label activation, not every point in the label rectangle. Mobile label bounding dimensions:118.2x16,53.2x16,64.5x16,249.5x16; desktop110.9x15,50.0x15,60.6x15,234.1x15. Label widths extend native input hit area; measured heights remain small. No missing-label/unreachable inference was needed. +- Source trace for label geometry: template290-310 wraps each checkbox+text in flex/items-center/gap-2 label with no label padding/min-height; spans use text-sm. CSS checkbox rule1458 applies h-5/w-5; matched generated rule1.25rem, text-sm uses font-size-sm/line-height-snug; tokens font-size-sm at442 and Tailwind config359 map text sizing. These are measured/source-associated small dimensions, not a reproduced historical overflow cause or authorization for token/CSS redesign. +- Keyboard40Tab steps per viewport: containment120/120 and focus-visible120/120; traversed flags/Advanced/Publish. Close restores Upload trigger3/3. Expanded optional inputs were geometrically measured; Tab traversal occurred after collapse, so expanded-input keyboard qualification remains unverified. Horizontal scroll probe still scrollX0/scale1 in3/3; no real pinch/zoom/touch/device/AA or contrast audit. +- Evidence JSON `/tmp/playback-qa-4-vertical-result.json`, SHA256 `c94c2cc1097fa2b3df8c42165b4ca51b2cda818c869f849f0c91db872e4522fb`. Bottom screenshots360 `8e88c0fe6e7731e38e48f359a164f3f57a1588041fa99b472e556cb6ee4403f6`,390 `125316df039ab5512a539e5c51fd4434f72000204ac9000db262bc0f7a6ad4a6`,1280 `a67d664f2b3b973d1180397fe400b9590d7aa01699461ef070ed196cb197dff5`; original runner/results retained. No product/CSS/test/helper/config/build/full/submission changes. QA3 CI remains open/unreproduced. + +## Source/results-only follow-up: mobile checkbox input interception + +Dispatch independently identified input sampling obstruction, distinct from passing associated-label activation. Reanalysis of the same JSON (no new browser): at360 and390 the three native input samples for `check-comments` and `check-no-nip71-mirror` hit `ASIDE#sidebar`, owned=false, in all seven recorded snapshots:42 samples per mobile viewport/84 total. Desktop sampled obstruction0. Other two flag input samples hit their inputs. This establishes point interception; no direct checkbox-box native click was executed, so do not report a measured checkbox click failure or unclickable overall label. + +Exact pre-Advanced-click lower snapshot: + +| Viewport/input | Input rectangle left/top/width/height | Three sampled x at common y | Hit | +|---|---|---|---| +|360/comments|38.375/584.4375/16/16|46.375,41.575,51.175 at592.4375|ASIDE#sidebar all3| +|360/no mirror|38.375/613.234375/16/16|46.375,41.575,51.175 at621.234375|ASIDE#sidebar all3| +|390/comments|38.375/628.4375/16/16|46.375,41.575,51.175 at636.4375|ASIDE#sidebar all3| +|390/no mirror|38.375/657.234375/16/16|46.375,41.575,51.175 at665.234375|ASIDE#sidebar all3| + +Associated label text points (e.g. comments x108.65625 and no-mirror x174.296875) were owned and native toggle+restore passed. Bounding label height16px mobile remains a separate size observation. Preserved extracted obstruction entries under `/tmp/playback-qa-4-input-obstruction-{360,390,1280}.json`; complete original JSON retains all input/label rectangles and matched rules. Visually inspected retained360-bottom screenshot: sidebar rail/icons visibly occupy left edge over Upload; screenshot is expanded-bottom state, not the pre-click table state. Existing hashes above unchanged. + +Source/rule trace and bounded hypothesis, not proven cause/fix: + +- `index.html:147` sidebarContainer is a body sibling of app; modalContainer is nested inside app at375. Sidebar template7 initially marks collapsed; CSS5076-5100 fixes sidebar top/left0, full dynamic height, width sidebar-width, z-sidebar, isolation:isolate. Token224 collapsed width4rem; tokens203-207 define sidebar20/modal100/110/120. Generated matched #sidebar rule confirms those declarations, but runtime computed sidebar rectangle/z-index/pointer-events and complete ancestor stacking contexts were NOT recorded. Do not derive actual stacking from declared z numbers alone. +- CSS101-108 hides/disables sidebar only under body.modal-open; CSS111 adds the is-modal-hidden guard. Modal container CSS4602-4623 sets fixed/inset/z-modal-root/pointer-events-none/isolation then direct-child pointer-events-auto. Generated matched modal/sheet and sidebar rules preserved; modalContainer itself was not one of the rule collector's selected elements, so its matched/computed runtime cascade is incomplete. +- Upload `js/ui/components/UploadModal.js:1507-1544` removes hidden, updates shared modal state and activates focus accessibility; it does not itself add body.modal-open/is-modal-hidden. Shared state setter `js/state/appState.js:167-182` updates/notifies state only. Its focus helper `js/ui/components/modalAccessibility.js:142-174` installs key/click listeners and schedules focus; it does not itself apply sidebar guard classes. This makes missing modal/sidebar suppression or an ancestor stacking context a source-supported hypothesis; it is NOT a demonstrated causal chain because other state subscribers/runtime class history were not fully traced or measured. +- Minimal independent reproduction/ownership review scope: Upload open/close sidebar suppression/state wiring (`UploadModal.js` and existing modal integration), sidebar-vs-modal stacking/guard rules in `css/tailwind.source.css`, and app/modal ancestor context in index.html. No token-value/color/redesign change proposed; no patch/product/CSS scope assigned. Need independent exact point/stack/computed-style confirmation before deciding which file owns a fix. No browser/full/build/submission granted now; client owns next Firefox diagnostic. + +Evidence retention candidate consists ONLY of existing dirty `docs/agents/backlog/playback-qa-4.json` plus the two unique notes `docs/testing/playback-qa-4-preparation.md` and `docs/testing/playback-qa-4-boot-blocker-2026-10-22.md`. No commit/merge/submission performed; await docs-only retention review. Historical overflow cause, expanded-field keyboard, real device/AA/contrast, live/media and QA3 CI remain unverified. + +## Parked source/results conclusion — 2026-10-24T19:24:15.475268+00:00 + +Dispatch19:23:54 requests a completed source-only outcome and parking, not a browser dependency. Source/results inspection is complete within available artifacts. The mobile input-point interception is measured, but its **cause is inconclusive**: saved evidence lacks computed sidebar/ancestor stacking geometry, actual modal-open/sidebar suppression class history and a direct checkbox-input click. Associated-label usability remains separately measured12/12. No further causal source evidence was established; declared stacking values alone do not prove effective ordering. Existing exact samples, screenshots and minimal CSS/template/lifecycle inspection scope above are the review handoff; no fix is proposed or authorized. + +Exact retention paths (all own dirty work preserved): + +1. `docs/agents/backlog/playback-qa-4.json` +2. `docs/testing/playback-qa-4-preparation.md` +3. `docs/testing/playback-qa-4-boot-blocker-2026-10-22.md` + +No additional browser/fix/gate/build/submission performed. All prior browser grants expired; heavy work remains HOLD. QA4 is parked pending review/routing, not waiting on browser availability. Historical strict overflow failures and QA3 CI limits remain open. diff --git a/docs/testing/playback-qa-4-causal-proposal-2026-10-25.md b/docs/testing/playback-qa-4-causal-proposal-2026-10-25.md new file mode 100644 index 00000000..2b11cce2 --- /dev/null +++ b/docs/testing/playback-qa-4-causal-proposal-2026-10-25.md @@ -0,0 +1,30 @@ +# QA4 smallest causal intervention recommendation + +Source HEAD `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`; source-only continuation requested by interpreter20261025T032539. Client owns sole short Firefox control. No QA browser/server/full/build slot granted. + +Plan: review preserved measured evidence, isolate one explanatory variable, record strict interpretation and likely owner before any implementation. No product/CSS/retained test/helper/config changes. + +## Evidence confirmed without execution + +Read-only Python verification exited0: saved result SHA256 ea7a6aa358c488e4937ff3df7c21e24b7cd25488ad81232e21196aa3c255b742 unchanged; native comments input click before=true/after=true and event target sidebar; app finished opacity animation has forwards fill. Previous run supplies one failed native click,18/18 sidebar-first input hits,4/4 strict360 widths,0 page errors. This verification is artifact inspection, not new runtime qualification. All prior notes/dirty backlog preserved. + +## Recommended separate one-run A/B measurement + +After explicit exclusive-slot grant and fresh resource health, create a distinct scratch script/result path. Reuse genuine actual-app setup/pins, readiness/disclaimer/UI flow, private31864/31865, local synthetic harness identity only and public HTTP/WS deny. Revalidate HEAD and dependency hashes before launch. Do not edit the completed runner or overwrite any original artifact. One360x800 context, one normal wheel to the same lower state. + +1. A: capture exact current classes, computed ancestor styles, finished app animation, strict document/client/scroll widths and ordered hit stacks. Make one native comments-input center click; capture before/after and event composedPath. Require the original failure (sidebar target, unchanged checkbox) to reproduce as prerequisite. If it does not, stop naturally and report NOT REPRODUCED without mutation or retry. Preserve this baseline independently of B. +2. B: remove ONLY the `fade-in` class from `#app` in the scratch DOM (`document.getElementById("app").classList.remove("fade-in")`). This mirrors the existing cleanup action in js/index.js. Do not alter sidebar classes/pointer events, modal-open, z-index, tokens, opacity/style properties, modal nesting or any other CSS. Capture the resulting animation list, computed style, class mutation events and exact rectangles before clicking. No timed sleep or synthetic animationend event. +3. Require checkbox rectangle/scroll position unchanged; otherwise record geometry change and stop causal comparison. At the same six comments/no-mirror sample points, capture ordered elementsFromPoint. Make one native comments-input center click at the original coordinates and capture checked-state change and event composedPath. Successful B must target checkbox and toggle once; a completed script exit alone does not count. Restore checked state through the previously safe label text only if needed, then close Upload and all owned resources. No restoring fade-in/replaying animation or third trial needed; context closure discards the sole DOM intervention. +4. Preserve strict width<=viewport checks in both A/B; keep original historical362/360 and394/390 failures explicit. Never describe B as replacing an earlier strict failure or certifying mobile/AA. No publish/file/cloud/media operation. Stop on first blocker, no reruns/retries, no CSS or product mutation retained. + +If A fails and B succeeds with unchanged geometry and suppression state, removal of retained app fade-in is sufficient to eliminate this local interception; it supports the retained animation-context diagnosis. It does not establish why animation cleanup was missed, whether suppression integration is independently defective, or parity across viewports/browsers/devices. If B fails, retain that failure and return to measured stacking ancestry; do not add a second suppression intervention to salvage the run. + +## Likely owning file and gate before fix + +First implementation review target: `js/index.js` animation cleanup at514-553, because the measured retained class is exactly the class that this existing handler intends to remove. This is a likely owner, not assigned write authority or an implementation recommendation. Need independent reproduction of A/B plus startup event timing (whether listener was registered before animation completion, and whether its callback ran) before choosing a minimal fix. A passing class-removal B alone does not justify an arbitrary timer, event dispatch, opacity/z-index change or unconditional global removal. + +`index.html` app nesting and `css/tailwind.source.css` opacity animation are read-only supporting scope. Upload suppression is a separate path: UploadModal.open sets appState but does not set body.modal-open; measured absence is real, yet no suppression intervention is proposed in this A/B because it would confound the variable. If dispatch chooses that path instead, it needs separately scoped modal open/close/state ownership and shared-modal lifecycle evidence. No app.js or token/color/redesign scope requested. + +Recommendation READY; script not created/executed for this new intervention. New browser/server/test/build/gate/commit/submission counts0. Await resource/ownership routing. No outgoing message attempted or denied-message retry. QA3 CI, historical horizontal overflow, cleanup mechanism and real-device/AA remain unverified. + +Recorded UTC: 2026-10-25T03:26:41.740021+00:00 diff --git a/docs/testing/playback-qa-4-causal-result-2026-10-26.md b/docs/testing/playback-qa-4-causal-result-2026-10-26.md new file mode 100644 index 00000000..91d1c70f --- /dev/null +++ b/docs/testing/playback-qa-4-causal-result-2026-10-26.md @@ -0,0 +1,42 @@ +# QA4 corrected actual-app causal diagnostic + +Printed HEAD `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`. One native Chromium360x800 run, no retry. Runtime2026-10-26T19:09:24.032Z–19:09:25.820Z; natural exit0. Dispatch190837 and interpreter190751 granted one corrected run; installed health190904 OK/TCPU73.1C/swap-in0. Prior startup90.1C and transient swap-in3/1page snapshots retained in post-restart note; no health configuration change. + +Exact command from own checkout: + +``` +node /home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/corrected-prep/run.cjs +``` + +Host execution used for private loopback bind; public HTTP/WS denied. Actual application readiness, native disclaimer acceptance/closed-state, existing test-only identity login and native Upload entry all completed. No substitute app, relay seeding or publishing. Genuine dependency archives byte/SHA256/SHA5123/3; original runner/result and corrected runner digest3/3; expected HEAD/source/unique-output guards passed. Runtime genuine Buffer API6/6. Original failed runnerce95cf5c/result868e6dd8 unchanged. + +## Measured comparison + +- A: native click at(46.375,592.4375) targets `ASIDE#sidebar`; checked true→true. All6 sampled checkbox points hit sidebar first. +- Sole B intervention: remove `fade-in` from `#app` via DOM classList, matching existing intended cleanup action. No retained product edit, sidebar/style/suppression intervention or synthetic event. +- B: same native coordinates target `INPUT#check-comments`; checked true→false. All6 sampled points hit intended inputs first. Both checkbox rectangles, body/sheet/document scroll and suppression class state deep-equal before/after intervention. +- Comments input rectangle x38.375/y584.4375/16x16; modal body scrollTop105; document x/y0. This is an input measurement, not total associated-label touch area or an AA audit. +- Strict widths A and B: inner/client/document/body360/360/360/360, width<=viewport true. Historical strict failures362/360 and394/390 remain open and unexplained; no390/1280 rerun in this diagnostic. +- Safe label restoration succeeded. Upload close restored focus to Upload button. No new Tab containment/zoom/real-device coverage. + +Result `A_FAIL_B_PASS_SAME_GEOMETRY`. This demonstrates that removing retained app fade-in is sufficient to eliminate the local sidebar interception under unchanged measured geometry. It does not establish why animation cleanup failed, whether startup listener installation missed completion, or that changing CSS/modal suppression is the right fix. Existing js/index.js514–553 listens for animationend/animationcancel and removes fade-in; independent reproduction and startup event/listener timing are required before implementation ownership. Relevant css/components/js/index.js source unchanged since filedAt. + +Counts: browser1/readiness1/disclaimer1/Upload1/wheel1/native-input-click2/intervention1. Page errors0; blocked public HTTP10/WS4; local relay events0. Context/browser/relay/server closed; tracked app sockets0 and resourcesClosedtrue. Browser slot RELEASE19:09:25.820Z, recorded in.task/progress and sent dispatch. Sandbox ss socket-list confirmation unavailable (netlink EPERM); cleanup proof is runner finalization plus natural process exit, not a successful independent OS socket inventory. Preview3000 and peers untouched. + +Durable runner SHA256 `593fc0556257e1943f61e9496d3fcd5b906dee9025ccbb6368f7ea26d78fc5f2`; result SHA256 `ecf85211a9a9c9c90f002cd925767c0bda5c546cbb2a5bcbc6147bde5f2aac3e`. Full ordered hits/computed styles/animations/mutation events/native composedPaths and cleanup reside in `/home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/corrected-prep/result.json`. + +No product/CSS/helper/config/existing-test edits, retained test, commit/merge/build/full-suite/submission. This scratch diagnostic preserves failing A as its prerequisite and contains no weakened baseline. QA4 stays open pending independent cause/cleanup timing/routing and durable evidence landing; QA3 CI failure remains blocked. Live media/decoder/peers/real accounts/signers/cloud/CDN/release/AA qualification unverified. + +Computed evidence: A app animation `0.28s ease-out forwards interface-fade-in`; finished app animation currentTime280ms/fillforwards/progress1 remains. B app animation `none` and app animation absent. Sidebar stays fixed/z-index20/isolationisolate/pointer-eventsauto. These values support the retained animation-context explanation; exact startup listener/event ordering was not instrumented. + +## Available startup timing data and source-only follow-up + +Runner init script installs a class MutationObserver and capture click observer before navigation. It does **not** instrument animationstart/end/cancel, document.addEventListener registration, module evaluation, cleanup callback entry, CSS load or performance resource timings. Consequently no measured cleanup-listener registration/event ordering can be recovered from this result. + +Observed performance.now(ms): app sidebar-collapsed class added957.4; Upload-open snapshot1340.9; A snapshot1396.7; A nativeclick1421.2; sole app.fade-in removal1446.2; B snapshot1449.0; B nativeclick1464.0. No earlier app fade-in removal appears in the captured class mutations. Finished app animation reports localTime280ms, which is animation timeline time, **not** measured navigation-time completion. + +Source: index.html164 places fade-in on app; module entry is index.html453. js/index.js static imports1–59 include Buffer mirror dependency via bufferPolyfill.js, plus bootstrap/services. Cleanup listeners are registered at552–553 after synchronous setup calls; function-local awaits earlier in the file do not themselves prove delayed top-level registration. No top-level awaited startup was demonstrated before these listeners. Module fetch/evaluation and initial animation timing remain a plausible race requiring independent instrumentation, not an established mechanism. Later initialization calls startApplication and bootstrapInterface. Do not infer a missing-listener fix from line order alone. + +Runner inputs guarded at runtime: peer generated CSS SHA256283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3; relay source in both checkouts SHA256ab11b29937690810900d3a16e01e16571ce68d7f02d50c86656b76f47776cba4; genuine mirror buffer.mjs SHA256ff1c4a2377a9df17eaffa1f5b00da3d835f748f3d832a0490bd309756434f73d. Existing peer Playwright loaded read-only; own app31864/local relay31865, synthetic fixture identity only. Archive pins/bytes/SHA256/SHA512 remain in corrected-prep/verified-provenance.json and parent verification record. No dependency installation or product build. + +Dispatch191024 directs independent inspection and later browser timing after client RELEASE; no extra QA runtime authorized. Consumed browser hold cleared; next source-only review. Unique evidence remains uncommitted pending normal documentation routing; not a landing claim. diff --git a/docs/testing/playback-qa-4-client10-acceptance-plan-2026-10-27.md b/docs/testing/playback-qa-4-client10-acceptance-plan-2026-10-27.md new file mode 100644 index 00000000..2299113d --- /dev/null +++ b/docs/testing/playback-qa-4-client10-acceptance-plan-2026-10-27.md @@ -0,0 +1,37 @@ +# QA4 acceptance plan for client10 animation cleanup + +Preparation only at printed QA HEAD0ffd92a8d2072a19fe61ac6edcf5097a9538fddd. Dispatch013223 routes source-only acceptance preparation; exact boundaries read in docs/agents/ROUTING-2026-10-27-client10-animation-cleanup.md from dispatch worktree. Client sole product writer. No runner execution or new browser/full/build/submit grant; implementation API/candidate not yet reviewed. + +## Before validation + +1. Review READY candidate, exact product SHA/source diff, focused scenarios, per-case failing mutants, meaningful real initialization wiring evidence and lint. Read current primary rules/KNOWN_ISSUES and candidate contract. Obtain separate exclusive bounded browser slot and fresh installed healthOK; START/task/progress before launch. Preserve preview3000/peer servers and use private own app/relay ports only. +2. Preserve original QA boot failure, native-click A failure and strict historical362/360+394/390 overflow. Keep593fc055 causal runner and all original results immutable. Prepare a new uniquely named durable candidate runner/result, hash/record all inputs and list dependencies in manifest; source/hash guards must identify the actual candidate and matching generated CSS. No fake application, installs/downloads/builds without routing. Verified genuine archive+Buffer mirror, actual application readiness, real disclaimer close UI, existing fixture-only identity, local relay/no events; block/log all other HTTP/WS, including media/trackers. No real account/signer/cloud/publishing. + +## Actual-app acceptance matrix (pending execution) + +| Scenario | Entry/action | Required observation | +| --- | --- | --- | +| 360x800, default motion | Fresh actual-app navigation, disclaimer through native UI, fixture login, native Upload; one bounded wheel to original saved rectangle if reachable | No intervention: native point(46.375,592.4375) hits INPUT#check-comments and toggles exactly once; retain exact checkbox rectangles and modal/document scroll before click. If geometry differs, report it and stop exact-coordinate comparison; do not silently replace with a convenient point. | +| 390x844, default motion | Same actual UI Upload flow | Measure input rectangle and native center hit/toggle, associated label target size, close and focus restoration; record control rather than assert historical360 coordinates apply. | +| 1280x720, default motion | Same actual UI Upload flow | Same measured native input/control/close-focus checks, no mobile coordinate assumption. | +| 360x800, reduced motion | Fresh context with browser reduced-motion preference, same actual UI | Input reachable/toggles once; app visible, no stuck transient context; record actual computed animations. Do not require an animationend event when CSS suppresses animation. | + +Every viewport records innerWidth/documentElement.clientWidth/documentElement.scrollWidth/body.scrollWidth plus component rectangles, scroll positions, ordered elementsFromPoint at original six checkbox sample points where applicable, computed app/sidebar/modal animation/stacking/pointer state, native event composedPath and checked before/after. Require strict document width<=viewport as a distinct original assertion, with each failure retained. A width failure must be reported independently of native-click recovery; never relax tolerance or label whole mobile baseline all-green. No app.fade-in removal or other causal B intervention in candidate acceptance: success must come from product initialization itself. Prior failed-A/passed-B remains historical evidence and is not a requirement for the fixed candidate to fail again. + +Capture Tab/focus-visible containment and close-focus restoration using native keyboard events with unchanged real UI, critical target rectangles and vertical reachability. Report exact number of Tab steps and element identities; stop if focus escapes. Input16x16 alone is not total associated-label target size/AA compliance. No unsupported zoom or real-device claim; horizontal scroll/zoom coverage only if explicitly included in the bounded run grant. Keep dimensions separate from usability and original overflow diagnosis. + +## Animation lifecycle controls + +Client focused regressions own deterministic helper/wiring behavior; QA reviews those results before using browser budget. Each retained new case needs its own failing mutant per routing. Candidate API determines the smallest real-module control harness; never claim a module harness is actual-app acceptance. + +- Late initialization: native finished interface animation exists before actual cleanup installation. Record native animationend timestamp and real listener/helper initialization timestamp; require finished fade-in/delay cleanup and original input reachability. Instrument transparently before navigation, forwarding original listener registration arguments. If natural app ordering does not exercise late-init, report boundary; do not force delays/retry to manufacture a passing assertion. Use approved deterministic real-module scenario for that branch. +- Active initialization: still-running matching entrance keeps class/delay until genuine finish/cancel; record native animation state and completion event. Future matching animation retains animation until completion. No synthetic animationend, arbitrary sleep, timeout extension or forced layout. +- Cancellation: genuine cancellation cleans only the matching supported transient state. Distinguish runtime event semantics from a helper test event model; report whichever was measured. +- Unrelated animation/classes remain; thumbnail cleanup keeps its existing target/data-attribute contract; repeated initialization does not duplicate listener effects/resources. Review existing controlled regressions/mutants for these rather than invent additional app navigation solely to repeat their coverage. +- Reduced-motion CSS may yield no animation object/event; review expected initialization behavior in candidate contract and independently record actual browser state. Do not assume no event is a product defect without matching accepted contract. + +## Stop, release and reporting + +One bounded pass only as granted; stop at first readiness/network/geometry/runtime hard blocker, preserve natural exit and partial counts, no retries or assertion weakening. Discrete strict-width failure remains FAIL even if other authorized measurements continue. Close only owned context/browser/relay/server; relay events must remain0. Record START/endUTC, exact command/SHA/hashes, browser/version/viewports, named checks/pass/fail/unexecuted counts, page/network errors and explicit cleanup evidence plus RELEASE/task/progress. New defect goes to dispatch for ownership; no QA CSS/product/helper/config/test patch. + +Remaining gates: READY/candidate review, fresh slot+health, actual candidate execution, unchanged normal landing/build/artifact proof by assigned owner, independent acceptance as routed. Source-only plan executes0 checks and proves no fixed behavior. Missed cleanup demonstrated in prior controlled runs; original horizontal overflow, FirefoxCI, real-device/AA/live playback qualification remain open. diff --git a/docs/testing/playback-qa-4-client10-candidate-result-2026-10-27.md b/docs/testing/playback-qa-4-client10-candidate-result-2026-10-27.md new file mode 100644 index 00000000..af9b111d --- /dev/null +++ b/docs/testing/playback-qa-4-client10-candidate-result-2026-10-27.md @@ -0,0 +1,44 @@ +# QA4 client10 prelanding native candidate acceptance + +Source candidate `aff884169484bd8be01c2620efa8b3b052b31b70` printed from frozen client worktree before launch and checked again after all flows. Read-only sourceRoot `/home/user/Documents/GitHub/bitvid-client-reliability`; actual new helper served directly from candidate. QA ownHEAD0ffd92a8 unchanged; no product/CSS/config/test/helper edits. Dispatch014100 sole bounded grant, prepared runner/input guards reviewed014348. Client full gate/build/landing remain pending, not proved by this pass. + +Exact command from own QA worktree (approved host loopback execution): + +``` +node /home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/client10-aff88416-20261027/run.cjs +``` + +RunnerSHA256 `4fb07ae4bf1c0c45ae0326945b7a37951d89fc216b41e58c91a3117a47202e99`; resultSHA256 `1adb3067cefdac8138d8dcb22c10130a7f7da3f052ff2715440b845c27104e15`. Unique output and immutable original causal runner/results preserved; exact.diff/manifest/README/dependency provenance durable beside runner and registered in state/deps/manifest.txt. Syntax node --check exit0; runtime naturalexit0, `CANDIDATE_ACCEPTANCE_PASS`, 2026-10-27T01:44:11.731Z–2026-10-27T01:44:17.902Z. Native Chromium145.0.7632.6. No retries/sleeps/relaxed widths or diagnostic class removal/intervention. + +Startup preparation health014116swap-in82pages,0142368pages,0143052pages recorded tight with ampleRAM/coolCPU; no runtime launched then. Own immediate014352.800 installedhealthOK TCPU58.1C/swap-in0/RAM68.3%/charging70%; no config override or unrelated process stop. START/progress then one browser, sequential fresh contexts. + +## Measured outcomes + +| Case | Native input center | Strict document/viewport | App animationend/listener registration ms | Native toggle | +| --- | --- | --- | --- | --- | +| 360-default | (46.375, 592.4375) | 360/360 | 339.9/758.9 | PASS | +| 390-default | (46.375, 636.4375) | 390/390 | 320.8/701.8 | PASS | +| 1280-default | (438.5, 592.765625) | 1280/1280 | 316/687.2 | PASS | +| 360-reduced | (46.375, 592.4375) | 360/360 | none/677.1 | PASS | + +All four native events targeted INPUT#check-comments, changed checked true→false exactly once and label text restored original checked state. Original360 center exactly(46.375,592.4375), same prior16x16 rectangle x38.375/y584.4375 and scroll placement; no intervention. Strict document<=viewport before+after click8/8 true; historical362/360+394/390 failures remain preserved/unexplained separately, not replaced or closed by these passes. + +Actual app readiness4/disclaimer native acceptance+closure4/Upload native entry4/nativewheel4/nativeinputclick4/intervention0. Native click controls4/4 pass; Tab containment24/24 across6stepspercase; closefocusrestore4/4; contextclose4/4. Zero widthfailures, zero pageerrors, relay events0. These are named count groups, not a whole release test count. + +Each Tab active element matched :focus-visible and stayed inside Upload, order check-nsfw→check-kids→check-no-nip71-mirror→btn-advanced-toggle→btn-submit→closeUploadModal. Immediate default-motion computed focus outlines/rings for several controls were transparent; reduced-motion rings colored. This run measures containment and selector matches, **not** visual ring visibility/contrast after transition or AA compliance. Do not label all focus accessibility green. No added sleep or second paint/visual rerun to salvage this limitation. + +Comments label target measured118.171875x16 at mobile widths and110.890625x15 desktop; no-mirror label249.453125x16 mobile/234.109375x15 desktop. Input16x16 mobile (desktop source measurement in full result). Both sampled inputs vertically inside viewport at measured wheel position. Target heights are measurements, not certification of adequate effective touch area or spacing. No real-device or zoom qualification. + +## Lifecycle coverage boundary + +All three default-motion navigations naturally exercised **late initialization**: genuine app animationend preceded real onComplete registration, yet candidate removed stale app.fade-in before native input click. Native event observer and registration hook installed before navigation; registration forwards original arguments unchanged. Reduced-motion360 had no app animation events and computed app animation none; fade-in class remained, input reachable. Class retention without an animation is not the original animation-context obstruction. + +No native active-at-initialization or cancellation branch was exercised, and no extra navigation was added merely to duplicate helper coverage. Those behaviors rely on separately reviewed client focused17case/19mutant evidence; this browser pass does not independently qualify them. Future animations/thumbnail/unrelated/idempotence/disposal likewise remain controlled helper/wiring evidence rather than newly measured native cases. Full timing/mutation/animation state/computed styles/ordered sixpoint hit stacks/click paths are in result.json. + +## Isolation, release and pending gates + +Public HTTP40/WS16 attempts denied/logged; only own app31864/local relay31865 permitted, genuine Buffer CDN import fulfilled with independently verified mirror. Archive bytes/SHA256/SHA5123/3 and candidate source/index-helper-test hashes3/3 guarded at runtime; Buffer APIs6/6. Peer generatedCSS283d0464 and relayab11b299 matched guarded provenance/source. No local relay seeding/publishing, real signer/accounts/cloud/media or public peers. No build/dependency installation. Preview3000/peer resources untouched. + +All4 contexts closed; browser/relay/server closed, tracked app sockets0/resourcesClosedtrue; natural process exit0. RELEASE2026-10-27T01:44:17.902Z recorded task/progress and dispatch outcome. No active runtime left from this diagnostic. No independent OS socket inventory claimed. + +Remaining: dispatch exact outcome review, unchanged fullgate/durablelanding+ancestry and build/artifact verification by assigned owner. Original overflow, FirefoxCI, real-device/AA/zoom/visual focus-ring qualification and live playback remain open. QA evidence currently uncommitted; no queue/landing claim. diff --git a/docs/testing/playback-qa-4-evidence-index-2026-10-27.md b/docs/testing/playback-qa-4-evidence-index-2026-10-27.md new file mode 100644 index 00000000..bc87e5cb --- /dev/null +++ b/docs/testing/playback-qa-4-evidence-index-2026-10-27.md @@ -0,0 +1,13 @@ +# QA4 evidence landing and remaining scope + +This index reconciles historical status language in the preserved preparation/blocker/handoff notes. Those notes describe the time they were written, not new launch authority. QA4's demonstrated native checkbox interception/late animation cleanup slice is complete; whole mobile QA is **not** complete. Backlog QA4 remains deferred for unresolved work rather than marked done. + +- Causal diagnostic: playback-qa-4-causal-result-2026-10-26.md. QAHEAD0ffd92a8, naturalexit0, original failed click then only app.fade-in removal toggles at same point/geometry. Original failed boot result and source hypotheses retained in preceding uniquely dated notes. +- Independent startup timing: playback-qa-4-independent-handoff-2026-10-27.md. Dispatch-attributed actualapp evidence at2754a8af; native app animationend514.8ms before cleanupregistration3245.7ms. One independent hostrun naturalexit0/resourcesclosed, not an additional QA run. +- Reviewed acceptance plan: playback-qa-4-client10-acceptance-plan-2026-10-27.md. +- Actual candidate outcome: playback-qa-4-client10-candidate-result-2026-10-27.md. Exactaff884169484bd8be01c2620efa8b3b052b31b70, nativeChromium4flows360/390/1280default+360reduced; original360point toggles with **zero intervention**. Naturalexit0, native4/strictwidth8/Tabcontainment24/closefocus4, lateinit3/reducedmotion1, all resourcesclosed01:44:17.902UTC. Full hashes/runtimeinputs/deniednetwork/counts/limitations in note and durable state evidence. +- Dispatch015221 reports product landed5b0adf6b via unchanged439-filegate exit0/439unique/discovery/ancestry0;015439 reports hostbuild36716dfe exit0/artifactPASS86fc5236/indexSHA50a89bd6/helperbyteequality and RELEASE. These gate/build details are dispatch evidence, not duplicate QA executions; QA verifies Git ancestry/source equality during documentation landing. This docs-only queue must have its own durable status/ancestry before declaring evidence landed. + +Still open: historical strict horizontal overflow362/360+394/390 and its cause; immediate computed default focusring transparency is not visual focus visibility/AA proof; short measured label heights do not certify touch adequacy; real-device/zoom/manual release remain unqualified. Native active/cancel/future/thumbnail lifecycle branches were not exercised in the fourflow acceptance; reviewed client focused/mutant evidence covers the controlled boundary only. QA3 FirefoxCI remains blocked on CI-equivalent failing evidence. No real signer/account/cloud/media/peer/live playback certification. + +No CSS/token/z-index/modal suppression/product/config/existing-test changes in this QA evidence slice. No native baseline rerun required absent input change. Next disposition after this docs landing: source-only/idle pending explicit bounded routing. Preserve all historical failures and artifact hashes. diff --git a/docs/testing/playback-qa-4-independent-handoff-2026-10-27.md b/docs/testing/playback-qa-4-independent-handoff-2026-10-27.md new file mode 100644 index 00000000..0490a915 --- /dev/null +++ b/docs/testing/playback-qa-4-independent-handoff-2026-10-27.md @@ -0,0 +1,9 @@ +# QA4 independent timing handoff + +Own printed HEAD0ffd92a8d2072a19fe61ac6edcf5097a9538fddd; no new QA runtime or product change. Dispatch002408 mailbox and independent note read directly at /home/user/Documents/GitHub/bitvid-dispatch/test_logs/TEST_LOG_2026-10-26_19-44-00_dispatch-qa4-independent.md. + +Dispatch evidence, not a second QA execution: appHEAD2754a8af1f8e83912e09068135671c453c6de0ce with relevant input diff0; host command naturalexit0,2026-10-26T19:43:59.797–19:44:06.690, A_FAIL_B_PASS_SAME_GEOMETRY. Browser1/readiness1/disclaimer1/Upload1/wheel1/nativeclick2/intervention1/pageerrors0; resourcesClosedtrue/context/browser/relay/serverclosed. Strict360/360. Same native point sidebar true→true, only app.fade-in removal then INPUT true→false. Original sandbox preparation gitEPERM/browser0 result retained separately. + +Independent pre-navigation observations: app animationend514.8ms; production animationend cleanup listener registration3245.7ms, lag2730.9ms. This supplies the event/listener order absent in QA's original result, establishing missed startup cleanup in this controlled run. No universal production/network timing claim. RunnerSHA2566feacc4f2c3339db7747f237b3543eccfa1351919c6c02f2aa397060a89ac12c; resultSHA25610f1d3e3ce03504f8e094182f85bb7741a1929096d17f7081a46deb4092c40a5 per directly read dispatch note. QA did not independently hash or rerun dispatch artifacts this turn. + +Recovery/handoff only: await dispatch evidence-based cleanup ownership and validation routing. No new browser/test/build/product/CSS/config/submit authority. Original boot failure, native-click evidence, and historical362/360+394/390 overflow remain preserved; original overflow cause and real-device/AA/manual/live qualification remain open. No baseline rerun needed absent changed inputs. diff --git a/docs/testing/playback-qa-4-post-restart-2026-10-26.md b/docs/testing/playback-qa-4-post-restart-2026-10-26.md new file mode 100644 index 00000000..e00a7213 --- /dev/null +++ b/docs/testing/playback-qa-4-post-restart-2026-10-26.md @@ -0,0 +1,11 @@ +# QA4 post-restart checkpoint + +HEAD `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`; QA4 re-claimed, QA3 remains blocked on unavailable CI-equivalent failing evidence. Existing dirty notes and original failed result retained. + +- Read primary Oct26 RULE-CHANGES and updated COMMON following interpreter grant20261026T190751. One corrected360 A/B permitted only after fresh installed health OK. Old75C/load8 blanket prerequisite superseded for this bounded run; 85C CPU threshold preserved. +- Startup ready deps missing0, TCPU90.1C. Subsequent health19:08:21 TCPU77.1C/RAM65% but tight swap-in3pages;19:08:42 TCPU79.1C/RAM63.8% but tight swap-in1page. No launch while verdict tight; no unrelated process stops or config override. +- Independent archive byte/SHA256/SHA512 verification3/3. Original runnerce95cf5c/result868e6dd8 and corrected runner593fc055 digest checks3/3. Expected HEAD/source/unique output guards pass. Corrected runtime not yet executed. +- Read-only source review: css/components/js/index.js unchanged versus QA4 filedAt; animationend/animationcancel cleanup still removes fade-in. No conclusion on missed event timing or causal fix. +- Pending: one corrected actual-app/native UI A/B, strict widths, resources cleanup, independent reproduction/ownership. Historical362/360+394/390 overflow remains open; real-device/AA/CI/live playback unverified. No product/CSS/test/helper/config edits, commit/merge/submission/build/full-suite. + +Saved UTC 2026-10-26T19:09:04.589658+00:00 diff --git a/docs/testing/playback-qa-4-preparation.md b/docs/testing/playback-qa-4-preparation.md new file mode 100644 index 00000000..8920703c --- /dev/null +++ b/docs/testing/playback-qa-4-preparation.md @@ -0,0 +1,62 @@ +# QA4 mobile investigation preparation + +2026-10-22, HEAD `4de357e4a7489d6a25466cdec9babd8ed1edd513`. QA4 claimed after newest rules/current prefix read. Prior QA3 remains blocked, not fixed/done; its evidence checkpoint is durable. Latest dispatch20261022T142538 HOLD supersedes the earlier browser grant: no Chromium, server, full suite or build started. Storage may consume the exclusive expensive slot. This note is source preparation, not a new actual-app reproduction. + +Plan after explicit browser resume: + +1. Serve own current actual app on a private port with verified genuine Buffer; require actual application/harness readiness. Reuse verified generated CSS only with explicit source/output provenance; no build or product rewrite. Deny public HTTP/WebSocket. Synthetic existing harness identity only if needed to reach Upload; no real account, relay events, signer/cloud/media. +2. Open Upload through real UI at360×800,390×844,1280×720. Capture strict document scrollWidth<=viewport separately per viewport; preserve every failure and prior QA2 failure. No tolerance or alternate relaxed pass. +3. Persist full DOM node identity/path, bounding/client/scroll geometry, overflow/position/transform/width/padding/border/computed source rules. Account for clipped and animated descendants before identifying a culprit; bounding-box overflow alone is not proof of document overflow. +4. Measure critical control size/reachability, actual Tab containment, focus-visible styles and close-trigger restoration. Capture screenshot and scroll positions. Local Chromium measurements do not qualify real-device/accessibility/AA behavior. +5. Stop/report first boot blocker or demonstrated culprit and minimal implicated source scope; release all own resources. No CSS/product/helper/config/retained test edits before independent reproduction/routing. + +Source checks: `git diff b1174163 HEAD -- css components/upload-modal.html index.html tailwind.config.cjs postcss.config.cjs` empty. Existing generated CSS at dispatch dist has SHA256 `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`, equal prior QA2 pin; no build run. Current source checkout lacks generated CSS. Existing genuine reconstructed Buffer bundle SHA256 `2f1f8730af8c9fb08068a41c9c18356b9f48f21635fe5deed6d17fc6b9b97be7` matches QA3 checks; different from historical bundle, with upstream cache/source pins and6 API sanity controls retained in QA3 evidence. + +Candidate source locations to inspect against runtime evidence: Upload `components/upload-modal.html` root/modal-sheet/header/body/source-selector; CSS `css/tailwind.source.css` modal rules around1525–1650 including mobile100vw padding, plus modal stack rules around4618. These are inspection locations, not established causes. Prior raw overflowing spans also appeared at desktop without document overflow, so do not infer culprit from that heuristic. + +Retained required baseline: QA2 Upload document362/360 and394/390; desktop1280/1280, critical controls horizontally inside, original strict FAIL remains open. No current runtime width/focus/touch result yet. Preview3000 and peer resources untouched. Await explicit resume/browser grant after storage release; no optional Firefox reruns. + +Restart checkpoint 2026-10-22: user fleet-start received; PROGRAM/RESUME/TODO/.task/mailbox (0 unread), repo memory, known issues and local/current-unstable RULE-CHANGES read. Current unstable `75bc92f3a47c06ead43c3e715ef8793475635518` newest rule explicitly retains QA4 HOLD/sourceprep. QA4 claim renewed; `git diff --quiet e04549f816f6ec61f305e20df0ce2b59797c4651 HEAD -- css components/upload-modal.html` exit0: relevant source unchanged, runtime applicability remains unverified. Preserved both dirty files without merge. Reported resource blocker to dispatch. No new test/build/browser/server/commit/submission; browser grant remains unconsumed; preview3000 untouched. Await explicit browser resume. + +Restart check 2026-10-22T20:40:52.521539+00:00: renewed QA4 claim; mailbox0 unread. HEAD `4de357e4a7489d6a25466cdec9babd8ed1edd513`, unstable `26e9a787cfdc31b77ac35417a5f10e218e2b9662`. Read newest unstable RULE-CHANGES: storage3 full/build, client9 next, QA4 HOLD/sourceprep. Relevant source comparison `git diff --quiet e04549f816f6ec61f305e20df0ce2b59797c4651 HEAD -- css components/upload-modal.html` exit0 (no changes); runtime applicability unverified. Fleet health at20:40:34UTC reports tight: swap52%; memory64.3% available, load3.81/8cores. Preserved dirty claim/preparation, no merge/commit/submission or browser/server/test/build. Await explicit browser allocation; preview3000 untouched. + +## Recovery and vertical/effective target audit plan — 2026-10-24T02:51:26.565965+00:00 + +Own HEAD `4de357e4a7489d6a25466cdec9babd8ed1edd513`; observed unstable `d1d6e0fed1737312129ce4c8a59d129cf3db5b7c`. Read newest unstable rules as well as local rules, latest dispatch/interpreter mail, PROGRAM, RESUME, TODO, repo memory and known issues. QA4 claim renewed. Dirty backlog and both unique notes preserved; no merge/reset/cleanup. Relevant filing applicability command `git diff --exit-code e04549f816f6ec61f305e20df0ce2b59797c4651 HEAD -- css components/upload-modal.html` exit0: no relevant source changes. This does not reproduce the historical failure. Current width3/3 and focus containment/restore3/3 are previous runtime evidence; no new runtime check performed. + +Source trace: `components/upload-modal.html:15` sheet combines flex-column, max-h-modal-shell and overflow-hidden; line45 body combines overflow-y-auto, flex-1 and min-h-0. `css/tailwind.source.css:1559-1598` sheet/mobile rules and1631 body rules must be checked against actual computed cascade, because mobile sheet also declares overflow:auto. Flags at template290-310 nest input and span inside clickable labels. Controller `js/ui/components/UploadModal.js:318-324` binds flags/advanced selectors. Submit at template365 is inside the body/form. These identify measurement locations, not demonstrated defects. + +Bounded measurement sequence for a NEW explicitly granted browser run: + +1. Preserve prior runner/results/screenshots; use a distinct scratch runner and unique result paths. Reuse pinned genuine Buffer/CSS/byte-identical genuine relay guards and prior successful disclaimer acceptance + harness relay setup. Fixed own app31864/local WS31865, no relay seeding, public HTTP/other WS denied. Verify source hashes again before launch; no build/install or blind setup retry. +2. Same actual-app viewports360x800,390x844,1280x720 and real Upload trigger. Record strict document/client/scroll widths initially AND after scrolling/expansion, without tolerance; retain historical362/360,394/390 failures and later passes separately. +3. Before keyboard auto-scroll, record viewport/visualViewport and document/modal/sheet/body scrollTop, clientHeight, scrollHeight, rectangles, computed overflowY/maxHeight/minHeight/flex and clipping ancestors. Position pointer over the modal body and send a bounded wheel delta based on its measured scroll range. Record which ancestor actually scrolls. Do not infer scrolling from offscreen rectangles alone or substitute scrollIntoView for the initial user-scroll check. +4. For each flag, record native input rectangle plus every associated label (`input.labels`), label text rectangles and intersection with viewport and clipping ancestors. Use elementFromPoint at visible label text/center sample points to establish unobstructed ownership. Click one safe visible label text point via native pointer, require exactly one checked-state change, then restore the flag via the same UI. Report effective sampled clickable area separately from input16x16; do not assume the full label bounding rectangle is usable or equate local geometry with an AA audit. +5. Scroll to lower flags, Advanced Options and Publish. For each, record visible clipped rectangle, obstruction at center, enabled state and whether user wheel scrolling made it reachable. Expand Advanced by normal UI, measure webseed/xs fields and submit reach again, then collapse. Do NOT click Publish, file pickers or cloud actions. If any control stays unreachable, preserve geometry/active matched rules and report first demonstrated culprit for independent reproduction/routing. +6. Check keyboard reachability of lower controls without activating submit; record focus-visible, modal containment and focus-trigger restoration on close. Prior12-Tab evidence did not traverse every lower control. No real pinch/zoom/touch or device claim; retain prior horizontal scrollX0/scale1 as its limited measurement. +7. Stop on first setup blocker or demonstrated culprit, record natural exit/counts and cleanup browser/private app/relay only. No browser/full/build/submission until fresh dispatch slot and resource clearance. No CSS/product/existing test/helper/config change or proposed CSS fix without reproduced cause and routing. + +This checkpoint executed source inspection and applicability diff only: browser launches0, servers0, tests0, builds0, submissions0. Existing source note and boot-blocker note remain unlanded. Next dependency: dispatch NEW browser slot after HLS qualification/cooling. Preview3000 and peer resources untouched. + +## Exact source-only runner prepared — 2026-10-24T02:57:28.454244+00:00 + +Dispatch02:56:07 accepts the plan but retains HOLD: HLS released02:55:23, latest dispatch79.8C reading02:55:40 requires two cooling snapshots and NEW grant. No browser/server launched. + +- Exact eventual command: `node /tmp/playback-qa-4-vertical-run.cjs` from own worktree, only after NEW grant/normal host execution approval. Distinct runner SHA256 `8bb9fcff8791b6dd7a19a5f4049a37eac41379c474977cd9355b8e20e60b541d`. `node --check /tmp/playback-qa-4-vertical-run.cjs` exit0. This verifies syntax only, not runtime. +- Original runner preserved at `/tmp/playback-qa-4-run.cjs`, SHA256 `80b2bd6a45b78c7d5e5dc1691ecff7a9b0370b5f6a2ce275064e5f21d42c1973`. New results `/tmp/playback-qa-4-vertical-result.json`; screenshots `/tmp/playback-qa-4-vertical-.png` and `...--bottom.png`, leaving prior artifacts intact. +- New scratch instrumentation records initial container/label/control clipping/scroll/computed geometry; three bounded user wheel actions; associated label text native clicks+restoration for four flags if visible/unobstructed; lower controls; Advanced expanded/bottom/collapsed geometry; forty nonactivating Tab steps and existing close restore. Strict width flags retained at every snapshot. Matched stylesheet rules include controls and labels. No submit/filepicker/cloud action. Direct native pointer ownership checks precede label clicks; unreachable labels remain recorded, not silently counted as passes. +- Rechecked Buffer mirror SHA256 `2f1f8730af8c9fb08068a41c9c18356b9f48f21635fe5deed6d17fc6b9b97be7`; generated CSS `283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3`; own and dispatch genuine relay source `ab11b29937690810900d3a16e01e16571ce68d7f02d50c86656b76f47776cba4`, `cmp` exit0. Runner retains these guards before any launch. +- Genuine Buffer provenance `/tmp/playback-qa-3-assets/provenance.json` records cached upstream buffer6.0.3, base64-js1.5.1, ieee7541.2.1 archive integrity+SHA256. CSS remains read-only storage artifact29cbe6f0/source d9424fb7, relevant sources previously verified identical to own HEAD4de357e4. No download/install/build. Relay imported from dispatch only for genuine ws resolution after pinned byte equality; no peer process action. +- New runtime counts remain0: browser/server/viewport/label/keyboard cases. No new tests or product/CSS/config edits or submission. Historical strict failures and subsequent3/3 width/focus passes retained separately. Execution remains unverified until grant. + +## Dispatch interpretation limits — 2026-10-24T02:58:20.718762+00:00 + +Dispatch02:58:03 reviewed runner8bb9fcff and retains HOLD pending a second cooling snapshot/new explicit grant. First cooling snapshot reported71C/load1.55/RAM62.7% at02:57:40; not a browser grant. + +Interpret eventual scratch fields narrowly: `reachable:false` in label sampling means **not measured visible at the sampled final scroll position**, not proven unreachable. Three large wheel deltas may scroll past a label; preserve and inspect all intermediate snapshots before drawing any conclusion. Do not classify a missing final label as a defect. Advanced `locator.click()` may auto-scroll; only recorded **pre-click** wheel geometry can support userwheel reachability. Post-click visibility proves only locator-assisted/native activation visibility. No runner change or retry required; these limitations govern the report. Original8bb9fcff runner remains pinned. Browser/server/new runtime cases still0. + +## Fleet restart — 2026-10-24T19:45:35Z + +Read mailbox (0 unread), newest RULE-CHANGES, PROGRAM, RESUME, area TODO, .task, known issues and repo memory. Own branch area/playback-qa and unstable both at `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`. QA4 claim renewed; QA3 remains blocked. Filing applicability check `git diff --exit-code e04549f816f6ec61f305e20df0ce2b59797c4651 HEAD -- index.html components/upload-modal.html css/tailwind.source.css` exit0: relevant source unchanged. This is source equality, not a new reproduction. + +Preserved three dirty retention paths; no merge or commit. Saved vertical runner/result SHA256 still match `8bb9fcff8791b6dd7a19a5f4049a37eac41379c474977cd9355b8e20e60b541d` / `c94c2cc1097fa2b3df8c42165b4ca51b2cda818c869f849f0c91db872e4522fb`. Queue check exit0: queue0/lander not running. New browser/server/test/build/submission counts0. Existing parked source/results conclusion stands; awaiting dispatch retention review/routing, all browser grants expired. Historical overflow cause and measured sidebar interception cause remain inconclusive; real-device/AA/CI qualification unverified. diff --git a/docs/testing/playback-qa-4-restart-handoff-2026-10-26.md b/docs/testing/playback-qa-4-restart-handoff-2026-10-26.md new file mode 100644 index 00000000..eccb079f --- /dev/null +++ b/docs/testing/playback-qa-4-restart-handoff-2026-10-26.md @@ -0,0 +1,19 @@ +# QA4 safe restart handoff + +Interpreter mailbox20261026T185756 requests handoff for owner-requested kit update/config approval/restart. This worker does not perform that update or alter config. HOLD all browser/test/build/submission until interpreter update+readiness complete and explicit renewed routing/health guidance. SAFE TO RESTART. + +Printed HEAD `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`. Prior browser slot released; no owned runtime resources left by completed diagnostic. No new process launch, test/build, commit, merge, submission, product/CSS/helper/config edit during handoff. + +Durable root: `/home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/`. + +- Corrected preparation `corrected-prep/run.cjs` SHA256593fc0556257e1943f61e9496d3fcd5b906dee9025ccbb6368f7ea26d78fc5f2. Previously syntax exit0; runtime never executed. `corrected-prep/exact.diff` shows only two changed lines versus original: source-correct disclaimer closed-state predicates after native button click, and exact local WebSocket URL components/root slash. No other assertions/deadlines changed. Read corrected-prep/README.md before any future run. +- Original `run.cjs` SHA256ce95cf5c8ae04db564e38569859a750403ccb8f5b6505c8e19ae8eb3017c8e69 and `result.json` SHA256868e6dd80a0887dce319b5a2a9e5f0be083721541a1f5820d306b8bfe12134a9 rechecked unchanged at handoff. Natural exit1 BOOT_STOPPED at15:25:46.532Z; actualready1, Upload0, wheel0, checkboxclick0/intervention0/A-B0. Failed reconstructed runner mistakenly checked HTML hidden property and denied normalized local WS slash. All context/browser/relay/server resources closed; tracked app sockets0. No causal result. +- Genuine restored archive directory `/home/user/.local/share/agent-fleet/bitvid/verified-fixtures/npm/` contains buffer6.0.3/base64-js1.5.1/ieee7541.2.1/provenance.json. Own `verified-provenance.json` records independent byte+SHA256+SHA512 checks3/3 before extraction. Durable prepare.py and original repo docs/testing/playback-qa-3-buffer-provenance.json supply recipe/pins. +- Genuine Buffer support `buffer.mjs` SHA256ff1c4a2377a9df17eaffa1f5b00da3d835f748f3d832a0490bd309756434f73d,65583bytes; six runtime API sanity controls passed on first attempt. Support copied byte-identically into corrected-prep. Bundle output differs from historic tmp mirror because absolute path comments differ; archive provenance exact. Existing dispatch esbuild0.27.3 used only for this dependency mirror. +- Actual-app serving relies on own checkout; existing dispatch generated CSS hash283d04642d201edba1e9bd5cbc3ede8d74fbd11b50ff0cd2732d463847d2adf3 with source equality guard; genuine relay source hashab11b29937690810900d3a16e01e16571ce68d7f02d50c86656b76f47776cba4; installed dispatch Playwright/ws/esbuild read-only. These are dependencies to register/revalidate after kit update. Never install/download/build/launch merely to test readiness without routing. + +Proposed future ONE command, NOT currently authorized to execute: `node /home/user/.local/state/agent-fleet/bitvid/evidence/playback-qa-4/ab-20261026/corrected-prep/run.cjs` from own worktree. Requires update/readiness, resource routing/new health rules, source/hash guards and START.task. Own app31864/relay31865 only; actual disclaimer/Upload UI, synthetic existing fixture identity, local relay/no seed or publishing; public HTTP/WS denied. A original failed native checkbox click is prerequisite; B removes ONLY app.fade-in at same geometry/points then requires genuine native toggle. Stop at first blocker/no retry/second intervention. Preserve strict width<=viewport and historical362/360+394/390 failures. No implementation before independent causal reproduction/startup listener timing and explicit ownership routing. + +Dirty existing work preserved: docs/agents/backlog/playback-qa-4.json plus six untracked QA4 evidence notes shown by git status before this handoff. This new note is a seventh untracked QA4 note. No stash/reset/merge/commit or worktree cleanup. QA3 remains blocked on CI-equivalent execution evidence. Original overflow cause, causal A/B, startup cleanup timing, real-device/AA and broader playback/live qualification remain unverified. + +Saved UTC: 2026-10-26T18:58:45.907920+00:00 diff --git a/docs/testing/playback-qa-4-source-cause-2026-10-24.md b/docs/testing/playback-qa-4-source-cause-2026-10-24.md new file mode 100644 index 00000000..681cea19 --- /dev/null +++ b/docs/testing/playback-qa-4-source-cause-2026-10-24.md @@ -0,0 +1,53 @@ +# QA4 source-only cause checkpoint + +Current source `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`; saved runtime source `4de357e4a7489d6a25466cdec9babd8ed1edd513`. Interpreter mail20261024T195244 requests source-only continuation, with no browser/server/build/tests and client next heavy slot. Existing three dirty retention paths preserved. + +## Measured evidence reanalysis + +Read-only Python inspection of `/tmp/playback-qa-4-vertical-result.json` exited0. Seven snapshots per viewport reconfirm42 native-input sample hits on sidebar at360,42 at390,0 at1280; associated-label toggle/restoration4/4 per viewport,12/12 total. Inputs are check-comments/check-no-nip71-mirror. These are sampled hit-test interceptions, not measured failed native-input clicks or a proven stacking cause. Existing strict widths21/21 and Tab120/120 are prior results, not newly executed tests. Historical362/360 and394/390 strict failures remain retained. + +## Additional source trace + +- ModalManager.js:64-83 obtains modalContainer and passes the imported appState setter through initUploadModal. initUploadModal.js:21-46 passes it unchanged to UploadModal. UploadModal.open:1507-1535 reveals root, sets upload state and activates focus; no body/sidebar suppression class write exists in that path. +- appState.js:167-182 updates modal state and notifies named/key subscribers. Repository JS search for subscribeToModalState and subscribeToAppState finds their definitions but no consumers; subscribeToAppStateKey consumers in app.js subscribe to pubkey/current user. No source connection from upload state to sidebar suppression was found. This narrows the lifecycle trace; it does not establish actual runtime classes or effective cascade. +- CSS:100-114 sidebar suppression requires body.modal-open or sidebar.is-modal-hidden. Search of JS/HTML found modal-open writes in other controllers and staticModalAccessibility, no is-modal-hidden writer. staticModalAccessibility synchronizes classes only when its own open/close functions execute; Upload uses createModalAccessibility directly, whose activate handles focus/listeners without that synchronization. +- index.html:162-166 app initially has fade-in; modalContainer is its descendant, sidebarContainer a body sibling. CSS:4412-4415 fade-in uses opacity animation with forwards fill; js/index.js:514-553 removes transient fade classes on animationend/animationcancel. Source therefore contains transient animation context, but saved results omit app class/animation state. No claim that animation persisted or caused interception is supported. +- CSS:5576-5604 app width/margin rules do not themselves declare z-index/transform/isolation. Sidebar and modal declarations alone cannot identify an intervening runtime stacking context. No minimal product/CSS patch is justified from these observations. + +## Exact missing evidence and smallest next measurement + +Cause remains INCONCLUSIVE. Saved snapshots omit body/html/sidebar/app class history, computed sidebar/modalContainer and ancestor styles/rectangles, and complete ordered elementsFromPoint at the intercepted points. A native checkbox-box click was not executed. Rule collector selected html/body/modal/sheet/controls/labels and overflow candidates; it did not deliberately include app/modalContainer/sidebar ancestor chains, so matched-rule presence cannot fill those gaps. + +Smallest proposed ONE new bounded run, only after explicit resource grant: reuse pinned genuine offline actual-app preparation and public deny; at360x800 only accept disclaimer/open Upload and wheel once to the same lower state. Before any click capture body/html/sidebar/app classes; sidebar and both ancestor chains with computed position/z-index/opacity/transform/filter/backdrop-filter/isolation/contain/will-change/animation/pointer-events/visibility/display, rectangles and active matched rules; record elementsFromPoint at the three saved relative input points plus label control point. Record class mutations from before Upload open through capture and document.getAnimations timing so transient context is distinguishable. Perform one native click at visible comments input center, recording checked state before/after and event target/composedPath; restore through known safe label UI if changed. Close Upload and capture suppression/stack state again. No style/class mutation experiment, publish, product patch or broad viewport campaign. If this does not reproduce, preserve that result without retries. This run is proposed only, not authorized or started. + +No new runtime/test/build/gate/commit/submission; no outgoing acknowledgement required by interpreter. QA3 CI-equivalent execution, historical overflow cause, real-device/AA and live media remain unverified. + +Recorded UTC: 2026-10-24T19:54:01.260766+00:00 + +## Post-outage measurement preparation + +Interpreter22:03:18 routes sourceprep only; client sole brief Firefox slot. New scratch script `/tmp/playback-qa-4-cause-run.cjs` SHA256 `fd70de6956c2df0dcdc0f08bfe35b38aae90520c2fdb45ba1c7019d3436e7c04`; `node --check` exit0. Syntax/preparation READY, runtime NOT executed. Exact eventual command `node /tmp/playback-qa-4-cause-run.cjs` from own worktree, only after client RELEASE, fresh health and explicit next-slot approval. Runtime guard requires HEAD `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`; drift stops before bind. Relevant source comparison against saved runtime4de357e4 returned no diff for index/template/CSS/index.js/appState/ModalManager/initUploadModal/UploadModal/modalAccessibility. + +Revalidated existing Buffer/CSS hashes2/2 and both genuine relay hashes2/2, matching prior pins. Script preserves actual-app readiness, real disclaimer/Upload UI, local synthetic identity, no relay seeding/submission, private31864/31865 and public HTTP/WS denial. Original runner/results untouched. One360x800 context; one wheel action then same relative input/label sample coordinates. Captures ancestor computed styles/rectangles/active rules, ordered hit stacks, class mutation history from pre-open and active animation timings; one native comments-input center click/event composedPath, conditional safe label restoration, close-state capture. It makes no style/class mutations. Result/screenshot paths unique `/tmp/playback-qa-4-cause-result.json` and `/tmp/playback-qa-4-cause-360.png`. Awaited browser/app/relay finally cleanup retained; stop on first blocker, no retry. + +No browser/server/build/tests/full gate/commit/submission executed. Historical strict failures and prior evidence retained; source cause remains inconclusive until runtime measurement. All earlier browser grants expired; no outgoing ACK sent as instructed. + +Prepared UTC: 2026-10-24T22:04:09.008941+00:00 + +## Approved ONE measurement result — 2026-10-24T22:06Z + +Interpreter grant22:05:39 after client RELEASE22:04:51. Verified exact runner SHAfd70de6956c2df0dcdc0f08bfe35b38aae90520c2fdb45ba1c7019d3436e7c04 and Buffer/CSS/relay pins before execution. Fresh fleet health22:05:50 max66C/load1.86/RAM70.5%/battery53%41min; swap91% advisory. Command `node /tmp/playback-qa-4-cause-run.cjs` received normal host approval; no review rejection. Source `0ffd92a8d2072a19fe61ac6edcf5097a9538fddd`, Chromium145.0.7632.6. START22:06:01.125Z, natural exit0, RELEASE22:06:03.621Z, elapsed2.496s. Diagnostic exit0 means completed capture, NOT a passing input interaction. + +Counts: one browser/context/360x800 viewport; actual readiness/disclaimer/Upload1/1 each; one wheel; one native checkbox-center click; failed intended checked-state change1/1. Click before=true/after=true at requested46.375,592.4375. Actual click event rounded46,592 targets ASIDE#sidebar; composedPath sidebar/sidebarContainer/body/html excludes checkbox/label. No restoration needed because state did not change. Before-wheel/lower/after-click six input samples each hit sidebar first with checkbox second:18/18 interceptions. Label-text sample points hit spans as expected; no label activation repeated in this run. Prior12/12 label toggles remain separate evidence. + +Strict widths360/360 at all four captures, including closed state4/4. Historical362/360 and394/390 failures remain unexplained. Page errors0; blocked public requests14; genuine Buffer route served from hash-pinned mirror, other public HTTP/WS denied. No relay events seeded or notes/media submitted. Own app31864/relay31865/browser closed via awaited finally; resourcesClosed=true. No independent socket inventory performed; preview3000 and peers untouched. Posthealth22:06:10 max63.9C/load1.93/RAM70.2%/battery52%37min, swap91%; no continuous peak-temperature claim. + +### Newly measured ordering and source-associated explanation + +Sidebar left0/right51.1875, fixed, z-index20, pointer-events auto, visible, opacity1/isolation isolate. modalContainer fixed viewport bounds, z-index100/isolation isolate inside app; Upload fixed z-index100/pointer-events auto. These exact declarations coexist with sidebar first in ordered input hit stacks. Both html/body class sidebar-collapsed; sidebar class sidebar-collapsed, no is-modal-hidden. No watched class changes from before Upload open through close. Suppression selectors body.modal-open #sidebar and #sidebar.is-modal-hidden do not match the measured classes. This confirms absent sidebar suppression during this run rather than merely assuming it from Upload source. + +App retains fade-in class with computed animation 0.28s ease-out forwards interface-fade-in. getAnimations records its opacity animation finished/currentTime280/fill forwards/progress1 before wheel, after wheel and after click. Matched generated .fade-in rule confirms opacity0 plus forwards animation; keyframes animate opacity0→1. App ancestors have transform/filter none, opacity1, z-index auto/isolation auto/contain none/will-change auto. This retained opacity-animation context is the source-supported explanation for why the modal descendant does not outrank the sibling sidebar despite its numeric100 vs20. Causal intervention (remove animation/class or apply suppression) was NOT performed. Animation-cleanup failure mechanism/timing remains unverified; do not claim why js/index.js animationend/cancel listener missed cleanup, or generalize to every boot/device. + +Minimal independent review/ownership scope is now narrower: index.html app fade-in/modal nesting; js/index.js:514-553 transient animation cleanup; css/tailwind.source.css:4412-4415/5758 opacity animation plus100-114 sidebar suppression and4602 modal root; UploadModal/appState modal-open integration as previously traced. No token/color/z-index value change or redesign proposed. Dispatch must independently reproduce and assign implementation before product/CSS edits. This measurement does not establish the original horizontal-overflow cause. + +Artifacts: `/tmp/playback-qa-4-cause-result.json` SHA256 ea7a6aa358c488e4937ff3df7c21e24b7cd25488ad81232e21196aa3c255b742; screenshot `/tmp/playback-qa-4-cause-360.png` SHA256 beb7fc91d13223021951a7f13363ad73181e92c40fbbbf81ccda22fa6eaaa383. Original artifacts unchanged. No retry/product/CSS/test/helper/config/build/gate/commit/submission. QA3 CI, historical overflow cause, causal intervention, other viewport parity and real-device/AA remain unverified. diff --git a/docs/testing/release-ci-runner-2026-10-29.md b/docs/testing/release-ci-runner-2026-10-29.md new file mode 100644 index 00000000..161c45bb --- /dev/null +++ b/docs/testing/release-ci-runner-2026-10-29.md @@ -0,0 +1,7 @@ +# Release CI runner compatibility correction + +Candidate a2dbaccc CI run 40896227464 failed all browser-installing jobs before tests: Playwright reports `does not support chromium on ubuntu26.04-x64`. The lint suite, app build, archive and DM checks passed separately. This is not a browser assertion result or a resolution of the earlier Firefox failures. + +Pin only build, visual-regression, e2e-tests and e2e-headless runner definitions to ubuntu-24.04, which is explicitly supported by the installed Playwright registry and native dependencies. Matrix coverage, assertions, retries, timeouts, steps and deploy gating are unchanged. The broader duplicate-build/browser-job simplification remains deferred for timing-based evaluation. + +Validation: exact four-line runner-only workflow delta verified; normal landing gate and hosted corrected-candidate CI results are recorded separately in the release evidence directory. Never treat unsupported-runner failure as a passing test suite. diff --git a/docs/testing/storage-cors-3-integrity.md b/docs/testing/storage-cors-3-integrity.md new file mode 100644 index 00000000..fb0ec08d --- /dev/null +++ b/docs/testing/storage-cors-3-integrity.md @@ -0,0 +1,81 @@ +# storage-cors-3 test integrity + +## Buffer boundary + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-storage3-buffer-newer-orders + given: "strictly newer same-root video and older deletion, in either order or one batch" + when: "actual subscribeVideos buffer ingests and replays the deletion" + then: "newer live version and unrelated content remain active; tombstone timestamp persists" + - id: SCN-storage3-buffer-zombies + given: "two versions and unrelated content" + when: "equal/newer tombstone then late older content then strictly newer content arrive" + then: "older versions stay deleted, unrelated stays active, strictly newer content may return" + - id: SCN-storage3-buffer-author + given: "legacy synthesized roots with equal d-tags from distinct authors" + when: "one author's deletion and late older event arrive" + then: "authoritative client tombstone keys suppress only the intended author's legacy series" + observable_outcomes: + - "real client active/history maps and tombstones after actual subscription transport callbacks" + determinism_controls: + - "synthetic events; explicit awaited EOSE; no sleeps, retries or real transport" + - "fake signature acceptance, metadata hydration and IndexedDB IO only; filtered localStorage remains real" + - "every fixture asserts zero network attempts and closed subscriptions" + anti_cheat_rationale: + prevents: ["unconditional deletion", "no-op deletion", "root-only legacy matching", "over-mocking ingest"] + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Retained `tests/storage-tombstone-buffer.test.mjs`: old buffer baseline 2pass/3fail. The preliminary legacy fixture used empty roots, which real conversion replaces with distinct event IDs; it was corrected to actual synthesized `LEGACY:author:d` roots before recording baseline, preserving strict author-scoping expectations. Fixed buffer 5/5. Existing buffer6/6 and delete-flow4/4 pass naturally. + +Restored mutants: unconditional deletion fails all3 ordering scenarios; omitted authoritative tombstone fails all5 scenarios. Commands/scripts/logs: `/tmp/storage3-buffer-mutants.py`, `/tmp/storage3-unconditional-marker-delete.log`, `/tmp/storage3-omit-authoritative-tombstone.log`, `/tmp/storage3-buffer-baseline.log`, `/tmp/storage3-buffer-focused.log`, `/tmp/storage3-buffer-existing.log`. No client policy changes: `recordTombstone` remains the sole timestamp-safe active deletion authority; the buffer keeps the guarded-event identity cleanup. + +An initial focused command named nonexistent `tests/video-event-buffer.test.mjs` and failed before tests. Corrected to existing `tests/nostr/videoEventBuffer.test.mjs`; no registration/assertion changes. + +No browser/full/build/live signer/cloud/relay qualification. Coordinator stale-render remains open, read-only and outside this change. + +## Service snapshot/persistence boundary + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-storage3-service-prune + given: "multiple cached versions and unrelated content" + when: "different-ID deletion marker and late older version arrive through actual subscription" + then: "service map, callback, emitter and persisted snapshot contain only unrelated content" + - id: SCN-storage3-service-empty + given: "single live version or an all-ineligible persisted cache" + when: "deletion empties feed or empty active cache loads" + then: "empty persistence replaces stale cache and replacement consumer sees no resurrected entry" + - id: SCN-storage3-service-persistent + given: "deleted and invalid persisted objects for public or author viewer" + when: "optimistic load filters the cache" + then: "only live content is notified, cached and persisted before author bypass" + - id: SCN-storage3-service-known-tombstone + given: "stale nondeleted persisted copy covered by authoritative client tombstone" + when: "reload or direct cache API encounters stale copy" + then: "stale copy stays absent while newer valid and unrelated entries survive" + - id: SCN-storage3-service-author + given: "legacy synthesized roots sharing d-tag across distinct authors" + when: "one author deletion then newer live content and replayed older deletion arrive" + then: "unrelated author and strictly newer content remain in every service snapshot" + observable_outcomes: + - "actual service map, callback, videos:updated and filtered localStorage snapshots" + determinism_controls: + - "same actual subscription/client fixture as buffer cases, explicit EOSE and zero real network attempts" + anti_cheat_rationale: + prevents: ["ID-only removal", "flag-only filtering", "author bypass of deleted state", "empty-persistence skip", "overbroad author cleanup", "over-mocking ingest"] + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +New service tests: old service baseline 0pass/8fail; fixed8/8. Eight restored mutants all fail: old-service8; allow-deleted4; allow-invalid4; ignore-known-tombstone2; skip-map-prune5; skip-empty-persist1; skip-direct-cache-guard1; skip-optimistic-persist2. Every retained scenario rejects the old service and at least one selective mutant. Mutator `/tmp/storage3-service-mutants.py` restores product source in finally; logs `/tmp/storage3-service-mutant-results.log` and uniquely named `/tmp/storage3-.log`. + +Existing live-feed regression exposed that an update may carry the original event ID and a separate deleted object rather than a shared client object. Preserve explicit deletion-ID removal alongside authoritative client tombstone cleanup; the regression was not modified. No new root/address matching implementation: service calls client `isOlderThanTombstone`, including its existing legacy/author key derivation. Existing access/privacy/NSFW/consumer/subscription behavior is covered by unchanged focused suites. diff --git a/docs/testing/storage-cors-3-investigation-2026-10-22.md b/docs/testing/storage-cors-3-investigation-2026-10-22.md new file mode 100644 index 00000000..1b765584 --- /dev/null +++ b/docs/testing/storage-cors-3-investigation-2026-10-22.md @@ -0,0 +1,42 @@ +# storage-cors-3: offline tombstone investigation + +- Baseline SHA: `c5b678f7c9d0b92fbf786b16ae831f1be1d0b899` (own clean branch fast-forwarded from `6609269e`). Item filed at `7f99c0456931c917082d6a6680c79655221dcc82`; current paths still exhibit the gap. +- Start: 2026-10-22T14:08:34Z. First reproduction completed by 14:11:03Z. No browser/gate wait; one Git metadata sandbox denial resolved by approved ff-only escalation. +- Routing: dispatch message 20261022T140749; current campaign/rules permit investigation and unique evidence only. No product or retained test changes. +- Reproducer: `/tmp/bitvid-storage-cors-3-audit.mjs`. +- Command: `env -u NODE_TEST_CONTEXT node /tmp/bitvid-storage-cors-3-audit.mjs`. +- Result: natural exit 1; 7 scenarios, 3 pass, 4 fail, 0 cancelled/skipped. Every fixture asserts zero attempted fetch/WebSocket calls. + +## Real execution and virtualized boundaries + +Real `NostrClient.subscribeVideos` creates the real `VideoEventBuffer`; a fake subscription-manager transport supplies synthetic kind-30078 events and explicit EOSE. Real conversion, root timestamps, tombstones, active/history maps, `NostrService.loadVideos`, filtering, notifications, service map, and filtered localStorage persistence execute. Synthetic verification accepts fixture IDs; unrelated NIP-71 metadata hydration and client IndexedDB scheduling are stubbed IO boundaries. No signer, relay, cloud, storage delete, browser, build or full suite runs. + +The stale-refresh scenario also executes actual feed engine, active source and coordinator `refreshFeed`. A controlled deferred decorator holds one completed source snapshot while a second refresh finishes. `renderVideoList` is captured at the presentation boundary; actual DOM/card rendering and the entire registered production stage pipeline are not exercised. + +## Passing controls + +1. Live original at t100 and unrelated root at t110 populate active state and service notifications. +2. Tombstone t200 followed by late nondeleted t90 leaves target absent from active map and actual active feed source; older stored event is marked deleted; unrelated remains. +3. Tombstone t200 followed by live same-root t300 allows the newer content under existing timestamp semantics. + +## Strict failures + +1. **Service snapshot/persistence:** original t100 followed by tombstone t200 correctly removes original from client active map. Service removes only the tombstone event ID, leaving original ID in service map. Callback and persisted filtered list contain `original` with `deleted:true` plus unrelated. Expected only unrelated. +2. **Reload/view consumer replacement:** another `loadVideos` emits `persistent-cache` containing original(deleted:true), then emits correct active cache containing unrelated only. Expected no deleted target in either notification. `shouldIncludeVideo` does not reject deleted entries. +3. **Newer live content loss:** live t300 then late tombstone t200 leaves newer history object `deleted:false`, but removes it from active map. `recordTombstone` correctly preserves newer state; `VideoEventBuffer` subsequently unconditionally deletes the active key for a deletion marker. Expected newer and unrelated remain; actual unrelated only. +4. **Stale refresh:** older run captures original/unrelated; delete arrives; newer refresh renders unrelated; release older run and it renders original(deleted:true)/unrelated last. Expected unrelated only. Coordinator applies async run results without checking current refresh/view ownership or deletion state. + +`VideoListView.render` source inspection at lines353–410 shows filtering for NSFW/private, with no deleted guard. This is source evidence, not browser/DOM qualification. + +## Proposed routing, not implementation + +- `js/nostr/videoEventBuffer.js`: preserve live active entries newer than a deletion marker, keeping existing timestamp guard semantics. +- `js/services/nostrService.js`: prevent deleted/tombstone-covered entries in public feed snapshots and optimistic persistence notifications, prune affected stale service entries rather than relying on matching tombstone event IDs. Preserve unrelated and strictly newer live content. +- `js/app/feedCoordinator.js`: investigate smallest refresh/view ownership and final eligibility guard before rendering stale results; do not assume a same-feed request counter alone covers delayed cross-view work. +- New unique retained integration regression after routing, with actual ingest/service and coordinator controls; old implementation must fail. No existing assertions changed. + +Dispatch received reproduction/script/counts and proposed scopes at14:11:18Z. Await independent reproduction and precise ownership routing before product/test edits. Full registered pipeline, DOM/browser view switching, equal-timestamp edge cases, legacy d-tag fallback, real signature verification, NIP-09 kind5 ingest, restart persistence/IndexedDB, public relay/cloud behavior remain unverified. These findings concern existing kind30078 soft-delete markers; they do not establish additional deletion policy. + +## Invocation limitation retained + +An initial `env -u NODE_TEST_CONTEXT node --test /tmp/bitvid-storage-cors-3-audit.mjs` returned only a child-file failure (exit1, no scenario output). The direct Node invocation above exposes all seven scenarios naturally. No runner/gate configuration changed and the opaque wrapper result is not counted as product evidence. diff --git a/docs/testing/storage-cors-3-registered-view-investigation-2026-10-22.md b/docs/testing/storage-cors-3-registered-view-investigation-2026-10-22.md new file mode 100644 index 00000000..c75ee0ef --- /dev/null +++ b/docs/testing/storage-cors-3-registered-view-investigation-2026-10-22.md @@ -0,0 +1,38 @@ +# storage-cors-3 coordinator follow-on investigation + +- Preserved candidate SHA: `89f8923c6e83b79a2b8874f811cdff2af6a809d0` (buffer/service commits already applied). +- Authorized by dispatch14:25:40Z: offline registered coordinator/feed/view/disposal investigation only. Coordinator, app, client and retained tests unchanged. No browser/full/build/queue slot used. +- Reproducer `/tmp/bitvid-storage-cors-3-registered-view-audit.mjs`; raw output `/tmp/storage3-registered-view-audit.log`. +- Command `env -u NODE_TEST_CONTEXT node /tmp/bitvid-storage-cors-3-registered-view-audit.mjs`: natural exit1;4 scenarios,2 pass,2 fail,0 cancelled/skipped;182.585705ms test duration. Zero attempted global fetch/WebSocket calls asserted per fixture. This is JSDOM/offline proof, not native browser or complete application startup proof. +- Completed and reported to dispatch by2026-10-22T14:29:48Z. + +## Execution fidelity + +Uses retained actual-subscription fixture from the buffer/service regressions: real client conversion/buffer/tombstone state and real service load/notifications/persistence. Synthetic signatures and fake subscription transport; metadata/IndexedDB IO virtualized as previously documented. + +Creates real feed engine and invokes actual coordinator `registerRecentFeed` and `registerExploreFeed`. Source, blacklist, dedupe, moderation, posted-at and Explore scoring stages, and actual sorters execute. Uses real playback coordinator timestamp helpers and real timestamp utilities. Calls actual `loadFeedVideos`, `refreshFeed`, `renderVideoList`, `VideoListView.render` and `VideoCard` against a JSDOM document; asserts actual `[data-component="video-card"]` DOM IDs. + +The controlled asynchronous IO boundary is the moderation adapter's `setActiveEventIds` method, which the registered moderation stage already awaits. Its first call containing `original` waits on an explicit deferred promise; subsequent calls resolve immediately. Moderator policy is no-reports/default, with other moderator methods absent; view-count subscriptions are disabled at the view adapter. App runtime builders provide empty/default runtimes; creator/moderation decorators are identity adapters, impression/similar-content/health-warning effects disabled. No custom engine stage/decorator or fake feed engine is inserted. This establishes registered pipeline and real list/card behavior under a controlled adapter latency, not real moderation relay timing or all navigation code. + +The view replacement scenario calls real `VideoListView.destroy`, assigns a new real view, then calls actual `loadFeedVideos(EXPLORE)` which mounts it and changes actual feed telemetry ownership. It does not invoke the full application navigation/router or auth teardown entrypoint. + +## Results + +1. PASS: valid delayed Recent pipeline resolves and renders original+unrelated cards. Pending valid work is not categorically discarded. +2. FAIL: Recent pipeline holds before deletion; actual deletion ingestion triggers current subscription refresh and DOM becomes unrelated-only. Releasing older Recent work restores original(deleted:true)+unrelated DOM cards. Expected unrelated-only throughout after current deletion refresh. +3. FAIL: during the same overlap, destroy old view and mount replacement through actual Explore loader. Explore is active and its DOM contains unrelated-only. Releasing previous Recent work renders original(deleted:true)+unrelated into the replacement view; Explore metadata is overwritten by old Recent metadata. Expected unrelated-only and Explore ownership/metadata preserved. +4. PASS: destroy the view and set `videoListView=null`, then release pending pipeline. Actual coordinator does not remount a fully absent view. No claim of a full-app destroy lifecycle test. + +The earlier buffer/service fixes do not prevent a source snapshot already captured by an asynchronous registered pipeline from passing deleted objects to the presentation boundary after later deletion. Service current active/cache snapshots remain corrected; coordinator lifecycle/late-result application is a distinct boundary. + +## Fixture development failures retained + +- Initial JSDOM run lacked the global `HTMLElement` constructor required by actual `VideoCard`:4 errors, no product conclusion. Added scoped constructor from this JSDOM instance, restored in teardown. +- Next attempt deferred timestamp-history IO, but all four scenarios cancelled waiting for an unreachable signal. Measured real `getKnownVideoPostedAt` returns converted `nip71Source.created_at`; no history request occurs. Debug script `/tmp/storage3-registered-view-debug.mjs` records this. Replaced the unreachable wait with the actual awaited moderation subscription adapter boundary; no timestamp fields were deleted or corrupted to force a path. +- Final four scenarios above exit naturally with strict failures and passing controls. No sleeps, retries, force exits, timeouts, assertion relaxation or product changes. + +## Proposed next scope (not authorized implementation) + +Coordinator should validate freshness/view ownership before applying async results, and validate latest deletion eligibility before passing results or fallback snapshots to a view. New retained regression should exercise the actual registered path, include live/current/error fallback/disposal and cross-view controls, and reject the original coordinator. A request counter alone must not authorize older work to populate a replacement view or discard unrelated current feed work. Exact ownership and semantics require dispatch routing before any coordinator/app changes. + +Native browser/full-app router/manual view switching, real moderation/signature/relay timing and complete auth disposal remain unverified. Existing buffer/service candidate remains the first READY gate; its full/build/normal submission slot is still pending. diff --git a/docs/testing/storage-cors-4-integrity.md b/docs/testing/storage-cors-4-integrity.md new file mode 100644 index 00000000..00e48d5b --- /dev/null +++ b/docs/testing/storage-cors-4-integrity.md @@ -0,0 +1,38 @@ +# storage-cors-4 registered lifecycle integrity + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-storage4-registered-completion + given: "Real registered Recent/Explore/For You pipelines and real JSDOM VideoListView/Card, with controlled moderation subscription IO" + when: "Older success or rejection resolves after a newer refresh, deletion, empty result, feed replacement, or disposal" + then: "Only eligible current request changes DOM, metadata, telemetry and presentation effects; stale caller still settles" + - id: SCN-storage4-current-controls + given: "A current registered pipeline or fallback and a live view" + when: "History or moderation IO is delayed, engine fails, or engine is unavailable" + then: "Live cards/fallback and metadata remain observable with natural completion" + - id: SCN-storage4-obsolete-load + given: "Retained onVideos callback or history preload belongs to replaced load" + when: "Same-feed replacement or different-feed load wins" + then: "Old callback starts no refresh; obsolete runtime starts no engine or presentation effects" + observable_outcomes: + - "Card DOM IDs, view container ownership, payloads, metadata identity, render/impression counts and telemetry calls" + determinism_controls: + - "Explicit deferred IO; synthetic offline events, fake subscription/storage adapters; no sleeps/retries/timeouts" + anti_cheat_rationale: + prevents: + - "hard-coded empty output via delayed/current/fallback controls" + - "decorator-only pipeline replacement via actual registered production stages" + - "success-only guards via delayed rejected engine with telemetry assertions" + - "per-feed counters ignoring view replacement via same-feed remount controls" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +16 retained scenarios in tests/storage-feed-lifecycle.test.mjs. Auto-discovered by unchanged normal runner; no registration bypass. Uses existing actual client/buffer/service fixture, synthetic signatures and controlled adapters. Source, filters, moderation, timestamps, scoring/sorting and actual DOM rendering execute. First moderation setActiveEventIds containing original pauses; no custom decorator stage. Engine error tests wrap real run and reject after its production stages return. History preload exercises actual For You await. + +Nine isolated runtime mutants all exit1 naturally, cancelled0; restored in finally. Original coordinator fails12/16; ignoring request order fails4; ignoring view eligibility fails2; ignoring stale errors fails2; ignoring stale runtime fails1; suppressing current rendering fails9; obsolete load callbacks enabled fails1; allowing orphan completion fails11; dropping fallback fails3. Each retained scenario rejects original or a relevant broken control variant. No assertion relaxation or existing-test edit. + +Live network/signatures/moderation timing, full application router/auth teardown, native browser and manual release not verified. No new source/cache/deletion policy. Return choice and pre-engine supersession are documented in decisions/DECISIONS_2026-10-22_20-45-00_storage-cors-4.md. diff --git a/docs/testing/storage-cors-delete-audit.md b/docs/testing/storage-cors-delete-audit.md new file mode 100644 index 00000000..8d81b954 --- /dev/null +++ b/docs/testing/storage-cors-delete-audit.md @@ -0,0 +1,21 @@ +# storage-cors-2 current delete-safety audit + +Started2026-10-20T14:59:46UTC after dispatch priority correction. HEADd88716e3c6351468a53bc7fdb84fe912c01697b5. Product/existing tests read-only. Assignment metadata queued behind QA normal gate; dispatch authorizes read-only audit before claim visibility. + +Plan: inspect actual service/caller/normalization and existing scenarios; drive real service with fake SDK/transport and synthetic persistence records; establish owned/external/locked/partial controls; report demonstrated loss/smallest fix scope before writes. + +`env -u NODE_TEST_CONTEXT NODE_ENV=test node /tmp/storage-cors-delete-audit.mjs`: exit1, six passing controls and two contract failures (expected0 rejected calls, actual2), networkAttempts0. Real R2Service/deleteVideoStorage/resolveConnection, actual StorageService.listConnections/isUnlocked, actual SDK wrapper/key collector/deleteObject; remote SDK classes replaced locally, S3Client.send fake, persistence `_getAccount` returns synthetic account record. No real account/credentials/cloud/delete operations. Fixture strings are invented, example.invalid URLs. + +Controls: owned media+(.torrent)+thumbnail3 requests/deleted3; external host+base-prefix sibling0 clients/deletes; locked-owned skip storage-locked; locked-external skip no-matching-objects; partial transport failure2success/1failed/3requests/no rejection; valid cached locked connection resolves storage-locked. + +Failure: synthetic cached R2 metadata retains numeric bucket17 or object publicBaseUrl through real resolveConnection mapping. deleteVideoStorage then calls `.trim()` outside catches and rejects TypeError in2/2 malformed cases,0clients/0deleteIO. SDK was successfully loaded in the same process; success control proves the transport is usable. This is a never-throw contract violation, not a demonstrated blocked note deletion: app.handleDeleteModalConfirm and editModalController already catch cleanup exceptions. The malformed locked connection can fail before returning a storage-locked summary/warning. + +Proposed route: defensively validate primitive string fields in R2Service.deleteVideoStorage and return structured skip without interpreting malformed object metadata as permission/bucket names; add focused real-wrapper transport regression + integrity/mutants. s3UploadService has no deleteVideoStorage implementation; mirrored upload/key code remains unchanged. Await dispatch scope/semantics before product or committed test edits. + +Existing evidence scope: tests/services/r2-delete-video-storage.test.mjs covers five service scenarios with overridden client/delete I/O; tests/delete-storage-locked.test.mjs covers three locked/generic controls. Neither covers malformed persistent metadata. Existing helpers implement owner-base prefix exclusion. Full delete caller passes only targetVideo, so historical all-version object enumeration remains followup. Current edit caller DOES compare changed nonempty thumbnails and queues old thumbnail cleanup; dated TODO “thumbnail cleanup skipped” is stale for this path. No new retention/deletion semantics proposed. + +Deferred feed3: /tmp/storage-cors-feed-audit.mjs preserved; attempted real client/buffer/service/engine/coordinator/view scratch failed its initial visible-seed control0/2; no deletion finding or full feed proof. No product/test edit made from that invalid fixture. + +No browser/build/full gate used. No tool/gate wait blocking read-only work; actual cloud/provider IAM/serialization and real malformed user records unverified. No production data or signer used. + +Routed15:06:18UTC after independent dispatch confirmation. Implemented bounded metadata guard and13 retained real-service scenarios; original method fails9/13;7 restored mutants rejected. Focused4files/24scenarios,lint11/build pass. See storage-cors-delete-integrity.md. Awaiting full/submit slot requested15:08:52UTC. Current HEAD/baseac99ae9a;service unchanged from filedAt before fix,claim2 now visible/in-progress. No upload-copy deletion counterpart exists; no unrelated copy edits. diff --git a/docs/testing/storage-cors-delete-integrity.md b/docs/testing/storage-cors-delete-integrity.md new file mode 100644 index 00000000..9cba3fa1 --- /dev/null +++ b/docs/testing/storage-cors-delete-integrity.md @@ -0,0 +1,49 @@ +# storage-cors-2 best-effort delete metadata guard + +Dispatch independently reproduced2 rejected calls vs0 required with6 controls, then routed15:06:18UTC. Only deleteVideoStorage in r2Service.js changes. Input shape/owner identifiers and string connection fields are validated before trimming or SDK client construction. Nonstring metadata returns invalid-connection; nonstring owners return invalid-owner; absent/null/non-object options return existing no-videos skip. Primitive string trimming/default behavior remains. No coercion of arbitrary objects. + +Actual affected counterpart check: `s3UploadService.js` has NO deleteVideoStorage counterpart. Shared provider-aware R2Service delete already chooses makeR2Client versus makeS3Client. Both real wrapper branches are exercised with transport fakes; forcePathStyle/endpoint/region preserved. No duplicated upload/key/magnet logic changes in either service. + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-storage-delete-malformed-cache + given: "synthetic persisted locked R2 metadata with numeric bucket or object base URL" + when: "actual StorageService listing and R2Service connection resolution feed deleteVideoStorage" + then: "structured invalid-connection skip, no client construction or object requests" + - id: SCN-storage-delete-direct-types + given: "nonstring direct credentials fields/owners or null/non-object options" + when: "real cleanup method is invoked" + then: "safe structured skip with zero IO and no object trimming/coercion" + - id: SCN-storage-delete-provider-safety + given: "valid R2 or generic-S3 connection with owned/external/prefix-sibling URLs, locked storage or partial delete failure" + when: "actual cleanup/key collector/SDK wrapper/deleteObject run" + then: "only owned media/torrent/thumbnail delete; provider settings retained; lock reasons correct; partial errors recorded and later objects continue" + observable_outcomes: + - "exact summaries, zero SDK constructions/requests for unsafe input, exact deletion keys/bucket/config and per-object failure" + determinism_controls: + - "synthetic _getAccount persistence IO; real connection resolution/mapping" + - "local remote-SDK replacement/recording transport; actual key collector and deleteObject; no real credentials/network" + - "no retries/sleeps/timeouts in retained regression" + anti_cheat_rationale: + prevents: + - "constant skip/no-op" + - "over-mocked service logic" + - "unsafe object coercion and cross-base deletion" + - "hidden partial failures or lost provider settings" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Atac99ae9a6bdbeae555a30f659b0720ae67e777e8,13 new scenarios pass. Original method fails9/13 (each malformed cached field, each of5direct fields, owner types, null options). Seven restored mutants rejected: remove settings guard(scenarios1–7); remove owner guard(8); null destructure(9); force wrong provider(10); bypass owner key collector(10–13); bypass lock guard(12); rethrow partial error(13). Every scenario is killed. No existing assertions changed. + +Focused existing+new runner4/4 files,24scenarios; lint11/11 and build/deploy-artifact exit0. Product scope29-line diff in one method. Pre-existing note-deletion callers catch cleanup exceptions,so this does not claim note deletion was blocked. Raw cloud/IAM/SDK serialization,actual malformed user records and arbitrary throwing JS proxies/getters are unverified. No real bucket/delete/signer/private-data/preview action. Feed3 remains deferred with invalid seed fixture preserved; no feed finding. Full/normal landing awaits dispatch slot. + +Timing: storage-audit acknowledgement14:59:46UTC; evidence15:01:20UTC; fix route15:06:18UTC (298s routing wait); ready15:08:52UTC. Metadata claim followed verified unstable merge; no metadata wait blocked read-only investigation. + +Merged validation at020f0577ee5fb212a689bb06260f10abd47e4868:full430/430 files exit0 (NODE_TEST_CONTEXT unset,UNIT_TEST_TIMEOUT_MS60000),focused4files/24scenarios,lint11/build-artifact pass. Product7b8100a6 committed before required merge. Slot request15:08:52→grant15:09:29UTC37s;full confirmed15:15:59UTC. Normal queue pending. No product edits after mutant proof. + +## Durable completion +Submission1650b3439fee7d419b68a0badcee67cee0cfa9aa durably landed2026-10-20T15:22:48.611Z ase7b9afb309d822fca9fdc1cc31e79ce7c9c33fbe. Unchanged normal install/npm-test gate exit0. Both submission and landedSHA ancestry on unstable exit0. Newer dispatch docs tip1b3ef49c was already on unstable by final log read;product remains ancestor,not necessarily latest tip. Backlog closed only after durable result and ancestry. Resource release notification actual15:23:08UTC;normal queue wait/gate15:16:23.181→15:22:48.611(~385s). Final notes only;then idle per owner local-review milestone. No extra optional feed item starts;deferred feed3 remains queued for future routing. diff --git a/docs/testing/storage-cors-integrity.md b/docs/testing/storage-cors-integrity.md new file mode 100644 index 00000000..521ae41e --- /dev/null +++ b/docs/testing/storage-cors-integrity.md @@ -0,0 +1,59 @@ +# storage-cors-1 investigation + +Start UTC 2026-10-20T13:38:00Z. Investigation checkpoint 13:39:21Z. +HEAD bbc1be79e941add929c9c51f984778676822b5f2; filedAt0c1dd7e helper/client unchanged. + +Actual caller behavior: R2Service.getCorsOrigins returns current window origin (plus HTTPS localhost counterpart). ensureBucketConfigForNpub and verifyPublicAccess use this list; generic prepareS3Connection takes supplied origins, and its getCorsOrigins likewise returns current-site origins. r2-s3 reexports the helper. Thus existing policies are not passed through callers. + +Offline command `env -u NODE_TEST_CONTEXT node /tmp/storage-cors-reproduce.mjs` exit0, 5 characterization scenarios. Real s3-client and s3-multipart imported; registerHooks replaces only remote SDK with input-holding command classes; fake S3 send implements full-policy replacement on PutBucketCors. Unexpected HTTP imports throw. Fixtures use example.invalid and no credentials. + +- Existing GET-only rule: origin first.example.invalid, header Range, ETag exposure, maxAge75, ID existing-read-only. Request second.example.invalid. Result retains0/1 prior rules; origins only second.example.invalid; helper default methods/headers replace prior fields. +- Repeat same request: policy identical, PUT total2 (no skip). +- No existing CORS config: PUT1/GET0, default policy installed. +- AccessDenied on read: PUT1/GET0; configured read error cannot surface because helper never reads. +- Empty origins: zero transport calls, policy untouched. + +Proposed semantics sent dispatch: preserve all existing rules/fields verbatim; add default rule for new origins without widening arbitrary existing rules; skip PUT when exact equivalent default rules already satisfy all requested origins; only explicit NoSuchCORSConfiguration permits absent-policy creation; other read failures reject without PUT. Wildcards/unknown fields preserved; no speculative permission merging. Scope needs dispatch routing before implementation. + +No regression test changes yet, so no Test Integrity Note/mutants claimed. No browser/full-suite/lint/build/cloud/live-policy/concurrency qualification. PR overlap check unavailable (curl DNS exit6). Preview3000 untouched. Tool/browser/gate contention before reproduction0s; routing wait starts13:39:21Z. + +## Routed implementation and integrity (2026-10-20T13:43Z) + +Dispatch routed bounded product/test fix at13:40:50UTC after independent failure. Routing wait89s. Existing rules remain verbatim/in order; only explicit origins in rules with identical default permission/header/exposure/maxAge fields count as covered. ID is preserved metadata; unknown fields disqualify coverage. Wildcards remain unchanged and are not expanded/interpreted. Append current defaults for uncovered deduplicated origins. Only NoSuchCORSConfiguration permits creation after failed read; denied/404/transport failures propagate without PUT. Existing method defaults unchanged, no SDK upgrade. + +```yaml +test_integrity_note: + change_type: ["new_tests"] + scenarios: + - id: SCN-storage-cors-preservation + given: "existing custom or wildcard rules, or equivalent default coverage" + when: "real ensureBucketCors adds/repeats requested origins through actual SDK wrapper" + then: "all original rules and fields persist; only uncovered explicit origins get default rule; repeats issue no PUT" + - id: SCN-storage-cors-read-safety + given: "absent configuration, denied read, non-CORS 404, transport failure, or empty origins" + when: "real helper configures CORS" + then: "only explicit NoSuchCORSConfiguration creates policy; other read errors propagate identically with zero PUT; empty origins zero IO" + observable_outcomes: + - "full persisted CORS policy, unchanged rule fields/order, exact write count, original error identity" + determinism_controls: + - "actual helper and SDK wrapper; fake remote SDK command classes and S3 transport; no network, credentials, clock, retries, sleeps" + anti_cheat_rationale: + prevents: + - "hard-coded success/no-op" + - "over-mocking internal helper logic" + - "policy overwrite or arbitrary permission widening" + - "snapshot rubber-stamping and retry/sleep masking" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + +Nine regression scenarios pass. Original helper fails8/9 with exit1; empty-origin control passes. Ten deliberately broken variants exit1 and were restored: overwrite existing rules; reverse rules; omit dedup; repeat PUT; include covered origins; reject absent policy; swallow read errors; empty-origins IO; ignore non-origin permissions (kills scenario5); remove SDK GetBucketCors export. Every scenario fails under at least one variant. Final focused runner passes3/3 files (13 scenarios total:9 CORS,3 cache-control,1 empty upload). + +`npm ci --offline --ignore-scripts`: exit0,441 installed packages in4s from local cache. Initial lint exit127 absent stylelint; after install lint exit2 sandbox EPERM read-only git subprocess. Unchanged lint with sandbox permission exits0,11 checks. `npm run build`: exit0 incl dist artifact verification. No dependency file changes. Normal full-suite/landing slot reserved after client; awaiting explicit grant. Cloud IAM permissions, actual provider response serialization, concurrent read-modify-write races, browser/live playback remain unverified. Read-modify-write has no provider CAS guarantee and concurrent writers can still race. + +Final merged gate atbfa36a3f350d6a9751a1a43c83fc890c855091fc: full normal runner428/428 files exit0 with UNIT_TEST_TIMEOUT_MS=60000/NODE_TEST_CONTEXT unset; focused3/3 files(13 scenarios),lint11 checks,build/artifact pass. Resource wait949s before13:58:27UTC grant; full completed14:03:49UTC. No product changes since restored mutants. Normal landing pending; no real-cloud/concurrent-writer claim. + +## Durable completion + +Submission699d72927bee46bdafb6a7775a12d05f75e3c7e1 landed2026-10-20T14:09:21.582Z asd3f4c2155fe38d1b73554a6163f732b4c0ba3308. Durable landq result: landed, unchanged normal install+`npm test` gate exit0. `git log -1 unstable` matched landed SHA; `git merge-base --is-ancestor 699d72927bee46bdafb6a7775a12d05f75e3c7e1 unstable` exit0. Backlog closed only after these checks. Slot release14:09:38UTC (dispatch notification14:09:47UTC). No product edits after checked tree. Actual cloud/provider permissions/serialization/concurrent writers remain unverified; no-CAS race limitation remains. No browser/cloud/preview changes. diff --git a/docs/testing/storage-cors-range-audit-2026-10-23.md b/docs/testing/storage-cors-range-audit-2026-10-23.md new file mode 100644 index 00000000..e47d00b0 --- /dev/null +++ b/docs/testing/storage-cors-range-audit-2026-10-23.md @@ -0,0 +1,12 @@ +# Hosted URL health Range fallback audit — 2026-10-23 + +Inspected clean storage-cors HEAD 265ce4261f7c42f653ce0ac6a49da0eea16f0f66. Source and retained tests show the public playback promise for a bounded Range GET fallback is not implemented in the hosted health probe. + +The card-health path reaches UrlHealthController.handleUrlHealthBadge via feedCoordinator and probes candidate URLs in probeUrlList; playback calls the same controller through playbackCoordinator.probeUrl (js/app.js:5302-5305, js/app/playbackCoordinator.js:384-401,685, js/app/feedCoordinator.js:1319-1323, js/ui/urlHealthController.js:471-503). The controller sends fetch(trimmed, { method: "HEAD", mode: "no-cors", cache: "no-store", signal }) (urlHealthController.js:592-603). On network error, timeout, opaque response, or non-OK status such as 405, confirmPlayable falls back to a timed