Repository navigation
Conversation
A steer whose ack fails falls back to cancel+merge: the event is released to the queue front and the in-flight turn is cancelled and re-prompted with the event merged in. Cancelled batches are preserved across failures by design (a cancel is not a failure), so neither requeue()'s retry budget nor the dead-letter paths bounded this loop. When the redispatched turn keeps failing (e.g. provider rate limit), the cycle repeats with no backoff and no ceiling: every cycle starts a fresh session and pays the full base-prompt input. Observed in production: 620 bootstrap turns in 37 minutes (~20/min, one per turn cycle) from 7 unique events, exhausting a shared provider plan and silencing the agent until the plan reset. The flush_next cancelled-batch fallback now: - counts redispatches per scope (cancelled_redispatch_counts) - applies the same exponential backoff as requeue() from the second redispatch on (the first stays immediate for legitimate !cancel re-prompts) - dead-letters the batch after MAX_CANCELLED_REDISPATCHES (10, matching MAX_RETRIES), clearing backoff state so fresh traffic is not throttled - resets the budget whenever fresh events merge into the scope New tests pin the bound, the backoff arming, and the budget reset. Signed-off-by: dziduchm <dziduchm@users.noreply.github.com>
🔐 Codex Security Review
|
…ices Address review findings on the cancel+merge redispatch bound: - The dead-letter return from flush_next was dispatched to the agent like any normal batch (dispatch_pending prompts every Some(batch); failure notices only originate from handle_prompt_result). The "discarded" batch was actually re-prompted one more time, paying a full base-prompt cost, and a Cancelled outcome on that final prompt would re-seed the loop via requeue_as_cancelled with a fresh budget. flush_next now returns None and parks the batch in a dead_letters side list; dispatch_pending drains it via take_dead_letters() and posts the user-visible failure notice through spawn_failure_notice. - mark_complete now clears cancelled_redispatch_counts on healthy completion (when no cancelled batch is pending for the scope), so a successful redispatch does not tax later cancel episodes with instant backoff or premature dead-lettering. - New test: healthy completion resets the redispatch budget; the bound test now pins the None return and the parked dead letter. Coverage note: the bound limits the no-fresh-traffic spin. A loop continuously re-seeded by fresh events (e.g. self-echo under BUZZ_ACP_SUBSCRIBE=all, issue block#7811) is bounded by the fresh-merge budget reset combined with this dead-letter only when fresh events stop arriving; the self-echo filter remains a separate upstream decision. Signed-off-by: dziduchm <dziduchm@users.noreply.github.com>
Pinky is updating this PR on Wes’s behalf. ## Summary - Treat exact `CLOSED restricted: channel access revoked` on a known channel-scoped live subscription as a hint to refresh authoritative channel state. - Reuse `useLiveChannelUpdates`’ existing debounced, idle-aware query refresh. Keep terminal retirement, explicit membership denial, and polling unchanged. - Cover archived, unchanged, removed, and failed authoritative results; burst coalescing; irrelevant/stale frames; exact-reason rejection; and unmount cleanup with seven production-path regression cases. The hint does **not** establish archive or membership state. This closes a missed-update recovery gap without changing the relay or blindly retrying subscriptions. Related symptom report: block#7026 (not claimed fully resolved; archived-channel delete policy is separate). Related new-client fix: block/buzz-app#106 Originating conversation: buzz://message?channel=cb38cd5a-c920-40a1-b770-e9b89c289476&id=d22c1eed473016cf1f73c889a4cc88f1491ee092cfe3794575f8fea8003aa19e ## Review follow-up and main integration At **`51ec7482ad1e600bd02ea6b9ed265db8a70e7266`**, based on main **`c213d90d5c450e2e579505da4a4145f528ecbcf2`**: - Addressed Jude’s P2: a known channel-scoped live subscription closed with `restricted: not a channel member` stays terminal but must not refresh channels. - Mutation verification: replacing the exact reason predicate with `startsWith("restricted:")` produced **6,588 passes / 1 expected failure**, specifically the new no-refresh assertion (`actual: 1`, `expected: 0`). Restored the exact predicate; the final full suite passed **6,589/6,589**. - Resolved both conflicts with block#7790, retaining main’s paced/cancellable subscriptions and this PR’s hint after terminal cleanup/drain cancellation. The fixture advances the production drain with a consistent virtual clock; it does not stub or bypass admission. - Integration crossed the session file’s 1,200-line ceiling (1,207). Extracted only this PR’s exact predicate/listener set into `relayChannelAccessRevocations.ts`, with unchanged session ownership and cleanup. No limit change, new timer, retry policy, or unrelated refactor. Final feature diff: 46 production additions across three files, plus the 333-line regression fixture. - Independent read-only review of the integration and final extraction found no blocking findings. This is not GitHub approval. ## Validation At exact pushed head **`51ec7482ad1e600bd02ea6b9ed265db8a70e7266`**: - Mandatory hooks passed without bypasses: desktop lint/policy checks, TypeScript, repository file-size gate, branch-skew check, and full desktop suite **6,589/6,589**, zero failed/cancelled/skipped. - Working tree clean after push. DCO passed on GitHub. - Fresh hosted CI and Desktop Release Candidate checks are running; prior-head green checks are not evidence for this head. Re-review requested from `jedwards27`; the changes-requested review and required approval remain for the reviewer to update. ## Evidence boundaries - Native IPC is stubbed; the fixture exercises actual subscription creation, raw CLOSED dispatch, mounted hook, authoritative query refresh, and IPC result conversion. It does not exercise native discovery or rendered sidebar behavior. Existing in-flight refresh retention remains source/helper-test coverage rather than a new integrated case. - No native app launch, live archive mutation, or release certification. This fixes a demonstrated recovery mechanism, not proof of the exact original incident ordering. - The initial broad local `just ci` attempt hit a 300-second timeout compiling `buzz-voice`; full local `just ci` remains incomplete. Current delivery uses mandatory path-scoped hooks and fresh hosted CI; no unrelated native/mobile lane was bypassed or claimed rerun locally. --------- Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
## Summary Aligns the NIP-FI wire contract with the post-sync enterprise identity decisions: - bind each community's canonical host URI as its RFC 8707 resource-indicator audience, resolve `Host` to an immutable community configuration before verification, and require both exact audience binding and explicit community-to-issuer authorization - require relays to ignore unverified claims and avoid logging or exposing them; recommend that issuers minimize relay-visible claims - make per-session (`sid`) revocation issuer-side while keeping the relay's revocation primitive pubkey-scoped and community-wide, keyed `(iss, target_pubkey)` - scope the disconnect close scan to connections registered under the command's issuer, including admission-in-progress, matching the deny-entry key; terminally cancel pending connections and close the cross-issuer session-termination gap - clarify proactive assertion re-exchange and reconnect before expiry without introducing in-band renewal Originating Buzz discussion: `buzz://message?channel=51940054-5a7a-4773-a337-8004496630fa&id=d551ee7c61882ef33843e752f63a837921e7c5df2671a88c68a84524490089cc` --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Signed-off-by: Paul <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@buzz.block.builderlab.xyz> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Paul <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@buzz.block.builderlab.xyz>
Development-signed iOS apps cannot enroll with the current gateway because their Apple App Attest AAGUID is rejected. Add an explicit personal development build feature and environment setting so a development gateway can use a developer's own application identity and sandbox APNs credentials. Ordinary gateway builds remain production-only. Special builds also default to production, enforce exactly the selected signed AAGUID, and retain certificate-chain, pinned-root, nonce, app-ID, key, credential-ID, and counter verification. Document the matching mobile signing configuration and expose the feature through an optional Docker build argument. ### Validation The development image deployed successfully to a personal Railway stack; private readiness, enrollment challenges, authenticated relay access, and APNs sandbox TLS/HTTP2 connectivity passed. Signed-off-by: Tom Brow <tomb@block.xyz>
## Summary Inbox forgets the **Show unread only** choice after navigation or restart. Save the toggle with the existing safe storage helpers and restore it when Inbox opens. The preference stays on this installation and applies across accounts and communities. Fresh installations keep the current off default. If storage fails, the current view remains usable, but the choice may not survive navigation. ### Related issue Related report: block#3669. Overlaps with block#6105 and block#3682. block#6151 also persists category filters and pane widths. This draft keeps the change to the unread toggle and reuses the existing storage helpers. ### Testing The mock Desktop regression passed: enable the toggle, navigate to Agents and back, reload, then repeat with the toggle disabled. Both choices were restored. This exercises the UI with a synthetic fixture; a native app restart was not tested. The full local repository CI gate is pending. Keep this PR in draft until validation completes. ### Before and after behavior Same synthetic Desktop fixture in Chromium, 1280×720, on macOS. **Before:** production source from `41c5ace93740261ee5a5d962c2a17e8d846c2c1b`. **After:** PR head `b50c1a06d53fdc080e7aae6ee9aa2e9ad6b60a82`. The capture test asserted the displayed toggle state before taking each screenshot. 1. Enable **Show unread only**, open **Agents**, go back to **Inbox**, and reopen **Inbox options**. 2. Enable the toggle again, reload the page, and reopen **Inbox options**. Before the fix the toggle is off after both journeys; after the fix it stays on. These are mock-backed UI captures, not a native Desktop restart or live relay test. | Journey | Before: resets to off | After: stays on | | --- | --- | --- | | Inbox → Agents → back |  |  | | Enable → page reload |  |  | Generated with Codex Signed-off-by: Diem Nguyen <diem@squareup.com>
…ck#7821) ## Summary Repair the mock-backed desktop smoke fixtures blocking release PR block#7817. No application unread logic, styling, audio component, scheduler, or release-tool changes. - Require an exact channel-scoped kind-9 subscription before injecting unread messages, including after navigation. The old readiness check could accept a global-only, wrong-channel, or wrong-kind consumer. The extracted predicate is used by the real mock bridge and has regression coverage for those false positives and legacy behavior. - Seed the 24 audio messages offscreen with distinct timestamps, then open the channel with all cards in the load margin. Same-second random event IDs previously reordered the settling timeline, causing different visible cards to start loads. This was viewport churn, not a component restart defect. - Expose the existing real audio scheduler snapshot through the E2E bridge. Assert 3 active / 21 queued before navigation and 0 active / 0 queued afterward. Preserve the peak-3 cap, exact 3 fetches / cancellations / releases, zero object URLs, and existing unread/bold assertions. No sleeps, retries, skipped tests, or relaxed limits were added. ## Validation - Before commit, all 30 tests in `channel-activity-popover.spec.ts`, `channel-mute.spec.ts`, and `voice-note.spec.ts` passed five repetitions with retries disabled: **150/150**. This was the working repair on base `312cf674f`, before final formatting/cleanup, not the entire smoke suite. - At committed head `1b126afbe75c2a2afbdfe13744f0938fa584764e`, normal pre-push hooks passed: **6,591/6,591 desktop unit tests**, desktop checks, TypeScript typechecking, differential file-size gate, and branch-skew check. Pre-commit Biome formatting passed; working tree is clean. - Independent sidebar and audio reviews found no substantive blocker. Their formatting findings are resolved in this commit. - Full repository `just ci`, native release packaging, and the entire Playwright smoke suite were not run locally. GitHub CI is the remaining broader gate; this PR is not a release certification. ## Release follow-up After this repair is merged and required checks pass, regenerate block#7817 through the desktop release tooling and validate the new candidate's exact head. This PR does not merge or publish a release. Originating discussion: buzz://message?channel=3428ec3f-a58b-429b-afbc-dc6a77918ce8&id=3303565b14334696e9bbf433ef3ad1aea4307acc69834f7f685a412e81354493 Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
## Buzz Desktop release v0.5.24 - **Frozen main:** `fe56d311a7156802408afb04023c3f75d4015007` - **Reviewed candidate:** `3befaf16002d802a97aa79007be29b23623ceb3f` - **Previous desktop release:** `desktop-v0.5.23` - **Proposed immutable tag:** `desktop-v0.5.24` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
🤖 Adds append-only channel canvas history, restore, and relay-authoritative optimistic concurrency so stale edits cannot silently replace a newer canvas. ## What this adds - **Desktop history and restore** — browse revisions with author, timestamp, preview, and diff; restore an older revision as a new signed head after explicit confirmation. Persisted empty canvases remain reachable, mutation outcomes are announced accessibly, and focus moves to a stable destination after save or restore. - **CLI history and restore** — `buzz canvas history --channel …`, `buzz canvas get --channel … --revision <id>`, and `buzz canvas restore --channel … --revision <id>` use keyset pagination over the retained revision stream. - **SDK canvas builders** — optional `expected-revision` tags, bounded ancestry classification, and shared timestamp discipline keep first-party writes ahead of the head they read without propagating poisoned future timestamps. ## Authoritative concurrency contract Tagged canvas writes use compare-and-swap semantics in the relay and database: - `expected-revision=none` succeeds only when no live canvas exists. - An event ID succeeds only when it names the canonical live head. - A byte-identical replay succeeds only when that event is already the canonical live head. - A successful candidate must sort strictly ahead of the current head under `created_at DESC, id ASC`. - Malformed preconditions and stale or non-advancing writes are rejected before event rows, mentions, or fan-out are committed. The database serializes tagged writes, unconditional kind `40100` writes, and canvas soft deletion on the same `(community, kind, channel)` advisory key. Untagged `buzz canvas set` remains an unconditional write, but participates in that serialization boundary so it cannot race a tagged save or deletion. Deletion classification is derived inside the datastore transaction; unrelated event kinds do not take the canvas lock. ## Read and timestamp consistency Precondition and post-write ancestry reads request strong consistency from `/query`, pinning them to the writer when replica routing is enabled. Display-only history remains replica-eligible. First-party canvas writers stamp `created_at = max(now, head + 1)` with a conservative client skew limit. The relay applies a canvas-specific future-timestamp ceiling before persistence, preventing an accepted boundary head from making subsequent first-party writes invalid while preserving the broader ingest limit for other event kinds. ## Durable outcomes and recovery After an accepted publish, a failed verification read is reported as durable success with `verified: false` rather than a failed save. Desktop and CLI preserve the new event ID and direct users to History when verification is unavailable. A successful read walks the bounded, cycle-guarded `expected-revision` ancestry chain so transitive descendants are not misclassified as supersession. Canvas caches invalidate for every settled mutation outcome, including a retained but superseded revision. The 409 reconciliation path in `cmd_restore_canvas` now separately establishes persistence evidence from ancestry classification. `canvas_write_survived` checks reachability only; a secondary writer-pinned IDs lookup distinguishes "A absent" from "A present but superseded by a legacy write." The four-way outcome (survived / persisted-but-superseded / genuinely-absent / read-fail) is now correct for all lost-response shapes including unconditional legacy writes. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
…on (block#7840) ## Summary Fix `reasoning_effort: Extra inputs are not permitted` for Databricks v2 model `data_workflow_tools.goose.goose-claude-opus-5-5` without dropping explicit High. - Add one provider-qualified exact capability record: Anthropic Messages, adaptive thinking, `output_config.effort`, medium upstream default. Mirror exact-record precedence in Rust/TS; keep uncurated UC services neutral and existing GPT routing unchanged. - Preserve ordered native content, including signed/empty thinking and redacted blocks, across tool results. Do not cache-stamp thinking blocks or leak native state into Chat reasoning details. Discard truncated native state; reject more than 64 native calls before any tool executes instead of altering signed content. - Add production-dispatch/ACP regressions and six shared capability vectors. No relay/ACP protocol, schema, settings, inheritance, retries, or other harness changes. ## Validation At final head `82f43f8d07bfcc3f4af492afc0a58a02f8e549c4`, clean tree: - Rebuilt real `buzz-agent`, drove ACP with a harmless MCP fixture against the actual service. Two POSTs to `/ai-gateway/anthropic/v1/messages`, both HTTP 200, exact FQN, High via `output_config`, adaptive/summarized thinking, no `reasoning_effort`. First response contained signed thinking + tool use; second request preserved signature/content order and paired result; final ACP stop `end_turn`. No credentials, signature contents, or conversation data included here. - Mandatory pre-commit/pre-push checks passed: formatting/lint, file-size gate, Rust test lanes, desktop typechecking and full JS tests, Tauri clippy/tests. - Full `cargo test -p buzz-agent` passed on the pre-commit tree (only hook formatting followed), including the new native truncation/65-call ACP regressions, ordered interleaved signed/redacted two-tool capture, nearby-ID negatives, and existing provider regressions. - Independent API/replay, regression-boundary, and Rust/TS capability reviews cleared. Full `just ci`, packaging and manual desktop UI testing were not run locally; repository CI remains the merge gate. Cross-harness internals and other UC service contracts are outside scope. ## References - [Databricks model services](https://docs.databricks.com/aws/en/ai-gateway/query-model-services) - [Anthropic thinking preservation](https://platform.claude.com/docs/en/build-with-claude/thinking#thinking-block-preservation-by-model) - [Anthropic effort](https://platform.claude.com/docs/en/build-with-claude/effort) - Originating Buzz channel: `fccdea96-0f31-4343-ba6a-324214dc8d73` - Originating thread: buzz://message?channel=fccdea96-0f31-4343-ba6a-324214dc8d73&id=33f242923a317e269c1ff7b960cacd6ec1f3371eff2f97592857804c50fb100c Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
…3.2 (block#7820) A valid 30-day push gateway grant lifetime could render in scientific notation (e.g. `2.592e+06`) in the chart, which the gateway binary does not accept. Always ormat the integer as decimal text, preserving the strict gateway parser. ### Testing Added regression coverage that feeds actual Helm-rendered environment values into the production config parser. Covers defaults, values files, command-line overrides, exact lifetime boundaries, and invalid input rejection. The default case failed before the fix and passes afterward. Signed-off-by: Tom Brow <tomb@block.xyz>
…7841) Git exports repository-local variables (`GIT_DIR`, `GIT_INDEX_FILE`, `GIT_WORK_TREE`, ...) into hook processes, and in linked worktrees `GIT_DIR` is absolute. Pre-push lanes inherited that environment unchanged, so any test fixture that spawns `git` in a temp dir (`git init --bare <tmp>`, `git config user.name ...`, `git commit`, `git push`) actually operated on the checkout being pushed. `current_dir(tmp)` and `git -C tmp` don't escape an inherited `GIT_DIR`. Because `.git/config` is shared by every worktree, one push from any worktree could silently rewrite the repo for all of them. In practice this set `core.bare=true` (breaking the main checkout with `fatal: this operation must be run in a work tree`), replaced the committer identity with a fixture `user.name=Agent`, disabled `commit.gpgSign`, and left fixture aliases behind. The fixtures that did this are the git-identity enforcement tests in block#6177, which run in the `rust-tests` lane via `just test-unit`. The pre-push lanes that run test suites or builds now go through `scripts/without-git-env.sh`, which unsets the variables listed by `git rev-parse --local-env-vars` (git's own definition of repo-local state, the same set `git submodule` clears) and then `exec`s the lane, so each subprocess rediscovers the repository from its working directory. Transport, credential, and TLS settings (`GIT_SSH_COMMAND`, `GIT_ASKPASS`, `GIT_TERMINAL_PROMPT`, `GIT_SSL_*`) pass through untouched. Putting this at the hook boundary covers every current and future test without each suite having to defend itself. The `branch-skew` and `push-head-scope` guards run unwrapped: they spawn no fixtures, and `branch-skew`'s `git fetch` needs the caller's transport environment. `pre-commit` isn't wrapped either, since its `stage_fixed: true` lanes need the hook's `GIT_INDEX_FILE` to restage into the temporary index during `git commit <paths>`. Related: block#6177 --------- Signed-off-by: Will Pfleger <wpfleger@block.xyz>
## Summary Make the relay read completed storage-accounting snapshots from PostgreSQL by default. An environment with no snapshot emits no storage metrics. When a worker publishes its first result, the relay picks it up on the next successful leader usage tick, without a restart or configuration change. This changes how storage usage is measured and reported. **S3 remains the object store. PostgreSQL stores the completed counts, byte totals, community breakdown, and calculation metadata—not the objects themselves.** ### Why move relay storage accounting entirely to database reads? Today the relay supports two sources: its own S3 scan (`inline`, the default) and the worker's saved snapshot (`external`). That requires operators to deploy a worker and then coordinate a separate relay-mode change in each environment. The worker already owns the S3 listing and calculation. It runs in a separate process with its own object cap, memory limit, and deadline. Keeping a second scan inside the serving relay retains the resource pressure and failure modes that this isolation is meant to remove. PostgreSQL provides a durable handoff. The worker replaces one completed snapshot atomically after a successful calculation. The relay reads that row and exposes its values through the existing metrics endpoint. A relay restart or leadership change can load the last completed result without scanning the bucket again. There is deliberately no S3 fallback when a snapshot is absent or a read fails. Such a fallback would put the expensive scan back in the relay precisely when a worker is absent or unhealthy. It would also leave two calculation paths with different resource limits and freshness behavior. ### What happens if no worker exists? **A worker that has never published is a normal, successful inactive state.** The snapshot table must exist through the normal database schema, but it may contain zero rows. The reader returns success, emits no storage totals or failed-load gauge, and checks again on the next usage tick. It does not exit the relay, start a worker, or list S3. The relay checks for a completed database row; it does not inspect Kubernetes Jobs or CronJobs. Worker presence and snapshot presence are therefore different: | Situation | Storage-reader behavior | |---|---| | No worker and no snapshot row | Return success, emit no storage metrics, and query again next tick. | | Worker starts later and publishes its first row | Read and emit it on the next successful leader usage tick; no restart or mode change. | | Worker is removed or fails, but its last valid row remains | Continue reporting the last completed totals with their increasing age. | | Query succeeds but a previously present row has been removed | Clear the cache and stop refreshing storage series; existing exporter series expire through the idle timeout. | | Database read fails, times out, or returns an invalid snapshot | Log a warning, set `buzz_storage_snapshot_load_ok=0`, retain any last-good cached totals, and retry next tick. | | Snapshot table is missing | Treat this as a schema error, not normal worker absence; follow the failed-read behavior above. | | `BUZZ_STORAGE_METRICS=off` | Skip the snapshot query and all storage metric emission. | No row means “not measured,” not zero bytes. A valid completed snapshot of an empty bucket can report zero. If a read fails before any good snapshot has been cached, only failed-load health is emitted. These reads happen in the existing background metrics task, not the relay startup path. A storage-read failure does not request process exit or change readiness directly. A wider database outage can still affect the relay's existing database-dependent operations and readiness. ### Read, parse, and emit path ```text Worker: S3 listing -> BucketSnapshot -> JSON -> PostgreSQL Relay: PostgreSQL -> JSON -> BucketSnapshot -> existing metric gauges Collector: relay /metrics endpoint -> Datadog ``` 1. `main.rs::run_storage_sweep_tick` calls the reader from the existing leader-only usage task. The default interval remains 300 seconds. 2. `storage_sweep.rs::run_storage_metrics_tick` coordinates the read, cache update, failure health, and metric emission. 3. `refresh_persisted_snapshot` calls the existing `Db::load_storage_accounting_snapshot()` method. A five-second timeout covers pool acquisition and the query. It uses the relay's existing pool. 4. The existing query in `buzz-db/src/store/storage_accounting.rs` reads `snapshot`, `completed_at`, `duration_ms`, `max_objects`, and `code_sha` from the singleton row. 5. `serde_json::from_value(stored.snapshot)` decodes the JSON into the existing `buzz_media::BucketSnapshot` type. Rust infers that type from `cache_persisted_snapshot`'s argument. The generated `Deserialize` implementation handles all fields, including the UUID-keyed `per_community` map. 6. `emit_cached_storage_metrics` sets the existing physical/logical totals and community byte/object gauges. The existing Prometheus exporter exposes them for Datadog collection; this code does not send snapshot JSON to Datadog. The worker's `BucketSnapshot`/`CommunityStorage` structures, JSON field names, SQL publication method, and database schema are unchanged. The old external-mode JSON conversion moves out of `main.rs` into the reader helper. No new format or hand-written field parser is needed. Duration and object-cap metadata are validated before replacing the cache. The five-second relay read timeout is separate from the worker's 30-second startup acquisition budget introduced in block#7770. This PR does not change the worker's pool or startup logic. ### Configuration and code changes | Configuration | Before | After | |---|---|---| | Unset, `inline`, or `on` | Relay-local S3 scan | Read completed database snapshots. `inline` logs one migration warning. | | `external` or `snapshot` | Read completed database snapshots | Continue reading snapshots. | | `off` | Disabled | Disabled. | | Unknown or empty value | Disabled with a configuration error | Same. | Existing `inline` settings intentionally become reader aliases. Merely changing the default would leave older deployments on their explicit `inline` setting and preserve the need for coordinated configuration PRs. Remove relay-local scan scheduling, in-flight scan state, scan configuration, and obsolete scan-attempt tests and health metrics. Preserve the physical/logical total and per-community metric names, leader-only emission, community scope filtering, and cleanup of old community labels. The shared relay-state field remains in place; its comment now describes a cached worker result. The Helm chart defaults to `relayMode: external` while leaving `storageAccounting.enabled: false`. Its rendering test verifies that the reader is configured even when no worker is created. The reader regression tests cover missing rows, later publication, updates, invalid data, timeout, removal, reactivation, and the explicit off switch. `docs/storage-accounting.md` describes this operating model. ### Freshness and rollout impact `buzz_storage_snapshot_age_seconds` uses the worker's original completion timestamp. Re-reading an old row never makes it fresh. `buzz_storage_snapshot_load_ok=1` means the row was read and decoded; it does not prove that the worker's latest attempt succeeded. Monitor snapshot age against the worker's schedule and allowed runtime, and monitor Job failures separately. The old relay scan-attempt health gauges are retired because the relay no longer runs those scans. Deploy this relay version through the usual release process. Once it reaches an environment, workers can be enabled independently. Already-released charts that still set `inline` work with the new binary; future chart releases default explicitly to `external`. An intentional `off` override still requires an operator to enable the reader. **Environments with no completed snapshot lose relay-calculated storage totals after this upgrade.** That is the intended tradeoff for removing scans from the serving process. Environments with a prior snapshot continue showing its last completed totals and age. This PR does not install workers in additional environments or deploy the new relay image. ### Related issue Related to block#4601. Follows the worker startup fix in block#7770. The separate fleet-usage collector work in block#7176 overlaps this code and may need reconciliation when it merges. ### Testing Built and ran the relay locally against isolated PostgreSQL and Redis with the legacy `inline` setting, five-second usage ticks, and a 15-second metric idle timeout. The same process was exercised through these steps: 1. Start with an empty snapshot table: relay becomes ready and exposes no storage series. 2. Publish a snapshot containing 123 bytes: the community gauge becomes 123. 3. Replace it with 456 bytes: the gauge becomes 456. 4. Remove the row: storage series expire without fabricating a zero-byte measurement. 5. Publish 789 bytes: metrics reactivate without restarting the relay; readiness remains healthy. The local S3 endpoint recorded zero requests. The unrelated Git conformance startup probe was disabled for this isolated reader check; the test covers storage accounting, not every other use of S3 in the relay. No new relay image was deployed to staging or production during this test. The broad local integration run also hit an existing `p0_pool_acquisitions_use_typed_operation_pairs_without_other` failure in `buzz-db/tests/observability_source.rs`. The same source-only test fails on unmodified `main` at `0ef7a2222`; this PR changes neither the test nor its failing source. The full local `just ci` run stopped at mobile native-asset setup: the `objective_c` build hook received no SDK path from `xcrun`. Mobile tests did not run. Generated with Codex Signed-off-by: Ravneet Arora <rarora@squareup.com>
discovered in block#7742 (comment) ## Summary Goose uses its native shell, so it bypassed the Git identity and signing setup inside `buzz-dev-mcp`. Agent commits could use the host Git identity, while MCP shells had a separate config. This moves Git setup into `buzz-acp`, where both paths receive the same agent identity, Nostr signer, and relay-scoped credential helper. Before: ```text Buzz Desktop └─ buzz-acp ← Desktop supplied relay Git authentication ├─ Goose → native shell ← no agent Git author or signing setup └─ Buzz Agent └─ buzz-dev-mcp └─ shell ← MCP created the keyfile and Git signing config ``` After: ```text Buzz Desktop or Sprig └─ buzz-acp ← owns the keyfile, identity, signing, and scoped credentials ├─ Goose → native shell ← inherits the complete Git environment └─ Buzz Agent └─ buzz-dev-mcp └─ shell ← receives the same Git environment via mcpServers[].env ``` The harness keeps the signing key in a private temporary file and removes it on normal exit, startup failure, and graceful termination. It composes inherited `GIT_CONFIG_*` entries, forwards the complete block to MCP servers, and bundles the helpers through the ACP multicall entrypoint. For standard `buzz-acp` launches, Desktop and Sprig no longer write Git config. A custom Desktop ACP command bypasses the harness, so Desktop preserves its prior relay-scoped credential helper for that supported override. This changes Git settings for every ACP runtime. The startup signal path also changes so a termination during adapter initialization removes the keyfile. The temporary file can remain after SIGKILL or a machine crash. ### Related issue Related: block#6177 covers stricter commit identity enforcement and touches the same Git paths. That PR and this one need to be reconciled before either lands. No issue was found for this runtime bootstrap gap. ### Testing - Ran the ignored `git_runtime_tests` with real Goose 1.50.0 and Buzz Agent ACP sessions. `real_goose_native_git_shell` passed again on this PR branch with `BUZZ_TEST_BIN_DIR` pointing to built binaries. Goose made a signed commit and tag through its native shell; Buzz Agent did the same through its MCP shell. Both verified signatures, identity, credential scoping, and keyfile cleanup. - Built the Linux Sprig image and ran `scripts/test-sprig-image.sh buzz-sprig:acp-git-bootstrap`. The container made and verified a signed commit and tag, then confirmed keyfile removal after SIGTERM. - `just ci` passed on the original branch. After rebasing onto `main` and addressing review feedback, the ACP Git bootstrap integration tests, focused desktop smoke test, custom-command regression test, formatting, and ACP/Desktop Clippy passed. The pre-push hook hit two timing-sensitive ACP deadline tests under parallel load; both passed alone and the complete ACP package passed serially (964 library, 2 Git bootstrap, 9 pool lifecycle tests). The push used `--no-verify` after those checks; GitHub CI is rerunning on the new head. - Under injected `BUZZ_ACP_ALLOWED_RESPOND_TO`, `BUZZ_ACP_LAZY_POOL`, and `BUZZ_ACP_IDLE_POOL_SLEEP`, the clean-env serial package command in `crates/buzz-acp/TESTING.md` passed: 964 active library tests, 2 Git bootstrap tests, and 9 pool lifecycle tests. Both named Databricks OAuth tests passed locally; another reviewer reproduced their environment-specific failures on `main`. - No authenticated relay clone or push was run. The local probes used disposable repositories and did not contact a relay. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com>
## Summary Add an opt-in, newest-first thread window to the authenticated HTTP query path. A filter with `thread_window: true` returns: - reply rows ordered by `(created_at DESC, id ASC)` with an exact composite cursor; - optional bounded reactions, edits, deletions, and delete-of-aux closure; - one relay-signed `kind:39007` bounds event bound to the normalized host, authenticated reader, channel, root, request cursor, kinds, depth, limit, and aux choice. Filters without the flag keep the existing oldest-first thread behavior. Channel windows and `kind:39006` are unchanged. No client opts into this mode in this PR. ### Why extend NIP-CW NIP-CW already defines relay-computed, cursor-paged conversation views over extended NIP-01 filters. Thread windows use the same protocol ideas: ordinary signed rows, composite keyset pagination, optional aux closure, and relay-signed bounds. Adding a thread mode keeps those related wire contracts together instead of creating a second NIP with duplicated terminology and compatibility rules. Extending a draft, optional NIP with an additive mode is normal protocol evolution. Existing fields and meanings are not reinterpreted: - `top_level: true` remains channel mode; - `kind:39006` remains channel-window bounds; - `thread_window: true` selects the new mode; - `kind:39007` has a distinct identity and root/request binding; - extension-unaware clients never send the selector; - legacy thread queries remain unchanged. NIP-CW documents only the wire contract and relay semantics. Database plans, replica internals, and rollout procedures remain implementation concerns. ### Why thread windows use independent modules The implementation deliberately keeps `thread_window.rs` separate in `buzz-core`, `buzz-db`, and `buzz-relay` rather than adding branches throughout the existing channel-window path. The modes share pagination concepts, but their invariants differ: - channel mode selects top-level rows; thread mode requires one root and depths `1..=depth_limit`; - thread mode accepts only conversation row kinds and rejects unknown/conflicting fields; - thread bounds bind the root and authenticated reader because cross-channel aux visibility can differ by reader; - thread aux includes the root and requires complete two-hop closure; - thread mode refreshes writer authorization before selection and before signing; - historical cursor pages may use a proved replica transaction, but fallback must restart both aux hops on the writer; - legacy thread traversal is oldest-first and must not inherit descending cursor semantics. Forcing these rules into channel-window code would create a mode flag across parsing, SQL predicates, bounds construction, authorization, replica fallback, and tests. That abstraction would not remove complexity. It would distribute it. Separate modules keep each contract local and leave the established channel and legacy paths untouched. ### Technical design - Strict request parsing normalizes UUIDs, event IDs, kinds, defaults, and cursors before computing the response binding. - SQL applies community, root, channel, depth, kind, deletion, and cursor predicates before `limit + 1`. - Ordered metadata scanning plus exact event-key lookup avoids the stale-statistics plan that previously exceeded the four-second SQL budget on a 10k-reply thread. - Aux work is bounded by target batches, query count, raw candidate count, response bytes, SQL timeouts, and one shared HTTP deadline. - Fresh authorization-set comparison catches both grants and revocations during closure. The relay returns retryable `503` rather than signing an incomplete page. - Replica failure permanently degrades the request to the writer and restarts closure from its original targets without resetting budgets. - Migration `0049_thread_window_index.sql` adds the mixed-direction index used by newest-first scans. It validates a prebuilt index and bounds startup lock/build time. ### Tradeoffs - The stale-statistics-safe plan is intentionally less clever. Measured warm 100k deep-cursor execution increased from roughly 0.30 ms to 4.80 ms, plus about 8 ms planning, in exchange for avoiding a reproduced multi-second timeout after rapid 10k-thread insertion. - Bounds are authoritative only for the served page. There is no cross-page snapshot guarantee, and replica insertion coverage does not imply latest edit/deletion/aux visibility. - Strict parsing rejects unsupported constraints instead of silently serving a different row set. - Required aux closure fails closed. The relay does not omit aux and sign plausible-but-incomplete bounds. - This PR ships relay capability only. Client rendering, retry UX, live-history overlap, and compatibility fallback remain separate work. ### Related issue No existing issue found. This work follows the thread-loading design and review discussion. ### Testing - `cargo test -p buzz-core`: 258 unit tests and 2 doctests passed. - `cargo check --workspace --all-targets`: passed at the rebased implementation head. - `cargo test -p buzz-db`: 131 non-PostgreSQL tests passed; 275 infrastructure tests were ignored as declared. - Focused PostgreSQL migration/runtime regressions: 3/3 passed against an isolated PostgreSQL container, including both corrected migration-ledger tests. - `cargo fmt --all --check`: passed. - Pre-rebase feature validation passed 398/398 PostgreSQL tests, signed-wire multi-tenant/auth/pagination/aux/revocation probes, clean Compose boot/reboot, and full core tests. - Independent HA validation exercised three relay pods and a physical PostgreSQL standby on the same runtime logic before the final documentation-only cleanup. - Independent final-delta review confirmed exactly two changed files, byte-identical executable migration SQL after comments/diagnostic normalization, matching prebuild/catalog/recovery instructions, and `git diff --check`; no runtime receipts were relabeled to the new SHA. - The pre-push hook ran repository checks but its Rust unit lane exited 100 without exposing an individual failed test in the captured multiplexed output. The independently run affected-package checks above passed; current-head repository CI remains required. Known unrelated baseline behavior remains: the mesh-demo echo test can return 504, and same-second archive → unarchive can deduplicate its membership notification. Neither path is changed here. --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: cid <d9f92a72922bf45c17379a47d64dae84b6020397c2d5a52b5317d512068cd9d3@buzz.block.builderlab.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: cid <d9f92a72922bf45c17379a47d64dae84b6020397c2d5a52b5317d512068cd9d3@buzz.block.builderlab.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Fix Databricks V2 Unity Catalog Claude service routing without erasing verified model capabilities. - preserve exact provider-qualified record precedence for `data_workflow_tools.goose.goose-claude-opus-5-5`, including adaptive thinking, explicit `output_config.effort` (default medium), and signed native tool continuation - route other boundary-matched `claude-*` service components to Anthropic Messages while preserving the full FQN and advertising no unverified effort controls - keep GPT-5+ service components on OpenAI Responses and other uncurated FQNs on MLflow Chat Completions; catalog/schema text does not affect routing - keep Rust and TypeScript resolver precedence aligned and regenerate the 168-vector normative corpus Validation at `1204f1eacff17ebe05eb2bba06b5eee210e21c25`: - `cargo test -p buzz-agent` - `pnpm -C desktop test` — 6,628 passed - `pnpm -C desktop typecheck` - `cargo fmt --all -- --check` - `cargo clippy -p buzz-agent --all-targets -- -D warnings` - `just test-unit` — passed on isolated rerun - pre-push file-size, desktop check, desktop typecheck, desktop tests, and Tauri checks passed; the repository-wide Rust lane exited without identifying a failing test in two concurrent hook runs, while its isolated `just test-unit` rerun passed --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
…ammar (block#7844) New Databricks endpoints (Claude Opus 5.5, GPT-6 Astra/Sol, etc.) showed as raw FQNs in the model picker until someone added a curated corpus record for each. This adds a strict token-class grammar as a third label tier inside the Databricks label helpers (Rust `model_capabilities.rs` + TS `modelCapabilities.ts`): exact record → unique alias → **generated label** → raw id. In TS it fires only for an explicit `databricks_v2` provider; providerless ids stay raw. ## Grammar Takes the last component of a UC FQN (or strips one wrapper: `databricks-`, `goose-`, `kgoose-`, `builderbot-`; an id that is neither stays raw), splits on `-`, and classifies each token as version, letter+digit version, size, trailing date (dropped), or word. Labels are built only from the id's own text, and the grammar never reinterprets a version. When in doubt it returns raw: - **Known families only.** The family (leading letters of the first token) must be in the manifest's existing `label_family_tokens`. Custom endpoints such as `prod.ai.rag-agent-3` or `builderbot-pr-reviews-v2` stay raw. Supporting a new vendor means adding one entry to that list, not a record per model. - **Attached digits stay whole** (`gpt10` → `GPT10`). If the family already carries digits and the next token is a version (`goose-qwen3-5-122b-a10b`, `system.ai.llama3-1-8b`, `system.ai.claude3-5-sonnet`), the id stays raw; this is checked before any reorder. - A third consecutive number (`gemini-3-7-1-flash`) returns raw rather than splitting off a patch version. - Any non-ASCII input returns raw, checked before trimming or case folding. - Any unclassifiable token, or no version-bearing token (`builderbot-pr-reviews`), returns raw. `(latest)`/`Preview` never appear unless an exact record supplies them. Special cases, each justified by an unseen-id shape rather than the corpus score: - `meta-llama-` → `Llama`: a namespace convention. - Claude numeric-first reorder: Goose names Claude endpoints `claude-4-7-opus`. Only 1–2 version tokens followed by `opus`/`sonnet`/`haiku` reorder (`claude-5-6-opus` → `Claude Opus 5.6`); anything else keeps its order (`claude-5-6-thinking` → `Claude 5.6 Thinking`). Both current numeric-first ids also have exact records. - Qwen compact decimal: Qwen's own naming `qwen35` → `Qwen3.5` when the second digit is nonzero. ## Colliding labels in the picker The same model is often served from several sources, so humanized labels collide (four rows are all `GPT-5.5`). `disambiguateModelLabels` in `formatAgentModelLabel.ts` suffixes only colliding Databricks labels with their source: - UC FQN: its `catalog.schema`, e.g. `GPT-6 Astra (system.ai)` / `GPT-6 Astra (sandbox.public)`. - Workspace endpoint: its wrapper, e.g. `GPT-5.5 (databricks)` / `GPT-5.5 (goose)` / `GPT-5.5 (kgoose)`. - Still colliding after that (e.g. a date variant in the same schema): the full id, e.g. `Gemini 3.7 Flash (system.ai.gemini-3-7-flash-0925)`. Unique labels, raw-id rows, the default row, and other providers are unchanged. It runs in `getDiscoveredPersonaModelOptions` (covering `AgentDropdownSelect`, `PersonaModelCombobox`, `PersonaDropdownField`, whose closed triggers read the option label) and in `ModelPicker`. Model search in `AgentDropdownSelect` and `PersonaModelCombobox` also matches the raw id, so searching `system.ai` finds those rows. Rust `ModelEntry.name` is unchanged. ## What labels drive Picker values, persistence, routing, catalog dedup, `resolve()` and capabilities all stay keyed on the raw id; two ids sharing a label both remain selectable. Labels do drive picker search (alongside the raw id), the workspace catalog's equal-time sort, and `TeamIdentityCard`'s "Mixed models" summary, which compares lowercased base labels, so `goose-` and `system.ai` GPT-6 Astra read as one model there (correct, since they are the same model). ## Tests Shared fixtures in `scripts/databricks-label-fixtures.json` are replayed by both Rust and TS. Each row asserts its tier: `exact` rows match a record, `alias` rows resolve with generation off, `generated` rows return nothing with generation off (so a future alias or record cannot mask them), and `raw` rows return nothing either way. A curator guard requires that across all 57 exact records, the ids the grammar does not reproduce equal exactly the 10 curator-only ids. `disambiguateModelLabels` has unit tests for namespace, wrapper, and full-id-fallback collisions, untouched rows, other providers, and case-insensitive grouping. The defaults-picker e2e checks the suffixed rows and trigger, raw-id search, and that choosing the second uncurated FQN by its visible name persists its raw id and restores its label. An edit-agent e2e covers the existing-agent model dropdown (`#edit-agent-model`): both suffixed rows render, the `system.ai` row is chosen by name, and after save and reopen the agent stores the `system.ai` id and shows `GPT-6 Astra (system.ai)`. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
## Summary Upgrade Buzz from MeshLLM `v0.76.0-rc9` to the latest stable [v0.76.2 release](https://github.com/Mesh-LLM/mesh-llm/releases/tag/v0.76.2) (via v0.76.1). - Update all eight Mesh git dependencies across desktop and relay smoke examples; both lockfiles resolve all 47 Mesh packages to `a0c1e66b0ac037dd56544b9e2d94969ea43d694f` (tag `v0.76.2`). All non-Mesh lock records are unchanged by the 0.76.1 → 0.76.2 bump. - Match Mesh's `ed25519-dalek = 3.0.0` and `rusqlite = 0.40` dependencies. - Explicitly enable rusqlite's `fallible_uint` feature, preserving Buzz's existing `u64` SQL conversions when **mesh is disabled**. Mesh-enabled builds previously supplied this transitively, so testing both configurations matters. - Temporarily pin **only** the optional `sqlx-sqlite` package to the merged upstream compatibility fix [transact-rs/sqlx#4402](transact-rs/sqlx#4402), revision `94aafe3a68884d923b0798a767c8d7f6cfda89d2`. SQLx 0.9.0 caps `libsqlite3-sys` below 0.38, while Mesh 0.76 requires 0.38. Cargo resolves the conflict even with SQLite disabled. The fix raises that upper bound; remove the patch once released. Buzz's active PostgreSQL driver stays on crates.io SQLx 0.9.0. - Preserve the explicit Linux keyring 3 `crypto-rust` feature so Secret Service sessions retain DH encryption independently of Mesh keyring 4. - Preserve the dedicated examples-only `buzz-mesh-smoke` crate and lifecycle workflow wiring, keeping Mesh/native-runtime dev dependencies out of relay CI builds. - v0.76.1 → v0.76.2 upstream delta: self-updater fix for composed product-v2 bundles (Mesh-LLM/mesh-llm#1844) plus release plumbing — no API changes affecting Buzz. - Merged `origin/main` (`7c789dee0`) without rewriting shared PR history. ### Validation at `ae4adba15` (local macOS ARM64, Hermit toolchain) - **Passed:** `cargo test --locked -p buzz-mesh-smoke --all-targets` (all six example harnesses compile; no live harness execution). - **Passed:** desktop Tauri workspace tests, both default and `--features mesh-llm` graphs (3,296 tests with mesh enabled, 0 failures). - **Passed:** `just desktop-tauri-clippy` (both feature graphs, `-D warnings`), root workspace clippy `-D warnings`, `cargo fmt --all --check`. - **Passed:** `--locked` metadata resolution for both workspaces; lockfile diff audited to touch only Mesh package records. - **Passed:** full pre-push hook suite on the final push (branch-skew, rust-tests, desktop-tauri-checks, file-size-check) — no hooks bypassed. - **Not run:** live Mesh inference/admission smoke or installed-app UI exercise. CI lifecycle smoke coverage applies. Originating Buzz conversation: buzz://message?channel=dffbc117-845a-4c02-bc3f-eaeeefc4deaa&id=670f9c262e1ec0a4df5600b3173a0c5de2abe17e08ba9ccfdd8eff109fadf1a8 --------- Signed-off-by: Thinker <75d8a808fa21bb8d1812e080cf471db601c6e8e6a62dcd516d37531e83a7bb77@meshllm.communities.buzz.xyz> Signed-off-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Thinker <75d8a808fa21bb8d1812e080cf471db601c6e8e6a62dcd516d37531e83a7bb77@meshllm.communities.buzz.xyz> Co-authored-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Galadriel <b81c841ed20a8e17caf4e6fac2f6f3bc61d304c84b8394720f6d00f37538c47e@meshllm.communities.buzz.xyz> Co-authored-by: Gu <gu@buzz>
) Mobile heart and warning reactions and message text render as monochrome symbols because bundled Inter supplies U+2764 and U+26A0. Remove only those two character mappings from both regular and italic Inter fonts so Flutter uses native font fallback. No production Dart or Markdown parsing changes are needed. The reproducible font-processing script verifies that every other mapping and font table is unchanged, apart from the required checksum. Glyphs, metrics, language coverage and symbols such as © ® ™ ↑ ↓ − ∕ are preserved; the bundled Inter license is retained. Validated on an actual iPhone 17 Pro simulator running iOS 26.5. Reaction hearts and warnings remain centered within 0.33 logical pixels of the pill center. Native screenshot checks cover reactions and plain, emoji, explicit text-style, bold, italic, and enlarged emoji-only message hearts and warnings, plus pixel-identical preserved text samples. **Fallback behavior:** on iOS 26.5, plain ❤ / ⚠ and explicit text-style ❤︎ / ⚠︎ also render as color emoji. Native font metrics add one logical pixel to each of the three mixed-message lines in this fixture. Android visual behavior has not been validated. ### Reactions | Before | After | | --- | --- | |  |  | ### Message text | Before | After | | --- | --- | |  |  | Images are cropped native simulator screenshots of production widgets with synthetic content. Before uses the original bundled fonts; after uses the processed fonts. Added tests: the existing mobile CI test lane now checks both bundled fonts against the verified generated artifacts, preventing restoration of the old mappings or unintended font changes. Validation: the two asset tests, font-table verification, byte-identical regeneration, native screenshot regression checks, and Flutter analysis pass. The screenshot test fails against the original rendering. Pre-push checks, including the full mobile suite, pass. Full repository validation also passes. The enlarged emoji-only screenshot check passes and rejects the old normal-sized fixture. GitHub CI, Codex review, and the security review pass for commit 538167a. Both prior Codex findings are addressed and resolved. --------- Signed-off-by: Tom Brow <tomb@block.xyz>
🤖 ## Summary Scripts, scheduled jobs, and other automation need to give an agent one prepared task and wait for a result without starting a long-running chat listener. This adds `buzz-acp run`: it reads a task from a local file or stdin, runs one fresh agent session with the configured tools and instructions, cleans up, and exits with a machine-readable status. ```sh buzz-acp run --task ./task.json cat task.json | buzz-acp run --task - ``` - The versioned JSON task specifies a task ID, intended agent public key, prepared prompt, and execution time limit. Invalid input or an identity mismatch is rejected before agent launch. Credentials, executable, tools, and permissions remain controlled by trusted launch settings, not the task document. - Each invocation uses the normal agent equipment: base/persona/team instructions, enabled memory, tools, model and effort settings, permissions, and Git identity, credentials, and signing. Both task and conversational modes obtain these capabilities from one shared runtime owner; new shared setup belongs there rather than in separate entry points. - Launch configuration reads have a separate ten-second bound and can be cancelled even when a prompt file blocks. A deadline covers adapter startup through the task turn. Interrupts and termination signals trigger bounded cleanup. Stdout contains one terminal JSON record; diagnostics stay on stderr. A normal turn ending is not proof that the task achieved its goal. - Normal invocation remains the conversational service. Task mode does not start chat subscriptions, presence, heartbeat turns, or a persistent agent pool. Tasks are trusted executable instructions, not sandboxed input. Reusing a task ID does not prevent duplicate execution, and uncertain outcomes must not be blindly retried. See [the task contract](crates/buzz-acp/TASKS.md) for the document format, exit codes, limits, and examples. URL retrieval, a task server, and a persistent execution ledger are outside this change. ### Related issue None found in the existing issue and PR search. ### Testing - At `4e86bbc97`: 964 unit tests and 22 integration tests passed, with three existing ignored tests. The package suite ran serially with inherited harness/Git options cleared. A final test-only shutdown-result assertion was then checked with the focused memory test and all-target Clippy. - All eight real-binary task tests independently passed on the exact candidate, including blocked configuration reads, file/stdin validation, session equipment, deadlines, cancellation, and cleanup. The memory test passed 20 repeated runs. Removing the oversized-input guard fails the new error assertion; restoring it passes. - Package all-target Clippy, formatting, file-size checks, and pre-push Rust/Tauri checks passed. All executed hosted checks passed on the updated head (54 successful, 28 skipped). The separate Codex review comment still requests authorization for this exact range. - Earlier revisions passed the release build and a real Goose 1.51.0 tool-use smoke test. No new release artifact or installed Janet runtime is claimed for this feedback revision. - Repository-wide `just ci` was previously attempted; workspace Clippy passed, then the desktop dependency check stopped on a local pnpm fetch failure. Hosted checks cover the applicable PR lanes. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…lock#7850) Pre-push hook failures were hard to diagnose in two ways that cost agents repeated reruns. **nextest printed no failures.** lefthook runs the `rust-tests` lane under a pty, and with an agent shell's `TERM=dumb` nextest picks its progress-bar reporter, which then swallows every FAIL line, panic, and summary — the lane only says `error: test run failed`. An orphaned nextest (left behind when a push is killed) also spins at ~100% CPU polling the dead pty for keyboard input. `rust-tests` now sets `NEXTEST_SHOW_PROGRESS=counter` and `NEXTEST_NO_INPUT_HANDLER=1` via lefthook's per-command `env:`. **Stale `node_modules` looked like a code error.** After merging main moved dependencies, `desktop-typecheck` failed with `Cannot find module` and no hint to reinstall. `desktop-check`, `desktop-typecheck`, and `desktop-test` now set `pnpm_config_verify_deps_before_run: warn` (a shared YAML anchor), so pnpm prints `Your node_modules are out of sync with your lockfile …` ahead of the script output. It is scoped to these lanes and set to `warn` because the check also gates `pnpm exec`, and `just bump-desktop-version` leaves the workspace flagged until the next install, so `error` would block desktop release pushes. pnpm uses recorded workspace state and manifest timestamps and is not a complete lockfile-content check, so this is a hint rather than a guarantee. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
🤖 I’m Larry. ## Summary Managed-agent definitions can now choose which installed ACP transport will launch the agent. The choice sits beside **Agent harness** while creating or editing an agent, so it is made before first deployment and remains visible afterward. Buzz discovers executable commands named `buzz-*-acp`, keeps **Buzz ACP (default)** as the safe default, and preserves existing custom command values. Discovery and launch share the same resolver, so a command offered in the picker is the command Buzz will execute. ## UX walkthrough ### 1. The ACP command picker appears while creating the agent The default is explicit, and the helper text explains that this selection controls deployment.  ### 2. Installed wrapper commands are offered by name Here Buzz has discovered `buzz-janet-acp` alongside the stock transport.  ### 3. The wrapper is selected before deployment  ### 4. The deployed profile reports the effective command  ### 5. Editing the deployed agent reopens its definition with the choice preserved  ## Details - Definitions carry the selected ACP command through relay publication, snapshots, imports, team adoption, deployment, and later edits. - Existing definitions without this field continue to use `buzz-acp`. - Unknown persisted commands remain visible but unavailable rather than being silently replaced. - Discovery is read-only and never executes candidates. It filters non-files and non-executables, supports Windows `.exe`, `.cmd`, and `.bat` shims, deduplicates aliases, and returns a stable sorted list. - Command names remain portable; Buzz does not persist machine-specific absolute paths. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@nostr> Co-authored-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Upstream MinIO image pulls are blocking CI (block#7867). Add a separate publisher for `ghcr.io/block/buzz-minio:latest`, so the reusable image can be bootstrapped before CI adopts it in block#7870. - Build the checksummed official MinIO/mc release binaries from block#7868 on a digest-pinned Alpine base. - Build only when image/workflow/Compose inputs change or on manual dispatch. PRs build and smoke-test; only `block/buzz` main can publish. - Run the real Compose healthcheck and initializer, object upload/read/delete, and anonymous-access denial before publishing `latest` plus a unique `sha-<commit>-run-<id>-<attempt>` tag. - Manual dispatch disables Docker's layer cache to refresh Alpine packages. Normal image builds retain caching; each maintenance rebuild keeps its own tag. ### Rollout This PR contains only the publisher and opt-in Compose override. **It does not switch ordinary CI consumers**, so its checks do not require the unpublished image. This is a separate PR from adoption, as required by Buzz's squash-only merge rules. 1. Merge this PR; its main-branch workflow builds, tests, and publishes the image. 2. Make the `buzz-minio` GHCR package public and verify an anonymous pull of `ghcr.io/block/buzz-minio:latest`. New GHCR packages default to private. 3. Rebase draft block#7870 onto current main, retarget it to main, run its integration checks, and merge it to switch all MinIO-backed CI jobs to the published image. ### Validation - Updated hosted [MinIO image build and smoke test](https://github.com/block/buzz/actions/runs/36018177877) passed at `700ab6d5346b37a64c9defde0016dc65c98aa790`; publication correctly skipped on the PR. - Hosted [ordinary CI](https://github.com/block/buzz/actions/runs/36018178123) passed at the same head. Its actual Git comparison detected only the five publisher files, all application path filters were false, and the MinIO-dependent integration checks were correctly skipped. Synced current main before this run to eliminate an earlier stale-base comparison that included unrelated ACP changes. - Actual five changed paths were passed through the repo's pinned `dorny/paths-filter`: all application domains were false. All 12 existing path-selection cases and the required-context isolation contract also passed. - Publisher actionlint, smoke-script shellcheck, and commit/push hooks passed. - Fresh local rebuild of `700ab6d5346b37a64c9defde0016dc65c98aa790` passed with `docker buildx build --no-cache --platform linux/amd64 --load`. Logs confirm new Alpine index fetches and package installation rather than a cached install layer. The committed smoke script passed under local AMD64 emulation using the current merged Compose services with isolated resource names and no published ports: healthcheck, initializer/private bucket, object upload/read/delete, and anonymous HTTP 403. Temporary containers, network, and volume were removed. Both pinned binary checksums match the official release SHA-256 files. - Full `just ci` was previously attempted: workspace formatting/Clippy, desktop checks, and Tauri formatting passed; the run was stopped during Tauri Clippy. No full local application-suite pass is claimed; hosted application suites correctly skip this publisher-only change. --------- Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
…ead (block#7806) Mobile half of the sidebar stale-reader fix (desktop: block#7805). It makes the channel sections, sort, stars and mutes managers converge on the latest relay head. ## Changes - **Tie-break:** sections, stars and mutes compare `(created_at, id)` with `id < cursor`, which matches the relay's `ORDER BY created_at DESC, id ASC`. Sort already did this. - **Coherent cursor:** `_publish()` records the signed event ID through `SignedEventRelay.submit(onSigned:)` and advances `(_lastRemoteCreatedAt, _lastRemoteEventId)` together. It only advances when the pair is after the current cursor, so a newer head learned during the await is kept. This applies to sections, stars and mutes. - **Stars/mutes startup retry:** one serialized `_syncWithRelay()` (`_syncInFlight`/`_syncAgain`) with 2s→30s backoff. It retries until the history head is applied and the live subscription is settled. The stars/mutes managers no longer re-subscribe on CLOSED: `RelaySessionNotifier` owns retryable and rate-limited CLOSED, and a terminal CLOSED is never re-sent. Reconnect works through session replay and provider rebuild. Sections and sort keep their existing manager CLOSED retry. - **Stars/mutes durability:** storage `write()` returns the `setString()` result. A decode or apply error, or a persist that returns false or throws, fails the attempt. The accepted head only advances once it is durable, so the same head is retried. - **Stars/mutes pending fence:** each read snapshots `_localRevision` and `_publishPending`. If a local edit or a publish happened while the read was in flight, the result is discarded. Startup recovery retries it; a resume read does not. Per-entry arbitration is unchanged. - **Sections publish commits content with its cursor:** the submitted blob is committed together with its cursor, so a same-second loser applied during the OK wait can't strand the device. A local edit made during the wait is kept. - **Stars/mutes undecodable head:** an undecodable head is skipped without holding startup open. Only a persist failure keeps the head unapplied and retried. - **Resume re-read:** all four providers listen to `appLifecycleProvider`. On `resumed`, each manager runs one guarded history read through its existing fetch/apply path. It is one-shot: skipped when a local edit or publish is pending as it starts, not retried on failure, and it starts no timers or poll loop. The result is discarded if a mutation lands before it applies, and a disposed or superseded manager (sort generation fence) never persists it. ## Residuals - If the app stays in the foreground the whole time and a healthy socket drops an EVENT, the missed head is picked up on the next resume or reconnect. - Sections: a live event can still overwrite a pending local edit. This is pre-existing, and the resume read adds no new path to it. - Sort: a greater remote timestamp wins, by policy. - Stars/mutes: entries with equal `updatedAt` can still resolve differently on different clients, under the existing merge policy. - Stars/mutes: publish reads first and keeps the entry with the later `updatedAt`, so a peer whose clock runs fast can override a pending local entry. This rule already exists on main and matches desktop. - Sections and sort persistence failures are pre-existing and not redesigned here. ## Size Production: 10 files, +495/−162. Tests: 3 files, +718. The new `sidebar_sync_fixture.dart` runs over the real `RelaySessionNotifier` seam and is shared by `channel_reader_recovery_test.dart`. Plus a sections tie-break case. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
Switch every CI job that starts MinIO to the public `ghcr.io/block/buzz-minio:latest` image published by block#7869. Integration jobs pull the image; image builds stay in the separate, path-filtered publisher workflow. - Apply the CI Compose override to both desktop integration shards, backend integration, relay E2E, and mesh lifecycle, including script-mediated Compose calls. Both the server and bucket initializer use the Buzz image. - Override both MinIO images in Helm's quickstart CI values, covering the server, bucket initializer, and relay's wait-for-bucket init container in the gated kind installation. - Select integration and mesh checks when either Compose file changes, with regression coverage using the real pinned `dorny/paths-filter` action. ### Publication verified block#7869 is merged, and [the first main-branch publication](https://github.com/block/buzz/actions/runs/36021389137/job/107706874954) succeeded. An anonymous pull using an empty Docker configuration returned the publisher's digest: `sha256:b8470bbeafbf57b20c86cf63804682b714bdcfdbb517f3770247e321e623f48f`. This PR is rebased onto main and ready for consumer integration checks. ### Validation - Ran the repository's MinIO smoke script against the actual published AMD64 image: healthy startup, bucket initialization, object upload/read/delete, and rejection of anonymous reads all passed. Used the rendered CI Compose services with isolated local ports and resource names. - Verified `mc` runs and writes its configuration as Helm's non-root UID/GID 65532 with dropped capabilities and no privilege escalation. - Helm 3.16.4 lint and quickstart rendering passed; all three rendered MinIO container references use the Buzz image. - All 14 CI selection cases and the required-context isolation check passed. Mesh workflow passes actionlint; shared workflows pass with shellcheck disabled for pre-existing findings. - Fresh repository-wide review traced workflow and script consumers and found no remaining CI image omissions. Full application integration checks and a real kind installation have not been run locally. --------- Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
## Buzz Desktop release v0.5.25 - **Frozen main:** `797012ff01a6d499959b45ed2e56f7927c6a4d6b` - **Reviewed candidate:** `c8f73213089cbd5a0f1e675d3193558280d46e10` - **Previous desktop release:** `desktop-v0.5.24` - **Proposed immutable tag:** `desktop-v0.5.25` This PR may be **squash merged** after the Desktop Release Candidate check and all protected-branch checks pass. Merging authorizes publication of the exact reviewed candidate; later or unrelated changes on `main` cannot alter it. The checked-in changelog accounts for every non-merge commit in the release range. The Desktop tag points to the reviewed candidate commit, not the later squash commit. Publication remains bound to that immutable candidate tag. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Release Automation <release-automation@users.noreply.github.com>
## Why Buzz server components need one typed feature-flag contract without forcing the open-source build to depend on LaunchDarkly. A staging or production relay must not be able to choose the wrong provider through runtime configuration. ## What - Adds typed boolean and signed `i64` flags with static, environment, and compile-gated LaunchDarkly evaluators. - Defines an executable relay artifact contract that compiles exactly one provider. - Rejects zero or multiple relay provider features at compile time. - Runs the LaunchDarkly feature tests and the provider compile matrix in the unit CI lane and fallback test script. ## How `buzz-feature-flags` stays provider-neutral. The future relay composition root will expose one build path per artifact feature: `static-feature-flags`, `environment-feature-flags`, or `launchdarkly-feature-flags`. Runtime configuration carries only settings for the selected provider. It never carries a provider name, fallback, or precedence rule. The crate still has no relay consumer. A locked compile fixture proves each singleton builds and every zero or multi-provider combination fails until the relay adopts this contract. This removes the proposed runtime `ProviderMode` and leaves one provider, one construction path, and one evaluator per artifact. ## Risk This PR still has no relay or app consumer, so it changes no user-visible behavior. Risk is limited to the new feature-flag crate, its compile contract, and its unit-test lane. ## Testing An external Rust consumer at `30cf968bfe85e82980fda7e63f5bebe649ca939f` exercised six environment-evaluator scenarios and 59 assertions. The compile-time correction adds no live consumer to exercise manually. ## Bigger picture When `buzz-relay` adopts this crate, its build manifests must map each distribution to one provider feature. That consumer PR must verify live Relay Proxy reachability, secret ingestion, lifecycle ownership, and user-visible behavior. Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ock#7873) Follow-up to block#7806. - **A retired sections manager never writes the shared prefs cache.** `_persist()` now returns once the manager is disposed. That covers every path a torn-down manager could take after an account or relay switch, including a late OK restoring its submitted layout and the `dispose()` flush's read-before-write merging a head. Before this change, both could overwrite the successor's cache on the same pubkey key. - **Coverage:** - a retired late OK and a retired flush pre-read both leave the successor's cache untouched - an edit made while an OK is held still publishes after the OK is released (`_lastPublishedStore` is built from the submitted store) - a same-second losing head adopted after a local edit still converges UI, cache and relay once our OK wins. The adopted layout's pending debounce is deliberately left armed, because its republish is what re-converges this race. **Residual:** an edit made just before our own echo arrives can be replaced by that echo. This is existing behaviour on main and on desktop. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
## Summary Move Invite, Appearance, and Remove community into a **Community settings** section in the community-avatar sheet, above the existing switcher. Keep the sheet untitled and use circle-minus for removal. Personal Settings groups **Set status** and **Copy public key (npub)** above an untitled card containing **Display name**, **Profile description**, and **Edit photo**. Remove the leading icons from status, public key, and Send identity to desktop; keep the latter in its own untitled card. Remove the avatar status badge and top Edit action, and rename the Theme page to **Appearance**. Opening the presence chip triggers selection haptics; copying plays native iOS success feedback and confirms with “Public key (npub) copied.” ### Testing - Mobile formatting, analysis, and file-size checks passed. - 111 affected widget tests passed; all 48 settings tests passed again after the final labels and icon changes. - Final release build installed and launched on a physical iPhone in the separate Buzz Community preview, retaining its existing test account. - Full repository `just ci` was blocked by local disk exhaustion during Rust compilation. The full mobile suite passed (2,609 passed, four skipped), plus three push-unconfigured tests. The presence and copy feedback passed focused widget tests and analysis, including native iOS success-channel coverage. ### Snapshots Light and dark Flutter widget renders with fixture data. Images are GitHub attachments, not committed to the repository. The personal-settings snapshots precede the final “(npub)” label clarification. | Surface | Light | Dark | | --- | --- | --- | | Community sheet | <img src="https://github.com/user-attachments/assets/d52a192e-bd40-4263-ac2d-05d589c7ebf2" width="280" alt="Community sheet, light"> | <img src="https://github.com/user-attachments/assets/8756ebe9-b6d4-4920-8fb7-1cb7ee8ef778" width="280" alt="Community sheet, dark"> | | Personal settings | <img src="https://github.com/user-attachments/assets/962b98bd-e4cf-4cd1-8948-69b349390ceb" width="280" alt="Personal settings, light"> | <img src="https://github.com/user-attachments/assets/223bb0a8-e10e-43b1-83ea-e0955af4627a" width="280" alt="Personal settings, dark"> | | Appearance | <img src="https://github.com/user-attachments/assets/334ba797-55cb-47ce-b677-0befafb7659b" width="280" alt="Appearance, light"> | <img src="https://github.com/user-attachments/assets/79b62503-d476-4dbc-8c75-8cee0ee37e98" width="280" alt="Appearance, dark"> | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
) ## Summary Use UIKit navigation bars across iOS pages, with large titles that collapse into the toolbar on scroll and a light native blur. Home uses the active community name as its title and shows community and profile avatars with centered emoji and stable loading, without flashing back to icons. Channel headers retain the member count, tap-to-settings title, and Huddle action. Their ultra-thin native material has a soft lower edge and stays visible at the timeline bottom. The hidden title-layout scroll view has its automatic edge effects disabled so it cannot add a second backdrop. | Expanded Home | Scrolled Home | Settings | | --- | --- | --- | | <img src="https://github.com/raw/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--home.png" alt="home" width="240"> | <img src="https://github.com/raw/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--scrolled.png" alt="scrolled" width="240"> | <img src="https://github.com/raw/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--settings.png" alt="settings" width="240"> | Channel title, member count, and Huddle control: <img src="https://github.com/raw/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--channel.png" alt="Channel navigation" width="280"> ### Related issue No matching open issue found. ### Testing - Added causal regressions for Profile/Edit Photo native titles, metrics-only collapse changes, and native-header suppression throughout channel/thread reaction and action backdrops. Each regression fails when its corresponding fix is removed. - Mobile formatting and analysis passed. Full mobile test rerun passed: 2,622 tests, 4 skipped, plus 3 push-disabled build tests. - Native iPhone Air regression passed: repeated title expansion/collapse and scroll material behavior. - Navigation and blur were accepted on the connected iPhone using the existing profile. Simulator snapshots use real community data. The subsequent review fixes have widget coverage; fresh physical-device verification of Profile states, Dynamic Type changes, and message backdrops is pending. - Full repository `just ci` was blocked by local disk exhaustion while compiling Rust dependencies. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
…on (block#8064) 🤖 Restores `docs/enterprise-identity-adapter.md`, the vendor-neutral HTTP contract between Buzz Desktop and an enterprise identity adapter, and adds the request that turns an adapter session into a NIP-FI relay assertion. The login, code exchange, session check, transport, and privacy sections are byte-for-byte the text from the head of block#7715 (`4ef08e23`). Buzz Desktop's enterprise login client ([buzz-app#525](block/buzz-app#525)) already calls those routes. The new **Relay assertion** section defines `POST /v1/identity/assertions`: - **Request:** a JSON body with `relay_url` and `nostr_pubkey`. The adapter session goes in `Authorization: Bearer`, and a NIP-98 proof signed by that key, bound to the endpoint URL, method, and exact body hash, goes in `Nostr-Authorization`. Clients and adapters must not log either header's value. - **Response:** `assertion`, `nostr_pubkey`, and `expires_at`. Under this contract an assertion lives at most 5 minutes and never outlives the adapter session; that cap is the adapter contract's rule, not a relay constant. The assertion's `typ` and `aud` rules point to NIP-FI. The assertion key must be the key that signs the NIP-42 relay login. - **Denials:** a table of status, error code, meaning, and the client action for each. A missing session is 401 `session_required`; missing or repeated proof headers, a repeated `Authorization` header, multiple session credential kinds, malformed bodies, and unknown fields are 400 `invalid_request`. 401s clear the session and return to login; 403 `authorization_denied` keeps the session and disconnects from that relay without automatic retry; other 403s, 400, and 413 keep the session and show the error without retry; 429, 503, and network failures retry with bounded backoff. Adapters may return `session_required` for any invalid, expired, or revoked credential, and clients handle both 401 codes identically. Agent keys are out of scope and need a separate design. Related: [cash-server#132756](squareup/cash-server#132756) implements this contract in kgoose. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Fix iOS navigation title taps and truncation, theme-aware blur (including chats opened at the newest message), and the search focus animation with a plain, correctly spaced Cancel button. Simplify DM details and header actions, move profile descriptions below names, and remove redundant Settings/Add Community titles and unnecessary “See all” links. Validation: mobile analysis, formatting, file-size checks, and the full mobile test suite passed in pre-push hooks; Release iOS build installed and launched on a physical iPhone. Full `just ci` was stopped during unrelated desktop compilation due to low disk space. Native XCTest additions have not been run locally. Signed-off-by: kenny lopez <klopez4212@gmail.com>
Keep member administration reachable when small channels omit “See all.” Tapping a member now opens profile-sheet rows to make a channel admin, change an admin to member, or remove the person with confirmation, using the existing grouped list style. Actions follow the live roster, respect channel permissions, prevent duplicate submissions, and surface failures. Existing guests can become members or admins; owners, self, archived channels, and DMs do not expose these controls. Validation: mobile analysis, formatting, file-size checks, and the full mobile suite passed. Release iOS build installed and launched on a physical iPhone. Full repository `just ci` was previously stopped for low disk space during desktop compilation. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary Replace the community switch sheet with a two-column picker. Selection enlarges the avatar, waits with the voice-note loader for destination channel content and layout to settle, then follows a curved path into the header. Cache raster icons at a maximum 512-pixel size and accept only resource-free emoji SVGs. Cancel timed-out or disposed downloads without delaying content readiness. Animate Edit badges in place, and open Add Community on the pairing welcome page. Community removal lives only in the switcher’s Edit mode. Keep the flow shared across Android/iOS, with native iOS controls and removal confirmation, and clear navigation backgrounds at rest. ### Related issue Related to block#8070 (community icon refresh/caching). ### Testing - 122 focused Flutter regression tests and seven native iOS tests passed after merging the latest navigation changes from `main`. Earlier feature build installed and launched on a physical iPhone. - Full mobile pre-push checks passed; regression coverage includes gradient rendering, rejected/oversized icons, bounded persisted raster decoding, SVG resource rejection and cache migration, timeout/disposal cancellation, disconnected destination channels, degraded unread-history settlement, and switching while icons refresh. Full `just ci` stopped during Rust checks for low disk space. Hosted CI remains required. - [Grid and Edit previews](block#8076 (comment)). --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary Use native Liquid Glass for the iOS tab bar and expanding + menu, preserving the existing Lucide icons, icon-only layout, badges, accessibility labels, and tab interactions. The + uses black-tinted glass with white controls; the selected tab has a subtle theme accent (6% blended into the existing selection color). Android retains its current styling. ### Related issue No duplicate issue found. Related prior navigation work: block#7281 (closed). This change covers the tab bar and quick actions; it does not add page-to-page navigation-bar morphing. ### Testing - `just mobile-check` passed (formatting, analysis, and file-size checks). - `just mobile-test` passed: 2,678 tests passed, 4 skipped; the separate unconfigured-push suite also passed all 3 tests. - Widget coverage verifies native glass parameters, black + tint, original icons, icon-only layout, tab selection, badges, and quick-menu open/close behavior. - Signed Release iOS build installed and launched on a physical iPhone; the final glass and tint styling was reviewed on device. No final-app screenshot was captured. - Full `just ci` was attempted but stopped during desktop compilation as available disk space dropped to 3.5 GiB. The complete repository-wide gate is not locally verified; hosted CI remains pending. Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary Use rounded, grouped rows consistently across mobile sheets and menus, including Members and list pickers. Keep sheet content above the system gesture area with 4dp of extra bottom padding on Android and iOS. ### Related issue No duplicate found; related member-picker work: block#4051. ### Testing - Mobile analysis, formatting, file-size checks, and full mobile test suite pass. - Android build installed and launched on Pixel 10. - Before/after Members previews use sample data (attached below). - Full `just ci` stopped during Rust compilation because local disk space ran out. Signed-off-by: kenny lopez <klopez4212@gmail.com>
…8082) ## Summary Align Android channel and DM headers with iOS: centered, tappable titles with presence or member counts and no avatars. Keep conversation navigation frosted through keyboard and scroll changes, and run the typing shimmer left to right. Add an Android reaction details sheet with All/emoji tabs, swipe navigation, and compact rows aligned to the tab gutters. Native iOS reaction presentation is unchanged. ### Related issue None found. ### Testing - Mobile analysis, formatting, file-size checks, and full mobile tests passed. - Android debug build installed and launched on Pixel 10. - Full `just ci` could not complete: disk space exhausted while fetching an unrelated Rust dependency. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
…8079) ## Summary - Keep neighboring carousel photos visible, add page-change haptics, and reuse appearance-style dots with native iOS glass and grouped actions. - Refine full-screen video controls with fading transport, cycling speeds, pinch-to-zoom, and double-tap reset before swipe-to-dismiss. ### Related issue N/A. Checked recent open PRs; repository issue search was unavailable. ### Testing - Mobile analysis/format checks, file-size guards, and full Flutter suite passed (2,680 tests; 4 skipped). - Built, installed, and manually verified on the connected iPhone. - `just ci` was attempted but blocked fetching a Rust dependency because this Mac could not connect to GitHub. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: klopez4212 <klopez4212@gmail.com>
…lock#8083) Fill mobile loading views across the screen and use known image, video, voice-note, and file shapes in message placeholders. Freeze each loading cycle’s layout so arriving metadata cannot reshuffle the shimmer just before content appears; preserve cached Home and forum content during reconnects. Validation: mobile analysis and full test suite passed (2,752 passed, 4 skipped), plus 3 unconfigured-push tests and file-size checks. Installed and launched the loading changes on Pixel 10 before the latest main merge. Full `just ci` was stopped during desktop checks due to low disk space. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
Wrap channel and DM titles in native glass and remove the temporary-channel clock. Apply a soft status-area fade across iPhone navigation, and fade the large title with scroll progress and fade in the centered title, with Reduce Motion support. Validation: mobile formatting/analysis and the full mobile suite passed. Native iOS 26/27 simulator checks covered actual text opacity, header resizing, quick reversal, and restored heading position; signed iPhone build, installation, and launch succeeded. `just ci` was blocked by disk exhaustion during Rust compilation. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
Fixes block#7558. Supersedes block#7630. The SQL predicate here comes from block#7630 by @olympusbuildz, who is credited as co-author on the commit. This PR keeps that fix unchanged and adds the lifecycle fixtures and worker-path coverage the fix needed. ## Problem `community_hosts_with_operation` ran `SELECT id, host FROM communities` with no filter. After a whole-community deletion, the `communities` row stays behind as a `tombstone` until retention expires, which is by design. But the NIP-43 maintenance sweep kept handing that community to `publish_nip43_membership_list`. The community write fence rejected the write, so the relay logged `NIP-43 membership reconciliation failed` about every 60s, indefinitely, for every deleted community. ## Change The helper is split by caller purpose: - `active_community_hosts` (maintenance) and `bootstrap_community_hosts` filter to `archived_at IS NULL AND deleted_at IS NULL AND deletion_state = 'active'`. That's the same active-community predicate already used in `community.rs` and `deletion.rs`. - `usage_community_hosts` still enumerates every row, exactly as on main. The startup large-roster reconciliation query (`list_large_channel_rosters_needing_reconciliation`) joined `communities` without any lifecycle filter, so it gets the same active predicate on its `JOIN communities`. Tombstone rows aren't modified. ### Caller impact | Caller | Enumeration | Effect | |---|---|---| | NIP-43 maintenance sweep (`side_effects.rs`) | active | Stops trying fenced writes and logging warnings forever. This is the bug fix. | | NIP-43 bootstrap reconciliation | active | Same as maintenance, at startup. | | Push-wake worker (`push_runtime.rs`) | active | Stops claiming wakes for these communities. Archived communities are unreachable through host resolution; quiescing, fenced and tombstone writes are DB-fenced. Wakes already queued for an archived community aren't delivered late after unarchive, because claim and send both re-check `expires_at`. | | Startup large-roster reconciliation (`side_effects.rs`, kind 39002) | active | Stops rewriting roster snapshots for archived communities and attempting fenced writes for deleting or tombstoned ones. | | Usage-metrics host map (`main.rs`) | all rows | Unchanged. Per-community labels, fleet totals and storage attribution keep covering the same rows as the unfiltered usage count queries. | ## Tests block#7630's test tombstoned a row with a plain `UPDATE`. On current main, the `enforce_community_tombstone` trigger from migration 0029 rejects that unless the transaction carries the deletion-executor GUCs. The fixtures here set those GUCs the way the executor does. - `buzz-db` `store::usage::postgres_tests::test_community_hosts_excludes_archived_and_deleted_communities` sets up active, archived, quiescing, fenced and tombstoned fixtures. Across two sweeps it checks that `active_community_hosts` (maintenance) and `bootstrap_community_hosts` both return only the active community, and that `usage_community_hosts` still returns all five. It also checks that the tombstone row still exists with `deleted_at` set. - `buzz-relay` `handlers::side_effects::tests::postgres_tests::maintenance_sweep_skips_archived_and_deleted_communities` is the worker-path regression. It runs `reconcile_nip43_membership_snapshots_with_purpose(Maintenance)` three times against the same five fixtures. It checks that `buzz_nip43_membership_reconciliation_failures_total` stays at 0, the active community gets exactly one membership snapshot, the four non-active communities get no maintenance write, and the tombstone row is still there. - `buzz-db` `store::channel_members::postgres_tests::large_roster_reconciliation_skips_archived_and_deleted_communities` seeds a channel whose roster has diverged from its live relay-authored kind-39002 snapshot in each of the five fixture communities. Only the active community's channel may be returned. All three tests are `#[ignore = "requires Postgres"]` inside `postgres_tests::`, so the PostgreSQL CI lane picks them up through its existing structural filter. ## What got simpler The background writers share one definition of "community eligible for maintenance": the same active predicate the rest of the store uses. Usage metrics keep their existing semantics, so this PR changes no metric. One enumeration helper is added (`active_community_hosts`), and the old `community_hosts_with_operation` indirection is gone. The executor-GUC lifecycle fixture lives once in `test_support` and is shared by both DB tests instead of being duplicated. --------- Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Olympusbuildz <Olympus.roots@outlook.com>
## Summary Enter a desktop-only, random six-digit code on mobile to pair without a desktop confirmation click. Verification uses the encrypted session, limits guesses to five, and never treats the QR-derived transcript as approval. Older clients retain desktop confirmation. Adds digit-only error shakes and native haptics, a separate biometrics choice, and a direct community-icon arrival animation. Fixes dropped early offers and resets completed pairing so reopening or signing out cannot get stuck. ### Related issue Related: block#8060; no duplicate code-entry PR found. <img width="780" height="1688" alt="01-enter-code" src="https://github.com/user-attachments/assets/a99ced3e-4305-4ac6-a6cc-d598ced423e7" /> <img width="780" height="1688" alt="02-incorrect-code" src="https://github.com/user-attachments/assets/19cdb109-1f10-40a9-a661-1d7ba89c81c1" /> <img width="780" height="1688" alt="03-biometrics" src="https://github.com/user-attachments/assets/af1b2924-9ab6-4d1a-b4b1-fccfdf446404" /> <img width="780" height="1688" alt="04-community-loading" src="https://github.com/user-attachments/assets/84145d82-843a-4c25-aef7-6492fc241d4b" /> <img width="897" height="388" alt="05-desktop-code" src="https://github.com/user-attachments/assets/d8a2e469-7e42-466a-988f-bab6b1df85fc" /> ### Release order Deploy the updated pairing relay first, then desktop, then mobile. The relay now allows eight events per connection (seven for a successful fifth guess plus room for cancellation), with a corresponding bounded delivery budget. Existing clients remain supported; older client combinations keep desktop confirmation. ### Testing - 103 mobile pairing tests, 270 core tests, 51 relay integration tests, and the new real-relay fifth-guess flow passed. - The real-relay regression verifies proof, decrypted identity import, and completion after four wrong guesses. Restoring the old six-event cap makes it fail. - A paused-clock desktop regression confirms slow readiness cannot extend the visible QR beyond protocol expiry. Mobile regression covers typing before negotiation completes. - Core/pair-relay Clippy and full native desktop Clippy/workspace tests passed. Mobile analysis passed; broad mobile runs each hit an unrelated failure (a gateway-script timeout and a native-menu missing-plugin error), and both affected test files passed separately. - Repository-wide Rust testing exhausted local disk space while compiling unrelated packages. The final push omitted that resource-blocked hook and avoided repeating the desktop/mobile runs above; hosted CI remains required. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
Allow operators to configure migration Pod annotations independently of runtime Pod annotations. This lets clusters with automatic sidecar injection opt the migration Job out without changing the gateway Deployment or Helm hook metadata. The new string-map setting defaults to empty. Chart version 0.3.3 is prepared here; publication requires a separate approved chart release PR. ### Validation The full chart render suite and focused migration annotation tests pass. Tests verify unchanged default resources and hooks, independent runtime annotations, and rejection of non-string annotation values. Independent agent review found no blockers. Repository-wide `just ci` exited 1 in unchanged `buzz-acp` tests: idle/stdout keepalive timing and steering assertions (one failure in the first group, four in the second). Subsequent recipes were not reached. No rerun or unrelated code fix was attempted. Human testing remains pending. Signed-off-by: Tom Brow <tomb@block.xyz>
🤖 ## Summary Some agents can take a new message into a turn that is already running. This is called "native steering": the agent keeps working and folds the new message in, instead of stopping and starting over. block#6132 made this safer in channels: a message is steered in only when it belongs where the running turn will reply. In a channel, each top-level message gets its own reply thread, so a second top-level message must not be steered into a turn that replies in the first one's thread. That rule also ran in DMs, where it does not fit. In a DM, a top-level message is answered at the top of the conversation, not in a new thread. So every top-level DM message replies in the same place. But the rule treated each one as its own thread. As a result, **every DM follow-up sent while the agent was working cancelled and restarted the turn**, instead of being steered in as it was before block#6132. Now: - **DMs:** a follow-up is steered natively when it replies in the same place as the running turn: both top-level, or both in the same thread. A thread reply during a top-level DM turn (or the reverse) still takes the cancel-and-restart path, because the running turn would reply in the wrong place. - **Channels:** unchanged from block#6132. ## Details - The queue now records each running turn's reply route: its thread root (or none) and its reply thread. One function, `ReplyRoute::accepts_steer`, decides whether a new message may be steered in. It compares thread roots in DMs and reply threads in channels. - The DM rule mirrors how the prompt is built: a top-level DM's `<context>` has no `--reply-to` target, and a threaded DM's target is in its thread. - The steer decision must agree with how the running turn's prompt was written. The prompt task records whether it writes the prompt as a DM, and the steer decision reads that recorded value. So if channel metadata was missing when the turn started (the prompt then uses the normal channel form) and loads later, the DM rule is not applied to that turn. - The prompt task records this value as soon as channel metadata resolves, before session setup. A follow-up sent during a slow `session/new` is steered in, as on main. When an `initial_message` setup prompt runs first, the value is recorded after that setup turn, because the setup prompt reads the same steer mailbox. - Before the value is recorded, the steer decision uses the listener's own channel classification. A channel the listener knows is not a DM is always written in channel form, so the channel rule applies at once, exactly as on main. A DM, or a channel whose type is not resolved, takes the cancel-and-restart path until the value is recorded. ### Related issue Follow-up to block#6132 (merged). Found in review after merge. No separate issue. ### Testing - New tests drive DM messages through the listener's real steer decision with a running turn. A second top-level DM message is steered natively with no cancel. A DM thread reply during a top-level DM turn is not steered and takes cancel-and-restart. - Direct tests of the steer rule cover channel and DM cases: same thread, different thread, top-level after top-level, and top-level mixed with thread replies. - With the DM rule disabled, the DM rule test and the top-level DM listener test fail. - Listener tests also cover a DM turn whose prompt was written as a channel (top-level follow-up is not steered) and a DM follow-up that arrives before the value is recorded (not steered). - Production-path tests dispatch a real turn through `dispatch_pending` and `run_prompt_task` to an agent that holds at `session/new` or `session/prompt`, then send a follow-up through the real listener. A top-level DM follow-up during the prompt or during `session/new` is steered with no cancel. A DM follow-up before an `initial_message` setup turn is not steered. A same-thread channel follow-up during setup is steered with no cancel. - Mutation checks: each fails its test. They are: not recording the value in `run_prompt_task`, passing a detached value from dispatch, not recording it before session setup, recording it before an `initial_message` turn, and removing the known-channel rule for the unrecorded case. - `cargo test -p buzz-acp` passes on the rebased head, with the timing-sensitive `acp::tests` run alone (`--test-threads=1`; this PR does not change them, and they fail only under full parallel load on this laptop). `cargo clippy -p buzz-acp --tests -D warnings` and `just desktop-tauri-clippy` are clean. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
## Summary By default on linux kimi is installed at `~/.kimi-code/bin/kimi` and this was not detected automatically. This now fixes it by adding this path to the detection ### Related issue N/A ### Testing Before it did not appear / did not detect. Now it does :) Signed-off-by: benthecarman <benthecarman@live.com>
…#4686) Fixes block#4384. A dim-less markdown image with no cached decode gets `DEFAULT_IMAGE_RESERVE = { height: 256, width: 384 }` and `useFixedReserveBox: true` in `resolveImageReserveBox`. The frame is that big on first view, and the inline `<img>` uses `object-contain` which scales UP to fill the frame — so a 46×20 shields badge renders as a giant blurry block. The same message renders at the natural size on the second view because `rememberDecodedImageDimensions` populates the cache, but the inconsistency between first and subsequent view is the bug. Switches `object-contain` → `object-scale-down` on `ProgressiveImage.tsx`'s shared `IMAGE_CLASS`. Both `object-contain` and `object-scale-down` scale down to fit, but only `object-scale-down` also refuses to scale up — a small image now sits centered in the reserve box on first view, never upscaled. The layout-shift guarantee for large images is preserved (they still scale down to fit the reserve), matching the issue's stated constraint. Chose option 2 ("Alternatively/minimally") from the issue over option 1 because: - It's a one-class-name change with zero new state, zero new effects, and zero risk to the existing layout-shift guarantee that the surrounding `useFrozenImageReserve` is explicitly designed to preserve. - Option 1 (collapse the frame on decode) is the issue author's preferred direction but requires deciding whether the layout shift is acceptable *shrink*-only — a direction question that should be confirmed with the maintainer first. The issue explicitly says "Happy to open a PR for option 1 if the direction is acceptable" — that's the sign-off step, not this PR. If maintainers want the full first-view-equals-second-view behavior, option 1 is a follow-up that builds on this base. Not added: a regression test. The current desktop test infra (`desktop/src/shared/ui/markdown/markdown.test.mjs` and siblings) doesn't cover `ProgressiveImage` directly; a DOM-style snapshot would need a new test harness. The change is one CSS class name; the visual regression is plain to eyeball in the screenshots CI already captures. Verified: - `pnpm check` (biome + file-sizes + px-text + pubkey-truncation) — clean - `npx tsc --noEmit` — clean Signed-off-by: kushaim <carlossilvajimenez@gmail.com> Co-authored-by: kushaim <carlossilvajimenez@gmail.com>
Prepares publication of the Buzz push gateway chart 0.3.3, including the optional migration Pod annotations already merged in block#8105. This PR adds release notes only; chart templates, values, and schema are unchanged from the reviewed implementation. Merging the `push-chart-release/0.3.3` branch triggers the normal release workflow: create `push-chart-v0.3.3`, validate the chart, then publish `oci:https://github.com/proxy/ghcr.io/block/buzz/charts/buzz-push-gateway:0.3.3`. Keep this PR in draft until publication is explicitly authorized. It does not deploy a gateway. ### Validation Focused migration annotation renders, release contract checks, and local chart packaging pass. The merged implementation passed chart CI and Jude's automated review in block#8105. The earlier repository-wide local `just ci` run failed in unchanged ACP tests; it was not rerun for release notes. The registry manifest digest can only be verified after publication. --------- Signed-off-by: Tom Brow <tomb@block.xyz>
Tightens `docs/enterprise-identity-adapter.md` to define the enterprise adapter contract implemented by kgoose in [cash-server#132770](squareup/cash-server#132770) and the Buzz Desktop client in [buzz-app#562](block/buzz-app#562). - Requires canonical `wss://host[:port]` relay URLs with a lowercase scheme, the lowercase ASCII (A-label) host form with no trailing dot, the default port omitted, and no trailing slash, path, query, or fragment. Adapters MUST configure relays in canonical form, compare `relay_url` against that exact form, and reject unknown relays with 403 `authorization_denied`. - Requires NIP-98 proof freshness of at most 60 seconds old and at most 5 seconds in the future, with the proof bound to the endpoint, method, and exact request body. - Rejects content-encoded request bodies with 400 `invalid_request`. - Caps assertions at `exp - iat <= 300` seconds and the adapter session expiry, requires `typ` `nip-fi+jwt`, and validates the echoed `nostr_pubkey`. - Defines refusal and retry handling: Clients MUST retry 429 and 503 with bounded backoff whatever the `error` code. Clients MUST treat any other status, a 400/401/403/413 response with a contract-undefined code, or a rejected `200` response including one it cannot parse, as a refusal: keep the session, show it as refused, and not retry automatically. Network failures retry with bounded backoff. This tightens the enterprise adapter contract restored in [block#8064](block#8064). --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz>
🤖 ## Summary The web and desktop app (`buzz-app`) saves a "caught up" mark when you scroll to the bottom of a channel and stay there, and another one for a thread. Mobile did not understand these marks. It relied on the separate per-message read marks that `buzz-app` also kept. `buzz-app` is about to stop keeping those per-message marks under the "caught up" mark (block/buzz-app#590), to save space in the synced read state. With this change, mobile reads the "caught up" marks with the same rules as `buzz-app`: - `activity:<channel>` reads ordinary top-level messages up to its time. It does not read thread replies, mentions, broadcasts or DM messages, because reaching the bottom of the channel does not prove you saw those. - `thread-activity:<root>` reads the replies in that thread up to its time. So a message you read in `buzz-app` stays read on mobile: in the app badge, the channel list unread state, the "new messages" divider when you open a channel, and the Mark read / Mark unread action. Mobile does not write these marks. It only reads them. ### Related issue None found. Companion to block/buzz-app#590. The old desktop app in this repo is intentionally not changed. ### Testing - New badge tests: `activity:` clears an ordinary top-level message, and does not clear a mention, a thread reply or a DM. `thread-activity:` clears replies in its own thread only. The two "clears" tests fail without this change. - New tests for the per-message read check and the "ordinary message" rule. - `flutter analyze`, `dart format` check and the full mobile test suite pass locally. - Not yet exercised on a device. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
…#8102) 🤖 ## Summary The mobile app's @mention chooser now follows the same rules as the desktop app (buzz-app). The same typed text finds the same people in the same order on both apps, and sending works the same way. - **Same matching and order.** Mobile uses the shared mention rules and runs their shared test cases. A multi-word search such as `@Mary J` still searches the community directory, so it can find "Mary Jane" even when she is not in the channel. - **DMs can mention people outside the DM.** The chooser marks them "not in DM". Nobody can be added to a DM, so the message sends without a prompt. Outside people go out as references, so they are not notified. This matches buzz-app block#510. - **Channels still ask.** In a stream or forum, mentioning someone outside the channel asks "Invite" or "Do nothing" before the message sends. A channel with no type tag counts as a stream. - **Sessions do not search the directory.** Only session members appear. - **A failed directory search stays visible.** The chooser shows the error and a Retry button. Before, it closed. - **Search results stay in their community.** If you switch communities while a search runs, its people and its error do not show in the new community. The search starts again there. - **A failed send check keeps the draft.** Before a message with a mention sends, the app checks who is in the channel. If that check fails, the message stays in the compose bar and a notice offers Retry. Before, nothing was sent and nothing was shown. - **Invite stays in its community.** If you switch communities while the "Invite" question is open, Invite adds nobody. ### Screenshots The real compose bar widget, rendered by Flutter's test renderer with the app's fonts and theme. The data is sample data. These are not device captures. | DM: outside person is "not in DM" | Stream: outside person is "not in channel" | |---|---| | <img width="320" alt="DM chooser" src="https://github.com/user-attachments/assets/0e884518-2386-4cd1-9f19-16661aaa194e" /> | <img width="320" alt="Stream chooser" src="https://github.com/user-attachments/assets/5395e4e1-032e-459b-98c6-0126c060c4d9" /> | | Stream: send asks first | Session: members only | Search failed: Retry | |---|---|---| | <img width="260" alt="Stream ask on send" src="https://github.com/user-attachments/assets/2b988257-3c6a-445d-acfa-fca47b9752fe" /> | <img width="260" alt="Session chooser" src="https://github.com/user-attachments/assets/e193551f-f5bf-41d9-aa29-bc8665c643f9" /> | <img width="260" alt="Search failed with Retry" src="https://github.com/user-attachments/assets/e8c4b9bb-067e-4fe9-9fdd-4e5691bc66be" /> | ### Details - `mobile/lib/shared/mentions/mention_rules.dart` holds the rules: query syntax, match tiers, sort order, Space selection of an exact unique name, stable rows while results arrive, and `p` and `mention` tags. A `p` tag notifies the person. A `mention` tag is a reference and does not notify. - `mobile/test/shared/mentions/mention-rules.fixtures.json` is an exact copy of the buzz-app fixture file. `mention_rules_test.dart` runs every case. - Known difference: desktop gives the directory to a channel with an unknown explicit type (not stream, forum, DM or session). Mobile does not. No such type exists today. ### Related issue None found. Desktop counterpart: block/buzz-app#510. block#7513 also edits `send_message_provider.dart` and may conflict. ### Testing `just mobile-install mobile-check mobile-test` passes: analyze clean, 2998 tests pass on `49c44653` (before rebase). The pre-push hook ran `just mobile-check && just mobile-test` again on the rebased head `de1ca080` and passed. Each review fix was mutation-checked: removing the fix makes its new test fail. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Resolve queue.rs purge-block conflict: keep upstream's event-collecting withheld_native_steer purge, retain our cancelled_redispatch_counts purge. Signed-off-by: dziduchm <dziduchm@gmail.com>
This was referenced Oct 30, 2026
matt2e
added a commit
that referenced
this pull request
Oct 31, 2026
Units 6-9 of the 23-30 Oct desktop Playwright flake audit. All are test-only. origin/main (1eb67bb, now including #8680) hasn't touched these specs since fae3f03. No open PR touches the tests changed here: #6134 (conflicting since 10-19) only swaps page.goto calls in three of these files, #8187's hunk starts just after workflow-local-controls:151, #5778 (draft, conflicting) creates projects-v3-screenshots from an older base, and #8622, #7452, #7812, #8244 and #3342 edit other tests in the same files. Unit 6: measuring before an animation finishes onboarding.spec.ts:1487, "hosted community address line stays within the card for a long name" Run: #8715 (Integration shard 1), which doesn't touch onboarding. Signature: the centring check at :1545 expects <= 2 and receives 5.13. Cause: "Connect and continue" ends in showPage("owned") (WelcomeSetup.tsx:323), which mounts an OnboardingSlideTransition. Its content slides in from translateX(48px) over 650 ms (components.css:521, :676). fill() doesn't wait for stability, and the surface, input and suffix boxes come from three boundingBox round trips, so they can be read at different points of the slide. Change: waitForAnimations(page) after the fill, before measuring. onboarding.spec.ts:2289, "connected first-community profile keeps navigation inside the card and balances the avatar editor" Runs: main 40746677244 and 40925172215, and #8666 (Integration shard 1). Signature: the spacing check at :2485 expects <= 4 and receives 4.255. Cause: the avatar dialog opens with MODAL_CONTENT_MOTION_CLASS, a 200 ms zoom-in-95 (modalMotion.ts:5), and has a 250 ms height transition. dialogBox is read in one round trip and the preview and editor boxes in a later one, so a scale change between them skews the gap comparison. Change: waitForAnimations(page) once the dialog is visible. projects-v3-screenshots.spec.ts:280, "projects v3 workspace screenshot states", failing in the expectInsetSection call at :893 Runs: main 39643531998, 40759209995 and 40969909906 (Smoke shard 8), each passing on retry. Signature: the header's right inset at :326 expects 16 and receives 8. Four more runs fail the menu edge check at :328 with fractional values (expected 984, received 975.6875): 39622053330, 39641311877, 40281448810 and 40856574572. Cause: switching the context rail back from chat to repository narrows it from the thread panel's 380 px to the repository's 280 px (useProjectPanelWidths.ts:5, :17), animated by transition-[width] duration-200 (ProjectContextRail.tsx:31). The workspace widens with it, and expectInsetSection reads the workspace, header and menu boxes in three separate round trips. The fractional widths are mid-transition values. Change: waitForAnimations(page) before that call, the only one that follows a rail width change. Unit 7: fill() racing a caret move the app makes a frame later mention-recipients.spec.ts:376, "editing to a longer typed member drops the original shorter reference", and :97, "ambiguous added mention blocks editing before clearing the draft" Runs: main 40969965981 (Smoke shard 5, a run with every earlier fix) for :376. 40969858676 for :97, before the fixes. Signature: the edit_message poll at :428 receives null. The error context shows the edited message as "@scout hello @scout Jones hello": fill() appended. Cause: entering edit mode sets the content, then focuses its end in a requestAnimationFrame (MessageComposer.tsx:413). toHaveText passes as soon as the content lands, before that frame. If the frame lands between fill()'s select-all and its typing, the selection collapses and the text is appended. #8682's replaceComposerText handles this, but these two call sites (:115 and :403) were missed. Change: replaceComposerText at both. workflow-local-controls.spec.ts:151, "inserts template variables with keyboard control and restores the caret" Runs: main 40926393897 and 40957616146 (before the fixes), and #8239, which changes no desktop files. Signature: :194 expects "Keep {{auth" and receives "Hello {{trigger.text}}Keep {{auth". Cause: selectVariable restores the caret in a requestAnimationFrame (WorkflowTemplateTextarea.tsx:66). Setting the controlled value has already put the caret at the end, so the selectionStart poll passes before that frame runs. The frame then collapses fill()'s select-all. Change: after the poll, wait two animation frames, then fill. This is a frame barrier, not a timer. The app queued its frame before the evaluate, so it runs first. Unit 8: clicks that hit the wrong element profile.spec.ts:1128, "renders agent profile ingress subviews from the Playwright mock bridge", failing at :1543 Run: #8523, which changes no desktop files. Its base (ef4130d) already includes #8681. Signature: archiveAgentRow.click() times out because three stacked Sonner success toasts (data-expanded="true") intercept the pointer. Cause: the test's Stop and Start steps raise success toasts (useAgentLifecycleActions.ts:51, :61). Sonner stacks them bottom right, over the profile panel's action rows, and pauses their dismissal while the pointer is over them. #8681 fixed this test's motion window, and fixed the toast problem only in agent-availability:602. Change: press the five action rows (:1528-:1547) through a local pressActionRow helper: toBeEnabled, focus, Enter, as agent-availability.spec.ts:744-745 does. toBeEnabled keeps the wait click() gave, since these rows are disabled while a delete or archive is pending. config-bridge-screenshots.spec.ts:314, "07 — profile side panel — Configuration section" Runs: main 40967147375 (before the fixes), and #8235, which changes no desktop files. Signature: "Model" isn't visible within 10 s at :352. The error context shows nadia's panel ("Not managed on this device"), not Charlie's. Cause: the spec clicked .last() of the rows with a [data-testid^="message-avatar-"] descendant. That matches only UserAvatar's -image and -fallback children. Radix renders the image only once it loads, and each fallback only after its own 200 ms delayMs timer (UserAvatar.tsx:67, :119). Neither avatar in #agents has a URL, so the matching set fills in row by row as the timers fire. Resolved in between, .last() is nadia's row (seeded at -85 s), just above Charlie's "reporting in." row (-80 s). The note blamed a misplaced click; the code points at locator resolution instead. Change: pick Charlie's row by its text, as profile.spec.ts does for Charlie's other #agents message. Then check the panel's name row reads charlie before opening Runtime, so a wrong panel fails there and says so. Unit 9: the createWorkflow copies (preventive) navigation.spec.ts:153, "direct workflow detail links close back to workflows" Runs: 7 failures on main, the last at 29 Oct 16:12Z, none after #8681. Signature: the Edit workflow dialog has no "Trigger: Message Posted" at :176. Runs: 14 first-attempt failures in Smoke shard 7, all passing on retry; on main 39827234118, 40718018673, 40789486699, 40802051064, 40840949170, 40891643205 and 40909291143. Cause: the same race #8681 fixed in workflows.spec's createWorkflow. Picking a channel opens the trigger pane (WorkflowDialog.tsx:702), and for Message Posted the conditions editor expands "Message text" by default (WorkflowTriggerConditions.tsx:157), rendering an input with that label. After "Send Message", AnimatePresence mode="wait" (WorkflowFormBuilder.tsx:907) keeps the trigger body mounted through its exit and holds the step body back. An unscoped getByLabel("Message text") can fill the trigger's filter. The step then has no text, which yamlToFormState rejects (workflowFormTypes.ts:348), so the Edit dialog opens in YAML mode (WorkflowDialog.tsx:88). That is the signature the earlier audit recorded for navigation:153. Change: wait for the step inspector's "Action" header and "Step details" control, then fill inside the inspector, as workflows.spec does. workflows.spec.ts:716 ("captures the built editor at desktop and narrow widths", fill at :726) and :820 ("pane routes use stable IDs and Form/YAML changes stay synchronized", fill at :831) pick a channel and then add a Send Message step the same way, with no failures in the window. So the wait moves into one addSendMessageStep helper in tests/helpers/workflows.ts, rather than four copies. Both specs' createWorkflow and those two tests call it. A change to the inspector's structure then needs one fix. Left out: workflow-reaction-picker.spec.ts:203-204. The note listed it, but its trigger is Reaction Added, whose condition fields are "Reaction emoji", "Author" and "Message" (workflowConditionExpression.ts:40). No trigger control there is labelled "Message text", so the unscoped fill can only reach the step's textarea. Verification Not run locally: Playwright bogs this machine down, and the Flake Check lane will repeat the touched specs. pnpm typecheck and pnpm check pass. biome's 3 warnings are in untouched files. An ad hoc tsc ran over the eight touched specs, using the desktop tsconfig with only those files, and over HEAD's copies of them. Both gave the same errors: onboarding 16, navigation 7, profile 7, workflow-local-controls 7, projects-v3-screenshots 2, and none in mention-recipients, config-bridge-screenshots or workflows. The messages are identical once line numbers are stripped, and all are pre-existing (no Node types, import.meta.env, MockBridgeOptions drift and the like). Once the helper moved to tests/helpers/workflows.ts, a rerun over navigation, workflows and the helper gave navigation's same 7 and none in the other two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e
added a commit
that referenced
this pull request
Oct 31, 2026
…kspace (#8744) ## Summary Units 6–9 and 14 of the 23–30 Oct desktop Playwright flake audit, in two commits. The specs now settle before they measure or act, and remote-owned-mentions clears its draft reliably. Everything is test-only. This is one of six PRs split from the reviewed `flakey-tests` branch (siblings below). ### Unit 6: measuring before an animation finishes Each spec now calls `waitForAnimations(page)` before measuring. - `onboarding.spec.ts:1487` (#8715, Integration shard 1; the centring check at :1545 receives 5.13): `showPage("owned")` slides content in from `translateX(48px)` over 650 ms. The spec reads its three boxes in separate `boundingBox` round trips, so they can come from different points of the slide. - `onboarding.spec.ts:2289` (main 40746677244 and 40925172215, and #8666; the spacing check at :2485 receives 4.255): the avatar dialog has a 200 ms zoom-in and a 250 ms height transition. - `projects-v3-screenshots.spec.ts:280`, at :893 (main 39643531998, 40759209995 and 40969909906, plus four runs with fractional menu-edge values): the context rail narrows from 380 to 280 px over 200 ms. ### Unit 7: `fill()` racing a caret move the app makes a frame later - `mention-recipients.spec.ts:376` and `:97` (main 40969965981 and 40969858676): edit mode focuses the end of the composer in a `requestAnimationFrame`. If that frame lands mid-`fill()`, the edit is appended instead of replacing the text. **Change:** use #8682's `replaceComposerText` at both call sites. - `workflow-local-controls.spec.ts:151` (main 40926393897 and 40957616146, and #8239): `selectVariable` restores the caret in a `requestAnimationFrame` after the spec's poll has passed. **Change:** wait two animation frames before filling. ### Unit 8: clicks that hit the wrong element - `profile.spec.ts:1128`, at :1543 (#8523): stacked Sonner success toasts intercept the pointer. **Change:** a local `pressActionRow` helper presses the five action rows with `toBeEnabled`, focus and Enter. - `config-bridge-screenshots.spec.ts:314` (main 40967147375, and #8235): `.last()` resolved while avatar fallbacks were still filling in on 200 ms timers, so it opened nadia's panel instead of Charlie's. **Change:** pick Charlie's row by its text, and check the panel's name before opening Runtime. ### Unit 9: the `createWorkflow` copies (preventive) - `navigation.spec.ts:153` (7 failures on main, none since #8681; 14 first-attempt failures in Smoke shard 7): an unscoped `getByLabel("Message text")` can fill the trigger's filter while `AnimatePresence` keeps it mounted. **Change:** wait for the step inspector, then fill inside it, as `workflows.spec` already does. The wait now lives in one `addSendMessageStep` helper in `tests/helpers/workflows.ts`, which `navigation.spec.ts` and `workflows.spec.ts` (`createWorkflow`, :716, :820) all call. - `workflow-reaction-picker.spec.ts:203-204` is left out. Its Reaction Added trigger has no control labelled "Message text". ### Unit 14 (second commit): `remote-owned-mentions.spec.ts:566` - **Run:** main 40998438103 (Smoke shard 9, job 123877347301). It passed on retry and failed 1 of the 6 attempts it got in the window. `sourceRecord()` at :673 returned thread A's draft instead of null. - **Cause:** on ProseMirror, `fill("")` selects through the DOM, then presses Delete. If ProseMirror hasn't seen the selection yet, it blocks the Delete. Navigating to B then saves A's live text as its draft. This is not a product bug: had the editor really been empty, the cleanup would have cleared A's draft. - **Change:** delete the text with one Backspace per character from the end, then wait for the composer to read empty. The guard assertions are unchanged. ### Related issue None found. Sibling #8743 also edits `navigation.spec.ts`, in a separate hunk. The two merge cleanly in either order. No open PR touches the tests changed here. #6134, #8187, #5778, #8622, #7452, #7812, #8244 and #3342 edit other tests in the same files. ### Testing Not run locally, because Playwright bogs this machine down. Flake Check will repeat the touched specs. `pnpm typecheck` and `pnpm check` pass, and biome's 3 warnings are in untouched files. Ad hoc `tsc` runs over the touched specs and the helper found the same pre-existing errors as on their `main` copies. Pushed with `git push --no-verify`, because the pre-push suites fail when the laptop is under load. ### Readiness An agent review of the combined `flakey-tests` branch (b432453af) found no blockers. `buzz-review-completed` is not added, because the human test step (AGENTS.md "Before opening a PR", step 3) hasn't happened yet. ### Siblings - #8739 ci: Hermit fetch deadline and sccache fallback - #8740 test(relay): thread_window aux-page hold and `api::media` Postgres tests - #8741 test(mobile): RunnerTests title fade and alert wait - #8742 fix(desktop): deep-linked message hold (product change) - #8743 test(desktop): four specs racing scroll settles and focus 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
matt2e
added a commit
that referenced
this pull request
Nov 2, 2026
Units 2-5 of the 23-30 Oct desktop Playwright flake audit. All four are test-only. origin/main (1eb67bb, now including #8680) hasn't touched these specs since fae3f03. Of the open PRs, #8622 adds an unrelated test at the end of navigation.spec, and #7812 (stale since 10-06) adds tests elsewhere in three of them. #8680 didn't touch them, but it carried navigation:472 mode A's product half (eb88a98), which is not part of this commit. The code confirms each cause, so none is dropped. navigation.spec.ts:472, "composer Buzz chip labels wrap without orphaning their icons" Runs: 11 jobs in the window, 6 after the earlier fixes, and 4 of the 7 main runs that carry every fix. Run 40970053997 (Smoke shard 7) failed all three attempts. Mode A: jobs 122971493553, 123066839967, 123572467962, 123819180597 and retry 2 of 123784825042. Signature: fragmentStep is null at :634. Cause: rows key on renderKey = localKey ?? id. On main, reconcileChannelWindowMessages keeps only pending sends and thread replies from the cache, so a head refetch or live echo after the ack drops the localKey. The key flips and the row remounts, losing the 220 px width the test set in an earlier evaluate. Change: re-land the test half of closed PR #6852, which never merged. Set the width and measure in one expect.poll, and set the width again before each fragment measurement. Mode B: jobs 123573056202, 123784199542, 123784825042 (twice), 123804428893, 123818728963, 123819543461, and 122924164125 (the Infinity variant). Signature: the |tooltipCenter - cursorX| < 1 poll at :667 receives 81.35 or Infinity. In failing attempt 40970053997 the post-send settle (scrollTop 200, 296, 260, 302) took its last step 21 ms or less before focus, and the tooltip read closed 12 ms after it. In the passing retry 40980513346, scrolling had stopped at least 15 ms before focus. Cause: Radix Tooltip 1.2.16 closes when an ancestor of its trigger scrolls (a capture-phase window listener). Reopening by pointer needs a pointermove while hasPointerMoveOpenedRef is false, then the chip provider's 500 ms delay. Any close clears that timer, and only pointerleave resets the flag. useInlineTooltipPosition moves the content only on pointermove and placement. So a scroll after focus can leave the tooltip closed, or open without the fragment offset, and the spec's single mouse.move never recovers it. Change: before focusing the chip, poll until the timeline sits at its floor (scrollHeight - clientHeight - scrollTop <= 1) at the start of one poll, is still there two animation frames later, and its scrollTop hasn't moved. Waiting only for scrollTop to hold for two frames isn't enough: a timeline whose settle hasn't started yet reads as still, and the failing run's last step landed under two frames before focus. The settle's end state is the floor. useVirtualizedBottomSettle scrolls only when Virtua's ResizeObserver reports a layout change, and after the send and the 220 px wrap the timeline sits short of the floor until the settle catches up. Focusing the chip and moving the pointer over it don't change layout. The send's ack does. It removes the row's "Sending…" status, and the confirmed row can lose its header, since a pending row is never shown as a continuation (MessageRow.tsx:167). The send's bottom intent is still armed, so that re-pins the timeline. So before the floor poll, wait for the sent row's message-send-status to reach count 0. If the ack has already landed, that wait is a no-op. The standalone toBeVisible after focus is gone. positionOverFragment asserts data-state matches /open/ instead, because a closing tooltip stays visible through its exit animation. No retry. Wrapping the measure, move and check in toPass, leaving and re-entering the chip on each attempt, would also pass, but it retries around the race instead of removing it. The ack and floor waits close the scroll window. The only other way to scroll under the tooltip was a remount that drops the row's 220 px width (mode A), and #8680's retainAcknowledgedLocalKeys, on this branch's base (1eb67bb), keeps the row's key. So positionOverFragment is a single pass, as on main, plus mode A's width re-set: set the width, measure, move, assert open, then poll the centre with the default timeouts. huddle-transcription.spec.ts:637, "animates the responding agent with the shared speaker ring" Runs: 9 jobs, always Smoke shard 4 attempt 1. Main: 39896948198, 40718743531, 40732061485, 40840949170, 40906253761, 40969965981 and 40983029157. PRs: #8715, #8666. Signature: toBeFocused() at :697 reads inactive for all 14 polls over 5 s. At +3.0 s in the failure video (40983029157, job 123827818084) the caret is in "Message #huddle". Cause: useComposerAutofocus re-runs when `focus` changes. That's focusEnd, which depends on the editor, so it runs again once the editor mounts. It backs off only for input, textarea, select and contentEditable focus, so it takes focus from the focused trigger. Change: wait for message-input to be focused before focusing the trigger. The same spec already does this at :855. scroll-history.spec.ts:1289, "fast middle-page scroll settles with continuous mounted coverage" Runs: 9 jobs in the window. After the fixes: main 40980404335 (Smoke shard 9, job 123819180518), two attempts on #8720, and the brain/native-account-setup branch. Signature: the viewportCoverage poll expects < 100 and receives 200. The failure screenshot shows the timeline at the floor. The 200 px is the bottom spacer (--composer-overlay-height) under the last row. Cause: TimelineMessageList arms useVirtualizedBottomSettle when the channel first positions. Its ResizeObserver then pins to the floor until listenForReaderScroll input or an accepted target cancels it. handleScroll deliberately doesn't cancel, and a prepend delta doesn't settle, so the intent survives the prepend and the spec's scrollTop writes. Virtua's re-measure after the jump pulls the list back down. Change: before the fast scroll, hover the timeline and wheel up 120 px. Then poll until it is more than 32 px from the floor (the timeline's at-bottom band) and scrollTop holds for two frames, so the writes stay the last scroll. custom-emoji.spec.ts:370, "reacting with a custom emoji renders via the loopback media proxy" Runs: main 40787609859 (10-28), main 40983029157 (Smoke shard 3, a run with every fix), and #8241 (agent and auth files only). Signature: the poll at :453 expects the inline "Add reaction" opacity "0" and receives "1". The error context marks button "Open reactions" [active]. Cause: the non-modal Radix Popover returns focus to its trigger when the picker closes. FocusScope does that in a setTimeout(0) after the content unmounts, and the content unmounts after its 100 ms exit animation. That can land after the spec's blur. The action bar is hidden by opacity only, so the trigger takes focus, and group-focus-within/message keeps the button visible. Change: wait for the "Open reactions" trigger to be focused, then blur. The note suggested waiting for em-emoji-picker to reach count 0. That still leaves the setTimeout window open. Focus reaches the trigger only after unmount, so this wait covers both. Verification Not run locally: the Flake Check lane will repeat the touched specs. pnpm typecheck and pnpm check pass. biome's 3 warnings are in untouched files. An ad hoc tsc ran over the four specs, using the desktop tsconfig with only those files, and over HEAD's copies of them. Both gave the same error counts: navigation 7, huddle 17, scroll-history 0, custom-emoji 2. The messages are identical once line numbers are stripped, and all are pre-existing: no Node types, findLast against the ES2022 lib, and the leadingBounds narrowing at navigation:576. With the floor wait in place and the retry dropped, navigation still gives the same 7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman
pushed a commit
that referenced
this pull request
Nov 2, 2026
…8743) ## Summary Units 2–5 of the 23–30 Oct desktop Playwright flake audit: four E2E specs that raced scroll settles and focus. All four fixes are test-only. This is one of six PRs split from the reviewed `flakey-tests` branch (siblings below). ### `navigation.spec.ts:472` "composer Buzz chip labels wrap without orphaning their icons" 11 jobs failed in the window, 6 of them after the earlier fixes, including 4 of the 7 main runs that carry every fix. Run 40970053997 (Smoke shard 7) failed all three attempts. - **Mode A** (jobs 122971493553, 123066839967, 123572467962, 123819180597 and retry 2 of 123784825042): `fragmentStep` is null at :634. Rows key on `renderKey = localKey ?? id`, and a head refetch or live echo after the ack drops the `localKey`. The row remounts and loses the 220 px width the test set earlier. **Change:** re-land the test half of closed #6852. Set the width and measure in one `expect.poll`, and set the width again before each fragment measurement. - **Mode B** (jobs 123573056202, 123784199542, 123784825042 twice, 123804428893, 123818728963, 123819543461, and 122924164125): the tooltip-centre poll at :667 receives 81.35 or Infinity. Radix Tooltip closes when an ancestor of its trigger scrolls. In the failing attempt, the post-send settle took its last step 21 ms or less before focus. **Change:** wait for the sent row's `message-send-status` to clear. Then poll until the timeline sits at its floor and holds there for two animation frames, and only then focus the chip. `positionOverFragment` asserts `data-state` is open. There is no retry loop around the race. ### `huddle-transcription.spec.ts:637` "animates the responding agent with the shared speaker ring" - **Runs:** 9 jobs, always Smoke shard 4, attempt 1. On main: 39896948198, 40718743531, 40732061485, 40840949170, 40906253761, 40969965981 and 40983029157. On PRs: #8715 and #8666. `toBeFocused()` at :697 reads inactive. - **Cause:** `useComposerAutofocus` runs again once the editor mounts and takes focus from the focused trigger. - **Change:** wait for `message-input` to be focused before focusing the trigger, as the same spec already does at :855. ### `scroll-history.spec.ts:1289` "fast middle-page scroll settles with continuous mounted coverage" - **Runs:** 9 jobs. After the fixes: main 40980404335 (job 123819180518) and two attempts on #8720. The `viewportCoverage` poll expects < 100 and receives 200, the bottom spacer. - **Cause:** the bottom-settle intent armed when the channel first positions survives the prepend and the spec's `scrollTop` writes. Virtua's re-measure then pulls the list back to the floor. - **Change:** hover the timeline and wheel up 120 px first, since reader input cancels the intent. Then poll until the timeline is more than 32 px from the floor and steady for two frames. - The commit message calls the cancelling listener `listenForReaderScroll`, a name introduced by sibling #8742. On `main`, the same wheel listener is inline in `useVirtualizedBottomSettle`, so this fix doesn't depend on that PR. ### `custom-emoji.spec.ts:370` "reacting with a custom emoji renders via the loopback media proxy" - **Runs:** main 40787609859 and 40983029157 (Smoke shard 3), and #8241. The inline "Add reaction" opacity reads "1", not "0". - **Cause:** the non-modal Radix Popover returns focus to its trigger in a `setTimeout(0)` after its 100 ms exit animation. That can land after the spec's blur. - **Change:** wait for the "Open reactions" trigger to be focused, then blur. ### Related issue None found. Sibling #8744 also edits `navigation.spec.ts`, in a separate hunk. The two merge cleanly in either order. #8622 adds an unrelated test at the end of `navigation.spec.ts`, and #7812 (stale since 10-06) adds tests elsewhere in three of these files. ### Testing Not run locally, because Playwright bogs this machine down. Flake Check will repeat the touched specs. `pnpm typecheck` and `pnpm check` pass. An ad hoc `tsc` run over the four specs found the same pre-existing errors as on their `main` copies (navigation 7, huddle 17, scroll-history 0, custom-emoji 2). Pushed with `git push --no-verify`, because the pre-push suites fail when the laptop is under load. ### Readiness An agent review of the combined `flakey-tests` branch (b432453af) found no blockers. `buzz-review-completed` is not added, because the human test step (AGENTS.md "Before opening a PR", step 3) hasn't happened yet. ### Siblings - #8739 ci: Hermit fetch deadline and sccache fallback - #8740 test(relay): thread_window aux-page hold and `api::media` Postgres tests - #8741 test(mobile): RunnerTests title fade and alert wait - #8742 fix(desktop): deep-linked message hold (product change) - #8744 test(desktop): five settle classes and remote-owned-mentions 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7811.
What
Bounds the cancel+merge redispatch loop described in #7811. The
flush_nextcancelled-batch fallback previously re-dispatched a cancelled batch immediately and unconditionally, with no retry accounting, no backoff, and no ceiling. It now:cancelled_redispatch_counts)requeue()from the second redispatch on (the first stays immediate so legitimate!cancelre-prompts are not delayed)MAX_CANCELLED_REDISPATCHES(10, matchingMAX_RETRIES), with the same ERROR log and state cleanup (including clearing staleretry_after) asrequeue()'s dead-letter pathWhy
Cancelled batches bypass
requeue()'s retry budget by design: a cancel is not a failure, and the "preserve as cancelled merge, never dead-letter like a real failure" invariant is load-bearing for normal steer/interrupt flows. But that invariant left a hole: when the redispatched turn fails again (e.g. provider rate limit), the batch returns viarequeue_as_cancelled, the fallback flushes it immediately, and the cycle repeats forever. Every cycle spawns a fresh session and pays the full base-prompt input, so a handful of events can burn a provider plan in minutes (620 turns / 37 minutes / 7 unique events in the linked report).The fix keeps the invariant for the normal case (single cancel+merge still re-prompts immediately with no accounting pressure) and only bounds the pathological repetition.
Testing
test_cancelled_fallback_redispatches_are_bounded_and_backed_off: pins the immediate first redispatch, backoff arming on the second, the third waiting on backoff, and dead-lettering with full state cleanup after the budget is exhausted.test_fresh_events_reset_cancelled_redispatch_budget: pins the reset when fresh traffic merges.cargo test -p buzz-acp --lib: 943 passed, 0 failed.cargo fmt --checkandcargo clippy --lib --testsclean.