Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

trunk-merge/pr-3513/bf9c9d45-d490-4247-99c9-8e4d31f251e9 - #3559

Closed
trunk-io[bot] wants to merge 5 commits into
mainfrom
trunk-merge/pr-3513/bf9c9d45-d490-4247-99c9-8e4d31f251e9
Closed

trunk-merge/pr-3513/bf9c9d45-d490-4247-99c9-8e4d31f251e9#3559
trunk-io[bot] wants to merge 5 commits into
mainfrom
trunk-merge/pr-3513/bf9c9d45-d490-4247-99c9-8e4d31f251e9

Conversation

@trunk-io

@trunk-io trunk-io Bot commented Jul 17, 2026

Copy link
Copy Markdown
Trunk Merge Pull Request Banner

This pull request was created and is being managed by Trunk Merge.

This pull request is based on the main branch at SHA b092f19fbc63d7c5a3ce62f3d87d5060043375f8.

See more details here.

When CI completes, this pull request will be closed automatically.

Pull Requests Being Tested

This pull request is testing the changes from pull request 3513.

adamleithp and others added 5 commits July 16, 2026 15:55
Sidebar channel names (and #me) go bold when there's activity the viewer
hasn't seen, and clear when they open that channel.

"Activity" is an @-mention for now: that's the only cross-channel,
all-users feed the client has, and it's what "notification" already means
here (it drives the Activity badge). The backend exposes no per-channel
activity timestamp, so a broader "any new message" signal would mean
mounting the all-users full-task poll — ~2.2MB/30s, documented in
useTasks.ts as the app's heaviest and deliberately retired. If that
timestamp lands, only latestActivityByChannel changes shape; the bolding,
the name join and the seen store stay as they are.

Seen state is per channel (unlike the Activity page's single lastSeenAt),
keyed by backend channel id so a rename doesn't mark a channel unread
again, and it never walks a timestamp backwards. The sidebar's rows are
folder channels while activity is keyed by backend id, so they're joined
by name — the same bridge useBackendChannel walks — resolved once per list
rather than per row. Unread reads through the existing mentions query
cache, so the sidebar adds no fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj
…annel

Channel rows and their # sit at muted-foreground, brightening on hover, so
the list reads quietly by default. Two rows lift out of it:

- unread: font-bold + foreground (bold stays unread's alone)
- the channel you're viewing: foreground, normal weight

Both already sit at full contrast, so they skip the hover brighten.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj
…epth

Seven findings from a review of this stack.

- The seen store's storage is async (IPC), so both sides raced hydration.
  Reads: an empty map is indistinguishable from "nothing ever read", so
  every channel with activity bolded for the first frames of each boot.
  Writes: a channel opened during boot stamped itself seen, then zustand's
  default merge replaced that stamp with what was on disk and lost it —
  the channel you just read stayed bold. Gate reads on `hasHydrated` and
  merge the two maps (later visit per channel wins) instead of replacing.

- CreateChannelModal relied on `disabled={busy}`, which only lands a render
  after the mutation starts, so a double-click (or a held ⌘Enter) fired two
  creates — and folder creation isn't idempotent by path, so that's two
  channels of the same name. Latch synchronously.

- ensurePersonalChannel's in-flight guard settled when the POST returned,
  but callers pass the `channels` from their last render — a click in that
  gap saw no existing "me" and no in-flight create, and made a second.
  Remember what was created until the list catches up.

- Marking a channel read only happened on its feed, so reading it via
  Artifacts/Recents/CONTEXT.md left it bold. Moved into ChannelHeader,
  which every channel surface renders — a new surface now gets it free.

- ChannelGroup's onOpenChange ignored the value Base UI emits and blind
  toggled, so a redundant event would invert the section.

- Unread was resolved two ways (by name for shared rows, by id for #me).
  One predicate now mirrors useBackendChannel's mapping for both.

- latestActivityForChannel built a map of every channel to read one key.

Covered by 10 new tests: the store's hydration merge (the clobber case
fails without the fix) and ensurePersonalChannel's races.

Not addressed, deliberately: the seen store stays in @posthog/ui rather
than moving to core per the layering rule. Every persisted store in the
app lives in ui because persistence goes through electronStorage, a
ui/shell adapter; core has no persisted store to follow, and its sibling
(activitySeenStore, the same concept for the Activity page) sits in ui.
Moving it needs a platform storage interface — worth doing, but as its own
change rather than smuggled into this one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj
@github-actions

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit ba7bef1.

@trunk-io trunk-io Bot closed this Jul 17, 2026
@trunk-io
trunk-io Bot deleted the trunk-merge/pr-3513/bf9c9d45-d490-4247-99c9-8e4d31f251e9 branch July 17, 2026 23:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant