Commit Graph
353 Commits
Author SHA1 Message Date
Michael Neale 2d53bc2ecc Pin mesh SDK example to direct shutdown commit 2026-05-29 14:22:44 +10:00
Michael Neale 5b9f018d84 showing how to use nascent sdk approach 2026-05-29 13:49:15 +10:00
tlongwell-blockandDawn 1cc536b03e mesh-llm: silence dead_code in client-side NIP-98 bearer module
The verifier side of the iroh-relay NIP-98 admission is fully wired and
tested in sprout-relay::iroh_relay. The client side (build_nip98_bearer)
is reserved for the deferred dial path — it's complete and unit-tested
on its own, but has no live caller until upstream mesh-llm PR A lands.

Adds a module-level #![allow(dead_code)] with a comment pointing at the
deferred-dial dependency, so the workspace stays warning-clean without
losing the test coverage on the helper.

cargo clippy --workspace -- -D warnings + cargo fmt --check clean.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-21 08:57:28 -04:00
tlongwell-blockandDawn 16299b4e3f mesh-llm: heartbeat republish — keep offers fresh while the panel is mounted
Without heartbeat, the expires_at TTL added in the previous commit makes
offers vanish from consumer UIs 15 min after the user toggles on, even
if the publisher is still running fine. The original commit message
even noted this gap but didn't implement it. Fixing now.

desktop/src/features/settings/hooks/useMeshOfferHeartbeat.ts (new):
- HEARTBEAT_MS = 5 minutes (~OFFER_TTL_SECS / 3 — so one missed beat
  still leaves the offer visible; only two consecutive misses drop us).
- While { enabled, irohRelayUrl } are both truthy, setInterval calls
  mesh_publish_offer, which re-stamps expires_at = now + 15 min as a
  NIP-33 replace under the same (pubkey, d_tag) address.
- Errors are logged and ignored — the user isn't waiting in front of the
  panel; the next tick or an explicit prefs change will surface a fresh
  error if the relay is durably down.

desktop/src/features/settings/ui/MeshComputeSettingsCard.tsx:
- New irohRelayUrl state, populated on mount and after every persist().
- useMeshOfferHeartbeat({ enabled, irohRelayUrl }) wired in.
- Mount-time relay probe so a user opening the panel with sharing
  already enabled from a previous session starts heartbeating
  immediately; failures are non-fatal (console.warn, prefs still
  editable).

pnpm typecheck + pnpm check clean.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 17:22:00 -04:00
tlongwell-blockandDawn f711e37a7a mesh-llm: Max review fixups — expires_at, EndpointAddr, same-relay filter
Per Max's pre-PR review of d9a791f:

1. Add MeshLlmOffer.expires_at: u64 (unix seconds). Hard-required by
   serde (no default) since consumers depend on it for correctness:
   crashed publishers cannot send the NIP-33 delete-by-replace
   tombstone, so the TTL is the only thing that reaps stale offers.

   New helpers + tests in sprout-core::mesh_llm:
   - is_expired(now) returns true when expires_at <= now.
   - matches_local_relay(current_relay) compares against the relay's
     NIP-11 iroh_relay_url with lightweight canonicalisation
     (lower-case scheme/host, trailing-slash collapse, query/fragment
     drop). v1 invariant: one relay = one mesh boundary.
   - is_publishable now rejects expires_at == 0.
   - canonical_relay_url() is a small private helper, deliberately
     separate from sprout_auth::nip98_canonical_url (different jobs).

   5 new tests (expires_at_required, is_expired_filter,
   matches_local_relay_canonicalises, is_publishable_rejects_zero_*,
   plus updated JSON fixtures throughout).

2. Fix doc wording: iroh NodeAddr -> EndpointAddr (rc.0 naming).
   Soften 'multiple relays bridging into membership scope' language to
   reflect that v1 is strictly single-relay and the field is reserved
   for future cross-relay only.

