Skip to content

fix(tabs): repair stale browser-tabs schema, never-empty strip; move usage to title bar#3345

Merged
adamleithp merged 7 commits into
mainfrom
fix/bluebird-tabs-and-titlebar-usage
Jul 10, 2026
Merged

fix(tabs): repair stale browser-tabs schema, never-empty strip; move usage to title bar#3345
adamleithp merged 7 commits into
mainfrom
fix/bluebird-tabs-and-titlebar-usage

Conversation

@adamleithp

@adamleithp adamleithp commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Browser tabs — dead "+" on an empty strip (bluebird follow-up)

Root cause: migration 0013 was amended in place on the bluebird branch (same folderMillis). Profiles that dogfooded a pre-merge build have it recorded as applied but kept the old panes-era browser_windows schema — no active_tab_id. Every browser-tabs query then throws no such column: active_tab_id, the whole service dies at boot, the renderer's snapshot seed swallowed the failure, and the strip rendered a permanently dead "+" (the handler bailed on a windowless mirror, and no mutation could bootstrap a window — deadlock).

image image 2026-07-10 13 52 36

Fix, four layers:

  • Migration 0020: tolerated ALTER TABLE ADD COLUMN repairs (browser_windows.active_tab_id, plus browser_tabs.task_id/channel_section/app_view for other stale variants). No-op on healthy DBs (the runner tolerates "duplicate column name"); heals divergent ones.
  • Server invariants: BrowserTabsService seeds a blank tab at boot when none survived — the strip should never be empty — and newBlankTab/openOrFocus heal an unknown windowId to the primary window.
  • Seed resilience: the renderer boot seed retries 3× with backoff and logs terminal failure instead of .catch(() => undefined).
  • Self-healing "+": with no window in the mirror, the handler re-pulls the authoritative snapshot (reseedMirror()) and creates the tab against the fresh primary window instead of silently returning.

Verified live on an affected profile: schema healed on boot, persisted tabs restored, "+" and Cmd+T work.

⚠️ Anyone who dogfooded early bluebird builds has the stale DB — this should ship before/with the next build. Amending shipped migration files in place is the underlying foot-gun.

Title bar

  • Usage moved to the title bar: the sidebar usage bar is now a Usage: N% outline button left of "PostHog Web"; hover opens the full plan card (plan · % used, progress bar, reset time, Upgrade) in a quill Popover (openOnHover on the trigger). Same gating as before (posthog-code-billing + free-plan usage). New titlebar/titlebar_card analytics surfaces.
  • Native drag region: the empty title-bar space around the tab strip now drags the window — the strip container no longer carves a no-drag rectangle; pills and "+" opt out individually (click/close/reorder unaffected).

Tests

  • service.test.ts: boot seeds primary window + blank tab from empty/tab-less stores, leaves populated snapshots untouched, creation mutations heal stale window ids.

🤖 Generated with Claude Code

@trunk-io

trunk-io Bot commented Jul 10, 2026

Copy link
Copy Markdown

😎 Merged manually by @adamleithp - details.

@adamleithp adamleithp force-pushed the fix/bluebird-tabs-and-titlebar-usage branch from 58c55f8 to 825a8aa Compare July 10, 2026 12:33
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 63f6921.

@adamleithp adamleithp added the Stamphog This will request an autostamp by stamphog on small changes label Jul 10, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates denied this PR because it touches database migrations and billing code, both of which are on the deny-list for automated approval. It also has zero reviews despite being a complex, multi-area change. A human reviewer must approve before this can be merged.

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label Jul 10, 2026
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(tabs): repair stale browser-tabs sch..." | Re-trigger Greptile

Comment thread .claude/skills/quill-code/scripts/sync-quill.sh Outdated
Comment thread packages/ui/src/features/billing/UsageButton.tsx Outdated
Comment thread packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx Outdated
@adamleithp adamleithp force-pushed the fix/bluebird-tabs-and-titlebar-usage branch from 825a8aa to 4d3249b Compare July 10, 2026 12:36
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk cancel

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk cancel

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk cancel

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk cancel

