Commit Graph
942 Commits
Author SHA1 Message Date
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 49db96c0d8 test(timeline): align eva e2e assertions to the virtualized contract
Three eva-authored e2e assertions encoded the pre-virtualization layout and
break once the timeline windows rows out of the DOM and positions them with
absolute/translateY:

- relay-reconnect: the reconnect-backfill test expected both the newest and the
  260-rows-old message mounted at once. A virtualized timeline windows the
  oldest rows out while the user sits at the bottom, so assert the newest at the
  bottom, then scroll to the top and poll until the oldest mounts -- the backfill
  depth is now proven by reachability, not simultaneous mounting.
- channels (x2): expectIntroBalancedAroundDayDivider compared the intro->divider
  gap against the divider->message gap for equality. The intro is a flex sibling
  above the timeline while the divider and first row are virtualized items, so
  the two gaps are measured across different layout regimes and no longer match
  within a pixel. Assert the intended reading order instead: intro, divider,
  then the first message, cleanly separated with no overlap.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 20:04:03 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger fe489d81a8 fix(timeline): cede the resize observer to the load-older index restore
The load-older index restore (useLoadOlderOnScroll) is the single scroll
writer across a prepend, and the layout effect cedes to it via the isPrepend
bail. The ResizeObserver in useAnchoredScroll did not: it ceded only for
convergence. So when the prepended rows measured late and grew scrollHeight,
the observer fired with the now-windowed-out message anchor, hit
restoreAnchorToMessage's all-gone fallback, pinned to the floor, and stomped
the index restore's correct offset.

Add a shared in-flight flag the index loop sets while it owns scroll, checked
at the top of the ResizeObserver callback alongside the convergence cede. The
restore math and the all-gone fallback are unchanged; the observer simply
defers to the single writer for the duration of the prepend.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 19:38:18 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 4ac28bfd67 fix(timeline): restore unconditional bottom-pin on load, cede observer mid-jump
The rebase resolution wrapped the ResizeObserver at-bottom re-pin in an isAtBottomNow(container) guard. On initial load the virtualizer grows scrollHeight a frame after the first pin (off-screen rows measure late) with no messages change, so the guard read ">2px from the new floor" and skipped the legitimate load-time floor pin, leaving the timeline not at bottom (scroll-history:190). Cede the whole callback while convergingTargetIdRef.current !== null \u2014 the precise jump-in-flight signal the geometry proxy was approximating, mirroring the layout effect's existing bail \u2014 and restore eva's unconditional at-bottom re-pin.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 19:12:20 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 4127d37bf6 feat(desktop): single-owner anchored scroll over the virtualized timeline
Re-platforms the index-anchor scroll model onto eva's single-owner anchor
(useAnchoredScroll) so the virtualized timeline keeps one scroll writer
under windowing. Removes eva's older-history IntersectionObserver and makes
useLoadOlderOnScroll the sole top-sentinel/fetchOlder owner; eva's anchored
scrollBy cedes to the index path on a prepend via a front-id/tail-id
discriminator (new prevFirstMessageIdRef). Adds a minimal restoreScrollPosition
writer that re-derives the anchor after a programmatic scroll instead of
re-introducing a competing scroll-owner. Windowed-out jump targets converge
through the virtualizer (indexByMessageId + getOffsetForIndex) rather than a
querySelector that goes null off-screen. Load-older roles are gated on
fetchOlder/virtualizer presence so MessageThreadPanel degrades to the passive
anchor. Reserves a fixed-height spinner slot so the fetch indicator does not
shift the bottom row.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 18:37:12 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 9c0eac5f41 style(desktop): wrap long lines in virtualization spec for biome
biome check enforces line-wrapping that biome lint does not. The
load-older test 07 had two over-width statements that passed local lint
but failed the Desktop Core biome check gate. Format-only, no behavior
change.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 18:30:42 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger fc54d84efb fix(desktop): stop competing scroll writers collapsing the load-older anchor
Loading older messages under virtualization let three writers fight over
scrollTop on overlapping frames, so the anchored row jittered or collapsed
to the top (~33% of prepends) and the library's reconcile spun the full 5s
MAX_RECONCILE_MS valve. Establish a single owner of scroll position across
the whole fetch+restore window:

- useLoadOlderOnScroll restores by scrollTop ONLY (drop scrollToIndex), via
  one getOffsetForIndex(anchorIndex + prepended, "start")[0] + intra-row gap
  write. getOffsetForIndex is a pure measurement-cache read, so no library
  scrollState is set and the reconcile loop has nothing to fight.
- The viewport ResizeObserver in useTimelineScrollManager no longer runs a
  competing restore during a fetch: it skips while isFetchingOlder is true
  (the spinner's clientHeight 720->590 mount-shift fires before the lock is
  set) and otherwise defers to lockedScrollTopRef when the load-older restore
  holds it. MessageTimeline threads isFetchingOlder into the manager.

The defect was invisible to unit tests (jsdom getBoundingClientRect -> 0) and
to static traces; the new load-older E2E drives a real prepend on six fresh
page loads and asserts the anchor holds every run, the scroller genuinely
grew, and the reconcile terminates. emitMockHistory now honors the relay
filter's until/limit so the mock relay paginates like a real one, which the
E2E needs to exercise a genuine older page.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 18:30:42 -04:00
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 913facde71 perf(desktop): virtualize message timeline to stop the cold-switch beachball
Channel switch streamed up to 200 uncontained MessageRows (each with
synchronous shiki markdown), then scrollToBottom("auto") forced a
full-document scrollHeight read-then-write reflow before paint over
every row — the macOS beachball Will reported on v0.3.25.

Windows the main timeline on @tanstack/react-virtual. The day-grouped
section tree is flattened to a typed TimelineItem[] stream plus a
messageId->itemIndex map from one walk (cannot drift), and every
DOM-querySelector scroll path (deep-link, search-jump, jump-to-unread,
scrollToBottom, load-older anchor) is re-pathed onto the index model so
windowing does not silently break jumps to off-screen rows.

Scroll convergence is split: @tanstack/react-virtual owns offset
convergence (its rAF loop re-aims getOffsetForIndex as rows mount and
measure); a pure reducer owns only staleness re-resolution and
termination — re-resolving the target's index by id each frame so a
concurrent prepend/delete cannot strand the loop on a stale index, and
terminating when the target is deleted or a 32-frame cap is hit. The
breaking math lives in lib/ under the .mjs suite.

The thread reply list stays content-visibility:auto rather than
virtualized — it is bounded, unpaginated, ungrouped, and shares the
scroll hook, so virtualizing it would force a second index re-path and a
head/prologue split for no beachball gain. Phase-2 route-chunk preload
warms the agents/channel/lazy-view chunks on idle to clear the
Agents-menu first-visit stall.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-06-18 18:30:41 -04:00
npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57ccandTyler Longwell 78ac6caf19 fix(desktop): restore anchor on in-viewport reflow when scrolled up
The ResizeObserver in useAnchoredScroll only re-pinned the scroller when
the user was at-bottom — when scrolled up reading older history and a row
above the reading row reflowed (link-card decode, async embed expand, late
font load, markdown that expands), the anchor row shifted on them and
nothing restored it. The PR description claimed image and embed loads all
flow through the anchor, but on a careful read only NIP-92 imeta images
are actually covered (their dim is reserved before decode, so no resize
fires). Every other in-viewport content growth fell into the gap.

Extract the anchor-restoration primitive — find the row (with the
nearest-newer fallback already used for prepends), measure its current
top, scrollBy the delta — into a shared restoreAnchorToMessage helper,
and call it from both the layout effect and the ResizeObserver. One
primitive serves the React-driven path (post-commit, on messages /
spinner change) and the non-React-driven path (image decode, embed
expand, font load), preserving the single-owner invariant. A messagesRef
is mirrored from the layout effect so the observer reads the same list
the DOM was last rendered from without resubscribing on every commit.

E2E coverage: scroll-history e2e adds an in-viewport reflow case that
seeds a scrollable channel, scrolls to a mid position, captures the
top-crossing row's offset, programmatically grows a row above the anchor
via style.minHeight, and asserts the anchor's offset is unchanged within
2px after the observer fires. Confirmed the assertion catches the
pre-fix behavior (80px drift) by reverting the implementation and
re-running.

tsc, biome (764 files), 998/998 unit, scroll-history e2e 7/7 green.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 16:39:26 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 5a42d3706a test(desktop): scroll-smoothness perf harness — steady-state + prepend cost
An instrument, not a gate. Seeds a busy channel against the mock bridge and
measures the main-thread cost the headless correctness suite cannot feel.

Two measurements:
- Fast-wheel scroll of the bounded ~200-row window: compositor-cheap
  (~0.2ms layout + 1.3ms recalc over a 241-frame, 12k-px burst).
- Prepend re-render cost while scrolled up: ~9-13ms main-thread tick,
  attributable to the O(rendered-rows) parent walk (videoReviewContext
  rebuild + day-group boundaries + element construction), NOT layout
  and NOT leaf-row reconciliation (MessageRow memo bails correctly).
  Anchor holds at 0px drift on this path.

Scope limit: measures Chromium reconciliation, not the WKWebView
compositor feel — that remains the real-Tauri macOS pass.

Run: pnpm build && npx playwright test --config=playwright.perf.config.ts
Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 16:38:22 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell feb9ac4243 fix(desktop): page older history until N visible rows, not a fixed message count
Scrolling up fetched a fixed 100-message batch and stopped. Because thread
replies collapse into their parent's summary and non-content events never
render, a reply-heavy window could fetch 100 events but grow the timeline by
only a handful of rows — making one wheel-up feel like it did nothing.

fetchOlder now pages in batches until at least MIN_TOP_LEVEL_ROWS_PER_FETCH
(10) *visible* top-level rows have been added, or history is exhausted. A new
pure helper, countTopLevelTimelineRows, counts rows the same way
buildMainTimelineEntries renders them: content kinds, minus deletions, top-level
or broadcast replies only. The dedup/exhaustion guards already in place
terminate the loop when the window stops advancing.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 16:10:46 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 53c580871e fix(desktop): key timeline day sections by calendar day, not first message
Each day group renders as a `<section>` whose React key was the exact
`createdAt` of its first message. Scrolling up prepends older messages,
and when a batch landed on a calendar day already on screen, the first
message of that day changed — flipping the section key. React can't diff
a changed key, so it unmounted and remounted the entire day section, all
its rows torn down and rebuilt above the reader's eye. That whole-section
remount is the residual flicker on scroll-up: the anchor restore was
correcting a full teardown instead of a clean prepend.

Key the section by the local start-of-day of its messages, which is
stable across same-day prepends, so an older message grows an existing
section's children (stably-keyed rows reorder, not remount) instead of
replacing the section. Fold the duplicate key derivation in the render
loop into the lib boundary's `key`, which was already documented as
"stable" but wasn't.

Pure helper `startOfLocalDaySeconds` plus lib tests for day-key stability
across a prepend and separation across calendar days. tsc, biome, 40 lib
unit, scroll-history e2e 6/6 green.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 15:49:11 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 02efffc956 fix(desktop): restore anchor when fetch-older spinner toggles
The older-history fetch spinner renders above the anchor row but toggles on its own render commit (messages unchanged). The anchor-restoration layout effect was keyed only on `messages`, so it never re-ran when the spinner appeared or disappeared — leaving the spinner's height as an uncorrected shift above the reader's eye, the residual flicker on prepend. Thread isFetchingOlder into useAnchoredScroll as an extra restoration trigger so the existing scrollBy correction fires on the spinner toggle too, making the anchor the single owner of every layout change above the fold.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 14:34:14 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell e067e83276 fix(desktop): keep deep-linked message spliced after route target clears
ChannelRouteScreen splices a getEventById-fetched target into targetMessageEvents so a deep link works in a channel whose feed doesn't already contain the message. The fetch effect wiped those events whenever the route target cleared — and onTargetReached clears the messageId URL param the moment the row is centered — so the only copy of the deep-linked message vanished and the timeline went blank.

Reset spliced events on channel/forum-post change only (guarded effect keyed on channelId/selectedPostId, seeded with the mount key so first-commit cache seeds survive), and have the fetch effect merge cached/fetched events additively instead of overwriting. e2eBridge: route the mock-engineering-shipped known event by 'h' tag so getChannelIdFromTags resolves it.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 13:35:16 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 2a56e08eb9 fix(desktop): restore overflow-anchor:none on live scroll containers
useAnchoredScroll is meant to be the single owner of scrollTop, but both
live scroll containers had lost the [overflow-anchor:none] class (it
survived only on the thread loading skeleton, which never scrolls). With
it gone, Chromium's native scroll-anchoring heuristic re-engaged and
applied its own scrollTop correction on prepend — picking its own anchor
element — while the hook applied a second scrollBy correction on top. When
the two anchors diverged the corrections stacked, producing the residual
jiggle that survived the single-owner rewrite.

Restoring the class on both live containers (MessageTimeline timeline +
MessageThreadPanel body) hands scroll ownership back to the hook alone.

This is a real-wheel-only symptom: native scroll anchoring barely fires
under synthetic scrollTop= writes, so the headless suite stayed green
while a manual macOS scroll pass surfaced it. scroll-history e2e 6/6,
tsc clean.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 12:56:40 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 0f32f49045 fix(desktop): single-owner anchored scroll for dynamic loading
Replaces the two competing scroll writers (useTimelineScrollManager +
useLoadOlderOnScroll) with one anchor-based primitive, useAnchoredScroll,
owned by both the main timeline and the thread panel.

The prior design had two hooks both mutating scrollTop on prepend, which
is the root of the timeline-jumping bug: a fetch-older restore and a
scroll-anchor adjustment would fight over the same frame. The new hook
keeps a single anchor (the row the reader's eye is on, picked by a
top-crossing walk) and restores it relative to its prior top offset after
every render — prepends, appends, image loads, and embed expansions all
flow through that one path.

Also fixes three concrete bugs surfaced by the ported E2E suite:

- Deep-link targets in older history: the post-mount target effect bailed
  once when the row wasn't loaded yet and never retried. It now keys on
  `messages` and re-runs until the spliced-in row commits, then centers.
- Root-message deep-link reopen: the initial-mount target path scrolled
  and marked the target handled but never fired `onTargetReached`, so the
  `messageId` URL param stuck and re-clicking the same link was a no-op.
  The initial path now fires the callback too, matching the post-mount one.
- Inline image height reservation: images never read their NIP-92 `dim`
  tag, so a tall image grew from ~0 on load and shoved the timeline. We
  now stamp intrinsic width/height so the row reserves space before decode.

