mirror of
https://github.com/block/buzz.git
synced 2026-11-14 05:12:27 +00:00
codex/buzz-271-draft-prep
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dafe31bfe5 | feat(mobile): persist frontier-only NIP-RS read state across devices and restarts (#8998) | ||
|
|
770ab7ff82 |
perf(mobile): decode relay query responses off the UI isolate (#8669)
### Context Profiling on an iPhone 12 mini (Profile build, real community) showed `RelaySessionNotifier._postForEvents` producing contiguous main-isolate busy runs with p90 110 ms and max 124 ms (27 runs of 50 ms or longer). Stacks were dominated by `jsonDecode` of `response.body`, with `NostrEvent.fromJson` smaller. The whole HTTP response decode ran on the UI isolate: gzip inflate (done by `dart:io`), the UTF-8 decode behind `response.body`, JSON parsing, and event parsing. ### What changes - Successful `/query` and `/profiles/search` responses (the only two callers of `_postForEvents`) are now decoded in a single `Isolate.run` from `response.bodyBytes`: inflate, UTF-8, JSON, validation, `NostrEvent.fromJson`. The parsed events come back by ownership transfer (`Isolate.exit`), not a copy. - Gzip: the query transport now sends `Accept-Encoding: gzip` and sets `autoUncompress = false`, so compressed bodies reach the isolate still compressed. Whether bytes are gzip is decided by the gzip magic bytes, so a transport that already inflated (or an injected test client) still works. Error bodies are inflated inline (small). - Unchanged: `FormatException` messages, the malformed-event wrapping, `RelayException.body`, and the rate-limit gate reading the error body. - Fencing (Review-Proven Rule 2): the `_disposed` / `_ageRestricted` / `_contextGeneration` check still runs before the decode and now runs again after it. If the session retires during the decode, the query reports `StateError` even when the decode itself failed, matching the old ordering where a retired session never decoded. ### Design notes - `Isolate.run` per response instead of a long-lived worker: no lifecycle or disposal to manage, no idle isolate heap on a small phone, easy to fake in tests, and the same shape as `parseProfileEventBatch`. The main-isolate side of a spawn measured at microseconds, so there is no size threshold; small responses take the same path. - Event id and signature verification does not happen on these events on the main isolate today (nothing in the mobile app verifies queried events; NIP-OA verification in profile parsing is already in an isolate), so nothing was moved. - Other HTTP decode sites found and left alone: invite create/join, hosted join policy, BuilderLab account, media upload response, community icon lookup, dev push lease NIP-11, archived-agents. These decode small, rare responses (one small JSON object each), where an isolate adds complexity without a measurable gain. Media and avatar byte downloads do no JSON decoding. The mobile app has no `/count` caller. ### Validation Added tests (`relay_session_test.dart`, through the production `queryRelay` / `searchProfiles` path with the real isolate decoder): parsing with non-ASCII content; profile search; gzip success body and `Accept-Encoding`; gzip error body feeding the rate-limit gate and `RelayException.body`; malformed JSON; non-list body; non-object element; malformed event; invalid UTF-8; error status skips decoding; session disposed, age-restricted, or retired-before-decode while a decode is outstanding. The fencing tests are falsifiable: removing the post-decode check fails the disposed and age-restricted tests; removing the retired-wins-over-decode-error branch fails its test; removing the pre-decode check fails the never-decodes test. `FakeRelay` (test helper) now injects an inline decoder, because `fakeAsync` cannot deliver isolate results; it calls the same parse function. The default transport still honors `http.runWithClient`, which the hosted journey tests use to fake the network (`relay_http_query_client_test.dart`; fails without the fix). `just mobile-check` and `just mobile-test` pass. Host benchmark (AOT executable on an Apple silicon Mac, median of 25, copy of the parse logic, main-isolate stall measured with a 0.5 ms heartbeat timer): | Response | Main-isolate blocked before | after | | --- | --- | --- | | 500 events, ~630 KB | 2.4 ms | 0 ms (max gap 0.05 ms) | | 500 events, ~630 KB gzip | 3.2 ms | 0 ms (max gap 0.19 ms) | | 500 events with 50 20 KB avatars, ~1.6 MB | 5.1 ms | 0 ms (max gap 0.28 ms) | | 500 events, ~4.5 MB | 14.0 ms | 0 ms (max gap 0.27 ms) | | 500 events, ~4.5 MB (827 KB gzip) | 17.4 ms | 0 ms (max gap 0.35 ms) | Total latency is unchanged within noise (for example 14.5 ms to 14.9 ms). The host is much faster than the 12 mini, so absolute device savings should be several times larger. On the iPhone 12 mini (Profile builds of base and this branch, real community over the production relay, two runs per scenario; main-isolate stalls measured with a 4 ms heartbeat timer, since the wireless device tunnel has no VM service): | Scenario | Main-isolate `_postForEvents` time, base | this branch | Max stall, base | this branch | | --- | --- | --- | --- | --- | | Cold start + 60 s (~16k events, 7.8 MB gzip) | 282 to 295 ms (max 116 ms) | 0.01 ms | 147 to 150 ms | 100 to 102 ms | | Inbox + search (~7k events) | 122 to 123 ms (max 111 ms) | 0.01 ms | 138 to 142 ms | 97 to 99 ms | | Open 6 channels (558 events) | 5 to 6 ms | 0.01 ms | 88 ms | 86 to 87 ms | | Background 10 s + resume (~7k events) | 118 to 124 ms | 0.01 ms | n/a (suspension) | n/a | - The production relay serves gzip (about 4.4:1; the largest catch-up response is 3.15 MB on the wire, 13.9 MB inflated, 7k events). Inside the isolate that response takes 30 to 47 ms to inflate, 39 to 55 ms for UTF-8, 65 to 105 ms for `jsonDecode` and 7 to 32 ms for `NostrEvent.fromJson`. - Many 16 to 90 ms stalls remain on both builds; they come from other work (mostly markdown build and layout), not response decoding. - Home, channels, inbox, search, profile and a thread render the same on both builds. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
b6aa5539e3 |
feat(mobile): account onboarding, key recovery and hosted reconnect (#8375)
## Behavior Adds optional mobile Builderlab sign-in, identity backup/recovery, community discovery/creation, and account-aware reconnect. **Builderlab features are disabled by default:** when `BUZZ_AUTH_DOMAINS` is unset or empty, account login, creation, backup/recovery, reconnect prompts, and background account restoration are hidden/disabled. QR/code pairing, links, and invites retain Nostr-only entry. Saved account state is retained for later opt-in. `--dart-define=BUZZ_AUTH_DOMAINS=communities.buzz.xyz,communities.public.builderlabstaging.xyz` opts those DNS domains and their subdomains into required account setup. The same policy gates pairing, deep links, durable community admission, and reconnect. A nonempty list enables Builderlab features; `BUZZ_BUILDERLAB_URL` alone does not. Enabled flows still require their backend services. Existing Nostr relay authentication remains mandatory. `BUZZ_HOSTED_RELAY_AUTH=true` enables scoped relay assertions and reconnect prompts only for configured authentication domains. Configure assertion issuance and each community's issuer binding before enabling assertions; enable relay enforcement last. Account credentials stay at Builderlab. Wrong-account recovery preserves keys; failed writes are not replayed; authentication is bounded by request deadlines. Hosted flows preserve keys across cancellation/restart, reconcile interrupted creation/invitation acceptance, and support manual nsec file import/export. Account identities are scoped to backend and authenticated subject; reusing an existing local key for an unbound account requires an explicit selection. Recovery reconciles the advertised and recovered inventories before automatic selection. Successful self-hosted admission retires its pending key, and iOS startup removes interrupted export files. Adds authenticated relay `GET /operator/communities/joined` for the companion Builderlab membership endpoint. ## Validation - Follow-up review repair at `66a12ac92`: self-hosted entry presents readable choices and import despite incomplete enumeration, while generation still requires a complete empty inventory; explicit selection uses exact-key lookup. Consuming a value-equal duplicate now advances dispatch. Reconnect requires an existing account binding or a subject-scoped backup receipt before signing an assertion, and pins the subject inside the serialized account request. An empty different account is refused without sending key proof. Focused checks: configured domains 58 passed; default 49 passed, 9 hosted-only skips. Separate isolated mutations fail the incomplete-inventory sheet, exact-key reuse, duplicate dispatch and reconnect journeys (including an empty-account-only bypass); restored controls pass. Evidence uses synthetic storage/network boundaries, not physical Android or live backend association. All mandatory commit/push hooks passed; fresh GitHub CI is pending. - Bounded review repair at `d3cc0003f`: incomplete secure-storage enumeration now blocks automatic identity generation while explicit known-key reads retain their fallback; declined/failed onboarding links release the queue; HTTP 429/5xx keeps Retry regardless of response encoding. Default app-gate coverage exercises a bind link followed by ordinary community entry. Focused checks: configured domains 47 passed; default 38 passed, 9 hosted-only skips. Removing the three production fixes in an isolated source/Git/build copy fails the identity-resolution, declined/failed queue, and Retry journey assertions; clean and restored controls pass. Storage and HTTP failures are synthetic; physical Android and live outage behavior were not exercised. Published after all mandatory commit/push hooks passed (mobile analysis/tests, branch-skew and file-size gates); fresh GitHub CI remains pending. - Existing whole-PR source review cleared the reconnect defects at `fb15d5b82`. - Domain-policy increment: focused environment, entry, app-gate, pairing and onboarding journeys pass with empty and configured domains. Existing pairing provider/page tests pass. Changed Dart files analyze cleanly. - Isolated source/Git/build mutation: forcing paired credentials through hosted login fails the default-build journey; clean and restored controls pass. CI now runs configured-domain journeys in addition to the default-build suite. - Integrated main through `5b433d929` at `0067c069c`, including its pinned Flutter upgrade. Preserved the PR welcome UI with the updated transition API and added Material surfaces around onboarding selection/consent rows. Focused post-merge runs pass in both configurations; all mandatory local push gates passed (mobile analysis/tests, Rust tests, desktop-native checks, branch-skew and file-size checks). - Merge-review repair at `a315bce11`: focused identity/storage/admission and widget journeys pass, including account switching with an empty or already-bound second account, explicit reuse, and self-hosted removal/rejoin with another key. Default-domain journeys pass too (hosted-only cases intentionally skip there). - Isolated source/Git/build mutation checks: bypassing account scoping, reuse consent, pending-key retirement, or recovery inventory reconciliation fails the relevant journey/invariant assertions; clean and restored controls pass. - Native `RunnerTests/IdentityFilePluginTests`: both tests pass on the iPhone 18 Pro iOS 27 simulator with an isolated app identifier. At `24a0dcaac`, the picker journey seeds residue in Library/Caches, matching path_provider_foundation, and verifies plugin startup removes it while preserving an unrelated file. The earlier tmp-only fixture did not exercise the actual export location. This is synthetic native cleanup/picker evidence, not a real terminated-share or live account round trip. - Follow-up: Caches and tmp cleanup; fallback to individually known identity slots when Android bulk enumeration fails; explicit replacement of a refused self-hosted key preserves its original before swapping the retry slot. Configured-domain journeys/invariants: 34 passed; default: 31 passed, 3 hosted-only skips. Isolated enumeration/retry mutations fail with passing controls. Bulk-read fallback preserves but cannot enumerate arbitrary other-account slots until bulk reads recover. - Configured-domain CI exposed a stale pairing-status assertion after account scoping. The repaired test checks the admission mismatch, preserved account/transfer keys, and no admitted community. The configured-domain CI selection passes locally (56 tests); removing the production mismatch guard fails the updated test, with clean/restored controls passing. This is synthetic coordinator evidence, not physical QR acceptance. - Final review repair (`40e1e6101`): native iOS export creation uses atomic complete file protection; normal and restart cleanup remain. Account/policy/claim HTTP uses one 30-second total deadline with request abort and body cancellation. Removed the unrelated NIP-FA spec edit. - Focused account/admission/export tests: 25 pass; configured-domain account/admission/onboarding journeys: 33 pass. Isolated deadline and native-dispatch mutations fail, with passing clean/restored controls. Native export/picker tests: 2 pass on iOS 27 simulator. The real file-protection attribute assertion is physical-device-only because simulator provides no Data Protection attribute; locked-device protection and interrupted real sharing have not been observed. - Opt-in UI: configured-domain CI selection passes (57 tests); default selection passes (7 tests, 10 hosted-only skips); URL-only plus whitespace/empty domains passes (6 tests, 9 hosted-only skips). Changed Dart files analyze cleanly. Independent mutations that unconditionally initialize the coordinator in the app gate, welcome entry, or link form fail the default journey; clean/restored controls pass. - Native welcome screens exercised on iPhone 18 Pro / iOS 27 simulator in default and configured builds: default exposes community link / QR / pairing code only; configured build additionally exposes Builderlab connection and community creation. No live hosted login was exercised in this check. - Delivery at `e48fe73a9`: all mandatory push hooks passed (full mobile analysis/tests, Rust tests, desktop-native checks, branch and file-size checks). GitHub checks had not yet populated at the immediate post-push snapshot. ## Review disposition | Feedback | Disposition | | --- | --- | | Explicit self-hosted entry under unreadable inventory, duplicate invite dispatch, reconnect cross-account binding ([entry](https://github.com/block/buzz/pull/8375#discussion_r4318421622), [queue](https://github.com/block/buzz/pull/8375#discussion_r4318421640), [reconnect](https://github.com/block/buzz/pull/8375#discussion_r4318455512)) | Repaired at `66a12ac92`; evidence above. Reconnect refuses unproven ownership rather than silently establishing a binding. | | Incomplete Android identity inventory, stranded deep-link queue, transient HTTP retry classification ([review](https://github.com/block/buzz/pull/8375#pullrequestreview-5576864143)) | All three repaired in `d3cc0003f`; focused journeys and isolated mutation evidence above. | | Account cross-link, incomplete recovery inventory, committed invite checkpoint, pending local key retirement/replacement | Repaired in the preceding commits; journey and mutation evidence above. | | iOS interrupted key export ([review](https://github.com/block/buzz/pull/8375#pullrequestreview-5575253252)) | Native cleanup plus complete protection at creation implemented. Physical-device lock and real interrupted sharing acceptance remains pending. | | Trickle responses bypass idle timeout; unrelated NIP-FA edit ([review](https://github.com/block/buzz/pull/8375#pullrequestreview-5575764556)) | Total request deadline/abort implemented; spec edit removed. | | Production domains rejected | Ordinary entry now needs no Builderlab configuration. Opt-in account-required domains must still use supported community mappings. | | Lost Android Keystore recovery | Backup exclusions prevent the reviewed stale-restore scenario; genuine key loss still has no in-app recovery/reset and remains deferred. | | Native secp256k1 dependency question | Obsolete against the current base: upstream C integration landed in #8398; no package diff remains here. | | Operator membership endpoint contract | Documentation now states the operator chooses the pubkey; no end-user session binding is implied. Additional handler-level coverage is optional follow-up. | | Optional Android partial document cleanup/activity retention; local work after backup cancellation | Follow-up hardening, not claimed implemented. Cancellation prevents remote backup upload. | | Semgrep crypto inventory notices | Informational inventory, not demonstrated defects. | ## Remaining draft gates Native QR/browser-return acceptance remains unverified for this revision. Optional Builderlab onboarding still needs backend backup-status and joined-community routes, recovery policy/configuration, and real backup/recovery/reconnect acceptance. The empty-domain build removes these services as prerequisites for ordinary pairing; it does not make the optional hosted flows backend-ready. Human acceptance and current CI are still required before merge. Review dispositions retained: production auth-domain configuration must use supported community mappings; enabling `builderlab.xyz` does not add support for `buzz.block.builderlab.xyz`. Recovery after genuinely losing an Android Keystore key remains a follow-up, not a newly implemented capability. Human acceptance steps: build once without `BUZZ_AUTH_DOMAINS` (including with a previously saved account) and confirm no Builderlab controls/restoration while ordinary link and QR/code entry work. Build with supported domains and confirm connection/creation appear; exercise browser return and live backup/recovery/reconnect. On physical iOS, exercise export and interrupted sharing/locked-device protection. Record results before marking review complete. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@wes.communities.public.builderlabstaging.xyz> |
||
|
|
b30f1f6129 |
Polish mobile profiles, DMs, and sheets (#5401)
## Summary
- add poster-first, tap-to-toggle animated avatars on profile surfaces
while preserving transparent/static behavior elsewhere
- align mobile DM headers, membership actions, and invisible agent
recipient addressing with established desktop semantics
- polish titled sheets and status editing, preserve native iOS sheet
corners, and batch relay reads to improve review-build responsiveness
## Snapshots
<table>
<tr>
<th>Profile avatar</th>
<th>Agent DM header and composer</th>
</tr>
<tr>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--profile-avatar.png"
width="360" alt="Mobile profile settings with animated avatar
surface"></td>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--agent-dm.png"
width="360" alt="Agent direct message with masked presence and normal
composer"></td>
</tr>
<tr>
<th>Members sheet</th>
<th>Status editor</th>
</tr>
<tr>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--members-sheet.png"
width="360" alt="Members bottom sheet with centered title and padded
content"></td>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--status-sheet.png"
width="360" alt="Status editor bottom sheet with duration and quick
statuses"></td>
</tr>
<tr>
<th colspan="2">Switch Community</th>
</tr>
<tr>
<td colspan="2" align="center"><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--switch-community.png"
width="720" alt="Switch Community bottom sheet with centered title and
aligned Edit action"></td>
</tr>
</table>
## Validation
- `just mobile-check`
- `just mobile-test` (1,283 tests)
- installed and reviewed isolated debug builds on iPhone and Pixel
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
|