Skip to content

Fix status pills not rendering on workspaces created after launch - #5660

Closed
obvioussean wants to merge 1 commit into
manaflow-ai:mainfrom
obvioussean:obvioussean/fix-new-workspace-status-pills
Closed

obvioussean wants to merge 1 commit into
manaflow-ai:mainfrom
obvioussean:obvioussean/fix-new-workspace-status-pills

Conversation

@obvioussean

@obvioussean obvioussean commented Jun 8, 2026 •

Copy link
Copy Markdown

Status pills set on a workspace created after app launch are stored in the Tab model but never render in the sidebar — while pre-existing workspaces render immediately. list-status confirms the data; the UI shows nothing.

Why

.onAppear fires synchronously during view insertion, before .onReceive subscriptions are live. Sidebar data set by socket commands (e.g. agent hook set_status) between .onAppear and subscription goes unobserved: the observation publishers use .dropFirst() to skip the current-value replay, making them change-only — so a mutation that lands before the subscriber connects isn't replayed, and the .onAppear snapshot captured the pre-mutation (empty) state. Pre-existing workspaces work because their .onReceive subscriptions were established before any status was set.

What didn't work: removing .dropFirst() globally

The initial approach removed .dropFirst() from sidebarObservationSignal and the $panels chain, turning the publishers from change-only into replay-on-subscribe. This fixed the immediate bug but introduced three problems caught by adversarial review (GPT-5.5 + GPT-5.4, independently):

  1. Extension sidebar refresh loop — extensionSidebarDebouncedObservationPublisher is a computed function that creates a new MergeMany per body evaluation. Subscription-time replay → handler increments @State token → body re-eval → new subscription → replay → self-sustaining churn.
  2. Immediate publisher fires ~7 un-debounced replays per row on the typing-latency-sensitive path, each calling makeWorkspaceSnapshot().
  3. Test breakage — existing tests assume change-only semantics (publishCount == 0 after subscribe + heartbeat mutation).

What works: targeted deferred refresh

Add a one-shot .task to TabItemView that calls refreshWorkspaceSnapshot() — running asynchronously on the next main-actor tick after the view's modifier chain (including .onReceive subscriptions) is fully established. It re-reads tab state at that point, catching any mutations that landed in the .onAppear-to-subscription gap. SidebarWorkspaceSnapshotRefreshPolicy.decision no-ops when nothing has changed, so the cost for pre-existing workspaces is one cheap snapshot comparison per row appearance.

The observation publishers keep their .dropFirst() and change-only contract. No other consumers are affected.

Validation

Couldn't build locally (SPM resolution failures in the worktree) — CI will validate. The change is +11 lines in one file (a .task modifier on TabItemView).

Closes #5659

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

@obvioussean is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bbb31900-84f9-4c76-a630-ed467ed83107

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add a one-shot deferred snapshot refresh (.task) to TabItemView, running
on the next main-actor tick after the view's .onReceive subscriptions are
established.

The sidebar observation publishers use .dropFirst() to skip the initial
current-value replay from @published, making them change-only. This is
correct and intentional — removing .dropFirst() would create a refresh
storm in the extension sidebar (its publisher is rebuilt on each body
evaluation, so subscription-time replay → state mutation → body re-eval
→ new subscription → replay → loop) and add ~7 un-debounced replays per
row on the typing-latency-sensitive immediate publisher path.

The bug: .onAppear fires synchronously during view insertion, before
.onReceive subscriptions are live. Sidebar data set by socket commands
(e.g. agent hook set_status) between .onAppear and subscription goes
unobserved — the change-only publishers don't replay it, and the snapshot
from .onAppear captured the pre-mutation state. Pre-existing workspaces
aren't affected because their .onReceive subscriptions were established
before any status was set.

The .task modifier runs asynchronously after the full modifier chain is
evaluated (including .onReceive subscription). It re-reads tab state at
that point, catching any mutations that landed in the gap. The snapshot
comparison (SidebarWorkspaceSnapshotRefreshPolicy.decision) no-ops when
nothing has changed, so the cost for pre-existing workspaces is one
cheap comparison per row appearance.

Closes #5659

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@obvioussean

Copy link
Copy Markdown
Author

Closing in favor of #5662 which fixes this at the publisher level with .prepend(()) — cleaner than my consumer-side .task approach, benefits all subscribers, and includes a regression test.

@obvioussean obvioussean closed this Jun 9, 2026
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.

Status pills don't render on workspaces created after app launch

1 participant