fix(tabs): exclude sidecar URL when picking the main Perplexity tab#16
Conversation
Comet renders its right-panel chat helper at a URL that also matches
the `perplexity.ai` substring (e.g. `perplexity.ai/.../sidecar`). Five
sites in the codebase pick "any tab whose URL contains `perplexity.ai`"
without excluding the sidecar:
- `reconnect()` "find best target" fresh-start fallback
-> after a reconnect, sendPrompt / stopAgent route to the wrong tab.
- `ensureOnPerplexityTab()` initial current-URL check
-> the function happily returns `true` when we're on the sidecar.
- `ensureOnPerplexityTab()` last-resort "any Perplexity tab" fallback
-> picks the sidecar if `listTabsCategorized().main` is null.
- `isOnPerplexityTab()` predicate
-> callers that check `isOnPerplexityTab` before dispatching prompts
silently send to the sidecar.
- `comet_connect` initial target picker + `comet_ask` recovery path
-> first-time connection lands on the sidecar.
The `listTabsCategorized()` helper already encodes the right filter
(`!t.url.includes('sidecar')`); we just weren't applying it
consistently. Each affected site now does the same exclusion, with a
secondary fallback to "any perplexity.ai tab" if literally no main tab
exists.
No public API change. Default user with one main tab + sidecar gets
the main tab from now on, instead of whichever appeared first in the
CDP target list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RapierCraft
left a comment
There was a problem hiding this comment.
Code Review: PR #16 — fix(tabs): exclude sidecar URL when picking the main Perplexity tab
Reviewed commit: 978c63c
Overall Assessment
The fix is well-motivated and the approach is sound. listTabsCategorized() already had the correct sidecar exclusion logic — this PR correctly propagates that pattern to the 5 call sites that were doing raw url.includes('perplexity.ai') matching. The tiered fallback (main tab → any perplexity tab → any page) is a good defensive pattern.
Findings
1. Missed site: src/http-bridge.ts:102 (Medium severity)
The HTTP bridge has an identical handleConnect() function that still uses the unfiltered pattern:
// src/http-bridge.ts:102
const perplexityTab = targets.find(t => t.type === 'page' && t.url.includes('perplexity.ai'));This suffers from the exact same sidecar-first-in-CDP-list bug. Users connecting via the HTTP bridge (Linux/n8n) will still land on the sidecar. Should get the same 3-tier fallback treatment as src/index.ts:187.
2. Missed site: src/index.ts:291 — newChat recovery path (Low severity)
const mainTab = targets.find(t => t.type === 'page' && t.url.includes('perplexity'));This uses the substring 'perplexity' (not even .ai), and does not exclude sidecar. Since this is in a catch block (navigation failure recovery), hitting it means the user is already in a degraded state — connecting to the sidecar here would compound the problem. Lower priority since it immediately navigates to perplexity.ai after connecting, but the sidecar tab could still receive the navigation.
3. Missed site: src/index.ts:312 — non-newChat isOnPerplexity check (Low severity)
const isOnPerplexity = currentUrl?.includes('perplexity.ai');After listTabsCategorized().main is connected (line 305-308), this check evaluates the URL. If tabs.main was null and we fell through, we'd evaluate whatever tab was already connected. However, if already connected to the sidecar, this returns true and skips navigation — so the prompt goes to the sidecar. Mitigated by the fact that listTabsCategorized().main uses the correct filter upstream, so this only triggers if tabs.main was found and connected successfully. Risk: if connect(tabs.main.id) fails silently, the old (possibly sidecar) connection remains active.
4. String-matching robustness (Informational — not blocking)
The filter !t.url.includes('sidecar') is a substring match. If Perplexity ever introduces a URL path containing "sidecar" for a non-sidecar purpose, this would create a false negative (legitimate tab excluded). Current risk is negligible — "sidecar" is a highly specific term unlikely to appear in normal Perplexity URLs. Consider whether matching a more specific path pattern (e.g., /sidecar) would be more precise.
5. isOnPerplexityTab() return value equivalence (Informational)
The original url?.includes('perplexity.ai') || false becomes !!url && url.includes('perplexity.ai') && !url.includes('sidecar'). Both handle null/undefined correctly. The new version also returns false for empty string "" — which is correct. No issue, just noting equivalence is maintained.
Summary
| # | Location | Severity | Type |
|---|---|---|---|
| 1 | src/http-bridge.ts:102 |
Medium | Missed site (same bug) |
| 2 | src/index.ts:291 |
Low | Missed site (recovery path) |
| 3 | src/index.ts:312 |
Low | Missed site (conditional) |
| 4 | Filter specificity | Info | Future-proofing |
| 5 | Return value equivalence | Info | No action needed |
Verdict
Approve with suggestions. The core fix is correct and well-reasoned. Finding #1 (http-bridge.ts) should ideally be addressed in this PR since it is the same class of bug and the HTTP bridge is a first-class entry point. Findings #2-3 are lower risk but worth fixing for consistency.
The PR does not introduce regressions — the fallback chains ensure that if no non-sidecar tab exists, the code gracefully degrades to picking any available perplexity tab (or any page). This is the right defensive behavior.
Summary
Comet renders its right-panel chat helper at a URL that also matches the `perplexity.ai` substring (e.g. `perplexity.ai/.../sidecar`). Five sites in the codebase pick "any tab whose URL contains `perplexity.ai`" without excluding the sidecar:
Fix
`listTabsCategorized()` already encodes the right filter (`!t.url.includes('sidecar')`); we just weren't applying it consistently. Each affected site now does the same exclusion, with a secondary fallback to "any perplexity.ai tab" if literally no main tab exists.
Compatibility
No public API change. Default user with one main tab + sidecar gets the main tab from now on, instead of whichever appeared first in the CDP target list (often the sidecar after a recent agent run).
Test plan
🤖 Generated with Claude Code