adamleithp and others added 4 commits July 10, 2026 14:01
…usage to title bar

Browser tabs (bluebird follow-ups):
- Add repair migration 0020: profiles that dogfooded a pre-merge bluebird
  build have migration 0013 recorded as applied but the old panes-era
  browser_windows schema (no active_tab_id), which killed the whole
  browser-tabs service at boot and left a dead "+" strip. Tolerated ALTERs
  heal stale DBs and no-op on healthy ones.
- BrowserTabsService now seeds a blank tab at boot when none survived
  (the strip should never be empty) and heals an unknown windowId on
  newBlankTab/openOrFocus to the primary window.
- The renderer snapshot seed retries with backoff and logs terminal
  failure instead of swallowing it; the "+" handler re-pulls the
  authoritative snapshot instead of silently no-oping when the mirror
  has no window.
- Make the empty title-bar space around the tab strip a native window
  drag region (container inherits drag; pills and "+" opt out).

Usage CTA:
- Move the sidebar usage bar into the title bar as a "Usage: N%" button
  (left of PostHog Web); the full plan card (bar, reset time, Upgrade)
  shows in a quill Popover on hover via openOnHover.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Radix scale classes (text-gray-11, bg-accent-9) don't resolve inside the
data-quill popover portal, so the card rendered colorless. Use quill
Progress and foreground/muted-foreground/primary tokens instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- sync-quill.sh: portable hashing (shasum) and sed rewrite (temp file +
  mv) so the script works on Linux, not just BSD/macOS
- UsageButton: mark the portalled PopoverContent no-drag — it opens under
  the title bar's drag region and clicks near its top edge were draggable
- new-tab recovery: resolve the primary window from the snapshot returned
  by reseedMirror, not the mirror — the store apply is skipped when a
  local write or newer remote push races the fetch

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@adamleithp adamleithp force-pushed the fix/bluebird-tabs-and-titlebar-usage branch from 9cf2736 to 692f31f Compare July 10, 2026 13:01
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk cancel

- Migration 0020: CREATE TABLE IF NOT EXISTS (full current schema) before
  the tolerated ALTERs, so a stale variant missing the browser tables
  entirely heals instead of failing the migration transaction on
  "no such table"; document the type/NOT NULL divergence limitation.
  Verified against healthy, panes-era, and tables-missing lineages.
- New-tab reseed failure now logs instead of a silent catch.
- Contribution seed loop is abort-aware: stop() cancels the backoff and
  a late snapshot can't apply after teardown; repeated start() can't
  stack loops.
- UsageButton: loading skeleton reserves the button's spot (no title-bar
  layout shift), the popover is controlled so the trigger click closes
  the card before opening settings, and hover-card opens track
  UPGRADE_PROMPT_SHOWN (new titlebar_card shown-surface).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

@k11kirky k11kirky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review pass (high effort, 8 finder angles + verification). Overall the fix is solid and well-tested for the reported symptom; flagging a few gaps in the fix's scope and one cleanup nit. Nothing here blocks merge in my view, but worth a look before shipping to the affected dogfood population.

Comment thread packages/workspace-server/src/services/browser-tabs/service.ts
Comment thread packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx Outdated
Comment thread packages/ui/src/router/routes/__root.tsx Outdated
Comment thread packages/ui/src/features/browser-tabs/browser-tabs-events.contribution.ts Outdated
Comment thread packages/ui/src/features/billing/UsageButton.tsx Outdated

Copy link
Copy Markdown
Contributor

One more from the same review pass, not inline-able since the file isn't part of this diff: packages/ui/src/features/browser-tabs/BrowserTabsDnd.tsx's onDragStart/onDragOver/onDragEnd (lines 40, 61, 89) all do if (!win) return; when primaryWindow(snapshot) is undefined — the same windowless-mirror dead end this PR fixes for the "+" button, just untreated for drag-and-drop. Not a regression from this PR, but worth a follow-up if the windowless-mirror state is expected to recur (e.g. new profiles created before boot finishes).