3. Desktop publisher (mesh_publish_offer) now computes
   expires_at = now + OFFER_TTL_SECS (15 min) and threads it through
   build_offer(endpoint_id, iroh_relay_url, expires_at). The frontend
   hook is expected to re-invoke publish on heartbeat well before the
   deadline (heartbeat plumbing is in the next commit).

4. Frontend useMeshLlmOffers hook:
   - probes the relay's NIP-11 iroh_relay_url once on mount and
     filters offers whose iroh_relay_url doesn't match (v1 same-relay
     invariant). When the relay doesn't advertise mesh-LLM, the filter
     correctly empties the list.
   - rejects events with schema v != 1 before storing.
   - 30s setInterval ticks 'now' so expired offers drop without waiting
     for a new event. O(1) timer regardless of how many offers are in
     the cache.
   - mirrors the canonicaliser logic from sprout-core in TS so JS-side
     accept/reject decisions match the Rust check.

Tests after change:
- sprout-core --lib: 174 -> 178 (+4 mesh_llm)
- desktop mesh_llm --lib: 11 unchanged (publisher signature change
  required updating build_offer call sites; tests pass exact-same set).
- pnpm typecheck + pnpm check (biome + file-sizes): clean.
- cargo clippy --workspace -- -D warnings: clean.
- cargo fmt --all -- --check: clean.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 17:20:29 -04:00
tlongwell-blockandDawn 72824221c0 mesh-llm: B5 discovery — subscribe + render in settings card
shared/constants/kinds.ts: KIND_MESH_LLM_DISCOVERY = 31990 (matches
sprout_core::kind::KIND_MESH_LLM_DISCOVERY).

shared/api/relayClientSession.ts: subscribeToMeshLlmOffers(onEvent)
issues a NIP-01 REQ for kinds=[31990], limit=200. Returns the latest
snapshot plus a live stream. Membership is enforced relay-side via the
existing NIP-43 fan-out gate, so consumers see only offers authored by
relay members.

features/settings/hooks/useMeshLlmOffers.ts: parses each event's
content as MeshLlmOffer (mirrors sprout_core::mesh_llm shape), keys
offers by (pubkey, d_tag) per NIP-33. Empty content => drop entry
(matches the Rust publisher's delete-by-replace path). Newest-first
sort. Returns { offers, error } for the consumer hook contract.

features/settings/ui/MeshComputeSettingsCard.tsx: new 'Compute offered
by other members' section between the caps fieldset and the identity
footer. Empty-state copy when no offers; otherwise a list with
pubkey-short / d_tag / advertised models / caps for each.

scripts/check-file-sizes.mjs: relayClientSession override 930 -> 960
to absorb subscribeToMeshLlmOffers; comment updated to mention it so
future readers know why.

pnpm check, pnpm typecheck both clean. The full publish/discover loop
runs end-to-end: user A toggles -> kind:31990 hits relay -> user B's
useMeshLlmOffers hook receives it -> card renders 'A is offering ...'.
The only remaining gap to actually use the compute is the iroh dial,
which is the deferred question for Tyler.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 17:10:26 -04:00
tlongwell-blockandDawn c5ec624ebc mesh-llm: B6 kind:31990 publisher + settings-card publish-on-save
desktop/src-tauri/src/commands/mesh_llm.rs: new mesh_publish_offer
command. Reads persisted prefs and the local iroh endpoint id; on
enabled=true, builds a kind:31990 event with the JSON-serialised
MeshLlmOffer envelope and a 'd' tag matching prefs.d_tag, then signs
+ POSTs via the existing submit_event pipeline (NIP-98 to /events).

On enabled=false, publishes the *same address* with empty content —
NIP-33's 'delete by replace' idiom — so consumers know the offer has
been withdrawn. PublishOfferResult.published_offer reports which path
was taken.

desktop/src/features/settings/ui/MeshComputeSettingsCard.tsx: persist()
now follows save-prefs with a relay capability probe and (when the
relay advertises iroh_relay_url) a publish call. If the relay doesn't
support mesh-LLM, prefs are still saved locally and the UI surfaces a
specific 'this relay does not advertise iroh_relay_url' message rather
than a confusing 'publish failed'.

The user-facing flow is now: open settings -> Share compute -> toggle
on -> a kind:31990 event hits the relay, NIP-43-fanned-out to other
members. Toggling off publishes the empty-content replacement.

Tests: 208 sprout-relay, 174 sprout-core, 11 desktop mesh_llm — all
unchanged-and-pass. desktop typecheck + biome clean.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 17:06:52 -04:00
tlongwell-blockandDawn e6b968a9de mesh-llm: C1 MeshComputeSettingsCard
desktop/src/features/settings/ui/MeshComputeSettingsCard.tsx (new):
- Toggle: 'Share this machine's compute' (master switch).
- Three numeric inputs: max VRAM (MB), max RAM (MB), concurrent peers.
  Empty = no cap, validated to non-negative integers.
- Displays the local iroh endpoint id (canonical Display form) so the
  user knows which device identity is publishing.
- All state persisted through the mesh_set_sharing_prefs Tauri command;
  loads via mesh_get_sharing_prefs + mesh_get_endpoint_id on mount.
- Error and saving states surfaced inline.

Registered as a new 'compute' SettingsSection in SettingsPanels.tsx
between 'agents' and 'channel-templates', with a Cpu icon. Reached
through the existing avatar-menu -> Settings flow (no popover
restructuring needed for the MVP).

desktop typecheck (tsc --noEmit): clean.
desktop biome check: clean.

UX (matches Tyler's [1]):
- avatar bottom-left -> ProfilePopover -> Settings -> Share compute
- one switch + three caps; the offering side decides everything.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 17:03:20 -04:00
tlongwell-blockandDawn d8ca1a9c3e mesh-llm: B7 Tauri commands surface
desktop/src-tauri/src/commands/mesh_llm.rs (new):
- mesh_get_endpoint_id(app) -> { endpoint_id }: creates the persisted
  iroh keypair on first call; returns the canonical Display form of
  the public key so the UI can show 'this device' identity.
- mesh_get_sharing_prefs(app) -> ComputeSharingPrefs: reads the
  persisted prefs file (defaults applied when absent).
- mesh_set_sharing_prefs(app, prefs) -> (): atomic write-through.
- mesh_relay_iroh_url(state, relay_ws_url) -> Option<String>: probes
  the relay's NIP-11 doc for iroh_relay_url; returns None gracefully
  when the relay doesn't advertise mesh-LLM.

All four registered in lib.rs's tauri::generate_handler! block.
Frontend hooks land in C1 (avatar-menu MeshComputeSettingsCard).

Cleanup: drop wildcard re-exports from mesh_llm/mod.rs so unused
publisher/dialer helpers don't trip top-level dead-code lints before
B5/B6 are wired in.

cargo check passes; clippy + fmt clean (relay-side workspace).

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 16:59:57 -04:00
tlongwell-blockandDawn 000c78c203 mesh-llm: B1 NIP-11 fetcher for iroh_relay_url
desktop/src-tauri/src/mesh_llm/nip11.rs: fetch_iroh_relay_url(ws_url)
converts ws:// -> http://, GETs / with Accept: application/nostr+json,
extracts the iroh_relay_url field from the NIP-11 JSON. Returns
Ok(None) on unreachable relays / malformed responses / missing field
so mesh-LLM silently disables itself rather than producing a deploy
mystery; only returns Err for un-fixable caller mistakes (non-ws URL).

Mirrors the probe_relay_supports_nip43 helper already in
commands/pairing.rs; deliberately doesn't share code since this is a
different decode shape with different graceful-failure semantics.

desktop mesh_llm tests: 9 -> 11 (+2 nip11 helper).

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 16:58:14 -04:00
tlongwell-blockandDawn 1dbc89cc11 mesh-llm: offer envelope (sprout-core) + desktop building blocks
B0 — sprout-core/src/mesh_llm.rs (new): MeshLlmOffer envelope, the
content of a kind:31990 event. Schema versioned (v: u32), with
deny_unknown_fields at the top level and a freeform 'extra' Value
escape hatch. ResourceCaps + ModelOffer sub-structs. d_tag charset is
limited to [A-Za-z0-9_-] (NIP-33 stability). 9 unit tests covering
round-trip JSON, optional caps, unknown-field rejection, d_tag
validation, is_publishable rule set.

B2 — desktop/src-tauri/src/mesh_llm/endpoint.rs: persists the iroh
endpoint keypair to {app_data_dir}/mesh_iroh.key as 32 hex bytes.
Atomic write via tempfile.persist; corrupt files quarantined to
.bad.{epoch} (same pattern as identity.key). 2 unit tests.

  Design note in the module doc: we deliberately do NOT derive the
  iroh key from the Nostr key, because that would couple key rotation
  (rotating the Nostr key would silently break active offers) and
  invent a new key-custody convention. Separate file, same Tauri
  sandbox.

B3 — desktop/src-tauri/src/mesh_llm/nip98.rs: build_nip98_bearer(keys,
iroh_relay_public_url) signs a kind:27235 event with the user's Nostr
key over the canonical relay URL (sprout_auth::nip98_canonical_url
with path '/relay'), base64-encodes the event JSON. This is the exact
token the relay's iroh_relay::verify_bearer decodes + verifies.
3 unit tests.

B-offer prefs — desktop/src-tauri/src/mesh_llm/offer.rs: persisted
ComputeSharingPrefs (the avatar-menu sliders). Default is disabled,
1 concurrent consumer cap. build_offer() returns None when disabled
so callers know to *delete* any prior offer rather than re-publish.
JSON round-trip + helper tests, 4 tests.

Workspace deps added:
- desktop pulls sprout-auth (for the canonical URL helper, nostr-free
  at the API surface so the 0.36/0.37 nostr split doesn't matter).
- desktop pulls iroh-base = =1.0.0-rc.0 with the 'key' feature for
  SecretKey/PublicKey/EndpointId.
- desktop pulls thiserror = '2'.

Tests: 9 desktop mesh_llm tests pass + 9 sprout-core mesh_llm tests
pass. Workspace clippy + fmt clean (relay side; desktop has expected
dead_code warnings until B4-B6 wire these in).

Note: desktop crate requires sidecar binary stubs in
desktop/src-tauri/binaries/ to typecheck; created via the existing
'just _ensure-sidecar-stubs' helper.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 16:55:45 -04:00
2fed74bdcd Show channels in command search (#617)
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-05-19 14:33:53 -04:00
3973080dce Refine conversation visual affordances (#615)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 08:54:41 -04:00
Will PflegerandGitHub 50b350dc58 fix(desktop): worktree support — workspace boundary + shared identity (#610) 2026-05-18 19:11:34 -04:00
tlongwell-blockandGitHub 6e1418e571 feat(huddle): fix concurrent-speaker mixing + jitter buffer + protocol v2 (up to 10 peers) (#609)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-18 16:34:52 -04:00
c0eb5affcf refactor(huddle): switch local TTS to Pocket TTS (Kyutai/sherpa-onnx) (#607)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-18 14:28:27 -04:00
137a4e268f Update inbox conversation experience (#608)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 13:48:47 -04:00
17eea2d267 Fix full-width message layouts (#591)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 09:04:09 -04:00
tlongwell-blockandGitHub 3daf8d6ae1 feat(huddle): swap STT default to Parakeet TDT-CTC 110M (EN, int8) (#562)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-05-15 22:22:05 -04:00
tlongwell-blockandGitHub d1097aa91f chore(desktop): upgrade Tauri to 2.11.x to fix blank window after sleep on macOS (#600)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-15 18:59:16 -04:00
tlongwell-blockandGitHub 7171d4954d fix(relay,desktop): only advertise NIP-43 when enforced; probe pairing by supported_nips (#601)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-05-15 15:11:37 -04:00
tlongwell-blockandGitHub 7763072ef9 fix(desktop): derive unread state from NIP-RS + relay catch-up only (#599)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-15 14:46:33 -04:00
tlongwell-blockandGitHub 32347d117c feat(desktop): per-persona and per-agent env var overrides (#594) 2026-05-15 10:43:00 -04:00
tlongwell-blockandGitHub 13dc0df688 fix(desktop): stop pinning agents to deprecated SPROUT_ACP_TURN_TIMEOUT (#592)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-14 21:51:38 -04:00
tlongwell-blockandGitHub 5cdb3062ed fix(desktop): populate member_count in get_channels so channel browser shows real counts (#548)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-14 20:38:01 -04:00
tlongwell-blockandGitHub 3a3501c77b fix(desktop): autofocus message composer on channel/thread open (#572)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-14 20:35:44 -04:00
4425f1f713 feat(desktop): add message more-actions dropdown menu (#590)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 15:31:55 -07:00
8a2d24e4fd Redesign Home as an inbox (#582)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 13:35:17 -07:00
tlongwell-blockandGitHub 1858e98d5e fix(desktop): drive unread badges from live subscription, not refetched lastMessageAt (#581)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-05-14 13:25:24 -04:00
9e76a08a47 fix(desktop): refine header scaling and shadow (#573)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 10:07:15 -07:00
b74ec95b8e fix(desktop): keep day dividers below header (#574)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:58:21 -07:00
aad564b130 Move agent activity below composer (#579)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Wes <wesb@block.xyz>
2026-05-14 09:46:52 -07:00
f0549b5038 feat(desktop): channel hover state and right-click mark-unread context menu (#578)
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-14 08:33:57 -07:00
efbdc7bfd5 feat(desktop): persona-first agent UX + bug fixes (#570)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 15:13:41 -07:00
tlongwell-blockandGitHub 9a403d3e8a fix: channel member search via NIP-50 + Typesense indexer fix for kind:0 (#569)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-13 15:03:35 -04:00
8716aefa3c fix(desktop): use resolve_command for ffmpeg discovery in sandboxed app (#568)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 11:00:00 -07:00
tlongwell-blockandGitHub 9c258e4beb feat(desktop): GUI for agent respond-to gate (owner-only / anyone / allowlist) (#557) 2026-05-13 12:48:42 -04:00
5db47a84b1 feat(desktop): add neutral accent color (#563)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 08:34:56 -07:00
5e6083ef9e fix(desktop): inline update control in header (#561)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 08:34:26 -07:00
8004bafaff fix(desktop): resolve unarchive revert and stale member list bugs (#558)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:55:25 -07:00
f41b9736ac fix(desktop): resolve O(n²) render cascade in activity sidebar (#555)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 15:48:05 -07:00
78508ea6cb fix(desktop): editor bug fixes — codeblock escaping, thread edit leak, channel links, and edit cancel (#553)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 19:35:10 +00:00
tlongwell-blockandGitHub 61c30add40 feat(desktop): multi-select in the paperclip file picker (#551)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-12 15:01:48 -04:00
tlongwell-blockandGitHub effa24f06c fix(desktop): forward cache headers in media proxy (#549)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-12 14:40:07 -04:00
ef5fdca792 feat(desktop): add Projects as first-class top-level view (#546)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 10:51:09 -07:00
11778f4250 feat(desktop): image download, drag-drop prevention, and drop zone indicator (#542)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 23:26:12 +00:00
19f290651a fix: pass profiles to mention autocomplete in threads and forum composers (#541)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 23:12:10 +00:00
1b9f6169ee feat(desktop): channel templates — reusable project settings (#538)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 12:31:25 -07:00
cec1143552 fix: side panel headers hidden by compact app header (#537)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 18:53:39 +00:00
tlongwell-blockandGitHub e2b6e8b25a Use dedicated sprout-agent avatar (#532) 2026-05-11 17:16:08 +00:00