mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
eva/nip-fi-comprehensive
2348
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
513e03b069 |
docs(nip-fi): fix pinned NIP source links and identifier-classification contradiction
Review findings from Mari and Quinn on #5946 at
|
||
|
|
2511d1baff |
docs(nip-fi): align the worked example with the repaired exit contract
The non-normative worked example still promised that the NIP-FI-CONF exit fixture pins complete byte-exact values. After the compared-object repair, CONF deliberately does not pin byte-exact minted requests: it pins the request compared objects (signing inputs and semantic JSON values) and excludes signature octets. Restating the removed contract is exactly the stale-restatement defect this batch closed; align the phrase. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
15d3fb2e0d |
docs(nip-fi): close the denominator contract in its own oracle
The denominator rewrite changed the body and left every summary of it behind. Three sites still promised the deleted requirement-level contract: the global-controls paragraph, the release gate, and -- worst -- the FI-CONF-MUTATION row itself, which is one of the four CONF entries the denominator counts. The gate contradicted the body it gates, so the original ambiguity survived inside the oracle that claims to close it. FI-CONF-INTEROP-EXIT likewise still demanded byte-identical requests the section above had already stopped requiring. All four now say listed oracle and compared object. The exit test promised a request on the WebSocket upgrade and defined a signing-input object only for NIP-98. Define the NIP-42 half alongside it, compared against its own transport's NIP-01 serialization, and pin the complete unsigned event fields for both transports in the fixture. The assertion object compared a protected header the fixture never pinned; pin the complete protected-header and claim-set JSON values. Correct a false premise: signatures were said to differ because ES256 and BIP-340 each draw fresh randomness. Deterministic ECDSA and fixed-aux BIP-340 are conforming and need not differ. The conclusion is unchanged -- conforming implementations may disagree on octets, so an object requiring equal octets fails conforming pairs -- but the reason now matches the algorithms as specified rather than as commonly configured. Give FI-TRACE-CAPABILITY-REVOCATION a lawful N/A. Core makes it conditional on a configured revocation-bounded external projection, while Applicability required every unlisted oracle, so a deployment with no such projection had no disposition it could honestly claim. Same defect class the offline-jwt fix closed for the current-status oracles, one oracle over. All findings are Wren's, verified against the cited lines before editing. A rewrite that changes a contract has to grep for every restatement of the old one; the body moved and the summaries did not, which is exactly the drift this document exists to make impossible. Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com> |
||
|
|
f29e2092b6 |
docs(nip-fi): make the conformance denominator countable
CONF's mutation adequacy said "each normative requirement in core," which admits four readings with a 3x spread. Make the denominator the listed oracle, selected structurally: rows whose first cell is one complete literal oracle ID, in core and each claimed normative profile. Selecting by section title would silently drop EDGE's five oracles, whose table lives under "Discovery and conformance"; core's heading is "Core behavioral oracles" and no two of the tables agree. Verified 30/5/11/7/4 = 57 rows, zero shorthand, zero duplicates, zero orphans, at a8fab1ad0. The exit test required byte-exact requests from two implementations. ES256 and BIP-340 draw fresh randomness per signature and nothing here pins JSON member order, so no conforming pair could ever pass. Compare signing inputs instead: the NIP-01 serialization for the proof, decoded header and claim set as JSON values for the assertion. Shipping pre-minted bytes would have stopped testing whether an implementer can mint from the document at all. Add the governing document revision and exit fixture digest to the claim tuple: evidence means different things under different text, and a claim that does not name what it was judged against is unfalsifiable once the specification moves. Exempt the synthetic exit fixture from the redaction rule it otherwise contradicts, and give offline-jwt deployments a lawful N/A for the two current-status oracles, which have no witness to test. Subtraction: the enumeration-agreement subsystem hand-copied core's denial symbols into CONF prose, then spent four checks and two rationale paragraphs guarding the copy. Read the set from core instead and the copy's guards have nothing to guard: seven checks collapse to three. Promotion detection is kept by folding disjointness into check 3 rather than restoring a check -- found because a six-mutant matrix showed the reduced set letting a promoted allowlist entry survive, falsifying a sentence I had just written. All six mutants now killed, green on the conforming document. Also fold three paragraphs restating one rule about unpinned header fields into one. Net +24 lines on CONF. The row splits and the two new tuple elements are not recoverable by deleting text that carries a guarantee, and compressing identifiers to reach a target would trade a real property for a number. Co-authored-by: Tyler Longwell <tlongwell@squareup.com> Signed-off-by: Tyler Longwell <tlongwell@squareup.com> |
||
|
|
edc66ef8d8 |
docs(nip-fi): add the promised worked wire example
The PR description promises a worked wire example in core; none existed. Add one compact non-normative section: a protected HTTP POST under client-attached with NIP-98 proof and a relevant body, showing how the two evidence fields, body binding, and the admission phases compose on the wire. Byte-exact values remain the province of the NIP-FI-CONF exit fixture; the example elides credentials so it cannot be mistaken for a conformance vector. Twenty-six lines, kept deliberately smaller than the alternative of striking the promise would save in credibility. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
ffc4e654c7 |
docs(nip-fi): close temporal, allocation, and denominator ambiguities
Three defects from the independent review lap, each fixed by making the text impossible to read two ways: DELEG evidence boundaries used 'live' (undefined) and declared equality at either temporal boundary expired, which is incoherent for a lower bound and contradicted core's own inclusive-nbf/exclusive-exp idiom. State both comparisons explicitly, matching core: now < mandatory_expiry and not_before <= now + skew. The delegated lease deadline mixed a configured duration into a minimum of absolute instants. Anchor it: lease issue instant plus the configured delegated maximum. EDGE allocated proof-transport codes 0x03/0x04 to Git and Blossom profiles that do not exist, violating the table's own allocation rule (a published stable specification with validation, binding, freshness, replay, and vectors). Unassign both; the unassigned range now starts at 0x03. Core's oracle table compressed six pairs of oracle IDs with slash shorthand that has no consistent expansion rule: three pairs read replace-last, three read keep-prefix, so no reader can infer the grammar. Split all six into one row per literal ID. The table now lists exactly 30 rows, one complete ID each, making the oracle set mechanically enumerable as the mutation-adequacy denominator CONF will reference. Every prose citation resolves to a table row; no shorthand remains. Review findings: Wren (DELEG boundaries, delegated maximum, EDGE codes, shorthand ambiguity), Dawn (denominator enumerability measurement). Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
d123e4e0bb |
docs(nip-fi): make the conformance oracles derivable
Two of the seven enumeration checks were red on the conforming document. Check 7 compared a set of rows against a set of symbols, and two core-attributed rows name no symbol, so it could not pass at any head. Check 5's head clause admitted the one row its own qualifier excluded. A check that is red on a conforming document detects nothing: it cannot be observed to flip, so every mutant reads as caught. Both are corrected and the requirement to run the checks green before scoring a mutant is now written where the checks live. Address the allowlist and the anonymity set by symbol instead of row number. This table is required to grow, and a profile appending one row renumbers the positional references: the allowlist would name the wrong condition and a new denied condition would fall outside the byte-identity requirement without any check firing. State the three readings the compared object left to convention. An independently written conforming implementation diverges on each by language default: a canonicalizing HTTP library emits Www-Authenticate, a server that sets no Content-Length frames the body as chunked, and a JSON encoder spaces its separators. Header names match case-insensitively per RFC 9110 Section 5.1, the compared body is the content per Section 6.4, and the Nostr message is compact JSON per NIP-01's serialization rules. Content-Length stays unpinned; framing is the sender's choice and pinning it would widen the object for no privacy gain. An exit test whose result depends on an unstated convention is the same defect as an oracle no pair can satisfy, one layer down. Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> |
||
|
|
d163ad172f |
docs(nip-fi): make conformance oracles satisfiable
The denial-fixture enumeration named four core denial symbols; core now denies six. Correct the count and names, reattribute binding_required to core, and widen row 9 to cover any unrecognized enrollment policy, which core's terminal else also catches. Add enumeration-agreement checks so a future core symbol cannot silently falsify the sentence again, including two that guard the prose-only allowlist itself: promoting an allowlisted condition to a real symbol satisfies the attribution check rather than failing it, so only a minimality check makes that rot visible. Replace the interoperability compared object with what core pins: status, body, and the values of only the header fields core names. Comparing ordered header names failed every conforming pair. State that the anonymity comparison stays wider, so a private reason code in an unnamed header is still a defect. Parameterize the exit test by a shared fixture. Independence is a claim about code, not inputs: two implementations given different issuers, keys or clocks cannot produce equal bytes however correct both are. Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> |
||
|
|
6b450122ac |
docs(nip-fi): close assertion and lifecycle semantics
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> |
||
|
|
1211a71591 |
docs(nip-fi): close core interoperability gaps
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> |
||
|
|
99d9155e6f |
docs(nip-fi): require domain and issuer separation
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> |
||
|
|
8fad46d5cb |
docs(nip-fi): close direct enrollment denials
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> |
||
|
|
7dfaa74aa6 |
docs(nips): specify FI conformance evidence profile
Defines what counts as evidence that an implementation has the behavior core and the profiles require: the immutable claim unit, evidence and anti-evidence rules, the complete per-private-condition denial fixture enumeration, mutation adequacy, and the two-implementation interoperability exit test. Three rules carry the weight. Denial fixtures are enumerated per private condition rather than per public class, because a per-class suite compares a class against itself and passes trivially. Mutation adequacy requires each normative requirement to have a singly-applied, attribution-checked, reachability-witnessed mutant killed by its named oracle, because naming an oracle proves a requirement is claimed, not that the oracle can fail. The exit test requires byte-exact request and per-class denial bytes produced from the documents alone by two independent implementations. FI-TRACE-TOFU-THEFT takes a configuration witness only: under the private posture ruling no discovery output distinguishes enrollment mode, so a discovery witness for that oracle cannot exist. Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Signed-off-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> |
||
|
|
528c847262 |
docs(nips): tighten FI assertion security semantics
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> |
||
|
|
8f6b5c7685 |
docs(nips): define federated identity core
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> |
||
|
|
0c283a558d |
docs(nips): specify trusted edge profile
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> |
||
|
|
9f2cb82ff3 |
docs(nips): specify FI binding lifecycle profile
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> |
||
|
|
19dce1ade6 |
docs(nips): skeleton for comprehensive NIP-FI restructure (core + 4 profiles)
Pins the file layout for the approved 9/9/9/9 blueprint: a ~350-line normative core plus separately claimable EDGE / LIFECYCLE / DELEG / CONF profiles and a non-normative model companion. Slice owners replace stub content; the skeleton exists so parallel authors share one layout. Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
85bacea52b |
Remove GitHub security advisory commitment (#6144)
Removes the promise in `SECURITY.md` to publish a GitHub Security Advisory after every security fix is released. The disclosure policy continues to state that Buzz follows coordinated disclosure and credits reporters unless they request anonymity. Checked with `git diff --check`. Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
076081bfc6 |
Rename Bumble agent to Pollen (#5864)
## Summary - Rename the built-in Bumble agent to Pollen across desktop, onboarding, docs, and test fixtures. - Migrate existing stock definitions and instances in place while preserving customized fields and the stable persona coordinate. - Reserve the Pollen name by removing it from Fizz's generated-name pool. ## Validation - Pre-push desktop checks, typecheck, 4,791 frontend tests, Tauri clippy, and 2,432 native tests - Desktop E2E build --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
d12d825778 |
fix(desktop): resolve agent profiles through one archive-aware selector (#5706)
Agent profiles resolve through one shared selector (`pickProfileAgent`) at every entry point — the persona card, the profile panel, and library grouping. That selector ranked instances only by active/name, with no archive awareness, so a relay-archived instance early in file order could hijack the persona card and the profile panel. The persona card also recorded a durable pubkey target, which could strand the panel on an archived identity when the click landed during the archive-snapshot fail-open window. The profile panel's Runtime → Instances roster had the same blind spot: it rendered every persona instance raw, so archived instances appeared mixed in with live ones as if active. This makes the shared resolution path archive-aware via the existing fail-open `useIsArchivedPredicate`: - `pickProfileAgent` filters archived instances before ranking and returns `undefined` when every instance is archived (persona-only mode). - `buildUnifiedGroups` drops archived agents from the standalone `Custom agents` and `Unknown agents` buckets; matched persona groups keep their full list and rely on the selector's persona-only fallback. - `useCanonicalManagedAgentProfile` resolves through a pure `resolveCanonicalManagedAgent` helper that applies the target-provenance rules: a deliberately requested archived pubkey stays exact (so its archive controller can unarchive it, even when a live sibling exists), `preserveRequestedInstance` still pins a Runtime → Instances selection, and non-archived historical navigation keeps its canonicalization. - The persona card's main click records a persona target that re-resolves every render, so it self-corrects to a live sibling after hydration. Deliberate instance navigation and the runtime-error affordance keep their explicit-pubkey path. - The Runtime → Instances roster (`ProfileInstancesSection`) buckets instances off the same predicate via `bucketPersonaInstances`: live rows render as before, and archived rows move under a labeled `Archived` subsection. The instance count reflects both buckets, and archived rows keep their explicit-pubkey click so unarchive stays UI-reachable (the deliberate-navigation path above). The predicate is fail-open (treats every identity as live while the relay archive snapshot loads) and self-exempt, so a cold start never hides an identity and a user is never folded from their own client. While the snapshot is loading, every instance renders in the live list — nothing hidden, nothing labeled. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
54f11219ef |
fix(acp): gate relay-signed workflow messages on their attributed author (#6129)
## Problem Scheduled workflow `send_message` actions fire and land in the channel with correct `p` tags for the mentioned agents — but the agents never wake. The wake-up is silently dropped. **Root cause:** workflow messages are signed by the **relay keypair** (`workflow_sink.rs` signs with `state.relay_keypair`), so `event.pubkey` is the relay's pubkey, not the workflow owner. In `buzz-acp`, the inbound author gate (`author_allowed`) runs **before** the `p`-tag mention check. Under the default `respond_to = owner-only`, the relay pubkey is neither the owner nor a sibling, so every workflow wake-up dies at the gate with a debug-level `"inbound author gate — dropping event"`. The relay-side comment even says the mention `p` tags exist *"so mentioned agents are woken (wake is p-tag gated)"* — but wake is also author-gated, and that path was missed. ## Fix Gate relay-signed workflow messages on their **attributed author** — the pubkey that created the workflow — instead of the relay pubkey: - **Relay:** `workflow_sink.rs` now emits an explicit `buzz:workflow-owner` tag carrying `workflow.owner_pubkey` (the workflow creator, which the executor already passes as `author_pubkey` and whose channel access the relay verifies before emitting). Ownership is never inferred from `p`-tag order; mention `p` tags play no role in attribution. - **Harness:** at startup, `buzz-acp` fetches the relay's NIP-11 `self` pubkey (new `RestClient::fetch_relay_self`, public `/info` endpoint). Best-effort: fetch failure just logs a warning and preserves pre-fix behavior. - **Gate:** an event that is (a) authored by the relay `self` key, (b) tagged `buzz:workflow`, and (c) carries a well-formed `buzz:workflow-owner` pubkey is gated on that owner, through the exact same owner/sibling/allowlist policy as a direct author. ## Security notes (all fail closed) - No NIP-11 `self` pubkey → no exemption. - `buzz:workflow` / `buzz:workflow-owner` tags on a non-relay-signed event → ignored (a member cannot forge the exemption; the relay verifies signatures on submission and only the relay holds its key). - Relay-signed event without the tags, or with a malformed owner value (not 64-hex) → plain author gate. - Who is @mentioned in the message has no bearing on whose authority is evaluated. - A workflow owned by a random channel member still cannot wake an owner-only agent — the owner's pubkey must pass the same policy. ## Testing - 7 unit tests (`workflow_attributed_author_tests`) covering attribution, fail-closed paths, p-tag independence, malformed owner values, and the forgery case. - Extended the PG-gated `workflow_send_message_p_tags_mentioned_member` integration test to assert the `buzz:workflow-owner` tag. - `cargo test -p buzz-acp`: 785 passed, 0 failed. `cargo test -p buzz-relay --lib workflow_sink`: 17 passed. Clippy + fmt clean. (9 pre-existing `buzz-relay` failures in unrelated `api::media`/`api::admin` tests fail identically on the base commit without this change.) Found while debugging scheduled automations in a Buzz review-pipeline channel: two cron workflows fired daily @mentions at agents that never responded, while direct human @mentions woke them instantly. --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Fizz <3a9f8a30fbb462abec1e2977b2280a7ae50c7ff794433790be15bd48bfd52d0b@buzz.block.builderlab.xyz> |
||
|
|
5b3f0375a2 |
fix(acp): replace Goose native system prompt (#5964)
## Why Buzz currently appends its managed prompt to Goose's native prompt, so managed agents receive both instruction sets instead of the intended Buzz-only system prompt. ## What - Send Goose's custom session system-prompt request with `mode: "set"` - Lock the replacement contract in the ACP request test ## Risk Assessment Low — the change is limited to Goose session setup; adapters that do not implement Goose's custom method keep the existing method-not-found fallback behavior. ## References Goose v1.46.0 routes `set` to `override_system_prompt`, and its prompt builder selects that override instead of rendering the native `system.md`: [ACP handler](https://github.com/aaif-goose/goose/blob/98c11ce2ee7b9b302978aa64b1eab7d0895607c7/crates/goose/src/acp/server/manage_sessions.rs#L57-L93), [prompt builder](https://github.com/aaif-goose/goose/blob/98c11ce2ee7b9b302978aa64b1eab7d0895607c7/crates/goose/src/agents/prompt_manager.rs#L153-L191). Validated end to end against the official Goose v1.46.0 binary with a local OpenAI-compatible capture server: the provider request contained the exact Buzz replacement prompt and did not contain Goose's native base-prompt marker. --- **Update Aug 15, 13:17 CDT:** Added the [Terra-high prompt-ablation comparison](https://github.com/squareup/buzz-benchmarks/blob/4492f76349ccb638219f7d070735a4d2b679bc26/data/prompt-ablation/20260815-terra-high/comparison.md). The Goose conditions used GPT 5.6 Terra at high effort on the same 11 Terminal-Bench 2.1 tasks, with two attempts per task and concurrency four. The matched `append-full` and `set-full` runs used the same persona and included the same Buzz platform prompt; Active-h is the primary measure because it excludes Buzz lifecycle overhead. | Goose condition | Pass | Active-h | Median active | Agent-h | Wall-h | Tool calls | |---|---:|---:|---:|---:|---:|---:| | Native prompt + Buzz prompt (`append-full`) | 21/22 | 0.3042 | 0.85 min | 0.3974 | 0.1496 | 234 | | Native prompt + persona only (`append-persona-only`) | 22/22 | 0.3050 | 0.75 min | 0.3990 | 0.1498 | 204 | | Buzz prompt replaces native prompt (`set-full`) | 22/22 | 0.3340 | 0.87 min | 0.4296 | 0.1551 | 275 | Replacing instead of appending produced one additional passing attempt, but it was not an efficiency improvement in this small sample: versus `append-full`, `set-full` increased Active-h by 9.8%, median active by 2.0%, Agent-h by 8.1%, Wall-h by 3.7%, and tool calls by 17.5%. It was faster on only two of eleven per-task active-time medians (`distribution-search` and `prove-plus-comm`). With two attempts per task, these are directional results rather than confidence intervals; they support this change as an instruction-isolation/correctness fix, not a performance optimization, and argue against Goose's appended native prompt being the main source of active-time cost. Generated with Codex Signed-off-by: Atish Patel <atishpatel2012@gmail.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
edc4a09aaa |
feat(workflows): add responsive library card actions (#6008)
**Category:** improvement **User Impact:** Users can scan what each workflow does and trigger, edit, duplicate, enable, disable, or delete it directly from the library. **Problem:** The workflow list buried common actions and did not expose each automation's trigger-to-action shape at a glance. **Solution:** Add a responsive workflow library with a persistent create tile, compact trigger/action diagrams, prominent workflow titles with supporting descriptions, and shared card actions while preserving existing detail, editor, and run-history entry points. Card toggles refresh both list and open-detail caches so status and definition stay consistent. <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx** Adds a shared card menu for trigger, edit, duplicate, enable/disable, and delete actions. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Reworks cards around the prototype's visual hierarchy: color-coded trigger, action flow, sentence-case eyebrow, prominent title, supporting description, status, channel, and update date without a footer clock icon. **desktop/src/features/workflows/ui/WorkflowsView.tsx** Adds the responsive grid, create tile, mutation wiring, and list/detail cache invalidation. Container breakpoints keep cards two-across at medium widths and three-across in the 1280px desktop layout. **desktop/src/features/workflows/ui/workflowDefinition.ts** Adds immutable enabled-state updates plus narrow trigger and first-action readers used only to select card icons. **desktop/src/features/workflows/ui/workflowDefinition.test.mjs** Covers neutral icon selection, enabled-state immutability, and status presentation. **desktop/tests/e2e/workflows.spec.ts** Covers the create tile, title/description hierarchy, selected-card enable/disable consistency, and deterministic narrow/medium/wide captures while retaining existing action coverage. </details> ## Reproduction steps 1. Open **Workflows** and confirm the create tile stays first as cards flow from one to three columns with available width. 2. Confirm each card shows a sentence-case trigger eyebrow, prominent workflow title, supporting description when present, status, channel, and update date without a clock icon. 3. Open a card's overflow menu and trigger, edit, duplicate, enable/disable, or delete the workflow. 4. Leave the detail panel open while toggling and confirm its badge and JSON definition update with the card. ## Screenshots Real built E2E UI with representative workflow data at three viewport sizes. ### Narrow — 800 × 720  ### Medium — 1024 × 720  ### Wide — 1280 × 720  ### Card actions  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
f716eef437 |
fix(desktop): enforce shared agent access across devices (#6086)
## Summary - discover shared managed agents from authenticated relay directory records instead of treating channel membership as sufficient proof - publish and refresh access-policy changes immediately so running clients converge across machines without a restart or five-minute poll - route profile edits through the exact managed instance and stop/restart runtimes around access changes so unrelated edits cannot silently widen access - keep mention send-time revalidation and Block owner-only build enforcement fail closed - explain invalid custom provider/model configuration instead of leaving Save silently disabled ### Related issue Fixes #3204 ### Known residuals - a brand-new remote agent's first policy record can wait for the bounded directory poll when no authenticated directory coordinate exists yet; send-time mention revalidation remains fail closed - a failed remote-provider policy redeploy is recorded but cannot undeploy the older provider instance until the provider protocol gains the destructor tracked by #5570 ### Testing - full Desktop unit suite: 4,961 tests passed - focused profile editor Playwright workflow passed, including Customize access edits and prompt-only edits after tightening an instance - Desktop TypeScript, Biome formatting, file-size ratchet, Tauri checks, and pre-push suites passed - independently reviewed for authenticated directory trust, live subscription teardown, runtime revocation ordering, fail-open edit paths, and per-agent provider deployment serialization --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> Co-authored-by: diegorumo <diegorumo@gmail.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> |
||
|
|
1b7e5ac1be |
feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)
## Summary Centralizes model capability knowledge — thinking mode, supported effort levels, wire routes, and human-readable labels — into a single manifest, `scripts/model-capabilities.json`. Rust and TypeScript each get a small interpreter that reads the same manifest, replacing hand-maintained tables scattered across both languages that had already drifted apart. A capability change is now a data edit, not parallel edits to two code paths. Supersedes the codegen approach explored in #3603. A cross-language contract keeps the two interpreters honest: `scripts/normative-corpus.json` is a golden snapshot generated from the Rust resolver (103 vectors covering all six capability axes) and replayed natively in TS. CI fails if either language disagrees with the corpus or the corpus drifts from the resolver. Regenerate with `just regen-model-corpus`. ## Behavior changes - **Effort dropdown for `openai-compat` providers** no longer offers `max`. The request path always clamped `max` to `xhigh` on the wire, so the UI stops offering a value that was silently rewritten. UI-only, wire-identical. - **Databricks v2 routing (wire-visible):** uncurated endpoint names carrying a bare Claude code-name segment (e.g. `goose-opus-5`) now route to the MLflow chat wire instead of Anthropic Messages — they lose Anthropic prompt caching but still succeed on a valid OpenAI-compatible wire. Curated `databricks-claude-*` records and any name starting with `claude` are unchanged. A handful of other uncurated/adversarial name shapes similarly fall back to MLflow chat instead of pattern-matched routes; every curated model resolves identically to before, all axes. - **Curated model labels on the real discovery path.** The Databricks API returns no display name, so discovery emits the raw endpoint id as the model `name` (`{id, name: id}`) on every path. `ModelEntry.name` is now curated at all four construction seams in `buzz-agent` — v2 discovery, v1 parse, the auth-empty default catalog, and the configured-model fallback — via a read-only `databricks_registry_label` lookup over the manifest's `databricks_v2` exact records; `id` stays the raw wire/config value. A known id renders its curated label (`databricks-gpt-5-5` → `GPT-5.5`), an unknown id passes through unchanged, and the default-catalog row reads `GPT-5.5 (default catalog)`. As a defense against older `buzz-agent` binaries and any harness that echoes ids, `resolveModelLabel` treats a discovered name equal to the trimmed id as absent and falls through to the registry tier; a genuinely distinct name (including the suffixed default-catalog label) still wins. ## Cleanup Deletes the duplicated capability tables and their tests: the `config.rs` gpt5 matchers, effort tables, and clamp logic; the legacy segment-based Databricks v2 route classifier in `llm.rs`; and the TS hand tables plus `effortTable.fixture.json`. All are replaced by manifest lookups through the shared resolver — no line of capability data exists in two places. --------- 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> |
||
|
|
f956e6fe06 |
docs: refresh agent development guidance (#6049)
## Summary - allow agents to build and run Flutter when it provides relevant implementation or validation evidence - keep mobile iteration fast by reusing simulators, incremental builds, and configured staging or production communities - correct stale CLI, E2E, CI, worktree formatting, and mobile launch guidance - point community singleton reset guidance at the canonical implementation instead of duplicating a drifting inventory ## Validation - `git diff --check origin/main..HEAD` - `cargo run -q -p buzz-cli -- --format compact messages thread --help` - `cargo run -q -p buzz-cli -- --format compact messages search --help` - `just desktop-tauri-fmt-check` from the worktree - pre-commit: mobile Dart formatting and `flutter analyze` - pre-push: branch-skew check and full mobile test suite (1,465 tests) Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
d8281b9c93 |
feat(mobile): require device authentication for identity export (#5116)
**Category:** new-feature **User Impact:** Mobile users must confirm with Face ID, biometrics, or their device passcode before sending their Buzz identity to Desktop. **Problem:** A signed-in phone could send its full identity, including the `nsec`, to a desktop without fresh local verification. **Solution:** Require OS device authentication before opening the identity-recovery scanner, retain that authorization only for the active pairing session and short pairing window, and require fresh authentication again if it expires before the identity payload is sent. Normal app opening, identity import, and community removal remain unchanged. ## Screencasts | Enable Face ID | Use Face ID | | --- | --- | |  |  | <details> <summary>File changes</summary> **Android and iOS integration** - `mobile/android/app/build.gradle.kts` declares the AppCompat dependency required by the biometric activity theme. - `mobile/android/app/src/main/kotlin/xyz/block/buzz/mobile/MainActivity.kt` uses the activity type required by the system authentication prompt. - `mobile/android/app/src/main/res/values/styles.xml` and `mobile/android/app/src/main/res/values-night/styles.xml` use the compatible launch theme. - `mobile/ios/Podfile.lock` records the native local-authentication dependency. - `mobile/ios/Runner/Info.plist` explains why Buzz requests Face ID access. **Identity policy and pairing flow** - `mobile/lib/shared/security/sensitive_action_authorizer.dart` wraps OS authentication and maps platform errors to stable app-level outcomes. - `mobile/lib/shared/community/community.dart` and `mobile/lib/shared/community/community_storage.dart` persist the sensitive-action policy. - `mobile/lib/features/invites/invite_join_provider.dart` assigns the explicit policy for invite-created communities. - `mobile/lib/features/pairing/pairing_provider.dart` gates export, binds grants to the active community/session, reauthenticates expired grants, and clears grants on every terminal path. - `mobile/lib/features/pairing/pairing_page.dart` lets users choose biometric protection while importing an identity. - `mobile/lib/features/settings/settings_page.dart` wires pairing into settings. - `mobile/lib/features/settings/settings_page/connection_section.dart` authenticates before opening export recovery and bounds the foreground-resume wait. - `mobile/pubspec.yaml` and `mobile/pubspec.lock` add and lock `local_auth`. **Coverage** - `mobile/test/shared/security/sensitive_action_authorizer_test.dart` covers native result mapping, unsupported devices, and single-flight behavior. - `mobile/test/shared/community/community_test.dart` and `mobile/test/shared/community/community_storage_test.dart` cover policy defaults and persistence. - `mobile/test/features/invites/invite_join_provider_test.dart` covers the invite policy. - `mobile/test/features/pairing/pairing_page_test.dart` covers import protection controls. - `mobile/test/features/pairing/pairing_provider_test.dart` covers export/import authorization, stale/reset/concurrent guards, malformed payload cleanup, and no-export failure paths. - `mobile/test/features/settings/connection_section_test.dart` covers the tap gate, lifecycle resume, and timeout behavior. </details> ## Reproduction steps 1. Pair an identity into the mobile app. 2. Open Settings and choose “Send identity to desktop.” 3. Verify Face ID, biometrics, or the device passcode is required before the recovery scanner opens. 4. Cancel device authentication and verify the scanner does not open and no identity transfer begins. 5. Authenticate, scan a Desktop recovery code, confirm the SAS, and verify the identity transfer completes. ## Validation At `be5620f5f10aa6cc16e86a4f01f102f3d9aeef9b`: - `cd mobile && ../bin/flutter analyze` — no issues - `cd mobile && ../bin/flutter test` — 1,368 tests passed - `cd mobile/android && JAVA_HOME=$(/usr/libexec/java_home -v 21) ./gradlew app:assembleDebug` — debug APK assembled successfully --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> |
||
|
|
78cbffeb64 |
fix(desktop): hide the offcanvas-collapsed sidebar so it stops painting over the community rail (#5947)
## Summary Collapsing the sidebar left a phantom copy of it painted over the community/relay rail — opaquely on flat themes (vesper et al., which made the rail look *removed*), and as ghost fragments (muted search-box fill, truncated channel-name tails) on the Buzz themes whose chrome is intentionally transparent for the gradient. **Cause:** #4281 made the app-sidebar layer `overflow-visible` (the huddle drawer needs to escape it). That removed the ancestor clipping the offcanvas collapse relied on: the sidebar slides to `left: -sidebar-width` but kept painting, exactly over the `z-0` rail (`z-10` sidebar layer). **Fix:** the offcanvas-collapsed sidebar container is now `invisible` + `pointer-events-none`, with `visibility` added to the transition list so the 200 ms slide-out still animates and the flip happens only at the transition's end. Theme-independent; no per-theme CSS touched; the huddle drawer's `overflow-visible` is preserved. ## Before / after Left 420px of the app with the sidebar collapsed. Before = unpatched `origin/main` @ 69107dc3b; after = this branch. Same seeded state, same build pipeline (`build:e2e` between checkouts). | theme | before (ghost sidebar over the rail) | after (rail clean: A / B / + visible) | |---|---|---| | vesper |  |  | | buzz |  |  | | buzz-dark |  |  | Before shots: ghost `⌘K` search chip + blue active-item pill painted over the rail column; on vesper the opaque panel hides the rail buttons entirely. After: the rail's community buttons (A, B) and `+` are visible and clickable in all three themes. Reported by Thomas P in #buzz-bugs: buzz://message?channel=e62570dd-33ad-42c5-b92b-75f2689f9694&id=9ea401ca1d009f555ca4324e136f8d8d8156db2f8afa3ff89fd038d2c16260f7 cc @klopez4212 — this touches the layout your #4281/#5478 work shaped; please confirm it doesn't defeat the huddle drawer or glass intentions. The change deliberately hides only the *offcanvas-collapsed* container, nothing in the expanded path. ## Test plan - [x] New Playwright regression spec `sidebar-offcanvas-rail.spec.ts` (buzz / buzz-dark / vesper): collapsed sidebar must be `visibility: hidden` + `pointer-events: none`, community rail stays visible and interactive. **Fails on unpatched build** (verified), passes with the fix. - [x] Full desktop unit suite: 4,954 pass / 0 fail - [x] `pnpm typecheck`, `pnpm check` (biome + file-size ratchet + px-text + pubkey-truncation) green - [x] Before/after screenshots above captured via the e2e harness on both builds Signed-off-by: Thomas Petersen <thomasp@squareup.com> Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz> |
||
|
|
69107dc3bf |
Polish mobile message threads and composer (#5645)
## Summary - refine mobile message metadata, search spacing, and Activity filter semantics - add channel-parity Latest navigation and stable tail following to threads - synchronize Android composer/keyboard geometry and keep Latest spacing stable across IME transitions ## Validation - `bin/just mobile-check` - `bin/just mobile-test` (1,276 tests) - Pixel 10 install/launch and channel/thread keyboard, Latest, tail, and back-navigation review - signed iPhone install/launch workflow ## Snapshots See the review snapshots below. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> |
||
|
|
82f7ed1532 |
chore(release): release Buzz Desktop version 0.5.14 (#5917)
## Buzz Desktop release v0.5.14 - **Frozen main:** `1b3dbcaaea882eeea90359c1db02e306d2f4f50a` - **Reviewed candidate:** `391495e7d347d20b67e39e3c240d17ef63c5c2c0` - **Previous desktop release:** `desktop-v0.5.13` - **Proposed immutable tag:** `desktop-v0.5.14` 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> |
||
|
|
1b3dbcaaea |
ci(release): remove desktop smoke gate (#5914)
## Summary - remove the GitHub-hosted desktop smoke job from the desktop release workflow - remove the smoke result from manifest assembly dependencies and promotion conditions - retain the local smoke tooling for future repair and targeted validation The first release execution of this gate spent its full 10-minute Playwright timeout traversing the 10,000-row fixture, then produced a 987 MB diagnostics upload. All signed platform builds succeeded, but the smoke prevented manifest publication. This restores the previously established release boundary while the harness is made suitable for CI separately. ### Testing - parsed `.github/workflows/release.yml` with Ruby Psych and asserted the smoke job/dependencies are absent - `scripts/test-release-ref-contract.sh` - exact pushed commit passed the repository pre-push hook Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
51beba6038 |
chore(release): release Buzz Desktop version 0.5.13 (#5912)
## Buzz Desktop release v0.5.13 - **Frozen main:** `09768100ec3420f0aa7cd278bd00fe0baab5de8d` - **Reviewed candidate:** `a239e0f6793ac6e88ccf92cc231054090a9753cc` - **Previous desktop release:** `desktop-v0.5.12` - **Proposed immutable tag:** `desktop-v0.5.13` 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> |
||
|
|
09768100ec |
fix(ci): read Playwright version without nested shell quoting (#5910)
## Summary - replace the nested one-line shell quoting used to read the Playwright package version - write the resolved version to `GITHUB_OUTPUT` from a multiline shell step ## Why The `desktop-v0.5.12` release smoke job failed before executing tests because Bash received escaped quotes inside command substitution and parsed the Node expression as shell syntax. ## Validation - `bash scripts/test-release-ref-contract.sh` - isolated execution of the new shell fragment with a fixture `@playwright/test/package.json`, producing `version=1.58.2` - `git diff --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
263c9bf76c |
fix(desktop): restore the agent trading-card mint button (#5900)
## Problem PR #5574's profile-panel redesign dropped `ProfileSummaryView`'s `onCreateCard` prop — the only caller of `setCardMintTarget` — so the entire Agent Trading Cards feature (#3278) became unreachable from the GUI while staying fully wired underneath: mint dialog, background job store, viewer, gallery, composer chip, and the Rust `mint_agent_card`/`save_agent_card` commands all survive at main. `git log -S 'setCardMintTarget('` shows exactly two commits: the feature and the accidental removal. ## Outcome The mint trigger returns as a management row in the agent profile's Info tab, directly under **Export agent**, gated `isBot && canManagePersona` exactly like Duplicate/Export. Target resolution is byte-for-byte the original logic: prefer the live instance pubkey, fall back to the persona/definition id, allow locking only when an instance keypair exists. ## Shape - `UserProfileAgentManagementRows`: new optional `onCreateCard` row (Sparkles icon, `user-profile-create-card-row`), placed after Export. - Prop threaded `UserProfilePanel` → `ProfileSummaryView` → `ProfileInfoTabContent` → management rows, mirroring `onExportAgent` at every layer. - The mint-target state + open callback move into a `useCardMint` hook in `UserProfilePersonaDialogs` (beside the `CardMintTarget` type it manages). This keeps `UserProfilePanel.tsx` at 999 lines — the file sits at the size-ratchet cap and may not grow. ## Validation - `pnpm check` green (biome, file-size ratchet, px-text, pubkey-truncation). - `pnpm typecheck` green. - Full desktop unit suite: **4888 passed, 0 failed**. - Profile e2e spec: **32 passed**, including the updated management-row-order assertion and a new click → mint-dialog-visible → Escape → closed exercise of the restored row. Verified at `bff3110a0aeb3d63683eac9ed3e587829f9436da`, one commit atop main `01f76ec97`. Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
122a8b8988 |
Projects v3: unify sharing, discussions, and issue ownership (#5792)
## Summary Projects v3 makes repository work shareable, discussion-aware, and easier to scan in one coherent workspace. People can copy canonical links, reopen the exact workspace tab, understand issue and pull-request context at a glance, find related channel conversations, and assign or unassign issues across Desktop and CLI. - **Unified workspace** — top-level sections sit above repository controls in one rounded workspace, with navigation positioned close to the page heading. README and Files retain branch selection; every section has a labeled icon header, and Issues and Pull Requests expose creation from a consistent right-aligned action. - **Repository management** — the repository selector is always available, including single-repository projects. Its integrated add flow lets project owners create a repository manually or select an existing repository without a separate toolbar button. - **Readable work-item lists** — issue and pull-request rows use plain-language context instead of opaque metadata. Files, commits, issues, pull requests, channels, and contributors share consistent row density and right-aligned timestamps, while deterministic fallback-avatar colors keep participants distinct on light backgrounds. Inbox pull-request metadata wraps between complete phrases and truncates long channel names instead of compressing copy into narrow columns. - **Reliable entity links** — projects, repositories, issues, pull requests, and commits have canonical `buzz://` links, preview cards, OS deep-link routing, and tab-aware navigation. Reopening the same link re-applies its destination instead of leaving the user on a locally selected tab. - **Related conversations** — repository and work-item views surface channels discussing the current entity, including participants, channel navigation, message context, and an explicit notice when discovery reaches its 500-result cap. - **Reversible issue ownership** — trusted assignment and unassignment events work across Desktop, Tauri, `buzz-sdk`, and `buzz issues`. Assignees appear in project views and the assigned inbox, while authorized users can remove assignments directly from the assignee row. Assignment state is derived chronologically from labeled Nostr notes. Issue authors and repository owners may change any assignee; other users may only assign or unassign themselves. Shared golden fixtures keep entity-link grammar and validation aligned across TypeScript and Rust. The branch also updates `webbrowser` to the patched release for RUSTSEC-2026-0257. ### Related issue N/A. ### Testing - [x] `just ci` — formatting, lint, typechecking, unit tests, and builds passed - [x] Full pre-push suite — organization, branch-skew, Desktop checks, typechecking, and tests passed on the latest push - [x] `cargo test -p buzz-cli` and focused `buzz-sdk` assignment tests passed - [x] Focused Tauri recipient-note and 500-result search-limit tests passed - [x] Desktop entity-link and issue-assignment unit tests passed - [x] Playwright smoke coverage passed for assignment, repeated entity-link navigation, repository create/select flows, section headers and actions, timestamp alignment, timeline icons, sentence-style issue/PR metadata, header spacing, avatar contrast, and Inbox metadata at stacked and side-rail breakpoints - [ ] Manual staging pass: link round-trips, Channels tab, assignment flows, and inbox routing ### Screenshots Pull requests explain who opened the request, where it lives, and which branch it comes from; fallback avatars remain visually distinct.  Issues use the same sentence-style hierarchy while keeping status and recency easy to scan.  The wide Inbox detail keeps author, timestamp, and origin context readable beside its metadata rail.  [View the complete six-state Projects v3 screenshot set](https://github.com/block/buzz/pull/5624#issuecomment-5268039672) and [the compact/wide Inbox comparison](https://github.com/block/buzz/pull/5624#issuecomment-5268614585). --- > Supersedes #5624, whose head commit accumulated permanently-queued required check suites (block-dco-check et al.) that GitHub never dispatched. History flattened into a single signed-off commit on latest main; tree verified byte-identical (`git merge-tree`) to merging the original branch into main. --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz> |
||
|
|
1f4c69eccf |
chore(release): release Buzz Desktop version 0.5.12 (#5903)
## Buzz Desktop release v0.5.12 - **Frozen main:** `757779bb1ef22cc4a1c233344baa0946d907e5a6` - **Reviewed candidate:** `bfc34904adc414efcd8e9c5548dff82c3545b677` - **Previous desktop release:** `desktop-v0.5.11` - **Proposed immutable tag:** `desktop-v0.5.12` 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> |
||
|
|
0bb7c60f82 |
fix(mobile): unwrap batched observer telemetry (#5805)
## Summary Buzz Mobile now expands decrypted ACP observer batch envelopes into their inner telemetry frames before sending them through the existing per-agent dedupe, ordering, cap, and channel-filter pipeline. Singleton observer events keep their existing behavior. Malformed batch envelopes remain visible as outer frames, matching the desktop consumer convention, while invalid inner frames use the existing observer decrypt error path. This restores batched agent progress, tool activity, and incremental transcript updates that Mobile previously ignored. ### Related issue Related to #4917. ### Testing Added tests: - [`observer_subscription_test.dart`](https://github.com/block/buzz/blob/main/mobile/test/features/channels/agent_activity/observer_subscription_test.dart) covers valid batches, singleton behavior, malformed envelopes, and invalid inner frames. Full mobile analysis, formatting, file-size validation, and Flutter tests passed. The repository pre-push gate also passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
757779bb1e |
perf(desktop): update active turns incrementally (#5897)
## Problem Every observer-store publication made the active-turn bridge scan every running/deployed agent and replay each agent's retained observer journal. Watermarks kept the replay idempotent, but did not remove the repeated work. Under an active fleet, one changed agent therefore caused work proportional to the whole fleet and its retained history. ## Change - observer publications now identify the changed agent and only the newly admitted, retained events - the active-turn bridge still performs one full hydration when its agent list mounts or changes - steady-state publications process only that changed active agent's delta - other observer-store subscribers keep their existing notification behavior - duplicate-only envelopes still do not publish ## Correctness Regression coverage pins: - retained/duplicate history is omitted from deltas - stopped-agent updates do not enter active-turn state - an incremental terminal clears a turn hydrated from retained history - batching still publishes once and preserves transcript/terminal outcomes - existing watermark, tombstone, pruning, community restore, clear, and eviction suites remain green ## Validation Exact pushed head: `a480ffd2531023ea32b2a5518b5d9d41f04577c8` - focused active-turn + observer-retention suites: 90 passed - full desktop suite: 4,891 passed - `pnpm --dir desktop typecheck`: passed - `pnpm --dir desktop check`: passed (pre-existing repository warnings only) - mandatory pre-push hook at the exact pushed head: passed `branch-skew`, desktop check/typecheck/test, mobile tests, Rust tests, and Desktop Tauri checks Packaged same-fleet CPU/RSS validation is follow-up evidence; this PR proves the algorithmic amplification is removed without claiming an installed-app percentage from unit tests. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
f086eb6544 |
fix(link-previews): send while previews finish in background (#5697)
**Category:** fix **User Impact:** Messages send immediately after submission while link previews finish in the background, with an option to skip delayed preview preparation. **Problem:** Waiting for link-preview metadata or snapshot uploads kept the composer occupied after users pressed Send, while races between completion, timeout, and cancellation risked inconsistent payloads. **Solution:** Freeze and promote speculative preview work into a bounded background send task, clear the composer immediately, and publish exactly once with prepared previews or gracefully without them when skipped, failed, or timed out. https://github.com/user-attachments/assets/987d2f2c-679f-473a-965f-dfb279951e52 <details> <summary>File changes</summary> **desktop/src/features/communities/useCommunityInit.ts** Resets pending link-preview preparation when community context changes so work cannot cross community boundaries. **desktop/src/features/messages/lib/linkPreviewPreparationStore.ts** Adds the coordinator-owned preparation state machine, bounded fallback, Skip behavior, and exactly-once terminal publication handling. **desktop/src/features/messages/ui/ComposerUploadProgressOverlay.tsx** Extends floating background progress UI to include link-preview preparation. **desktop/src/features/messages/ui/ComposerUploadProgressPill.tsx** Adds the preparing-link-preview label and Skip action to the progress pill. **desktop/src/features/messages/ui/MessageComposer.tsx** Hands submitted preview work to the background coordinator and clears the composer immediately. **desktop/src/features/messages/ui/messageComposerAutoSubmit.test.mjs** Updates auto-submit unit coverage for coordinator-owned preview preparation. **desktop/src/features/messages/ui/messageComposerAutoSubmit.ts** Allows submit to promote unfinished preview work instead of blocking composer submission. **desktop/src/features/messages/ui/useComposerLinkPreviews.tsx** Starts preview work speculatively and exposes frozen preparation jobs for adoption by the send flow. **desktop/src/features/messages/ui/useMentionSendFlow.helpers.ts** Carries prepared preview tags through the mention and media payload helpers. **desktop/src/features/messages/ui/useMentionSendFlow.ts** Integrates prepared preview tags into final message publication. **desktop/src/shared/lib/useResolvedLinkPreviews.ts** Exposes the in-flight metadata promise so promoted work can be adopted rather than restarted. **desktop/tests/e2e/messaging.spec.ts** Covers immediate submit, upload handoff, Skip/completion races, failure fallback, auto-send, and exactly-once publication. </details> ## Reproduction steps 1. Enter a supported link and press Send while preview metadata or snapshot upload is still pending. 2. Confirm the composer clears immediately and the floating progress UI shows **Preparing link preview · Skip**. 3. Let preparation finish and confirm one message is published with its preview. 4. Repeat and choose **Skip**; confirm one message is published without waiting for the preview. 5. Simulate preview failure or timeout and confirm the message still publishes once without preview tags. ## Validation - TypeScript, Biome/format, file-size, px-text, and pubkey checks - Full desktop unit suite: 4,734 passed - Focused Playwright messaging suite: 5 passed - Push hooks at `86c0aa7de2ff81b79286c99bf23db12345adc6ca`: desktop check, typecheck, and tests passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
01f76ec971 |
fix(desktop): cut steady-state relay traffic from polls and read-state echo (#5879)
## Problem Desktop webview CPU stayed high after the presence-scope fix (#5830) and the shared useNow ticker (#5861). A per-kind byte tap hot-patched into `relayClientSession.ts` on a live desktop (~500 channels, large agent fleet; 850 s capture correlated with CPU sampling) showed the remaining steady-state relay traffic is mostly self-inflicted: | kind | what | share of inbound bytes | shape | |------|------|-----------------------|-------| | 30078 | read-state | **34%** | our own ~44 KB nip44 blob echoed back every ~10-30 s while reading | | 30030 | emoji union | **33%** | 2-min poll refetching every member's full set (~300 KB burst) | | 30175 | persona catalog | **13%** | same 2-min backstop pattern, ~150 KB per walk | CPU tracked the bursts directly: 3-5% in quiet 10 s buckets vs 44-54% in buckets containing a poll burst or read-state echo. (The kind-24200 observer-frame theory was tested and disproven by the same tap: 9.7% of bytes, steady trickle.) ## Outcome - **Read-state echo drop.** `ReadStateManager` remembers the ids of events it just published (FIFO set capped at 64) and drops their relay echoes before the nip44-decrypt + `JSON.parse` step. Ids are recorded *before* publishing so relay fan-out can't race the OK. The drop consumes the id, so a reconnect replay of the same event still parses normally. Events from other clients of the same pubkey are untouched. - **Poll backstops stretched 2 min → 20 min** for the emoji union and persona catalog queries. The live subscriptions (invalidate on any new 30030/30175) and the reconnect invalidations remain the freshness paths; the poll only exists to cover a silently dropped live event. Behavior on publish, focus, and reconnect is unchanged. - Mechanical: localStorage identity helpers moved to `readStateIdentity.ts` (no behavior change) to keep `readStateManager.ts` under the file-size ratchet. Expected effect on the measured profile: the poll stretch cuts the 30030/30175 bursts (46% of inbound bytes) by 10x; the echo drop removes the recurring ~44 KB nip44-decrypt + parse per publish cycle (the echo still arrives on the wire — nostr filters cannot exclude own-author events — so this is a CPU/IPC saving, not a bandwidth one). ## Acceptance - New tests: echo dropped **before** decrypt (mutation-checked: disabling the drop fails the test), replayed duplicate of the same id still parses, foreign-client events always parse, published-id set stays capped when publishes fail (never-echoed ids). - Full desktop suite **4794/4794**, `tsc --noEmit` clean, `pnpm check` (biome + ratchets) clean at head. ## Not addressed (follow-ups) - The 44 KB blob itself (one read-state event carries all ~500 channels; a delta or per-channel-shard format is a protocol change). - Duplicate delivery of the same events on concurrent `history-` subscriptions (relay/client dedupe). - Webview RSS of 12.5 GB observed on the same machine — retention hunt is separate work; shrinking the heap multiplies the value of this PR since the GC floor scales with live-heap size. Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |
||
|
|
207154706c |
fix(desktop): support channel message path links (#5889)
## Summary - accept `buzz://channel/<uuid>/<64-hex-event-id>` as a compatibility message deep link - activate the desktop window and route path-form message links through the existing durable message-navigation queue - support the same path form when rendered or pasted inside Buzz, while canonicalizing composer output to `buzz://message?...` - retain the existing one-segment channel-link behavior and reject malformed event IDs or extra segments ## Context Buzz Desktop 0.5.11 has no native `channel` route. The recently merged channel-link handling on main recognizes `buzz://channel/<uuid>`, but rejects the externally shared `<channel>/<event-id>` form before window activation. On macOS that presents as Buzz taking the menu bar while its window neither foregrounds nor navigates. ## Test plan - `cargo test --manifest-path desktop/src-tauri/Cargo.toml parse_channel_deep_link` - focused channel-link, composer-link, and markdown unit tests - `pnpm typecheck` - mandatory pre-push hook: desktop checks, full desktop unit tests, and Tauri/Rust checks Installed-app external-open behavior requires a build containing this change; 0.5.11 cannot exercise it because that release predates native channel-link handling. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
5ddf23d700 |
feat(mobile-messages): render compact Buzz permalink chips (#5639)
**Category:** improvement **User Impact:** Buzz channel, message, repository, pull request, and issue links now display recognizable context and navigate reliably in the mobile app. **Problem:** Bare Buzz permalinks appeared as raw or ambiguous URLs on mobile, while channel and message links were not handled consistently across Markdown forms and startup states. **Solution:** Normalize eligible bare Buzz URLs without consuming Markdown syntax, render them as semantic icon-prefixed chips, and route channel/message targets through the mobile deep-link dispatcher while preserving authored Markdown labels as ordinary links. <details> <summary>File changes</summary> **mobile/lib/features/channels/deep_link_dispatcher.dart** Routes parsed channel and message links through the appropriate in-app navigation callbacks. **mobile/lib/features/channels/message_content.dart** Presents all bare Buzz permalinks as semantic icon chips and keeps authored labels as ordinary links. **mobile/lib/features/channels/message_content/link_normalizer.dart** Normalizes bare and autolinked Buzz URLs without consuming Markdown delimiters, code, or punctuation. **mobile/lib/shared/deeplink/deep_link.dart** Adds strict channel and project-entity parsing alongside message deep links. **mobile/lib/shared/deeplink/pending_deep_link_provider.dart** Preserves pending navigation until the mobile routing surface is ready. **mobile/test/features/channels/channel_detail_page_test.dart** Updates navigation integration coverage for icon-prefixed channel chips. **mobile/test/features/channels/deep_link_dispatcher_test.dart** Covers channel/message dispatch and missing-target behavior. **mobile/test/features/channels/message_content/link_normalizer_test.dart** Exercises Markdown-safe normalization across the full Buzz link suite. **mobile/test/features/channels/message_content_test.dart** Verifies chip labels, icons, semantics, authored-label opt-out, and navigation callbacks. **mobile/test/shared/deeplink/deep_link_test.dart** Covers strict parsing for channel, message, repository, pull-request, and issue links. </details> ## Reproduction steps 1. Run the mobile app and open a channel containing bare `buzz://channel`, `buzz://message`, `buzz://repo`, `buzz://pr`, and `buzz://issue` URLs. 2. Confirm each bare URL renders as one cohesive chip with a type icon, a useful name or shortened identifier, and no duplicated channel `#` character. 3. Add an authored Markdown link such as `[design discussion](buzz://issue?...)` and confirm the supplied label remains an ordinary link rather than becoming a chip. 4. Select channel and message links and confirm they navigate correctly from inline and autolinked forms. ## Screenshots / demos **iOS Simulator — channel, message, repository, pull request, and issue permalink chips** Real app build (`37b2cb5eb`) running on an iPhone 17 Pro simulator.  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
dbee2914ad |
test(desktop): await channel E2E bridge readiness (#5886)
## Summary - wait for the channel mutation and cache invalidation E2E hooks before using them - make those hooks required after readiness instead of silently skipping fixture setup - keep the production channel settings behavior and assertion unchanged ## Why On slower CI startup, `page.goto()` can resolve before the E2E bridge installs its globals. The test used optional calls, so all three fixture operations could silently do nothing and leave the seeded `General discussion for everyone` description in React Query. The assertion then failed deterministically, including both retries. ## Validation At commit `5b4d5d290b316db5eef78c3596a17c7a270c8163`: - `pnpm -C desktop build:e2e` - focused Playwright test repeated 30 times: 30 passed - `pnpm -C desktop exec biome check tests/e2e/channels.spec.ts` - mandatory pre-push hooks passed on the exact pushed head: `branch-skew`, `desktop-check`, `desktop-typecheck`, `mobile-test`, `desktop-test`, `rust-tests`, and `desktop-tauri-checks` - `git diff --check origin/main...HEAD` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
fd0ab47a1b |
fix(link-preview): refetch a link when it re-enters the composer (#5510)
### Overview **Category:** fix **User Impact:** When a user re-pastes (or finishes typing) a link that previously failed to load a preview, the composer now refetches it immediately and can never send a snapshot preview built from the old, stale metadata. **Problem:** The link-preview cache is shared with passive message-list scroll, so a URL that resolved to a negative result (a hard `null` miss or a transient fetch failure) stayed cached and re-usable. Re-pasting that exact link into the composer served the stale negative and never refetched. Worse, the stale metadata was still `snapshotReady`, so a fast clear-then-repaste could attach a **stale snapshot preview tag** to the sent message — a preview that no longer matched the link. **Solution:** A freshly-entering link is forced to refetch, and the composer is fenced against ever shipping a tag built from pre-re-entry metadata. This closes three distinct races surfaced over successive review passes: (1) the shared negative cache being reused on re-entry; (2) the resolver's debounce swallowing a fast clear+re-paste so the re-entry was invisible and the stale tag stayed sendable; and (3) an in-flight media upload started from the stale metadata publishing its tag after fresh metadata had already arrived. Healthy cached hits are never touched (instant card, no redundant fetch), and passive message-list scroll — which never opts in — keeps riding the shared cache exactly as before. <details> <summary>File changes</summary> **desktop/src/shared/lib/useResolvedLinkPreviews.ts** Adds a loader `invalidateNegative(href)` that drops a cached negative result (resolved `null` or transient fail) while leaving healthy hits and in-flight promises alone, and a `refetchNewNegatives` option that invalidates each newly-present href's negative entry before the peek/load loop reads the cache. Also adds an optional `liveHrefs` input so newness is judged against the caller's LIVE (undebounced) content — a debounce-swallowed leave/re-entry of the same URL still counts as new. Because the hook retains its own resolved metadata (the render that scheduled the effect already read the stale negative from it), it also clears its OWN negative key for every re-entered href, so the link renders as pending until the fresh load wins. `buzz://` entity links are skipped (they resolve off the relay, not this cache). **desktop/src/features/messages/ui/useComposerLinkPreviews.tsx** Opts the composer into `refetchNewNegatives` and feeds it the live hrefs. Detects a same-URL re-entry at render time (React batches the empty→repaste renders, so an effect keyed on the live set never observes the transition), then blocks the re-entered href until the resolver's forced refetch visibly cycles through pending: its stale ready tag is dropped from state and excluded from the sendable output until a fresh result re-tags. Only the sendable negative case (`fallback`) is blocked; a healthy (`image`) re-entry keeps its instant card. Adds a per-href upload generation token (`uploadsRef` becomes `Map<href, generation>`): a live re-entry bumps the generation, the upload effect's dedup guard and completion are generation-aware, so an in-flight upload from stale metadata cannot publish its tag after settling and a fresh upload can start even while the superseded one is still in flight. **desktop/src/shared/lib/useResolvedLinkPreviews.test.mjs** Adds resolver-level regressions: `invalidateNegative` drops a cached miss (next load refetches) but preserves a healthy hit (no redundant fetch); transient failure → URL removed → re-entered renders pending/not-`snapshotReady` until a successful retry; and the retained-negative + shared in-flight-fetch + re-entry interleaving clears the local negative regardless of the shared entry's shape. **desktop/src/features/messages/ui/useComposerLinkPreviews.test.mjs** Adds composer-hook regressions driving the REAL hook through the hostile gestures: a fast clear+re-paste inside the debounce window drops the stale tag and holds Send pending until a fresh tag carrying the newly-fetched media lands; and a stale in-flight upload held across the clear+re-paste and fresh-metadata resolution cannot publish its pre-clear tag, while a fresh upload starts and its tag wins. </details> ### Reproduction Steps 1. Paste a link whose preview fails to resolve (force a transient fetch failure) so the composer shows a blank/collapsed card. 2. Clear the composer and re-paste the same link (quickly, within the ~350ms debounce window). 3. Observe the preview refetches immediately rather than reusing the stale negative result, and Send stays disabled until a fresh tag lands. 4. Send the message and confirm the attached preview tag reflects the fresh fetch, never the stale pre-clear metadata. 5. Confirm passive message-list scroll of already-resolved links still shows cards instantly with no extra fetches. ### Notes Scope grew across three review passes from the original single resolver opt-in into a full defense against shipping stale snapshot tags on link re-entry — see the scope-adjustment comment on this PR for the detail. Stacked on #5245 (`tho/link-preview-snapshot-race`), whose rewrite of `useComposerLinkPreviews.tsx` is the sole overlapping file. The transient-retry work stays in #5502, which touches no composer file and remains based on main. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
5acb930821 |
feat(desktop-messages): render compact Buzz permalink chips (#5638)
**Category:** improvement **User Impact:** Buzz channel, message, repository, pull request, and issue links now open reliably and display recognizable context in the desktop app. **Problem:** Buzz links could appear as raw or ambiguous URLs, and navigation links received during startup or community transitions could be dropped before the UI was ready. Repository and issue shares in particular required hover context to understand at a glance. **Solution:** Queue desktop channel/message navigation until the UI is ready, then render bare Buzz permalinks as icon-prefixed chips with concise entity context while preserving user-authored Markdown labels as ordinary links. <details> <summary>File changes</summary> **desktop/src-tauri/src/deep_link.rs** Adds validated channel-link parsing and a deduplicated, acknowledged queue so navigation survives frontend startup. **desktop/src-tauri/src/lib.rs** Registers the pending-navigation state and commands with the desktop application. **desktop/src/features/communities/useCommunityInit.ts** Resets queued navigation safely across community boundaries without leaking stale destinations. **desktop/src/features/messages/lib/channelLink.test.mjs** Covers valid, malformed, and canonical channel permalink forms. **desktop/src/features/messages/lib/channelLink.ts** Defines strict parsing and detection for `buzz://channel/<uuid>` links. **desktop/src/features/messages/lib/composerMessageLinkNode.test.mjs** Extends composer-node coverage for normalized Buzz link content. **desktop/src/features/messages/lib/composerMessageLinkNode.ts** Keeps composer link-node handling aligned with the expanded Buzz link surface. **desktop/src/features/messages/lib/remarkChannelDeepLinks.test.mjs** Verifies bare channel URLs become renderable deep-link nodes without touching code. **desktop/src/features/messages/lib/remarkChannelDeepLinks.ts** Transforms eligible bare channel links into dedicated Markdown nodes. **desktop/src/features/messages/lib/remarkEntityLinks.test.mjs** Covers bare repository, pull-request, and issue detection and code-span exclusions. **desktop/src/features/messages/lib/remarkEntityLinks.ts** Adds dedicated Markdown nodes for bare Buzz project entities. **desktop/src/shared/deep-link.test.mjs** Exercises queued navigation, acknowledgement, serialization, and community-switch behavior. **desktop/src/shared/deep-link.ts** Serializes pending deep-link drains and acknowledges destinations only after successful navigation. **desktop/src/shared/styles/globals/markdown.css** Aligns permalink icon geometry and spacing with agent mention chips. **desktop/src/shared/ui/markdown.test.mjs** Adds integration coverage for every permalink chip, authored labels, fallbacks, icons, and static rendering. **desktop/src/shared/ui/markdown.tsx** Routes channel and entity nodes through the shared presentation path while preserving authored link text. **desktop/src/shared/ui/markdown/BuzzLinkChip.tsx** Introduces the shared interactive/static permalink chip and authored-label inline-link components. **desktop/src/shared/ui/markdown/ChannelDeepLink.tsx** Renders channel shares and references with Hash icons, names, and shortened-ID fallbacks. **desktop/src/shared/ui/markdown/MessageLinkPill.tsx** Renders ordinary message shares with message icons and channel/message context while retaining sent-from-thread behavior. **desktop/src/shared/ui/markdown/entityLinks.tsx** Maps repositories, pull requests, and issues to Projects-aligned icons and contextual labels. **desktop/src/shared/ui/markdown/nodeCache.ts** Includes entity-link rendering in cached Markdown node handling. **desktop/src/shared/ui/markdown/utils.ts** Allows validated channel links through the Buzz URL transform. **desktop/src/shared/useMessageDeepLinks.ts** Drains queued navigation links safely and clears them during teardown. **desktop/src/testing/e2eBridge.ts** Extends the mock bridge with pending-navigation command behavior. **desktop/tests/e2e/community-rail.spec.ts** Verifies queued links do not cross community boundaries. **desktop/tests/e2e/navigation.spec.ts** Covers channel/message deep-link navigation during startup and active sessions. **desktop/tests/helpers/bridge.ts** Adds reusable deep-link mock state and acknowledgement helpers. </details> ## Reproduction steps 1. Run the desktop app and open a channel containing bare `buzz://channel`, `buzz://message`, `buzz://repo`, `buzz://pr`, and `buzz://issue` URLs. 2. Confirm each bare URL renders as one cohesive chip with a type icon, a useful name or shortened identifier, and no duplicated channel `#` character. 3. Add an authored Markdown link such as `[design discussion](buzz://issue?...)` and confirm the supplied label remains an ordinary link rather than becoming a chip. 4. Select channel and message links and confirm they navigate correctly in warm and cold-start states. ## Screenshots / demos Houston dark theme with custom purple accent (`#a855f7`), captured from rebased visual implementation `ad411cc06`; current head `0aafa144f` only adjusts E2E expectations for the visible mention-label behavior shown here. **Composer — channel, message, repository, pull request, and issue pills**  **Message list — channel, message, repository, pull request, and issue pills**  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
17d2147eca |
Fix video comment effect wrapping (#5748)
## What changed - render video-review timecode chips inside the first Markdown paragraph so comment text wraps naturally around them - reuse the canonical video-review chip treatment across the timeline, Inbox previews, and Inbox detail - preserve video-review context in Inbox so timestamp chips remain interactive ## Why Video comments now support Markdown-like effects, but non-player surfaces rendered the timestamp beside a separate text layout. That kept the chip and comment from sharing the same inline flow and made Inbox behavior inconsistent with the player. ## Validation - `pnpm --dir desktop check` - 100 focused Markdown, timecode, video-review, and Inbox unit tests - `pnpm --dir desktop build:e2e` - focused `video-attachment.spec.ts` Playwright scenario - pre-push desktop typecheck and 4,761-test desktop suite - native Builderlab staging with the configured profile Focused timeline and Inbox snapshots will be attached in a PR comment. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> |
||
|
|
1d51081b8a |
Teach agents to inherit Buzz product intent (#5875)
## Summary - make `VISION.md`, relevant `VISION_*.md`, and applicable testing guides explicit planning and review inputs for non-trivial Buzz changes - teach managed agents to load repository-root and path-local `AGENTS.md` files after selecting a checkout - distinguish CI evidence from exercising the live workflow for user-visible and integration behavior - turn repeatable mistakes into same-session durable lessons, keeping only load-bearing rules in core memory and promoting shared lessons to team guidance - pin the new managed-agent prompt invariants in tests - preserve the exact display name shown in Buzz when mentioning or addressing someone; never infer or look up a surname merely to sound more complete ### Related issue None found after searching `block/buzz` issues and PRs for agent instruction, vision, and product-intent routing. ### Testing At commit `07ef705b42f58d3be6981165c6959d541ada0ba7`: - `cargo fmt --all -- --check` - `cargo test -p buzz-acp agent_draft_prompt_tests` (4 passed) - mandatory pre-push hooks passed on the exact pushed head: `branch-skew`, `desktop-check`, `desktop-typecheck`, `mobile-test`, `desktop-test`, `rust-tests`, and `desktop-tauri-checks` - `git diff --check origin/main...HEAD` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
caa64b5e8f |
feat(desktop): one relative date ladder across chat and the Inbox (#3769)
Slice 6 of #2216. Independent of #3642 — cut from `main`, no shared files in conflict. ## Why Five surfaces formatted the same thing five ways, and none of them matched the writing standard's Today / Yesterday / weekday / date progression. | Surface | Before | |---|---| | Chat day divider | `Monday, March 31st` — ordinal suffix, which the standard says to avoid | | Inbox section header | `Yesterday`, but never `Today`; always printed the year | | Inbox list row | A third implementation | | Inbox thread pane header | `Jul 8, 2026, 2:34 PM` — always absolute, always with the year, never relative at any distance | | Channel message header | `9:05 AM` — a bare clock, so a message from last week has nothing to anchor it once its day divider scrolls away | There were three separate date implementations doing this, which is the symptom worth naming: **two different jobs were being solved ad hoc at each call site.** A header that labels a *group* of items needs a different label than an individual item's own timestamp. ## What `shared/lib/datetime.ts` owns both ladders: ``` formatDayGroupLabel formatItemTimestamp (day divider, section header) (list row, message header) Today → Today withTime:false withTime:true Yesterday → Yesterday 2:34 PM 2:34 PM 2–6 days → Monday Yesterday Yesterday at 2:34 PM this year → June 20 Monday Monday at 2:34 PM older → June 20, 2025 Jun 20 Jun 20 at 2:34 PM Jun 20, 2025 Jun 20, 2025 at 2:34 PM ``` ## Two deliberate deviations from the standard Both are documented at the definition, not just here. **The oldest band keeps the day.** The standard collapses anything over ten months to month-and-year (`Aug 2022`). A group label has to *identify* its day — collapsing would give every day in a month the same divider, so scrolling old history would show a run of identical headers with no way to tell one day from the next. Only the year is conditional. There's a test asserting three consecutive 2022 dates produce three distinct labels. **Roomy surfaces keep the time of day at every band.** `Yesterday at 9:05 AM`, not `Yesterday`. This is a chat and collaboration workspace rather than a transactional product — where you read conversation, the time is content, not chrome. Narrow list rows still drop it (`withTime: false`) and rely on the existing hover tooltip, which stays the absolute value. `withTime` is a surface decision, not a preference. Today needs no date word in either mode: a bare clock already reads as today, and "Today at 2:34 PM" is longer without saying more. ## Derived rather than captured `MessageTimestamp` now takes only `createdAt` and derives both of its labels, instead of receiving a pre-formatted `time` string. A relative label captured when the message list was formatted would be frozen at that wording; deriving it means each render recomputes. This does not make it live — `MessageRow` is memoized, so a row already on screen when the clock passes midnight keeps saying "Today" until something re-renders it. The day divider above it has always had the same property, and both correct themselves on the next message, scroll, or navigation. Called out in the component doc so the next person doesn't read "derived" as "reactive". The memo comparator moved from `message.time` to `message.createdAt`. Behavior-identical — `time` was a pure function of `createdAt` — but it now names the prop the row actually reads. The 36px continuation hover gutter stays clock-only. A relative label doesn't fit in `w-9`. ## Middot between metadata segments `managed by you 9:53 AM` ran two unrelated facts together as if they were one phrase. Now `managed by you · 9:53 AM`. - `aria-hidden` — punctuation for the eye only. The header already reads as separate nodes to a screen reader, and `MessageAgentOwner` supplies its own "Agent managed by" label. - Grouped with the segment it precedes, so it can't wrap to the start of a line on its own — as loose siblings in a `flex-wrap` row, an orphaned divider is exactly what happens. - No margin; spacing comes from the container gap. - **No separator after the author name.** "Alice 9:53 AM" already reads as a name followed by a time. Dividers go between metadata segments only. Middot is already the app's separator for this — `MessageThreadSummaryRow`, the mention list, project rows, 46 files in total. Applied to the channel message header, channel system rows, and the Inbox thread pane. Left-side Inbox activity rows deliberately unchanged. ## Verified Screenshots taken through `just desktop-screenshot`: - `#agents` — `nadia 🤖 managed by you · 10:20 AM`, and the `Today` divider with clock-only rows - Inbox thread pane — `alice 🤖 owner unavailable · 12:00 PM` **Gap worth naming:** every mock channel message is same-day, so the past-day labels (`Yesterday at 9:05 AM`, `Jun 20 at 2:34 PM`) are covered by unit tests rather than by a rendered screenshot. Happy to add a spec that seeds an older `created_at` if a reviewer wants to see them. ## Validation - `pnpm check`, `pnpm typecheck` — clean - Unit: **3800/3800**, including 17 new tests in `shared/lib/datetime.test.mjs` and 4 in `messageTimestampContract.test.mjs` The datetime tests pin the things that are easy to regress: Today/Yesterday as *calendar* boundaries rather than 24-hour windows (a message 15 hours old across midnight is "Yesterday"; one 22 hours old on the same day is "Today"), the weekday band bounded at both ends so a future timestamp from clock skew never gets labelled with a past weekday, no ordinals across all the tricky days (1/2/3/11/12/13/21/22/23/31), the year omitted within the current year, and compact labels staying ≤12 chars for a narrow row. - Smoke E2E: **783 passed, 2 failed, 1 skipped** Both failures are pre-existing and unrelated, confirmed by re-running each against a clean tree: 1. `video-attachment.spec.ts:223` — fails deterministically on clean `main` 2. `community-rail.spec.ts:797` (keyboard drag-and-drop reorder) — flaky on clean `main`: 2/5 failures there vs 3/5 with this branch, i.e. noise ## Mobile Mobile had the same divergence, so it moves with desktop rather than drifting until the next pass. `mobile/lib/features/channels/date_formatters.dart`: | Before | After | |---|---| | `formatDayHeading` → Today / Yesterday / `Tuesday, March 31, 2026` | Today / Yesterday / `Tuesday` / `March 31` / `March 31, 2025` | | `formatThreadSummaryLastReplyTime` → `on May 19th` | `on May 19` | Same two departures from the standard as desktop, documented at the definition and cross-referenced to `datetime.ts` so the next person editing one finds the other. Day comparison also moved to a rounded start-of-day difference, so a DST transition counts as one calendar day rather than zero — Dart's `Duration.inDays` truncates. **Message timestamps stay clock-only on mobile.** Desktop message headers now read `Yesterday at 9:05 AM`; mobile keeps `9:05 AM` at every band. That's the compact side of the same surface split the desktop change makes — a mobile timestamp sits inside a chat bubble on a narrow screen with the day divider a short scroll away, where a date word costs width it doesn't earn. Recorded as a decision at `formatMessageTime` so it doesn't read as an oversight. Mobile needs no middot work: message headers have no "managed by" segment, and the mention suggestion list already uses `\u00b7`. Validation: `dart format` clean, `flutter analyze` no issues, `flutter test` **911 passed, 1 skipped** — 8 new day-heading tests covering the weekday band, the year boundary, ordinals across 1/2/3/11/12/13/21/22/23/31, distinct labels for consecutive days in the oldest band, and calendar-day rather than 24-hour bands. ## Out of scope - **Search results.** `SearchResultItem.tsx` and `TopbarSearch.tsx` hand-roll a `5m ago` elapsed format. That's a third *kind* of label — elapsed rather than relative-calendar — and deciding whether search should switch is a separate call. - **`formatThreadSummaryLastReplyTime`** keeps its own "3 hours ago" elapsed scale on both platforms; only its old-reply fallback lost the ordinal (`on May 19th` → `on May 19`). - **Mobile search.** `relativeTime` returns `7/31/2026` past a week, matching the desktop search format that's also out of scope above. Both should change together or not at all. --------- Signed-off-by: Clay Delk <clay.delk@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
17977814d3 |
fix(desktop): amortize observer journal eviction with a low-water mark (#5808)
Refs #5718. ## What happens `appendAgentEvents` evicts the per-agent live observer journal back to *exactly* `MAX_OBSERVER_EVENTS`: ```ts const trimmed = sorted.length > MAX_OBSERVER_EVENTS; const final = trimmed ? sorted.slice(sorted.length - MAX_OBSERVER_EVENTS) : sorted; ``` Once an agent's journal reaches 3000, `current.length` is 3000 forever, every later append makes `sorted.length >= 3001`, and `trimmed` is `true` on every call. That permanently disables the incremental-fold gate: ```ts if (allAtEnd && !trimmed) { /* incremental fold */ } else { transcriptByAgent.set(key, buildTranscriptState(final)); } ``` So every steady-state append then replays the whole retained window through `buildTranscriptState`, which is itself O(streamed-text) because streaming chunks fold as uncapped string concat. Nothing shrinks `eventsByAgent` except a store reset, so the state is permanent for the life of the renderer process, per agent. At ~90 frames/min an agent crosses the cap in ~33 minutes; from then on live CPU escalates (issue receipts: 188x on a headless ingest, renderer CPU climbing to 119% of a core after five minutes idle). This is not an off-by-one — a cap of 3000 does want `>`. The defect is that trimming *to* the cap re-arms eviction on the very next append, and eviction is what forces the replay. ## Fix Evict to a low-water mark below the cap: ```ts const OBSERVER_EVENTS_LOW_WATER = Math.floor(MAX_OBSERVER_EVENTS * 0.9); ``` The journal still never exceeds `MAX_OBSERVER_EVENTS`; it now has to be refilled by ~300 ordinary appends before the next eviction, so one replay is amortized across the appends that refill it. Retention semantics (newest-N at trim time) and the derived transcript are unchanged. The mark is a **fraction of the cap** rather than a fixed count so the math stays correct if the cap is ever made per-agent — a fixed headroom could exceed a smaller cap and drive the slice length negative. ### Eviction floor Low-water eviction leaves headroom below the cap, and the dedup set is built only from the *retained* array — so once eviction discards the oldest frames, the journal no longer remembers them. A relay reconnect replaying a pre-eviction frame (normal relay behavior, and the reason the dedup set exists) would be re-admitted into the headroom, and a later refill to the cap would then trim away up to 300 legitimate retained events with **no new activity** — a bounded display-window loss plus rebuild churn that partially defeats the amortization. To close that, each agent carries an **eviction floor**: the ordering key of the newest event eviction has ever discarded (`evictionFloorByAgent`, recorded at trim time as the entry just below the retained window). `appendAgentEvents` rejects any arrival at or before the floor (`isObserverEventAfter`, so an equal key is rejected — the floor event itself was evicted); a stale-only batch returns `false` with no rebuild and no notify. Out-of-order frames *newer* than the floor are still admitted via the rebuild fallback, so the fold-gate semantics are unchanged. The floor is cleared in `resetAgentObserverStore` alongside the other per-agent maps. ## Evidence `observerTranscriptRetention.test.mjs` asserts the retention window's **shape** — the observable signal for which ingest path runs, since transcript *content* is identical on both paths by design — plus boundary cases and the invariant that the derived transcript still equals a full replay of the retained window. Against the pre-fix trim-to-cap shape, three tests fail on the mechanism itself (`test_append_crossing_cap_trims_to_exactly_low_water`, `test_headroom_refills_before_next_eviction`, `test_single_batch_larger_than_cap_trims_to_low_water` — each expects headroom the old shape never leaves), and the cost shows up directly in runtime: | | `observerTranscriptRetention.test.mjs` (single-event appends past the cap) | |---|---| | trim-to-cap (pre-fix) | **429,105 ms** | | this branch | **16,221 ms** | ~26x on this workload, consistent with the 188x the issue measured on a heavier one (their events accumulate streaming text; these do not, so this understates it). Three further tests pin the **eviction floor** against reconnect replay: a replay of already-evicted frames leaves the retained window byte-identical and notifies no listener; a pre-floor frame arriving after a refill to the cap drops no retained events; and an out-of-order frame *newer* than the floor is still admitted. Deleting the floor check turns exactly the first two red while the out-of-order case stays green — confirming the tests pin the floor's rejection without over-constraining legitimate out-of-order delivery. ## Merge-order note This PR collides with #5596 (bounded renderer accumulators) on `observerRelayStore.ts` by design — #5596 refactors this exact eviction into `mergeObserverEventBatch` in a new `observerEventOrdering.ts` and adds a second, unpinned-agent tier (`truncateUnpinnedAgentWindow`, `UNPINNED_AGENT_EVENT_TAIL`). This PR merges first; #5596 rebases over it, porting the low-water cap-math **and the per-agent eviction floor** into `mergeObserverEventBatch`, and applying the same headroom to the unpinned-tier truncate (which must also record a floor when it trims). The fraction-of-cap form makes the low-water port mechanical — it feeds either the 3000 pinned cap or the 100 unpinned tail without a fixed-count underflow. ## Credits Supersedes #5767 (Chessing234's low-water-mark approach and the runtime measurements). Closes #5718. Issue receipts from the reporter, GeneralJah215 (188x headless, 119%/core after 5min idle). --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |