aboutsummaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md55
1 files changed, 54 insertions, 1 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 1ea823f..4f39e36 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -28,7 +28,9 @@ meshbay/
├── packages/
│ ├── meshbay-common/ # Shared crypto + protocol — python3-meshbay-common RPM
│ ├── meshbay-hub/ # Hub server (FastAPI + PostgreSQL) — meshbay-hub RPM
-│ └── meshbay-node/ # Node daemon + local UI — meshbay-node RPM
+│ ├── meshbay-node/ # Node daemon + local UI — meshbay-node RPM
+│ ├── meshbay-client/ # Desktop client (Electron)
+│ └── meshbay-android/ # Android client (WebView shell, Kotlin) — README.md
├── poc/ # POC spike scripts (reference, not production)
├── docs/ # Architecture drafts and POC plans
├── packaging/ # RPM spec files, DEB control files, systemd units
@@ -882,6 +884,43 @@ do. Read them before writing anything that touches the same mechanism.
message later by `update_groups`, which assigned its list verbatim. A limit
enforced on one path is not enforced
+- **Chromium freezes a hidden page sixty seconds after hiding it**, in an
+ Android WebView as in a tab, and nothing about the *process* changes that: a
+ media-playback foreground service, a partial wake lock, a Wi-Fi lock and
+ `setRendererPriorityPolicy` all failed to keep a casting page alive with the
+ screen off (spike, measured by a heartbeat and the page's own `freeze`
+ event). What works is the shell telling the WebView its window is still
+ visible (`ShellWebView.keepVisible`) — set while a cast runs and only then.
+ Audible audio also exempts a page; a phone humming in the room is not a fix
+
+- **A first chunk is not a header.** The relay took the page's first decrypted
+ chunk as the stream's header. On a real film that chunk was by turns 64 KB
+ (header plus film), 28 bytes (the ftyp alone, the moov in the next push), or
+ the header exactly — depending on when the node read ffmpeg's output — so the
+ same film cast on the third try and not the first. The header is everything
+ before the first moof; boxes, not chunks, are the unit
+
+- **A seek's first segments came during its own landing and were thrown away.**
+ `reinitAt` waits on the SourceBuffer; the new stream's first segments arrived
+ in that gap and the `awaitingInit` flag dropped them as the old film's. The
+ local player never showed it — its SourceBuffer kept the header from the
+ start of the film — and a cast relay restarted at that landing received a
+ stream with no header. Ordering, not the flag, says which stream a segment
+ belongs to: everything after `stream_init` is held and replayed
+
+- **A drop threshold sized for small chunks drops whole segments.** The relay
+ dropped a fragment once 8 MB waited for a receiver; at a film's bitrate one
+ fragment is 5–10 MB, so a receiver simply reading at playback speed lost
+ fragments and the picture froze for each. Nothing logged it until a drop
+ counter was added. The lead now waits in a spool file
+
+- **When a receiver stops answering, prove which leg failed before reading code.**
+ A first cast failed every time and two code fixes changed nothing; the cast
+ framework's own log said `onSocketConnectionFailed … IO Error` to the
+ receiver's port 8009, and the phone could not ping the receiver while this
+ machine could. The receiver was half-crashed; a power cycle fixed it. A
+ machine that reaches the receiver proves nothing about the phone
+
**Corrections that used to live here** — `punch_nat()` is not a traversal stack,
the node keystore's Argon2id parameters, what group chat actually uses, and what
is sealed on the wire — are now design statements in `docs/MESHBAY_DESIGN.md`
@@ -989,6 +1028,20 @@ here are kept only where they are a rule about *editing* the code.
| Node page | `node-page.js` | §6.7 |
| i18n | `i18n.js`, `locales/*.js` | `en.js` is the source; **ten catalogues, and a new key goes in all ten** |
+### Android (`packages/meshbay-android/`)
+
+Built with `./gradlew assembleDebug` / `assembleRelease` (JDK 17+, an Android
+SDK); `./gradlew testDebugUnitTest` runs the JVM tests, which pytest also runs
+when `ANDROID_HOME` is set (`test_android_shell.py`).
+
+| Need | File | Note |
+|---|---|---|
+| The shell, the asset loader, the CSP | `MainActivity.kt`, `shell/UiAssets.kt` | the CSP is `main.js`'s, held equal by `test_android_shell.py` |
+| The bridge | `assets/bridge/meshbay-bridge.js` (the preload's counterpart), `bridge/Bridge.kt`, `Channels.kt`, `KeyChannels.kt` | **absent, not refusing**, for what a phone lacks; every channel the shim names is the preload's |
+| Keys | `keys/Kdf.kt`, `Keyring.kt`, `Transcripts.kt`, `SecretStore.kt`, `DeviceKey.kt` | **held to `meshbay-hub/tests/vectors/keyring.json`**; regenerate it with `gen_keyring_vectors.js` only for a deliberate format change |
+| Downloads | `save/SaveSinks.kt`, `SaveNames.kt` | `OPENABLE` is `downloads.js`'s, compared by `test_android_downloads.py` |
+| Casting | `cast/CastRelay.kt`, `CastControl.kt`, `CastChannels.kt`, `CastService.kt`, `shell/ShellWebView.kt` | `MeshBayCast` in logcat says what the receiver and the relay did |
+
### Test harnesses that drive the real thing
| Need | File |