@adamleithp adamleithp requested a review from k11kirky July 10, 2026 20:06
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk cancel

- migrate.ts: repair migrations (0020) are best-effort — statements
  tolerate any SQLite error, so an unforeseen schema divergence degrades
  to the pre-repair status quo instead of rolling back the batch and
  killing boot. Covered by migrate.test.ts including a pathological
  browser_tabs-as-VIEW variant, plus lineage tests for the panes-era and
  tables-missing variants.
- service.ts: document why windowId healing is creation-only (reorder and
  focus mutations from a desynced mirror carry stale tab ids; the shared
  transforms no-op safely).
- BrowserTabStrip: log when no window is resolvable after a reseed
  instead of silently skipping.
- __root.tsx: gate the title-bar right-side group on
  billingEnabled || channelsEnabled so an empty Flex can't claim a
  no-drag rect in the drag region.
- backoff.ts: sleepWithBackoff takes an optional AbortSignal; the seed
  retry loop uses the shared helper instead of a local reimplementation.
- UsageButton: Upgrade CTA is a quill Button variant=link per CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adamleithp

Copy link
Copy Markdown
Contributor Author

/trunk merge

Copy link
Copy Markdown
Contributor

Re-reviewed the two follow-up commits (0e104ba7, 63f69217). All six inline findings from the last pass are addressed, and well-tested:

  • Migration boot-abort risk → fixed properly: migrate.ts now has an explicit BEST_EFFORT_MIGRATIONS set (keyed by the exact 0020 timestamp from _journal.json) that tolerates any error for listed migrations, not just duplicate-column. New migrate.test.ts cases cover the panes-era variant, a variant missing tables entirely, and a deliberately unhealable case (browser_tabs as a VIEW) that now degrades to "still broken" instead of killing boot. This is a nice generalization — turns the one-off repair migration into a documented, tested pattern for future repair migrations.
  • resolveWindowId asymmetry → not code-changed, but now has a clear comment explaining why setOrder/setActiveTab are deliberately excluded (they carry stale tab ids too, so retargeting would apply wrong state; the shared transforms already no-op safely). Reasonable, and resolves my concern.
  • Silent no-op in handleNewTab's fallback → fixed, now logs logger.error when no window is found after reseed instead of silently skipping.
  • Title-bar drag-region regression → fixed, the wrapper Flex is now gated on billingEnabled || channelsEnabled so an empty group can't claim a no-drag rect.
  • Backoff reuse → fixed, sleepWithBackoff in packages/shared/src/backoff.ts now takes an optional AbortSignal and seedWithRetry uses it instead of hand-rolling the timer/listener logic.
  • Raw <button> CTA → fixed, now a quill Button variant="link". Bonus: the trigger button was also converted to render={<Link .../>} per the CLAUDE.md link-CTA convention (there's existing precedent for Button render={<Link/>} in ChannelTabs.tsx, though nesting it inside PopoverTrigger's own render prop is new — worth a quick visual check that click/hover still behave through that triple-nested composition, just to be safe).

The BrowserTabsDnd.tsx gap from my general comment above is unaddressed, but that's expected — it's a pre-existing, non-regressing follow-up item, not something this PR needs to fix.

Traced the openSettings/prepareSettingsPage split against every other call site (billing.contribution.ts, UsageLimitModal.tsx, SignalSourcesSettings.tsx, WorkspaceModeSelect.tsx, ProjectSwitcher.tsx, UsageView.tsx, TaskInput.tsx) — all still call openSettings unchanged, so the extraction is safe. No new issues found. CI's unit-test/typecheck/build were still pending at review time — worth confirming green before merge given the new migrate.ts logic path.

@adamleithp adamleithp merged commit d7de367 into main Jul 10, 2026
27 checks passed
@adamleithp adamleithp deleted the fix/bluebird-tabs-and-titlebar-usage branch July 10, 2026 21:35
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.

2 participants