@pro-vi/designer 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/browser.js +12 -2
- package/dist/setup.js +33 -2
- package/dist/ui-anchors.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,7 +118,7 @@ Writes `tasting.html` with variant tabs + 1/2/3 shortcuts + persistent notes, se
|
|
|
118
118
|
## Operations
|
|
119
119
|
|
|
120
120
|
- `designer doctor` — diagnose setup state. Exits 2 on failure.
|
|
121
|
-
- `designer health [--json]` — probe
|
|
121
|
+
- `designer health [--json]` — probe every UI anchor designer depends on. Wire into cron to catch claude.ai UI regressions.
|
|
122
122
|
- **Daily CI** in `.github/workflows/`: `daily-health.yml` runs the auth-required UI probe on a self-hosted macOS runner once per day; `ci.yml` typechecks + builds + does a Docker clean-room install smoke on every PR; `release-please.yml` opens a release PR on conventional commits, merging it tags + publishes via `release-publish.yml`. Selector regressions land as auto-opened PRs under the `selectors-drift` label.
|
|
123
123
|
|
|
124
124
|
## Known quirks
|
package/dist/browser.js
CHANGED
|
@@ -12,9 +12,10 @@ export function createBrowser({ session = DEFAULT_SESSION, headed = true, timeou
|
|
|
12
12
|
function connectFlags() {
|
|
13
13
|
if (!cdp)
|
|
14
14
|
return [];
|
|
15
|
+
const scope = ['--session', `designer-cdp-${cdp.replace(/[^a-zA-Z0-9.-]/g, '_')}`];
|
|
15
16
|
if (cdp === 'auto' || cdp === '1' || cdp === 'true')
|
|
16
|
-
return ['--auto-connect'];
|
|
17
|
-
return ['--cdp', cdp];
|
|
17
|
+
return [...scope, '--auto-connect'];
|
|
18
|
+
return [...scope, '--cdp', cdp];
|
|
18
19
|
}
|
|
19
20
|
function run(args, { input, parseJson = false } = {}) {
|
|
20
21
|
return new Promise((resolve, reject) => {
|
|
@@ -64,6 +65,15 @@ export function createBrowser({ session = DEFAULT_SESSION, headed = true, timeou
|
|
|
64
65
|
activateTab: async (index) => {
|
|
65
66
|
await run(['tab', String(index)]);
|
|
66
67
|
},
|
|
68
|
+
reload: () => run(['reload']),
|
|
69
|
+
cookies: async () => {
|
|
70
|
+
const out = await run(['cookies', 'get', '--json']);
|
|
71
|
+
const env = JSON.parse(out);
|
|
72
|
+
if (env.success === false) {
|
|
73
|
+
throw new Error(`agent-browser cookies get failed: ${JSON.stringify(env.error)}`);
|
|
74
|
+
}
|
|
75
|
+
return env.data?.cookies ?? [];
|
|
76
|
+
},
|
|
67
77
|
snapshot: ({ interactive = true, scope } = {}) => {
|
|
68
78
|
const args = ['snapshot', '--json'];
|
|
69
79
|
if (interactive)
|
package/dist/setup.js
CHANGED
|
@@ -162,7 +162,14 @@ async function step3Chrome(port) {
|
|
|
162
162
|
log('chrome', 'fail', `Chrome not found at ${CHROME_BIN}. Set CHROME_BIN to override.`);
|
|
163
163
|
return false;
|
|
164
164
|
}
|
|
165
|
-
const child = spawn(CHROME_BIN, [
|
|
165
|
+
const child = spawn(CHROME_BIN, [
|
|
166
|
+
'--remote-debugging-port=' + port,
|
|
167
|
+
'--user-data-dir=' + PROFILE,
|
|
168
|
+
'--no-first-run',
|
|
169
|
+
'--no-default-browser-check',
|
|
170
|
+
'--disable-search-engine-choice-screen',
|
|
171
|
+
'https://claude.ai/design'
|
|
172
|
+
], {
|
|
166
173
|
detached: true,
|
|
167
174
|
stdio: 'ignore'
|
|
168
175
|
});
|
|
@@ -184,7 +191,28 @@ async function step4SignIn(port) {
|
|
|
184
191
|
const browser = createBrowser({ session: 'designer-setup', cdp: port });
|
|
185
192
|
await browser.open('https://claude.ai/design').catch(() => undefined);
|
|
186
193
|
await sleep(2500);
|
|
187
|
-
const
|
|
194
|
+
const MAX_RECOVERY_NAVS = 4;
|
|
195
|
+
const hasAuthCookie = async () => {
|
|
196
|
+
try {
|
|
197
|
+
return (await browser.cookies()).some((c) => /^sessionKey/.test(c.name) && /claude\.ai$/.test(c.domain) && c.value.length > 20);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
let recoveryNavs = 0;
|
|
204
|
+
const checkSignedIn = async () => {
|
|
205
|
+
if (await verifySignedIn(browser))
|
|
206
|
+
return true;
|
|
207
|
+
if (recoveryNavs < MAX_RECOVERY_NAVS && (await hasAuthCookie())) {
|
|
208
|
+
recoveryNavs++;
|
|
209
|
+
await browser.open('https://claude.ai/design').catch(() => undefined);
|
|
210
|
+
await sleep(3000);
|
|
211
|
+
return verifySignedIn(browser);
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
};
|
|
215
|
+
const ok = await pollUntil('login', checkSignedIn, {
|
|
188
216
|
intervalMs: 2000,
|
|
189
217
|
timeoutMs: 10 * 60_000,
|
|
190
218
|
reminder: 'Sign in to Claude in the DEBUG Chrome window I just opened (a separate window with no extensions/bookmarks — NOT your normal Chrome; the two have separate cookie jars). Then return to claude.ai/design. I am polling.',
|
|
@@ -192,6 +220,9 @@ async function step4SignIn(port) {
|
|
|
192
220
|
});
|
|
193
221
|
if (!ok) {
|
|
194
222
|
log('login', 'fail', 'Timed out waiting for a signed-in claude.ai/design session. Re-run setup when ready.');
|
|
223
|
+
const watched = await browser.url().catch(() => '(unreachable)');
|
|
224
|
+
const authCookie = await hasAuthCookie();
|
|
225
|
+
log('login', 'fail', `Watched tab: ${watched} | Claude auth cookie: ${authCookie ? 'present — login succeeded but the tab stayed stale; re-run: designer setup' : 'absent (or watched tab is off claude.ai origin) — see watched URL above'}`);
|
|
195
226
|
return false;
|
|
196
227
|
}
|
|
197
228
|
const url = (await browser.url().catch(() => '')) || 'claude.ai/design';
|
package/dist/ui-anchors.js
CHANGED
|
@@ -9,6 +9,25 @@ async function hasButtonMatching(browser, pattern) {
|
|
|
9
9
|
.catch(() => false));
|
|
10
10
|
}
|
|
11
11
|
export const UI_ANCHORS = [
|
|
12
|
+
{
|
|
13
|
+
id: 'login.signedIn',
|
|
14
|
+
category: 'pattern',
|
|
15
|
+
description: 'signed in (claude.ai is rendering the app shell, not the login wall)',
|
|
16
|
+
requires: 'any',
|
|
17
|
+
check: async (b, url) => {
|
|
18
|
+
if (/claude\.ai\/login/.test(url)) {
|
|
19
|
+
return { ok: false, detail: `signed out — Chrome is on the login wall (${url.slice(0, 80)}). Run: designer setup` };
|
|
20
|
+
}
|
|
21
|
+
if (/claude\.ai\/design/.test(url)) {
|
|
22
|
+
const signedIn = (await hasSelector(b, '[data-testid="project-creator"]')) ||
|
|
23
|
+
(await hasSelector(b, '[data-testid="chat-composer-input"]'));
|
|
24
|
+
return signedIn
|
|
25
|
+
? { ok: true }
|
|
26
|
+
: { ok: false, detail: `login wall rendered at ${url.slice(0, 80)} (no app shell) — signed out. Run: designer setup` };
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, detail: `not on a claude.ai/design surface (url=${url.slice(0, 60)}) — sign-in not checked here` };
|
|
29
|
+
}
|
|
30
|
+
},
|
|
12
31
|
{
|
|
13
32
|
id: 'home.creator',
|
|
14
33
|
category: 'home',
|
|
@@ -65,6 +84,31 @@ export const UI_ANCHORS = [
|
|
|
65
84
|
requires: 'session',
|
|
66
85
|
check: async (b) => ({ ok: await hasSelector(b, '[data-testid="chat-composer-input"]') })
|
|
67
86
|
},
|
|
87
|
+
{
|
|
88
|
+
id: 'session.composerFillable',
|
|
89
|
+
category: 'session',
|
|
90
|
+
description: 'composer is fillable (textarea or contenteditable, per _submitPrompt)',
|
|
91
|
+
requires: 'session',
|
|
92
|
+
check: async (b) => {
|
|
93
|
+
const shape = await b
|
|
94
|
+
.evalValue(`(() => {
|
|
95
|
+
const el = document.querySelector('[data-testid="chat-composer-input"]');
|
|
96
|
+
if (!el) return { found: false };
|
|
97
|
+
const fillable = el instanceof HTMLTextAreaElement || el.isContentEditable;
|
|
98
|
+
return { found: true, tag: el.tagName, contentEditable: el.isContentEditable, fillable };
|
|
99
|
+
})()`)
|
|
100
|
+
.catch(() => ({ found: false }));
|
|
101
|
+
if (!shape.found)
|
|
102
|
+
return { ok: false, detail: 'composer not found' };
|
|
103
|
+
if (shape.fillable) {
|
|
104
|
+
return { ok: true, detail: shape.contentEditable ? 'contenteditable' : `<${(shape.tag || '').toLowerCase()}>` };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
detail: `composer is <${(shape.tag || '?').toLowerCase()}> — neither textarea nor contenteditable; _submitPrompt cannot fill it (composer shape drifted)`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
},
|
|
68
112
|
{
|
|
69
113
|
id: 'session.sendButton',
|
|
70
114
|
category: 'session',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pro-vi/designer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP + CLI for autonomous iteration of claude.ai/design — drives the design surface via agent-browser, downloads handoff bundles, and exposes a tasting harness for full-viewport variant comparison.",
|
|
6
6
|
"license": "MIT",
|