镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Update desktop icon and persist window state - #9

Merged
wesbillman merged 1 commit into
mainfrom
codex/use-updated-sprout-icon
Mar 10, 2026
Merged

wesbillman merged 1 commit into
mainfrom
codex/use-updated-sprout-icon

Conversation

@wesbillman

Copy link
Copy Markdown
Collaborator

Summary

  • switch the desktop app to the updated sprout.svg favicon and remove the old sprout.png reference
  • regenerate the Tauri icon assets from the updated SVG and add padding so the dock icon matches typical macOS sizing
  • persist desktop window size, position, and maximized state across app restarts with the Tauri window-state plugin

Testing

  • pnpm build
  • cargo check --manifest-path desktop/src-tauri/Cargo.toml
  • pre-push hooks via lefthook (desktop-check, desktop-build, desktop-tauri-check, rust-fmt, rust-clippy, rust-tests)

@wesbillman
wesbillman merged commit 998d839 into main Mar 10, 2026
7 checks passed
@wesbillman
wesbillman deleted the codex/use-updated-sprout-icon branch March 10, 2026 00:50
tlongwell-block added a commit that referenced this pull request Mar 10, 2026
* origin/main:
  Add desktop Home feed (#12)
  Add desktop Playwright e2e harness (#11)
  Update desktop icon and persist window state (#9)
  feat: add channel creation flow (#8)
  Improve message markdown display and formatting (#7)
  feat(desktop): connect chat to relay (#6)
  docs(readme): clarify desktop setup (#4)
  feat: add desktop app (#3)

# Conflicts:
#	crates/sprout-test-client/tests/e2e_rest_api.rs
tlongwell-block added a commit that referenced this pull request Mar 11, 2026
* origin/main:
  feat: soft-delete for events/channels, enriched API responses, NIP-29 group management (#17)
  feat: Channel management, messaging, threads, DMs, reactions, and NIP-29 support (#16)
  Improve chat scrolling and multiline composer (#14)
  chore: remove redundant inline comments across all crates (#13)
  Initial backend revisions, workflow expansion (#5)
  Add desktop Home feed (#12)
  Add desktop Playwright e2e harness (#11)
  Update desktop icon and persist window state (#9)
  feat: add channel creation flow (#8)
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
wpfleger96 added a commit that referenced this pull request May 22, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (#1), UTF-8 truncation
panic (#2/#4), adds install concurrency guard (#11), exact provider ID
match (#15), error display stdout fallback (#5), success banner
suppression when already available (#12), misleading re-run text (#13),
IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7),
configurable e2e mocks (#9), shared raw type exports (#8), and
classify_provider unit tests (#10).
kaalph pushed a commit to kaalph/buzz that referenced this pull request Aug 21, 2026
…iew findings

The original implementation created a second parallel Tauri command
(discover_all_acp_providers) alongside the existing one to avoid
changing the return type. This produced two commands, two hooks, two
query keys, and two raw type converters. Consolidates into a single
command returning the full catalog, with a useAvailableAcpProviders
hook that type-narrows for callers needing non-null command/binaryPath.

Also fixes: pipe deadlock in install command (block#1), UTF-8 truncation
panic (block#2/block#4), adds install concurrency guard (block#11), exact provider ID
match (block#15), error display stdout fallback (block#5), success banner
suppression when already available (block#12), misleading re-run text (block#13),
IIFE refactor in PersonaDialog (block#14), hidden internal query lift (block#7),
configurable e2e mocks (block#9), shared raw type exports (block#8), and
classify_provider unit tests (block#10).
BradGroux pushed a commit to BradGroux/buzz that referenced this pull request Aug 23, 2026
birdblues added a commit to birdblues/buzz that referenced this pull request Sep 5, 2026
…side

Review follow-up (Codex + Grok on e4bf6fa). Both flagged the same blocker:
the WebView loaded the app door over plain HTTP and trusted the response
CSP, so a LAN MITM (or a header regression) could strip the policy and
restore fetch/XHR/WebSocket/subresource exfiltration — the navigation
delegate only sees navigations. Grok also warned that iOS 17+ ATS may block
raw private-IP HTTP in WKWebView regardless of NSAllowsLocalNetworking.

Both go away by never letting the WebView touch the network, the way the
desktop proxy works:
- shared/relay/app_content.dart: fetchAppDocument — Authorization header,
  no redirects (a 3xx fails; a custom header must never follow one),
  text/html only, 8 MiB cap, timeout; stampSandboxCsp — the relay/desktop
  policy minus `sandbox` (not expressible in <meta>) inserted as the first
  element after a leading doctype so no script can precede it.
- app_webview_page.dart: loadHtmlString with no base URL → about:blank,
  opaque origin, no storage. Navigation is one-shot: exactly the first
  main-frame about:blank load; reloads, window.open, same-URL posts and
  every subframe are prevented (decideAppNavigation, Codex block#2). Retries are
  generation-fenced so a stale callback cannot overwrite the current load
  (Codex block#4). Before running, Dart asks the native side whether the WebRTC
  hook is installed and fails closed otherwise (Codex block#3, Android included).
- SandboxWebViewHardening.swift: hook loadHTMLString(_:baseURL:) instead of
  load(_:) — the page's only entry point now; `installed` is set only after
  the exchange succeeds; kill list gains WebTransport and
  webkitGetUserMedia (Grok block#4; desktop script updated for parity).
- Info.plist: the ATS exception is removed — nothing in the WebView loads
  from the network any more.
- message_content.dart: an `![…]()` reference to an HTML app never reaches
  the image decoder; without a door it renders an inert download pill
  (Codex block#6). New `allowAppCards` flag turned off on the forum post card and
  compose-note preview, which crop with a box instead of maxLines and could
  hide a card's safety chrome (Codex block#5). authorLabel wired on forum thread
  posts/replies and pulse notes (Codex block#7, Grok block#7).
- relay_info.dart: a failed NIP-11 lookup while connected retries after 30 s
  instead of waiting for the next reconnect (Grok block#9).
- AppCard: the preview is a plain tap target; Run is the one announced
  action (Grok block#8).
- docs/sandboxed-apps.md: threat-model rows and the mobile table updated;
  iPad step 3 no longer checks ATS, the probe must see location.href ==
  about:blank.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UAm5qbNPszvcwnMMbfGVGa
Signed-off-by: dhseo <birdblues@mac.com>
TheSentinel454 added a commit that referenced this pull request Nov 5, 2026
…er pod (#9132)

## Problem

A routed read that misses the reader's 150ms acquire budget
(`reader_acquire_timeout`) falls back to the writer, whatever the read
is. Under a burst of subscription-history reads (REQ history, channel
and thread windows, feeds, search hydration, COUNT), the reader pool
fills, and every miss moves a heavy history query onto the writer. There
it competes with the authentication, tenant-resolution and moderation
lookups that only the writer serves, and their latency climbs with it.
Nothing bounds how many history reads a pod sends to the reader at once,
so the reader queue grows without limit as well.

Separately, the compatibility families
`buzz_db_pool_acquire_wait_seconds` and
`buzz_db_pool_acquisitions_total` have no `operation` label, so a
breakdown by operation reports every sample as unlabelled.

## Changes

1. **History reads no longer spill onto the writer when the reader is
busy** (`Db::route_read`). `route_read` now returns
`Result<RouteDecision>`. For `ReaderOperation::SubscriptionHistory`, a
`reader_acquire_timeout` on a saturated reader pool rejects the read
with `sqlx::Error::PoolTimedOut`. Saturated means every connection the
pool may hold exists and none is idle (`Db::reader_saturated`). The
check is needed because sqlx also reports a reader that refuses dials as
`PoolTimedOut`, and that reader is down, not busy. The relay already
answers that with its generic retryable `CLOSED` `error: database
error`, so there is no new client contract. A reader that is wrong
rather than busy (unreachable, validation failure, proof timeout, stale
token) and prechecks that never reach the reader still fall back to the
writer. Authorization reads (`is_relay_member`) keep the writer fallback
unchanged.
2. **Per-pod cap on reader-served history reads**
(`runtime/history_reads.rs`). A FIFO `tokio::sync::Semaphore` admits at
most the reader pool size less one fifth (at least one) history reads at
once. For example, the default pool of 20 admits 16. The remaining
connections are normally free for authorization reads. A read waits at
most 1s for a permit and is otherwise rejected the same way. The permit
is owned by the reader transaction (`ReaderTx`) and released when the
transaction is dropped; routed reads drop it before re-running a failed
replica read on the writer. That holds whether the transaction is a
per-call routed read or a `ReadSession` the relay keeps for follow-up
queries. The cap bounds history transactions, not connections: sqlx
returns a dropped transaction's connection asynchronously through the
reader release check. Released connections can therefore briefly occupy
the free slots, for one round trip normally and at most the 5s release
timeout when sockets stall. In that window, authorization reads fall
back to the writer as before, and history reads on the saturated pool
are rejected, not spilled.
3. **Rejections are observable**:
`buzz_db_route_decision{decision="rejected",reason="history_read_cap"|"reader_acquire_timeout"}`.
4. **Legacy pool families gain `operation`.** The population is
unchanged, and summing over `operation` gives the original series.
5. **HTTP reads answer 503 `database unavailable` when the database
can't be reached** (`api::db_read_error`). This covers a pool timeout,
closed pool or socket failure on `POST /query`, `/count` and every
bridge `/query` branch that serves a history read (channel window, the
three feeds, thread replies), including a shed history read. These used
to return a generic 500. The thread-window path already answered 503 for
pool timeouts; this applies the same mapping to the shared helper.

Pool sizes are unchanged on purpose. A larger pool adds lock contention
on the writer rather than relieving it.

Docs: `deploy/charts/buzz/README.md` (operator contract) and
`PLANS/REPLICA_FULL_READ_ROUTING_DESIGN.md` (fail-closed exceptions).

## What got simpler

The concept count is roughly flat. Instead of a caller-side permit, the
permit lives in the one type every replica transaction already flows
through (`RouteDecision::Replica` / `ReadSessionInner::Replica`). The 11
routed call sites change only by a `?`, and the compiler enforces that
change: a call site cannot silently fall through to the writer on a
rejection. No new error variant, CLOSED reason or env var.

## Tests

-
`postgres_tests::saturated_reader_spends_one_acquire_budget_and_rejects_history_reads`
(rewrites the former
`routed_fallback_spends_one_acquire_budget_when_aurora_cache_is_cold`).
A size-1 reader is held saturated with a cold Aurora cache. A history
read (`count_events_routed`) is rejected with `PoolTimedOut` within one
acquire budget, records `rejected/reader_acquire_timeout`, and releases
its permit. An authorization read on the same reader still falls back as
`writer/reader_acquire_timeout`.
- `postgres_tests::match_nothing_reads_take_no_connection` (unchanged):
its control still requires a history read against an unreachable reader
to fall back to the writer. It caught the first cut of this change,
which rejected that read.
-
`postgres_tests::history_permit_follows_the_read_session_and_a_full_gate_rejects`:
a held replica `ReadSession` keeps its permit. With the gate full, the
next history read waits the full permit budget and is then rejected as
`rejected/history_read_cap`, with no writer route recorded. Dropping the
session frees the permit and the next read routes to the replica.
- `history_reads::tests`: a cap sizing table, plus bounded-wait
rejection and reopen under paused time.
- `observability` label tests now require `operation` on both legacy
families.
-
`api::db_read_error_tests::unavailable_database_maps_to_retryable_503`:
pool timeout, closed pool and I/O map to 503 `database unavailable`.
Other errors stay 500.
-
`postgres_tests::shed_history_read_maps_to_503_on_every_bridge_branch`:
with the reader saturated, `/query` channel-window, mentions,
needs_action, activity and thread-reply requests each answer 503
`database unavailable` (each serves 200 beforehand).
- Mutation-checked on Blox: re-enabling the writer spill, forcing the
saturation check either way, skipping the gate, dropping the permit
early and removing the legacy label each fail a test.

## Residual

- During a reader failover whose old sockets stall, both slots sit in
the release check, so the pool reads as saturated. History reads are
shed as retryable for up to the release bound (5s) instead of loading
the writer.
`stalled_reader_sessions_release_their_slots_for_new_connections` now
retries through that window and still requires recovery onto the reader.
- A reader that accepts no TCP at all can fill the pool with in-flight
dials and look saturated. Its history reads are then rejected rather
than moved to the writer, which is the same outcome as a full gate.
- Shedding relies on clients backing off on a retryable `CLOSED` (RPR
#9). WebSocket clients already treat `error: database error` as
retryable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
TheSentinel454 added a commit that referenced this pull request Nov 6, 2026
…ia feature flags (#9170)

## Goal

Give operators a way to relieve database pressure in seconds, without
restarting relay pods. Today every concurrency limit is an environment
variable, so changing one means a rollout. During an incident, that
rollout's restart sets off a reconnect storm of its own.

## What changes

**Two runtime-tunable caps.** Each is read from an integer feature flag
in a new process-wide evaluation context, and re-read every 5 s:

| Flag | Caps | Ceiling |
|---|---|---|
| `buzz.relay.concurrency.history-reads` | subscription-history reads
holding a reader connection (the #9132 gate) | the existing `pool −
pool/5` share, which keeps readers free for authorization |
| `buzz.relay.concurrency.auth` | verified NIP-42 AUTH frames running
their database checks | none (unbounded today) |

- **Flags can only tighten.** A positive value is clamped to the
ceiling. Zero, a negative value, no value, or a provider that never
initialized leaves the gate at its ceiling, which is today's behavior.
Once LaunchDarkly has delivered values, they stay in force through a
later outage, so a cap tightened during an incident survives a provider
blip.
- **Lowering never cancels admitted work.** A lowered gate admits
nothing new until the work in flight falls under the new limit.
- **Visibility:** `buzz_concurrency_limit{gate}` reports the applied
limit (`0` means no limit), and each change logs a line.

**One resizable gate.** `HistoryReadGate` becomes
`buzz_db::ConcurrencyGate`, a FIFO semaphore whose limit can move
anywhere in `1..=ceiling`:
- Lowering it forgets free permits and records any shortfall as debt.
- A permit released while there is debt is retired instead of returned.
- Raising it repays debt first, then adds permits.

All of this, and admission itself, settles against the debt under one
lock, so a newer limit always supersedes an older one; there is no
background resize task to race. Tokio hands a released permit to a
queued waiter before it runs; if that waiter is then cancelled, tokio
returns the permit to the semaphore directly, so admission retires any
permit it gets while debt is outstanding. The AUTH cap uses the same
type.

**AUTH shedding.** The permit is taken after signature verification, so
only real logins queue for the database checks. The wait is bounded at 2
s. A shed login gets a bare close with no `OK`, exactly like an auth
timeout. I chose this from what each client does on main today:
- **`OK false`:** the desktop app retries on an unjittered 60 s ladder.
Mobile parks the session until the app resumes. The agent harness fails
at startup.
- **Bare close:** the desktop app, mobile, the agent harness and the CLI
all reconnect on their jittered, capped backoff.

On the relay, the shed is visible as access line
`denied/concurrency_limit` and as
`buzz_auth_outcomes_total{outcome="concurrency_limit"}`.

**The feature-flag provider is chosen at startup.**
`BUZZ_FEATURE_FLAGS_PROVIDER` takes `static` (the default),
`environment` or `launchdarkly`, with `BUZZ_LAUNCHDARKLY_SDK_KEY` and an
optional `BUZZ_LAUNCHDARKLY_RELAY_PROXY_URL`:
- **Exactly one provider.** The value is parsed into a single provider,
so there is no stacking and no fallback, and the evaluator never changes
for the life of the process. The variable sits outside the
`BUZZ_FEATURE_FLAG_` namespace, so it can never be read as a flag.
- **Startup failures.** Local misconfiguration fails startup: an unknown
provider, a missing key, or a malformed proxy URL. LaunchDarkly being
unreachable, or rejecting the key, does not. The relay starts on
declared defaults, the SDK keeps connecting, and
`buzz_feature_flags_ready{provider}` shows whether the provider has
initialized. A revoked key must not crash-loop every pod over a tuning
knob.
- **Every build carries the LaunchDarkly adapter.** It stays inert
unless selected, so one published image serves every deployment and each
deployment's configuration pins its provider.

## Design-doc amendments (`crates/buzz-feature-flags/README.md`)

- **Provider selection.** The compile-time "exactly one provider"
contract becomes the startup selection above. The only-one guarantee now
holds per process instead of per build.
- **Startup.** Failing closed now covers local configuration only.
- **Guardrails.** "Flags may choose between equivalent implementations
only" gains one exception: a flag may set an operational limit as long
as it can only tighten it, and shedding under that limit stays
retryable.
- **Evaluation context.** `EvaluationContext::for_process()` adds a
process context, which LaunchDarkly sees as a single `relay`-kind
context keyed `relay`.

## What got simpler

- **Removed the compile-time provider contract:** the nested fixture
crate (with its own 2.8k-line `Cargo.lock`), its 229-line contract test,
and the exact-one `compile_error!` guards it required consumers to copy.
One startup parse plus a unit test replaces them.
- **One gate type serves both caps.** The history-read gate became that
generic type instead of gaining a sibling.
- **Net change:** about −2.3k lines.

## Review-Proven Rules applied

- **#1:** shedding is retryable (`PoolTimedOut` → CLOSED for history, a
bare close for AUTH) and never an empty success. Provider failures are
logged and surfaced through a gauge.
- **#2:** the gate settles every limit change and permit release under
one lock. The table test covers lowering while idle, lowering below the
count held, lowering then raising before holders leave, lowering then
raising past the count held, and restoring the ceiling.
- **#3:** the tests drive the production call sites:
`Db::get_channel_window_with_session` under a lowered and raised cap
(Postgres), and `handle_auth` under a full AUTH gate. The controller
test runs the real refresh loop.
- **#4:** values are clamped, waits are bounded, and the refresh runs on
a fixed interval with skipped missed ticks.
- **#9:** AUTH shedding sends clients down their jittered reconnect
backoff rather than an unjittered or terminal path (see above).

## Review rework (`92a975c8`, `3f09cb33`)

- **Keys renamed** to `buzz.relay.concurrency.*`: LaunchDarkly keys
cannot be renamed later, and the flag project may be shared with other
services.
- **Shutdown:** LaunchDarkly's final flush has no deadline of its own,
so `close()` now runs it on a detached thread and waits at most 2 s,
after the audit drain. (The blocking pool would not bound it: the
runtime waits for blocking tasks when it drops.)
- **Refresh loop:** a panicking refresh is caught, logged and counted
(`buzz_concurrency_limit_refresh_panics_total`), and the next tick
retries, so the caps cannot freeze silently.
- **Runbook:** `docs/operator-concurrency-caps.md`, covering how to
tighten a cap and the cases in which a tightened cap returns to its
ceiling (mistyped or deleted flag, pod started during a provider
outage).
- **AUTH test:** asserts the second login passes the gate and returns
its permit; the gate permit is renamed so it no longer shares a name
with the NIP-FI effect permit.

Gated on a Linux workstation at `92a975c8` (`3f09cb33` is rustfmt only;
`cargo fmt --check` passes there): clippy `-D warnings`,
`buzz-feature-flags` (50), `buzz-relay --lib --bins` (1740), the new
modules under parallel `cargo test`, and the Postgres AUTH tests (27).
Each new test fails under its mutant: the refresh without
`catch_unwind`, `close()` with no deadline (hangs until killed), and the
AUTH handler with the gate skipped.

## Testing

Re-run in full at `3c289134` after review rework, on a Linux
workstation:

- `cargo fmt --check`; `cargo clippy -p buzz-feature-flags -p buzz-db -p
buzz-relay --all-targets --all-features -D warnings`.
- `buzz-feature-flags` with and without `launchdarkly` (50 and 24
tests), `buzz-db --lib` (209), and `buzz-relay --lib --bins` (1738),
plus a plain parallel `cargo test` of the new relay modules.
- Postgres: the `buzz-db` routed-read and history-gate tests (55), and
the `buzz-relay` AUTH and connection tests (38).
- `a_cancelled_waiter_cannot_reopen_a_lowered_gate` fails with the debt
check in `admit` removed.
- The new infra-free relay tests are added to the CI unit lane
(`Justfile` and `scripts/run-tests.sh`).
- E2E against a local relay at `06c39d64`: cap gauges, sequential logins
and history under cap 1, a 20-login burst (19 shed with a bare close,
all reconnected), and LaunchDarkly misconfiguration and unreachability.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant