@pro-vi/designer 0.3.7 → 0.3.8
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/dist/designer-controller.js +58 -9
- package/dist/ui-anchors.js +18 -4
- package/package.json +2 -2
- package/selectors.json +1 -1
|
@@ -195,17 +195,42 @@ export class DesignerController {
|
|
|
195
195
|
const { promptTextarea, sendButton } = this.selectors.composer;
|
|
196
196
|
await this.browser.waitFor(promptTextarea);
|
|
197
197
|
await this.browser.evalValue(`(() => {
|
|
198
|
-
const
|
|
199
|
-
if (!
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
198
|
+
const el = document.querySelector(${JSON.stringify(promptTextarea)});
|
|
199
|
+
if (!el) throw new Error('composer input not found');
|
|
200
|
+
const text = ${JSON.stringify(prompt)};
|
|
201
|
+
if (el instanceof HTMLTextAreaElement) {
|
|
202
|
+
// Bypass React's value ownership via the native setter, then fire a
|
|
203
|
+
// bubbling input event.
|
|
204
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
|
|
205
|
+
setter.call(el, text);
|
|
206
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
207
|
+
el.focus();
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (el.isContentEditable) {
|
|
211
|
+
// Deliver the text as a synthetic paste so the editor's own paste
|
|
212
|
+
// pipeline updates its internal state; execCommand('insertText')
|
|
213
|
+
// flattens multi-line prompts into one paragraph.
|
|
214
|
+
el.focus();
|
|
215
|
+
const sel = window.getSelection();
|
|
216
|
+
const range = document.createRange();
|
|
217
|
+
range.selectNodeContents(el);
|
|
218
|
+
sel.removeAllRanges();
|
|
219
|
+
sel.addRange(range);
|
|
220
|
+
const dt = new DataTransfer();
|
|
221
|
+
dt.setData('text/plain', text);
|
|
222
|
+
const unhandled = el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }));
|
|
223
|
+
if (unhandled) {
|
|
224
|
+
// No editor intercepted the paste — plain contenteditable fallback.
|
|
225
|
+
document.execCommand('insertText', false, text);
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
throw new Error('composer input is neither textarea nor contenteditable: ' + el.tagName);
|
|
205
230
|
})()`);
|
|
206
231
|
for (let i = 0; i < 30; i++) {
|
|
207
232
|
await new Promise((r) => setTimeout(r, 150));
|
|
208
|
-
const disabled = await this.browser.evalValue(`(() => { const b = document.querySelector(${JSON.stringify(sendButton)}); return !b || b.disabled; })()`);
|
|
233
|
+
const disabled = await this.browser.evalValue(`(() => { const b = document.querySelector(${JSON.stringify(sendButton)}); return !b || b.disabled || b.getAttribute('aria-disabled') === 'true'; })()`);
|
|
209
234
|
if (!disabled)
|
|
210
235
|
break;
|
|
211
236
|
}
|
|
@@ -520,7 +545,9 @@ export class DesignerController {
|
|
|
520
545
|
if (!opened)
|
|
521
546
|
await this._clickButtonByText(/^Export$/);
|
|
522
547
|
await new Promise((r) => setTimeout(r, 400));
|
|
523
|
-
await this.
|
|
548
|
+
const viaSendTo = await this._clickClaudeCodeSendTo().catch(() => false);
|
|
549
|
+
if (!viaSendTo)
|
|
550
|
+
await this._clickButtonByText(/handoff to claude code/i);
|
|
524
551
|
let handoffUrl = '';
|
|
525
552
|
for (let i = 0; i < 30; i++) {
|
|
526
553
|
await new Promise((r) => setTimeout(r, 300));
|
|
@@ -583,6 +610,28 @@ export class DesignerController {
|
|
|
583
610
|
return true;
|
|
584
611
|
})()`);
|
|
585
612
|
}
|
|
613
|
+
async _clickClaudeCodeSendTo() {
|
|
614
|
+
const tabClicked = await this.browser.evalValue(`(() => {
|
|
615
|
+
const tab = Array.from(document.querySelectorAll('button[role="tab"]')).find(t => /send to/i.test(t.textContent || ''));
|
|
616
|
+
if (!tab) return false;
|
|
617
|
+
tab.click();
|
|
618
|
+
return true;
|
|
619
|
+
})()`);
|
|
620
|
+
if (!tabClicked)
|
|
621
|
+
return false;
|
|
622
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
623
|
+
return this.browser.evalValue(`(() => {
|
|
624
|
+
const sends = Array.from(document.querySelectorAll('button')).filter(b => (b.textContent || '').trim() === 'Send');
|
|
625
|
+
const target = sends.find(b => {
|
|
626
|
+
let row = b;
|
|
627
|
+
for (let i = 0; i < 3 && row.parentElement; i++) row = row.parentElement;
|
|
628
|
+
return /claude code/i.test(row.textContent || '');
|
|
629
|
+
});
|
|
630
|
+
if (!target) return false;
|
|
631
|
+
target.click();
|
|
632
|
+
return true;
|
|
633
|
+
})()`);
|
|
634
|
+
}
|
|
586
635
|
async _dialogText() {
|
|
587
636
|
return ((await this.browser
|
|
588
637
|
.evalValue(`(() => {
|
package/dist/ui-anchors.js
CHANGED
|
@@ -70,7 +70,7 @@ export const UI_ANCHORS = [
|
|
|
70
70
|
category: 'session',
|
|
71
71
|
description: 'send button',
|
|
72
72
|
requires: 'session',
|
|
73
|
-
check: async (b) => ({ ok: await hasSelector(b, '[data-testid="chat-send-button"]') })
|
|
73
|
+
check: async (b) => ({ ok: await hasSelector(b, '[data-testid="chat-send-button"], button[title^="Send ("]') })
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
id: 'session.htmlViewerIframe',
|
|
@@ -122,16 +122,30 @@ export const UI_ANCHORS = [
|
|
|
122
122
|
{
|
|
123
123
|
id: 'share.handoffMenuItem',
|
|
124
124
|
category: 'share',
|
|
125
|
-
description: 'Handoff-to-Claude-Code
|
|
125
|
+
description: 'Handoff-to-Claude-Code action (Share → Send to… tab → Claude Code row, or the legacy dropdown item)',
|
|
126
126
|
requires: 'session',
|
|
127
127
|
check: async (b) => {
|
|
128
128
|
const opened = await b.evalValue(`(() => { const btn = Array.from(document.querySelectorAll('button')).find(x => (x.textContent||'').trim() === 'Share'); if (!btn) return false; btn.click(); return true; })()`).catch(() => false);
|
|
129
129
|
if (!opened)
|
|
130
130
|
return { ok: false, detail: 'Share button not clickable' };
|
|
131
131
|
await new Promise((r) => setTimeout(r, 400));
|
|
132
|
-
|
|
132
|
+
let found = await hasButtonMatching(b, /handoff to claude code/i);
|
|
133
|
+
if (!found) {
|
|
134
|
+
const tabClicked = await b.evalValue(`(() => { const tab = Array.from(document.querySelectorAll('button[role="tab"]')).find(t => /send to/i.test(t.textContent || '')); if (!tab) return false; tab.click(); return true; })()`).catch(() => false);
|
|
135
|
+
if (tabClicked) {
|
|
136
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
137
|
+
found = await b.evalValue(`(() => {
|
|
138
|
+
const sends = Array.from(document.querySelectorAll('button')).filter(x => (x.textContent || '').trim() === 'Send');
|
|
139
|
+
return sends.some(x => {
|
|
140
|
+
let row = x;
|
|
141
|
+
for (let i = 0; i < 3 && row.parentElement; i++) row = row.parentElement;
|
|
142
|
+
return /claude code/i.test(row.textContent || '');
|
|
143
|
+
});
|
|
144
|
+
})()`).catch(() => false);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
133
147
|
await b.evalValue(`document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); true`).catch(() => null);
|
|
134
|
-
return { ok: found, detail: found ? undefined : 'Share opened but no
|
|
148
|
+
return { ok: found, detail: found ? undefined : 'Share opened but no Claude Code handoff action (checked legacy item and Send to… tab)' };
|
|
135
149
|
}
|
|
136
150
|
},
|
|
137
151
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pro-vi/designer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"smoke": "bash scripts/install-smoke.sh"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@anthropic-ai/sdk": "^0.
|
|
43
|
+
"@anthropic-ai/sdk": "^0.102.0",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
45
45
|
"zod": "^4.4.3"
|
|
46
46
|
},
|
package/selectors.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"composer": {
|
|
20
20
|
"promptTextarea": "[data-testid=\"chat-composer-input\"]",
|
|
21
|
-
"sendButton": "[data-testid=\"chat-send-button\"]",
|
|
21
|
+
"sendButton": "[data-testid=\"chat-send-button\"], button[title^=\"Send (\"]",
|
|
22
22
|
"stopButton": null,
|
|
23
23
|
"attachButton": "button[aria-label=\"Attach file\"]",
|
|
24
24
|
"modelButton": "[data-testid=\"model-selector-button\"]"
|