Verification: tsc clean, biome (764 files) clean, 989/989 unit tests,
scroll-history 6/6, full e2e smoke 253 passed (3 failures all reproduce
on a clean main checkout — pre-existing, unrelated to this change).

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 12:36:44 -04:00
826945e1bb feat(desktop): unify unread pills into one shared UnreadPill component (#1111)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-06-18 07:56:33 -07:00
762ca43d26 fix(cli): use relay workflow id on create (#872)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
2026-06-18 10:46:44 -04:00
6335447887 Fold agent core memory into the session system prompt (#1112)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
2026-06-18 10:04:58 -04:00
cd8292fe9e feat(cli): add patches and issues commands for NIP-34 git collaboration (#1073)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Co-authored-by: Dawn (buzz agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-06-18 09:30:33 -04:00
0181603eba fix(desktop): stop random timeline message loss + page reconnect replay (#1105)
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-18 09:19:28 -04:00
tlongwell-blockandGitHub c338d8840c Update README.md 2026-06-17 22:06:58 -04:00
d24eac61a8 fix(desktop): keep thread replies from scrolling channel (#1109)
Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
2026-06-17 22:05:53 -04:00
dd9ce09020 fix(buzz-acp): accept siblings under allowlist author gate (#1108)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 21:40:35 -04:00
629fb57bf0 feat(deploy): add production Helm chart for Buzz (#990)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
2026-06-17 21:26:33 -04:00
aadbe67354 fix(desktop): keep MembersSidebar input usable while an add is in flight (#1106)
Signed-off-by: npub1xhqc4cnnln86lqxk983qulu8yxusfxfhntwl75es2jkvy5zvz26qzr0685 <35c18ae273fccfaf80d629e20e7f8721b90499379addff533054acc2504c12b4@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1xhqc4cnnln86lqxk983qulu8yxusfxfhntwl75es2jkvy5zvz26qzr0685 <35c18ae273fccfaf80d629e20e7f8721b90499379addff533054acc2504c12b4@sprout-oss.stage.blox.sqprod.co>
2026-06-17 21:11:06 -04:00
Will PflegerandGitHub 25001eb741 chore(release): release version 0.3.25 (#1102) v0.3.25 2026-06-18 00:17:44 +00:00
718b596e5b fix(desktop): stop dimming deferred message lists (#1104)
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-17 20:03:20 -04:00
d47572209d Smooth channel loading: single-surface timeline state machine (#1099)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-06-18 00:01:28 +00:00
5004758fbf feat: surface base + persona system prompts in observer feed (#1103)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 19:41:03 -04:00
466bb993ca ci: move reminder e2e to a dedicated backend-integration job (#1098)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 19:27:42 -04:00
959fc6e9d0 fix: give agent-observer sub a replay-capable limit (#1100)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 19:21:39 -04:00
4201311821 fix: make managed-agent spawn and teardown portable to Windows (#1097)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 18:49:01 -04:00
7de5517f1d fix(desktop): constrain message timeline width with min-w-0 (#1092)
Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-17 17:53:20 -04:00
22b47db8ec feat(desktop): reminders notifications, snooze, overlay, and inbox view mode (#1093)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 17:47:41 -04:00
010e8022b0 feat(prompt): add memory hygiene and hoist universal engineering discipline to base prompt (#1085)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@sprout-oss.stage.blox.sqprod.co>
2026-06-17 21:21:07 +00:00
c8e1120fc2 fix(desktop): correct thread-unread badge flicker, stale clear, phantom count, mention gate, and nested count (#1080)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 16:12:18 -04:00
4f93d52e15 Fix mention chip alignment (#1094)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-17 19:38:13 +00:00
a4fbebb397 perf(desktop): virtualize unbounded lists and warm the emoji index (#1089)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 19:01:09 +00:00
3bf2ac0047 Adjust unread pill spacing (#1091)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-17 11:56:45 -07:00
klopez4212andGitHub bb9b6674e3 Improve image lightbox controls (#1084) 2026-06-17 19:54:20 +01:00
klopez4212andGitHub 7b0500c93e Use Inter for app typography (#899) 2026-06-17 18:56:57 +01:00
422a90f5e1 fix(desktop): paint thread replies on open without scroll nudge (#1090)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-06-17 10:50:06 -07:00
klopez4212andGitHub d4cd919b85 Polish sidebar update and relay cards (#1009) 2026-06-17 18:48:35 +01:00
klopez4212andGitHub 1a37ee3951 Normalize desktop icon sizing (#1088) 2026-06-17 18:08:53 +01:00
771086fd69 fix(desktop): normalize loopback host for HTTP writes and stop Reminders nav flicker (#1086)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 12:19:14 -04:00
770266d2a9 feat(composer): link editing — inline popover, modal picker, buzz:// in-app nav (#1045)
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@sprout-oss.stage.blox.sqprod.co>
2026-06-17 08:39:27 -07:00
48994e01bc chore(Pulse): gate Follow button behind Pulse feature flag (#1019)
Signed-off-by: tho <80c5f18be5aafa62cf6198c6335963ba3306b595288117c8ea2f805fc9bdc94a@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: tho <80c5f18be5aafa62cf6198c6335963ba3306b595288117c8ea2f805fc9bdc94a@sprout-oss.stage.blox.sqprod.co>
2026-06-17 08:18:34 -07:00
b7e22f4b5c fix(desktop): wrap long markdown autolinks (#1081)
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-17 11:03:58 -04:00
ff824a365e feat(desktop): add NIP-ER reminder UI — create, view, and manage encrypted reminders (#963)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <wpfleger@squareup.com>
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 11:03:45 -04:00
26563968cb feat(relay): add NIP-ER push scheduler with cross-pod delivery (#957)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-17 10:43:22 -04:00