Updated CLAUDE.md to include missing scripts (audit, test:load, telemetry:aggregate) that were present in package.json but undocumented. Created completion log. Co-authored-by: PR0M3TH3AN <[email protected]>
21 KiB
CLAUDE.md — AI Assistant Guide for bitvid
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.
Project Overview
bitvid is a decentralized video sharing platform built on the Nostr protocol. It operates as a static client — no backend server, no custody of user keys, no server-side signing. All authentication and state management happens client-side or via connected Nostr signers.
Key Technologies
- Nostr: Decentralized identity and event protocol (NIP-07, NIP-33, NIP-44, NIP-46, NIP-51, NIP-71, etc.)
- WebTorrent: P2P video streaming fallback
- Tailwind CSS: Utility-first styling with custom design tokens
- Playwright: E2E and visual regression testing
- Node.js 22+: Development environment
License
GPL-3.0-or-later (see LICENSE)
Codebase Structure
bitvid/
├── js/ # Main application JavaScript
│ ├── app.js # Main orchestrator (bitvidApp)
│ ├── nostr/ # Nostr protocol implementation
│ │ ├── client.js # Core Nostr client with signer adapters
│ │ ├── nip71.js # NIP-71 video note parsing
│ │ ├── sessionActor.js # Anonymous session actors for telemetry
│ │ └── adapters/ # NIP-07, NIP-46, nsec signer adapters
│ ├── services/ # Business logic services
│ │ └── playbackService.js # Video playback orchestration
│ ├── ui/ # UI components and controllers
│ ├── state/ # State management (cache.js)
│ ├── utils/ # Utility functions
│ ├── constants.js # Feature flags and WSS trackers
│ ├── config.js # Re-exports from instance config
│ ├── magnetUtils.js # Magnet URI helpers
│ ├── nostrEventSchemas.js # Event schema definitions (source of truth)
│ └── nostrClientFacade.js # Main Nostr client entry point
├── config/
│ └── instance-config.js # Deployment configuration
├── css/
│ ├── tokens.css # Design token definitions
│ ├── tailwind.source.css # Tailwind input file
│ └── tailwind.generated.css # Generated (gitignored)
├── tests/ # Test suites
│ ├── unit/ # Unit tests
│ ├── e2e/ # End-to-end tests
│ ├── visual/ # Visual regression tests
│ ├── nostr/ # Nostr-specific tests
│ └── moderation/ # Moderation feature tests
├── docs/ # Documentation
│ ├── nostr-event-schemas.md # Event schema reference
│ ├── playback-fallback.md # Playback architecture
│ ├── moderation/ # Moderation system docs
│ └── nips/ # NIP reference documentation
├── views/ # HTML view templates
├── components/ # HTML component templates
├── content/ # Markdown content (docs, roadmap)
├── torrent/ # WebTorrent integration
├── scripts/ # Build and utility scripts
└── .github/workflows/ # CI/CD configuration
Essential Files to Know
| File | Purpose |
|---|---|
AGENTS.md |
AI agent guide with architectural rules and mission |
KNOWN_ISSUES.md |
Pre-existing test failures and environmental quirks |
CONTRIBUTING.md |
Developer setup and contribution guidelines |
js/app.js |
Main application orchestrator (bitvidApp) |
js/nostr/client.js |
Core Nostr client with signer registry |
js/nostrEventSchemas.js |
Source of truth for all Nostr event schemas |
js/constants.js |
Feature flags and runtime configuration |
config/instance-config.js |
Deployment-specific settings |
js/testHarness.js |
Playwright test harness (window.__bitvidTest__) |
tests/e2e/helpers/bitvidTestFixture.ts |
Reusable Playwright fixture for agent testing |
scripts/agent/simple-relay.mjs |
Mock Nostr relay with HTTP seeding API |
docs/nostr-event-schemas.md |
Event schema documentation |
context/ |
Agent working state: current goal, scope, assumptions (see AGENTS.md §15) |
todo/ |
Agent task checklist with done/blocked sections (see AGENTS.md §15) |
decisions/ |
Agent decision log: choices, alternatives, rationale (see AGENTS.md §15) |
test_logs/ |
Agent verification log: commands run and results (see AGENTS.md §15) |
Development Workflow
Initial Setup
# Clone and install
git clone https://github.com/PR0M3TH3AN/bitvid.git
cd bitvid
npm ci # Use ci for exact lockfile deps
# Build (generates Tailwind CSS)
npm run build
# Start local server
python -m http.server 8000
# or
npx serve
Before Every PR
# Required checks
npm run format # Format CSS/HTML/MD
npm run lint # All lint checks
npm run test:unit # Or use shards for faster feedback
# Recommended for UI changes
npm run test:visual
Available Scripts
| Script | Purpose |
|---|---|
npm run build |
Full build (Tailwind + dist + verify) |
npm run build:css |
Regenerate Tailwind only |
npm run format |
Format with Prettier |
npm run lint |
Run all linting checks |
npm run lint:css |
CSS token validation |
npm run lint:inline-styles |
Block inline styles |
npm run lint:tokens |
Design token enforcement |
npm run lint:file-size |
Enforce file size limits |
npm run lint:innerhtml |
Enforce innerHTML baseline |
npm run audit |
Run design system audit |
npm run build:beacon |
Build torrent beacon assets |
npm run test:unit |
Full unit test suite |
npm run test:unit:shard1 |
Shard 1/3 (faster local dev) |
npm run test:unit:shard2 |
Shard 2/3 |
npm run test:unit:shard3 |
Shard 3/3 |
npm run test:smoke |
Run critical path smoke tests |
npm run test:dm:unit |
Direct message unit tests |
npm run test:dm:integration |
DM integration tests |
npm run test:e2e |
Headless E2E tests |
npm run test:load |
Run load tests |
npm run test:visual |
Visual regression tests |
npm run test:visual:update |
Update visual regression baselines |
npm run telemetry:aggregate |
Aggregate telemetry logs |
node scripts/agent/validate-events.mjs |
Validate Nostr event schemas |
CI/CD Pipeline
The CI workflow (.github/workflows/ci.yml) runs on every push to main and all PRs:
| Job | Timeout | Description |
|---|---|---|
build |
45 min | Lint, build, visual regression |
unit-tests |
30 min | Sharded unit tests (3 shards) |
dm-unit-tests |
20 min | Direct message unit tests |
dm-integration-tests |
30 min | DM integration tests |
e2e-headless |
45 min | Playwright E2E tests |
Concurrency: cancel-in-progress: true — new commits cancel running jobs.
Code Conventions
Logging
Always use the logger utility, never call console.* directly:
import { logger } from './utils/logger.js';
logger.user.error('User-facing error'); // Production visible
logger.dev.log('Debug info'); // Dev mode only (IS_DEV_MODE)
Styling Rules
- Token-first: Use design tokens from
css/tokens.css, never raw hex colors - No inline styles:
style=attributes andelement.styleare blocked by lint - Semantic tokens: Use
bg,text,accent,border,danger, etc. - Theme scopes: Toggle themes via
data-themeattribute, not code
Nostr Event Schemas
All Nostr events must be defined in js/nostrEventSchemas.js:
import { buildVideoPostEvent, NOTE_TYPES } from './nostrEventSchemas.js';
const event = buildVideoPostEvent({
pubkey: hexPubkey,
created_at: Math.floor(Date.now() / 1000),
dTagValue: 'video-id',
content: {
version: 3,
title: 'My Video',
videoRootId: 'video-id',
url: 'https://example.com/video.mp4'
}
});
Magnet Handling
Critical rules — magnets are fragile:
import { safeDecodeMagnet, normalizeAndAugmentMagnet } from './magnetUtils.js';
// Always decode first
const raw = safeDecodeMagnet(userInput);
// Then normalize with hints
const enriched = normalizeAndAugmentMagnet(raw, { wsHint, xsHint });
// NEVER use new URL() on magnets — it corrupts hashes!
- WSS trackers only (from
js/constants.js) - Persist raw magnet strings, decode only at playback
- Include HTTPS
ws=/xs=hints when available
Architecture Pattern
Separation of concerns:
bitvidApp(js/app.js) — Orchestrator, manages app-wide state- UI Controllers — Handle specific UI components (e.g.,
ProfileModalController) - Services — Business logic (e.g.,
playbackService.js) - State — Central truth in
js/state/cache.js
Data flow:
- User action triggers controller event handler
- Controller invokes callback provided by
bitvidApp bitvidAppexecutes logic, updates central statebitvidAppnotifies controller to re-render
Feature Flags
Feature flags in js/constants.js gate experimental behavior:
| Flag | Default | Purpose |
|---|---|---|
URL_FIRST_ENABLED |
Depends on config | Try URL before magnet |
FEATURE_WATCH_HISTORY_V2 |
true | Watch history system |
FEATURE_PUBLISH_NIP71 |
false | NIP-71 video publishing |
FEATURE_SEARCH_FILTERS |
Dev mode only | Advanced search |
FEATURE_TRUST_SEEDS |
true | Baseline trust seeds |
FEATURE_TRUSTED_HIDE_CONTROLS |
true | Trusted mute/spam hide controls |
FEATURE_IMPROVED_COMMENT_FETCHING |
true | Improved comment fetching logic |
Toggle at runtime:
import { setUrlFirstEnabled } from './constants.js';
setUrlFirstEnabled(false); // Disable URL-first playback
Nostr Integration
Key NIPs Used
| NIP | Purpose |
|---|---|
| NIP-07 | Browser extension signing |
| NIP-33 | Parameterized replaceable events (videos) |
| NIP-04 | Legacy DM encryption |
| NIP-44 | Modern encryption |
| NIP-46 | Remote signing |
| NIP-51 | Lists (mute, admin) |
| NIP-56 | Moderation reports |
| NIP-71 | Video events |
Signer Adapters
import { nostrClient } from './nostrClientFacade.js';
import { setActiveSigner } from './nostr/client.js';
// Register a signer
setActiveSigner({
pubkey,
signEvent: (event) => signer.sign(event),
nip04Encrypt: (target, text) => signer.nip04.encrypt(target, text),
nip04Decrypt: (actor, cipher) => signer.nip04.decrypt(actor, cipher),
// Optional: nip44Encrypt, nip44Decrypt
});
NIP-33 Addressing (Important!)
Video events are addressed by kind + pubkey + d-tag:
// CORRECT: Use the d-tag for addressing
const dTag = event.tags.find(t => t[0] === 'd')?.[1];
// WRONG: Don't use logical IDs like videoRootId for relay lookups
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:
- Identify the scenario/spec that defines correct behavior.
- Explain why the previous expectation was incorrect.
- Replace it with an equally strict (or stricter) behavioral assertion.
- 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):
test_integrity_note:
change_type: ["new_tests" | "refactor_tests" | "spec_correction" | "flake_fix"]
scenarios:
- id: SCN-<slug>
given: "<preconditions>"
when: "<stimulus>"
then: "<observable outcomes>"
observable_outcomes:
- "<boundary/output asserted>"
determinism_controls:
- "<fixed seed / fake clock / hermetic env / service virtualization>"
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.)
Testing Guidelines
Before Starting Work
- Read
KNOWN_ISSUES.mdfor pre-existing failures - Run relevant test shard to verify baseline
Running Tests
# Fast feedback during development
npm run test:unit:shard1
# Full suite before PR
npm run test:unit
# Visual changes
npm run test:visual
# DM features
npm run test:dm:unit && npm run test:dm:integration
# E2E journeys
npm run test:e2e
Test Artifacts
Failed visual tests store artifacts in artifacts/test-results/:
- Screenshots
- Videos
- Traces
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:
import { test, expect } from "./helpers/bitvidTestFixture";
test("my test", async ({ page, gotoApp, loginAs, seedEvent }) => {
await seedEvent({ title: "Test Video", url: "https://example.com/v.mp4" });
await gotoApp();
await loginAs(page);
// interact with the app using data-testid selectors
await expect(page.locator('[data-testid="upload-button"]')).toBeVisible();
});
Key capabilities:
- Programmatic login:
loginAs(page)useswindow.__bitvidTest__.loginWithNsec()— no modal interaction needed - Mock relay: Each test gets an isolated in-memory relay with HTTP seeding
- Relay override:
?__test__=1&__testRelays__=ws://localhost:8877redirects all connections - State inspection:
window.__bitvidTest__.getAppState()returns login status, relays, etc. - Stable selectors: All key elements have
data-testidattributes (see AGENTS.md Section 14)
Common Tasks
Adding a New Nostr Event Type
- Define schema in
js/nostrEventSchemas.js - Add builder function
- Document in
docs/nostr-event-schemas.md - Add tests
Creating UI Components
- Create controller in
js/ui/ - Wire callbacks through
bitvidApp - Read state via provided getters, never directly
- Use design tokens for styling
Modifying Playback Behavior
- Check
js/services/playbackService.jsfor stream handling - Update
js/constants.jsfeature flags if needed - Test with both URL and magnet sources
- Document rollback steps in PR
Troubleshooting
Build Issues
# Browserslist warning
npx update-browserslist-db@latest
# Tailwind not regenerating
rm css/tailwind.generated.css && npm run build:css
Test Hangs
- Unit tests may hang after completion — use
Ctrl+C - Use shards for faster local runs
- Set
UNIT_TEST_TIMEOUT_MS=120000for slow tests
Lint Failures
- Inline styles: Move to CSS classes or tokens
- Hex colors: Use design tokens
- Design tokens: Use
theme()or existing utilities
Release Channels & Promotion Pipeline
Code flows through three branches with increasing stability:
unstable → beta → main
(dev) (soak) (prod)
| Branch | Purpose | CI Required | Manual QA |
|---|---|---|---|
unstable |
Active development, AI agent PRs land here | Yes | No |
beta |
Stabilization soak (hosted for testing) | Yes | Yes — hosted domain |
main |
Production | Yes | Verified via beta |
Promotion rules:
unstable → beta: After a batch of improvements passes CI and local testingbeta → main: After weeks of soak time on the beta hosted domain with no regressions- Never push directly to
mainorbeta— always promote from the previous stage
Emergency Response
If a change breaks playback or magnet handling:
- Revert immediately
- Note rollback in PR
- Document in AGENTS.md
Multi-Agent Development Workflow
This project uses multiple AI coding agents working in parallel:
- Claude Code — Refactoring, convention enforcement, codebase-wide changes
- OpenAI Codex — Isolated feature implementation with clear specs
- Google Jules — Issue triage and straightforward bug fixes
Coordination Rules
All agents must follow the subsystem boundaries and PR discipline rules in AGENTS.md Section 12. The key principles:
- Check before you start. Look at open PRs before beginning work by running:
If another agent has an open PR touching the same files, stop and flag the conflict.
curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '{count: length, titles: [.[].title]}' - One subsystem per PR. Don't mix unrelated changes. A lint fix and a feature addition are two separate PRs.
js/app.jsis single-writer. Only one PR at a time should modify the main orchestrator.- Merge fast, branch short. Long-lived branches cause exponential merge pain with multiple agents. Keep PRs small and mergeable in one sitting.
For Human Maintainers
When assigning work to agents:
- Update the "Currently In-Flight Work" section in
AGENTS.mdto reserve subsystems - Avoid sending two agents at the same subsystem simultaneously
- Review and merge agent PRs promptly to keep the queue short — stale PRs compound conflicts
- Use PR title prefixes (
[nostr-core],[ui],[playback], etc.) to make scope visible
Key Documentation
| Document | What It Covers |
|---|---|
AGENTS.md |
AI agent rules, mission, architectural decisions |
CONTRIBUTING.md |
Setup, PR process, DCO |
KNOWN_ISSUES.md |
Pre-existing test failures |
docs/nostr-event-schemas.md |
Event definitions |
docs/playback-fallback.md |
URL-first strategy |
docs/moderation/README.md |
Moderation system |
docs/logging.md |
Logger usage |
docs/agents/TORCH.md |
TORCH distributed task locking protocol |
context/ / todo/ / decisions/ / test_logs/ |
Agent persistent state files (see AGENTS.md §15) |
Quick Reference
Import Patterns
// Nostr client
import { nostrClient } from './nostrClientFacade.js';
// View events (analytics)
import { recordVideoView } from './nostrViewEventsFacade.js';
// Watch history
import { updateWatchHistoryListWithDefaultClient } from './nostrWatchHistoryFacade.js';
// Event building
import { buildVideoPostEvent } from './nostrEventSchemas.js';
// Feature flags
import { URL_FIRST_ENABLED, setUrlFirstEnabled } from './constants.js';
// Logger
import { logger } from './utils/logger.js';
Content Schema v3
{
version: 3,
title: string, // Required
url?: string, // HTTPS video URL
magnet?: string, // WebTorrent magnet
thumbnail?: string,
description?: string,
mode: 'live' | 'dev',
isPrivate: boolean,
deleted: boolean,
videoRootId: string // Stable identifier
}
Validation: Must have title + at least one of url or magnet.
Final Notes
- Read AGENTS.md first — it contains mission-critical architectural decisions and the Agent Execution Protocol (§15)
- Check KNOWN_ISSUES.md — avoid investigating pre-existing failures
- Use sharded tests — faster feedback during development
- Never commit generated CSS —
css/tailwind.generated.cssis gitignored - Token-first styling — no raw colors, no inline styles
- Keep magnets raw — decode only at playback time
- Document rollback steps — especially for playback changes