Skip to content

Commit e2e144a

Browse files
committed
Popup: show a Console-aware message instead of "unsupported" on the Console tab
When the popup's active tab is the extension's own Console page, the lede now reads "You're on the Rossum Console." instead of "This tab isn't supported by the extension.". Detection via a new isConsoleTab() helper (matches only our own console.html via chrome.runtime.getURL). The open-Rossum-tabs switcher, the "It works on" fallback, and the footnote are unchanged. No manifest, permission, or storage changes.
1 parent ae66019 commit e2e144a

6 files changed

Lines changed: 410 additions & 6 deletions

File tree

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Console-aware Popup Copy Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** When the popup's active tab is the extension's own Console page, replace the "This tab isn't supported by the extension." lede with "You're on the Rossum Console." — keeping the rest of the panel (Rossum-tab switcher / static fallback) exactly as-is.
6+
7+
**Architecture:** Add an `isConsoleTab(url)` helper to `src/popup/utils.js`; compute it in `App.jsx` when there's no detected site and pass it to `UnsupportedSite`, which swaps only the lede line. `UnsupportedSite` is exported so it can be unit-tested.
8+
9+
**Tech Stack:** Preact, Vitest (jsdom).
10+
11+
## Global Constraints
12+
13+
- Lede for the Console, verbatim: **`You're on the Rossum Console.`** For any other unsupported tab, the lede stays **`This tab isn't supported by the extension.`**
14+
- Copy-only: no new actions, buttons, or Console-specific UI. The tab switcher, the "It works on … Open one of these sites to get started." fallback, and the "Also works on NetSuite and Coupa." footnote are unchanged.
15+
- No manifest change, no new permission (`activeTab` already exposes the active tab's URL on click), no new chrome.storage keys.
16+
- Prop is named `isConsole` (not `console`) to avoid shadowing the global `console`.
17+
- No git commits in this run (owner rule) — stay on `master`, leave changes uncommitted; each task's final step is a test gate. Per-task diffs are working-tree diffs scoped to the task's files.
18+
- Tests: `// @vitest-environment jsdom`, `h(Component, props)` + preact `render`, `vi.mock`.
19+
20+
---
21+
22+
### Task 1: `isConsoleTab` helper
23+
24+
**Files:**
25+
- Modify: `src/popup/utils.js` (add exported `isConsoleTab`)
26+
- Test: `tests/popup-utils.test.js` (add an `isConsoleTab` describe block + import)
27+
28+
**Interfaces:**
29+
- Produces: `isConsoleTab(url: string) => boolean` — true iff `url` starts with `chrome.runtime.getURL('console/console.html')`.
30+
31+
- [ ] **Step 1: Write the failing test**
32+
33+
In `tests/popup-utils.test.js`, add `isConsoleTab` to the existing import line:
34+
35+
```javascript
36+
import { runInTab, openConsoleTab, detectSite, findRossumTabs, activateTab, isConsoleTab } from '../src/popup/utils.js';
37+
```
38+
39+
Then add this describe block at the end of the file (before the final closing lines). The file's `beforeEach` already sets `chrome.runtime.getURL = (path) => \`chrome-extension://abc/${path}\``:
40+
41+
```javascript
42+
describe('isConsoleTab', () => {
43+
it('matches this extension\'s Console page, with or without a query string', () => {
44+
expect(isConsoleTab('chrome-extension://abc/console/console.html')).toBe(true);
45+
expect(isConsoleTab('chrome-extension://abc/console/console.html?authId=x')).toBe(true);
46+
});
47+
48+
it('rejects other extensions, other own-pages, sites, and empty input', () => {
49+
expect(isConsoleTab('chrome-extension://zzz/console/console.html')).toBe(false);
50+
expect(isConsoleTab('chrome-extension://abc/popup/popup.html')).toBe(false);
51+
expect(isConsoleTab('https://elis.rossum.ai/queues')).toBe(false);
52+
expect(isConsoleTab('')).toBe(false);
53+
expect(isConsoleTab(undefined)).toBe(false);
54+
});
55+
});
56+
```
57+
58+
- [ ] **Step 2: Run the test to verify it fails**
59+
60+
Run: `npx vitest run tests/popup-utils.test.js`
61+
Expected: FAIL — `isConsoleTab` is not exported (import resolves to `undefined`, so calling it throws / assertions fail).
62+
63+
- [ ] **Step 3: Add the helper**
64+
65+
In `src/popup/utils.js`, add immediately after the `detectSite` function (after its closing `}` near line 14):
66+
67+
```javascript
68+
// True when the URL is this extension's own Console page. chrome.runtime.getURL
69+
// embeds our extension id, so this matches only our Console — never another
70+
// extension's pages or a real site. The Console URL carries a ?authId=... query,
71+
// which startsWith tolerates.
72+
export function isConsoleTab(url) {
73+
return !!url && url.startsWith(chrome.runtime.getURL('console/console.html'));
74+
}
75+
```
76+
77+
- [ ] **Step 4: Run the test to verify it passes**
78+
79+
Run: `npx vitest run tests/popup-utils.test.js`
80+
Expected: PASS (existing `detectSite`/`findRossumTabs`/etc. blocks plus the new `isConsoleTab` block).
81+
82+
- [ ] **Step 5: Verification gate (no commit)**
83+
84+
Run: `npx vitest run tests/popup-utils.test.js`
85+
Expected: all green. Leave changes uncommitted on `master`.
86+
87+
---
88+
89+
### Task 2: Wire the Console lede into `App.jsx`
90+
91+
**Files:**
92+
- Modify: `src/popup/components/App.jsx` (import `isConsoleTab`; compute `isConsole`; pass to `UnsupportedSite`; export `UnsupportedSite`; swap both ledes)
93+
- Test: `tests/popup-unsupported-site.test.js` (new)
94+
95+
**Interfaces:**
96+
- Consumes: `isConsoleTab` from `../utils.js` (Task 1).
97+
- Produces: `export function UnsupportedSite({ tabs, isConsole })` from `src/popup/components/App.jsx`.
98+
99+
- [ ] **Step 1: Write the failing test**
100+
101+
Create `tests/popup-unsupported-site.test.js`. (App.jsx's import graph has no top-level `chrome.*`, so importing it in jsdom is safe; `UnsupportedSite` renders without calling `chrome``activateTab` only fires on click.)
102+
103+
```javascript
104+
// @vitest-environment jsdom
105+
import { describe, it, expect } from 'vitest';
106+
import { h, render } from 'preact';
107+
import { UnsupportedSite } from '../src/popup/components/App.jsx';
108+
109+
function mount(props) {
110+
const root = document.createElement('div');
111+
render(h(UnsupportedSite, props), root);
112+
return root;
113+
}
114+
115+
const TABS = [{ id: 1, url: 'https://elis.rossum.ai/queues', title: 'Rossum', favIconUrl: '' }];
116+
117+
describe('UnsupportedSite', () => {
118+
it('shows the Console lede and keeps the tab switcher when on the Console with open Rossum tabs', () => {
119+
const root = mount({ tabs: TABS, isConsole: true });
120+
expect(root.textContent).toContain("You're on the Rossum Console.");
121+
expect(root.textContent).not.toContain("isn't supported");
122+
expect(root.querySelector('.rossum-tab-list')).toBeTruthy();
123+
expect(root.textContent).toContain('Switch to one of your open Rossum tabs');
124+
});
125+
126+
it('shows the Console lede with the static fallback when on the Console with no Rossum tabs', () => {
127+
const root = mount({ tabs: [], isConsole: true });
128+
expect(root.textContent).toContain("You're on the Rossum Console.");
129+
expect(root.textContent).toContain('It works on');
130+
expect(root.querySelector('.rossum-tab-list')).toBeNull();
131+
});
132+
133+
it('keeps the unsupported lede for a non-Console unsupported tab', () => {
134+
const root = mount({ tabs: [], isConsole: false });
135+
expect(root.textContent).toContain("This tab isn't supported by the extension.");
136+
expect(root.textContent).not.toContain('Rossum Console');
137+
});
138+
});
139+
```
140+
141+
- [ ] **Step 2: Run the test to verify it fails**
142+
143+
Run: `npx vitest run tests/popup-unsupported-site.test.js`
144+
Expected: FAIL — `UnsupportedSite` is not exported from `App.jsx` yet (import is `undefined`), and the `isConsole` lede branch does not exist.
145+
146+
- [ ] **Step 3: Export `UnsupportedSite` and make its lede conditional**
147+
148+
In `src/popup/components/App.jsx`:
149+
150+
(a) Change the function declaration (line 57) from:
151+
152+
```jsx
153+
function UnsupportedSite({ tabs }) {
154+
```
155+
156+
to:
157+
158+
```jsx
159+
export function UnsupportedSite({ tabs, isConsole }) {
160+
```
161+
162+
(b) In the `hasTabs` branch, replace the lede (line 66):
163+
164+
```jsx
165+
<p class="unsupported-lede">This tab isn't supported by the extension.</p>
166+
```
167+
168+
with:
169+
170+
```jsx
171+
<p class="unsupported-lede">{isConsole ? "You're on the Rossum Console." : "This tab isn't supported by the extension."}</p>
172+
```
173+
174+
(c) In the no-tabs branch, replace the lede (line 92) with the identical conditional:
175+
176+
```jsx
177+
<p class="unsupported-lede">{isConsole ? "You're on the Rossum Console." : "This tab isn't supported by the extension."}</p>
178+
```
179+
180+
- [ ] **Step 4: Wire `isConsole` in the `App` component**
181+
182+
In `src/popup/components/App.jsx`:
183+
184+
(a) Add `isConsoleTab` to the utils import (line 5):
185+
186+
```jsx
187+
import { openConsoleTab, runInTab, detectSite, findRossumTabs, activateTab, isConsoleTab } from '../utils.js';
188+
```
189+
190+
(b) Immediately after `const site = detectSite(tab?.url || '');` (line 105), add:
191+
192+
```jsx
193+
const isConsole = !site && isConsoleTab(tab?.url || '');
194+
```
195+
196+
(c) Update the `UnsupportedSite` usage (line 232) from:
197+
198+
```jsx
199+
<UnsupportedSite tabs={rossumTabs} />
200+
```
201+
202+
to:
203+
204+
```jsx
205+
<UnsupportedSite tabs={rossumTabs} isConsole={isConsole} />
206+
```
207+
208+
- [ ] **Step 5: Run the new test to verify it passes**
209+
210+
Run: `npx vitest run tests/popup-unsupported-site.test.js`
211+
Expected: PASS (3 tests).
212+
213+
- [ ] **Step 6: Run the full suite and rebuild**
214+
215+
Run: `npm test`
216+
Expected: all tests green (new file + the `isConsoleTab` block from Task 1 + no regressions).
217+
218+
Run: `npm run build`
219+
Expected: clean build into `dist/` (popup.js emits with no errors).
220+
221+
- [ ] **Step 7: Manual verification + handoff (no commit)**
222+
223+
Reload the unpacked extension, open the Console tab (`console/console.html`), and click the extension action: the popup lede reads "You're on the Rossum Console." (with the Rossum-tab switcher if any Rossum tabs are open, else the "It works on …" fallback). On a plain website the lede still reads "This tab isn't supported by the extension.". On a Rossum/NetSuite/Coupa tab the normal popup is unchanged. Leave uncommitted on `master`.
224+
225+
---
226+
227+
## Self-Review
228+
229+
**Spec coverage:**
230+
- `isConsoleTab` helper (spec §Design) → Task 1. ✓
231+
- Wire in `App.jsx` with `isConsole` prop (spec §Design) → Task 2 Step 4. ✓
232+
- `UnsupportedSite` lede swap in both branches, exact wording (spec §Design) → Task 2 Step 3. ✓
233+
- Export `UnsupportedSite` for testing (spec §Testing) → Task 2 Step 3(a). ✓
234+
- `isConsoleTab` unit tests (spec §Testing) → Task 1 Step 1. ✓
235+
- `UnsupportedSite` render tests, both branches + non-Console (spec §Testing) → Task 2 Step 1. ✓
236+
- Backward compat (no manifest/permission/storage) → Global Constraints; no such changes in any task. ✓
237+
238+
**Placeholder scan:** No TBD/TODO; every code step shows complete code; every command has an expected result. ✓
239+
240+
**Type consistency:** `isConsoleTab(url) → boolean` defined in Task 1, imported/used in Task 2 Step 4, tested in both tasks. `UnsupportedSite({ tabs, isConsole })` exported in Task 2 Step 3, imported in the Task 2 test. Prop name `isConsole` used consistently. ✓
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Console-aware popup copy
2+
3+
**Date:** 2026-07-09
4+
**Status:** Design approved, ready for implementation plan
5+
**Area:** `src/popup/`
6+
7+
## Problem
8+
9+
When the active tab is the extension's own **Console** page
10+
(`console/console.html`), the popup shows the same panel it shows for any random
11+
website: the lede "This tab isn't supported by the extension."
12+
(`src/popup/components/App.jsx:66,92`). That framing is wrong — the Console is
13+
part of this extension, not an unsupported site.
14+
15+
## Verified facts (grounding)
16+
17+
1. **The popup opens on any tab.** `manifest.json` declares
18+
`action.default_popup` with no tab restriction; `popup.jsx:5` resolves the
19+
active tab via `chrome.tabs.query({active, lastFocusedWindow})`.
20+
2. **Site detection.** `App.jsx:105` calls `detectSite(tab.url)`
21+
(`utils.js:8`) → `'rossum' | 'netsuite' | 'coupa' | null`. The Console URL is
22+
`chrome.runtime.getURL('console/console.html')` =
23+
`chrome-extension://<own-id>/console/console.html`, which matches none of the
24+
site patterns, so `detectSite` returns `null` and `App` renders
25+
`<UnsupportedSite>`.
26+
3. **URL access.** `manifest.json` grants `activeTab`, which exposes the active
27+
tab's `url` when the user invokes the action (opens the popup). So the popup
28+
can read the Console tab's URL without a new permission. No `tabs` permission
29+
is added.
30+
4. **The switcher already works on the Console.** The `findRossumTabs()` effect
31+
(`App.jsx:115-118`) runs whenever `!site`, so on the Console the "Switch to
32+
one of your open Rossum tabs" list already populates when Rossum tabs are
33+
open. The only thing wrong today is the lede copy.
34+
5. **Own-id match is exact.** `chrome.runtime.getURL` embeds this extension's id,
35+
so a `startsWith` check matches only our own Console page — never another
36+
extension's pages (different id) and never a real site.
37+
6. **No test asserts the lede string.** Confirmed no test references "isn't
38+
supported"; `tests/popup-utils.test.js` covers `detectSite`/`findRossumTabs`
39+
and already stubs `chrome.runtime.getURL`.
40+
41+
## Design
42+
43+
### `src/popup/utils.js` — new helper
44+
45+
```js
46+
// True when the URL is this extension's own Console page. getURL embeds our
47+
// extension id, so this matches only our Console — not other extensions or sites.
48+
export function isConsoleTab(url) {
49+
return !!url && url.startsWith(chrome.runtime.getURL('console/console.html'));
50+
}
51+
```
52+
53+
Placed next to `detectSite`. The Console tab URL carries a `?authId=...` query,
54+
which `startsWith` tolerates.
55+
56+
### `src/popup/components/App.jsx` — wire it
57+
58+
- When `!site`, compute `const isConsole = isConsoleTab(tab?.url);` and pass it:
59+
`<UnsupportedSite tabs={rossumTabs} isConsole={isConsole} />`. (Prop named
60+
`isConsole`, not `console`, to avoid shadowing the global `console`.)
61+
- Import `isConsoleTab` from `../utils.js` (add to the existing import).
62+
63+
### `UnsupportedSite` — lede swap only
64+
65+
Signature becomes `function UnsupportedSite({ tabs, isConsole })`. In **both**
66+
branches (has-tabs and no-tabs), the lede line changes:
67+
68+
```jsx
69+
<p class="unsupported-lede">
70+
{isConsole ? "You're on the Rossum Console." : "This tab isn't supported by the extension."}
71+
</p>
72+
```
73+
74+
"Rossum Console" matches the popup's existing button label (`App.jsx:224`).
75+
Nothing else changes: the "Switch to one of your open Rossum tabs:" heading + tab
76+
list, the "It works on: … Open one of these sites to get started." fallback, and
77+
the "Also works on NetSuite and Coupa." footnote all stay exactly as today.
78+
79+
## Testing
80+
81+
- `tests/popup-utils.test.js`: add an `isConsoleTab` describe block — true for
82+
`chrome-extension://abc/console/console.html` and `...?authId=x` (with the
83+
existing `getURL: (p) => \`chrome-extension://abc/${p}\`` mock); false for a
84+
Rossum URL, another extension's page (`chrome-extension://zzz/console/...`),
85+
empty, and undefined.
86+
- New `tests/popup-unsupported-site.test.js` (jsdom, render via
87+
`h(UnsupportedSite, props)`): `isConsole=true` with tabs → lede "You're on the
88+
Rossum Console." AND the tab list still renders; `isConsole=true` with no tabs
89+
→ lede changes AND the "It works on" fallback still renders; `isConsole=false`
90+
→ lede unchanged "This tab isn't supported by the extension.".
91+
(`UnsupportedSite` is not exported today — export it for the test.)
92+
93+
## Backward compatibility
94+
95+
- No new storage keys, no manifest change, no new permission.
96+
- Only the Console tab's popup rendering changes (unsupported lede → Console
97+
lede). Every other tab (Rossum/NetSuite/Coupa/other sites) is unaffected.
98+
- The tab switcher and fallback behavior are unchanged.
99+
100+
## Out of scope
101+
102+
- Any Console-specific actions in the popup (open-another-Console, app links) —
103+
explicitly declined; copy-only.
104+
- Showing site toggles on the Console (they can't act on an extension page).
105+
- Reconciling the broader Console/Dataset-Management/Master-Data-Hub naming.

src/popup/components/App.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { h, Fragment } from 'preact';
22
import { useEffect, useState } from 'preact/hooks';
33
import Toggle from './Toggle.jsx';
44
import MdhProvenancePanel from './MdhProvenancePanel.jsx';
5-
import { openConsoleTab, runInTab, detectSite, findRossumTabs, activateTab } from '../utils.js';
5+
import { openConsoleTab, runInTab, detectSite, findRossumTabs, activateTab, isConsoleTab } from '../utils.js';
66
import { readAuthInfo, readPageFlag, togglePageFlag } from '../tab-readers.js';
77
import { createUnlockCounter } from '../experimental.js';
88

@@ -54,7 +54,7 @@ function hostFromUrl(url) {
5454
try { return new URL(url).host; } catch { return ''; }
5555
}
5656

57-
function UnsupportedSite({ tabs }) {
57+
export function UnsupportedSite({ tabs, isConsole }) {
5858
// tabs === null means we haven't queried yet — render the static fallback
5959
// immediately rather than showing a loading flicker; the list will reveal
6060
// when the query resolves.
@@ -63,7 +63,7 @@ function UnsupportedSite({ tabs }) {
6363
if (hasTabs) {
6464
return (
6565
<div class="unsupported-site">
66-
<p class="unsupported-lede">This tab isn't supported by the extension.</p>
66+
<p class="unsupported-lede">{isConsole ? "You're on the Rossum Console." : "This tab isn't supported by the extension."}</p>
6767
<p class="unsupported-heading">Switch to one of your open Rossum tabs:</p>
6868
<ul class="rossum-tab-list">
6969
{tabs.map((t) => (
@@ -89,7 +89,7 @@ function UnsupportedSite({ tabs }) {
8989

9090
return (
9191
<div class="unsupported-site">
92-
<p class="unsupported-lede">This tab isn't supported by the extension.</p>
92+
<p class="unsupported-lede">{isConsole ? "You're on the Rossum Console." : "This tab isn't supported by the extension."}</p>
9393
<p>It works on:</p>
9494
<div class="supported-sites">
9595
<span class="supported-site">Rossum</span>
@@ -103,6 +103,7 @@ function UnsupportedSite({ tabs }) {
103103

104104
export default function App({ tab }) {
105105
const site = detectSite(tab?.url || '');
106+
const isConsole = !site && isConsoleTab(tab?.url || '');
106107
const version = chrome.runtime.getManifest().version_name || chrome.runtime.getManifest().version;
107108

108109
const [storageValues, setStorageValues] = useState(null);
@@ -229,7 +230,7 @@ export default function App({ tab }) {
229230
</header>
230231

231232
{!site ? (
232-
<UnsupportedSite tabs={rossumTabs} />
233+
<UnsupportedSite tabs={rossumTabs} isConsole={isConsole} />
233234
) : (
234235
<div id="mainContent">
235236
<div class="content-row">

0 commit comments

Comments
 (0)