Fix Netlify build by restoring dist build script and config

- Restore `scripts/build-dist.mjs` to clean `dist/`, build CSS, and copy assets.
- Update `package.json` to use the new build script and serve `dist`.
- Update `netlify.toml` to publish the `dist` directory.
- Update `dist/` artifacts to match current source.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-01 13:30:49 +00:00
1 parent a432d3d4c0
commit c862cbc358
72 files changed
+1555 -5883

No files matched your search

+2 -18
View File
@@ -28,30 +28,14 @@ an operator might change it.
debug affordances. The value is re-exported as `isDevMode` in
`js/config.js` and controls `window.__BITVID_DEV_MODE__` as well as
logger routing.
- **Why change it:** Defaults to `false` for production builds. Set to `true`
for local development, or use the runtime override described below to
toggle dev mode without editing this file.
- **Why change it:** Set to `false` for production builds so only the stable
UX and user-facing logging remain.
- **`IS_VERBOSE_DEV_MODE`**
- **What it does:** Enables high-volume diagnostics inside dev mode (for
example, repeated COUNT failures).
- **Why change it:** Keep `true` while debugging noisy workflows and set to
`false` when you want a quieter dev console without disabling dev mode.
Defaults to `false` for production.
To override the dev flags at runtime (for example in a staging build) without
changing `config/instance-config.js`, inject one or both of the following
globals **before** `js/config.js` loads:
```html
<script>
window.__BITVID_DEV_MODE_OVERRIDE__ = true;
window.__BITVID_VERBOSE_DEV_MODE_OVERRIDE__ = true;
</script>
```
The overrides accept boolean values or the strings `"true"` / `"false"` so they
can be injected at build time.
- **`IS_LOCKDOWN_MODE`**
- **What it does:** Toggles a lockdown posture that limits access and switches
+3 -16
View File
@@ -59,23 +59,10 @@ The source of truth lives in `config/instance-config.js`:
- Set `IS_DEV_MODE = true` while developing locally so the dev logger and other
diagnostics are available.
- Keep `IS_DEV_MODE = false` for production builds. This value propagates to
`isDevMode` (the module export), `window.__BITVID_DEV_MODE__`, and
`logger.dev`, silencing development-only messages.
- Flip `IS_DEV_MODE = false` before shipping a production build. This change
propagates to `isDevMode` (the module export), `window.__BITVID_DEV_MODE__`,
and `logger.dev`, silencing development-only messages.
Always commit the flag change alongside deployment-ready configuration tweaks.
Forgetting to update the flag leaves verbose traces in user consoles and can
expose experimental behavior intended only for QA environments.
If you need to enable dev logging without editing `config/instance-config.js`,
inject a runtime override before `js/config.js` loads:
```html
<script>
window.__BITVID_DEV_MODE_OVERRIDE__ = true;
window.__BITVID_VERBOSE_DEV_MODE_OVERRIDE__ = true;
</script>
```
Overrides accept boolean values or the strings `"true"` / `"false"` so they can
be injected by a build pipeline.
+70 -54
View File
@@ -1,76 +1,92 @@
# NostrClient Architecture Overview
# NostrClient Overview
The `NostrClient` (`js/nostr/client.js`) is the central controller for all Nostr network interactions in the application. It acts as a singleton facade over the `nostr-tools` library, managing relay connections, event subscriptions, state synchronization, and cryptographic signing.
`NostrClient` (`js/nostr/client.js`) is the central controller for all Nostr network interactions, state management, and persistence in the application. It acts as the bridge between the raw Nostr protocol (relays, events) and the application's UI domain model (Videos, Profiles).
## Core Responsibilities
1. **Connection Management**: Maintains a persistent connection pool (`SimplePool`) to multiple relays.
2. **State Management**: Tracks the global state of video events, including history, active versions, and deletions.
3. **Event Ingestion**: Handles high-volume event streams with buffering and deduplication.
4. **Caching**: Implements a persistent IndexedDB cache to support "Stale-While-Revalidate" loading (instant UI render).
5. **Signer Orchestration**: Manages NIP-07 (Browser Extension), NIP-46 (Remote Signer), and Local (nsec) signers seamlessly.
1. **Connection Management**: Maintains a pool of WebSocket connections to relays via `nostr-tools/SimplePool`.
2. **Event Processing**: Subscribes to event streams, buffers incoming bursts, validates schemas, and converts raw events into domain objects.
3. **State Management**:
* **`allEvents`**: A complete history of every video version fetched.
* **`activeMap`**: A materialized view of the *latest* valid version of each video series (deduplicated by Root ID).
* **`tombstones`**: A set of deletion timestamps to prevent "zombie" events (deleted items reappearing from old caches) from being displayed.
4. **Persistence**: Implements a "Stale-While-Revalidate" strategy using IndexedDB (with LocalStorage fallback) to restore the UI instantly on load.
5. **Signer Orchestration**: Manages NIP-07 (Browser Extension), NIP-46 (Remote/Bunker), and Local (nsec) signers.
## State Management
## State Management Architecture
The client maintains three primary state maps to ensure data consistency:
The client maintains three primary maps to handle the "Eventual Consistency" nature of Nostr:
* **`allEvents`** (`Map<string, VideoObject>`):
* Stores *every* valid video event fetched from relays, indexed by Event ID.
* Used for history preservation and resolving direct links to specific versions.
* **`allEvents` (`Map<EventID, Video>`)**:
* Stores every valid video event received.
* Used for history hydration (viewing edit logs) and debugging.
* **`activeMap` (`Map<RootID, Video>`)**:
* Stores only the *most recent* version of a video.
* Keyed by `videoRootId` (or `pubkey:dTag` for legacy).
* **Invariant**: `activeMap.get(id).created_at` is always >= any other version of that ID.
* **`tombstones` (`Map<RootID, Timestamp>`)**:
* Stores the timestamp of the latest deletion event for a video.
* **Invariant**: Any event with `created_at <= tombstone` is implicitly treated as deleted, even if the "deleted" flag isn't set on the event itself. This handles out-of-order delivery where a "delete" arrives before the "video" it deletes.
* **`activeMap`** (`Map<string, VideoObject>`):
* The "materialized view" used by the UI.
* Keys are `videoRootId` (prefixed with `ROOT:`) for versioned edits, or `pubkey:dTag` for addressable events.
* Values are the *latest* valid version of the video.
* **Invariant**: Only one entry exists per "video entity", ensuring the UI displays the most recent edit.
## Caching Strategy
* **`tombstones`** (`Map<string, timestamp>`):
* Tracks deletions (Kind 5).
* Keys are the `activeKey` of the deleted item.
* Values are the timestamp of the deletion.
* **Invariant**: Events older than the tombstone are strictly ignored, preventing "zombie" events from reappearing.
The client uses a multi-layered approach to ensure fast boot times:
## Data Ingestion Flow
1. **IndexedDB (`events` store)**:
* Primary cache for video events.
* Stores fingerprints of objects to avoid redundant writes.
2. **LocalStorage (`bitvid:eventsCache:v1`)**:
* Fallback if IndexedDB is unavailable.
* Size-limited (typically < 5MB).
3. **Buffering**:
* Incoming events are buffered in memory and flushed to IDB periodically (debounced) to avoid I/O thrashing during heavy relay traffic.
The `subscribeVideos` method implements a buffered ingestion pipeline to handle high-throughput streams without freezing the UI:
## Event Processing (Buffering & Debouncing)
1. **Subscription**: Opens a subscription to all connected relays.
2. **Buffering**: Incoming events are pushed to an `eventBuffer` array immediately.
3. **Debouncing**: A timer flushes the buffer every ~75ms (or when full).
4. **Processing (Flush)**:
* Events are parsed and validated.
* **Tombstone Check**: If an event predates a known deletion, it is discarded.
* **Latest-Wins Resolution**: The new event's `created_at` is compared against the existing entry in `activeMap`.
* **Update**: If the new event is newer, `activeMap` is updated, and the UI callback is triggered.
5. **Persistence**: The updated state is asynchronously persisted to IndexedDB.
When `subscribeVideos` is called, the client expects a flood of events (e.g., 500+ items from relay history). Processing these one-by-one would freeze the UI.
### Flow
```mermaid
graph TD
Relay[Relays] -->|Event Stream| Buffer[Event Buffer]
Buffer -->|Debounce Timer| Processor[Processor]
Processor -->|Check| Tombstones{Is Deleted?}
Tombstones -->|Yes| Discard[Discard]
Tombstones -->|No| Compare{Is Newer?}
Compare -->|No| Discard
Compare -->|Yes| ActiveMap[Update ActiveMap]
ActiveMap --> UI[Update UI]
ActiveMap --> IDB[(IndexedDB Cache)]
sequenceDiagram
participant Relay
participant ClientBuffer
participant Processor
participant UI
Relay->>ClientBuffer: Event 1
Relay->>ClientBuffer: Event 2
Relay->>ClientBuffer: ... Event 50
ClientBuffer->>ClientBuffer: Buffer (Debounce 75ms)
loop Flush Timer
ClientBuffer->>Processor: Flush Batch (50 events)
Processor->>Processor: Validate & Convert
Processor->>Processor: Apply Tombstones
Processor->>Processor: Update activeMap (Last-Write-Wins)
Processor->>UI: Callback(onVideo) (Single Render)
Processor->>IndexedDB: Persist Snapshot
end
```
## Caching Strategy (`EventsCacheStore`)
## History Reconstruction
To provide an "app-like" experience, the client persists its state to IndexedDB (`bitvid-events-cache`).
Videos in Nostr are immutable events. "Editing" means publishing a new event with the same identifier. `NostrClient` reconstructs the history chain using:
* **Fingerprinting**: To minimize I/O, the store tracks JSON fingerprints (hashes) of all persisted events.
* **Incremental Persistence**: Only changed events (where fingerprint differs) are written to IDB during a snapshot.
* **Stale-While-Revalidate**: On startup (`init()`), the client immediately loads data from IDB to render the UI, then connects to relays to fetch updates.
1. **`videoRootId` (V3)**: The canonical identifier for a video series.
2. **`d` Tag (NIP-33)**: The addressable identifier.
3. **Legacy IDs**: Fallback to Event ID for V1/V2 posts.
## Signer Integration
`hydrateVideoHistory` fetches all events matching these identifiers, sorts them by `created_at` descending, and returns the full edit log.
The client abstracts the signing mechanism, allowing the user to switch between methods without changing application logic:
## Why it works this way
1. **NIP-07 (Extension)**: Delegates signing to a browser extension (e.g., Alby, nos2x).
2. **NIP-46 (Remote/Bunker)**: Uses a partial `NostrClient` instance to communicate with a remote signer via a relay.
3. **Local (NIP-01)**: Uses a local private key (nsec) stored in `sessionStorage` (encrypted).
* **Eventual Consistency**: The `tombstones` map is essential because relays guarantee neither order nor completeness. A client might receive a "delete" event for a video it hasn't seen yet, or receive an old version of a video after receiving the deletion. Tombstones ensure that once something is known to be deleted, it stays deleted (until a newer re-creation event appears).
* **Performance**: The `eventBuffer` mechanism allows the client to ingest thousands of events without blocking the main thread, as React rendering is expensive. Batching updates reduces render cycles from O(N) to O(1) per batch.
* **Offline-First**: Restoring from IndexedDB immediately allows the app to be interactive before any network connection is established.
The `activeSigner` object is normalized to expose a standard interface (`signEvent`, `nip04Encrypt`, `nip44Encrypt`), regardless of the underlying implementation.
## When to change
* **Storage Backend**: If the cache schema becomes too complex for IndexedDB, or if we move to a native wrapper (React Native/Tauri) with a SQL backend, the `EventsCacheStore` class should be swapped out.
* **Event Loop**: If we switch to a worker-based architecture (off-main-thread processing), the `subscribeVideos` logic would move to a Web Worker, and `NostrClient` would become a message passer.
* **Protocol Upgrades**: If NIP-33 (Parameterized Replaceable Events) is superseded or the video event kind (30078) changes, the filters in `subscribeVideos` and `fetchVideos` must be updated.
+3 -4
View File
@@ -341,12 +341,11 @@ valid events:
```bash
# Run the validation script
node scripts/agent/validate-events.mjs
node scripts/agent/validate-events.test.mjs
# Run the unit test suite
npm run test:unit
```
When modifying schemas or builders, use `scripts/agent/validate-events.mjs` to
verify your changes locally. This tool also scans the codebase to ensure all
active event builders are covered by tests.
When modifying schemas or builders, use `scripts/agent/validate-events.test.mjs` to
verify your changes locally.