mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8017cbef8 | ||
|
|
dff411d8e8 | ||
|
|
2564531fd0 | ||
|
|
8c880d75f9 | ||
|
|
cc988144a0 | ||
|
|
dc5c4b2184 | ||
|
|
9a7fae4361 |
Generated
+16
-16
@@ -509,7 +509,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"impeccable-browser",
|
||||
@@ -528,7 +528,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-browser"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"impeccable-core",
|
||||
@@ -542,7 +542,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-bundle"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"impeccable-core",
|
||||
@@ -551,14 +551,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-common"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-comp"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"once_cell",
|
||||
@@ -570,7 +570,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-comp-verbs"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-common",
|
||||
"impeccable-comp",
|
||||
@@ -583,7 +583,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-context"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"impeccable-common",
|
||||
@@ -600,7 +600,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-core",
|
||||
"impeccable-foundation",
|
||||
@@ -612,7 +612,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-detect"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-common",
|
||||
"impeccable-core",
|
||||
@@ -624,7 +624,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-foundation"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
"once_cell",
|
||||
@@ -637,7 +637,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-hook"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-common",
|
||||
"impeccable-context",
|
||||
@@ -651,7 +651,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-html"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
"ego-tree",
|
||||
@@ -672,7 +672,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-live"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"impeccable-common",
|
||||
@@ -690,7 +690,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-skills"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-common",
|
||||
"impeccable-context",
|
||||
@@ -709,7 +709,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "impeccable-wasm"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-core",
|
||||
"impeccable-detect",
|
||||
@@ -1681,7 +1681,7 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"impeccable-bundle",
|
||||
]
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ resolver = "2"
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
publish = false
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.1.0
|
||||
0.1.1
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
"zod": "^4.3.6",
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@impeccable/cli-darwin-arm64": "0.1.0",
|
||||
"@impeccable/cli-darwin-x64": "0.1.0",
|
||||
"@impeccable/cli-linux-arm64": "0.1.0",
|
||||
"@impeccable/cli-linux-x64": "0.1.0",
|
||||
"@impeccable/cli-windows-x64": "0.1.0",
|
||||
"@impeccable/cli-darwin-arm64": "0.1.1",
|
||||
"@impeccable/cli-darwin-x64": "0.1.1",
|
||||
"@impeccable/cli-linux-arm64": "0.1.1",
|
||||
"@impeccable/cli-linux-x64": "0.1.1",
|
||||
"@impeccable/cli-windows-x64": "0.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -72,6 +72,16 @@
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
|
||||
|
||||
"@impeccable/cli-darwin-arm64": ["@impeccable/cli-darwin-arm64@0.1.1", "", { "os": "darwin", "cpu": "arm64", "bin": { "impeccable-darwin-arm64": "bin/impeccable" } }, "sha512-1/DZYaiZqDoNwpXpyoG4gRQpgLZ4YDCMADGzA5wYmNiG279b3KPt2RLYUww6NF2hxoMxdVQUAGzdc5062KZKHg=="],
|
||||
|
||||
"@impeccable/cli-darwin-x64": ["@impeccable/cli-darwin-x64@0.1.1", "", { "os": "darwin", "cpu": "x64", "bin": { "impeccable-darwin-x64": "bin/impeccable" } }, "sha512-/itjFZEHPcz1RQDBx3+2aeTebQ4pCD30VKAJ3Zst5KxHteJQOW3hoiTFE7jAOXg3vG2D/qAaEh/7Eo+xzEawew=="],
|
||||
|
||||
"@impeccable/cli-linux-arm64": ["@impeccable/cli-linux-arm64@0.1.1", "", { "os": "linux", "cpu": "arm64", "bin": { "impeccable-linux-arm64": "bin/impeccable" } }, "sha512-uGJ2DNVq3NzH8+RlTlyn1XWpAsNS1bv6kix7PsAnzCa0aBiOHaYSUkPXbqDVUTDy9X4oEOsZYuzbgoY+otkoMQ=="],
|
||||
|
||||
"@impeccable/cli-linux-x64": ["@impeccable/cli-linux-x64@0.1.1", "", { "os": "linux", "cpu": "x64", "bin": { "impeccable-linux-x64": "bin/impeccable" } }, "sha512-wPul+V7w9g0MZAgFmJJvEOXpy8AYt4htmLQJIx7XvakSp8CS+PYBjEFqmWSW41NlrNAaifzDKdUe6rzeRli97A=="],
|
||||
|
||||
"@impeccable/cli-windows-x64": ["@impeccable/cli-windows-x64@0.1.1", "", { "os": "win32", "cpu": "x64", "bin": { "impeccable-windows-x64": "bin/impeccable.exe" } }, "sha512-dqcQ8VQFschjA1iFzhKvO44UEzsbQQGHAZW3MmsxJbqo/uSDENIL8WA5l4K8Q9wqjf4Kbc/H1hqo6MjOuQw85A=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "impeccable",
|
||||
"version": "4.0.1",
|
||||
"version": "4.0.2",
|
||||
"author": "Paul Bakaus",
|
||||
"description": "Design skills, commands, and anti-pattern detection for AI coding agents",
|
||||
"keywords": [
|
||||
@@ -71,11 +71,11 @@
|
||||
"check:engine-release": "node scripts/check-engine-release.mjs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@impeccable/cli-darwin-arm64": "0.1.0",
|
||||
"@impeccable/cli-darwin-x64": "0.1.0",
|
||||
"@impeccable/cli-linux-x64": "0.1.0",
|
||||
"@impeccable/cli-linux-arm64": "0.1.0",
|
||||
"@impeccable/cli-windows-x64": "0.1.0"
|
||||
"@impeccable/cli-darwin-arm64": "0.1.1",
|
||||
"@impeccable/cli-darwin-x64": "0.1.1",
|
||||
"@impeccable/cli-linux-x64": "0.1.1",
|
||||
"@impeccable/cli-linux-arm64": "0.1.1",
|
||||
"@impeccable/cli-windows-x64": "0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ai-sdk/anthropic": "^4.0.7",
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.1.0
|
||||
0.1.1
|
||||
|
||||
@@ -7834,7 +7834,6 @@
|
||||
if (editBadgeEl && editBadgeEl.style.display !== 'none') renderEditBadge('idle-disabled');
|
||||
showBar('generating');
|
||||
saveSession();
|
||||
sendCheckpoint('generate_started');
|
||||
writeScrollY(window.scrollY);
|
||||
if (variantObserver) variantObserver.disconnect();
|
||||
variantObserver = startVariantObserver(currentSessionId);
|
||||
@@ -7916,7 +7915,6 @@
|
||||
showBar('generating');
|
||||
startScrollTracking();
|
||||
saveSession();
|
||||
sendCheckpoint('generate_started');
|
||||
writeScrollY(window.scrollY);
|
||||
if (variantObserver) variantObserver.disconnect();
|
||||
variantObserver = startVariantObserver(currentSessionId);
|
||||
@@ -8238,7 +8236,8 @@
|
||||
// rasterization from delaying the fetch itself.
|
||||
if (!hasAnnotations) {
|
||||
basePayload.clientSentAt = Date.now();
|
||||
await sendEvent(basePayload);
|
||||
const created = await sendEvent(basePayload);
|
||||
if (created?.ok && currentSessionId === basePayload.id) sendCheckpoint('generate_started');
|
||||
}
|
||||
|
||||
let screenshotPath;
|
||||
@@ -8279,7 +8278,10 @@
|
||||
// is semantic input. Plain requests were already dispatched above.
|
||||
if (hasAnnotations) {
|
||||
basePayload.clientSentAt = Date.now();
|
||||
sendEvent(screenshotPath ? { ...basePayload, screenshotPath } : basePayload);
|
||||
const created = await sendEvent(screenshotPath ? { ...basePayload, screenshotPath } : basePayload);
|
||||
// Capture/upload can take seconds. Progress before this acknowledgment
|
||||
// refers to an unknown session and would clear our own active work.
|
||||
if (created?.ok && currentSessionId === basePayload.id) sendCheckpoint('generate_started');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
"readyPattern": "Local:\\s+https?://[^:]+:(\\d+)",
|
||||
"readyTimeoutMs": 120000,
|
||||
"steer": false,
|
||||
"liveChrome": {
|
||||
"annotations": {
|
||||
"selector": "h1.hero-title",
|
||||
"uploadDelayMs": 300
|
||||
}
|
||||
},
|
||||
"pickSelector": "ul.expense-list",
|
||||
"pickPosition": {
|
||||
"x": 10,
|
||||
|
||||
@@ -2,12 +2,53 @@ import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { runInNewContext } from 'node:vm';
|
||||
|
||||
const SOURCE = readFileSync(join(process.cwd(), 'skill/scripts/live-browser.js'), 'utf-8');
|
||||
const PENDING_DOCK_POSITION_SOURCE = SOURCE.match(/function positionPendingDock\(\) \{[\s\S]*?\n \}/)?.[0] || '';
|
||||
const CAPTURE_AND_EMIT_SOURCE = SOURCE.match(/async function captureAndEmit\([\s\S]*?\n \}/)?.[0] || '';
|
||||
|
||||
describe('live-browser source contracts', () => {
|
||||
it('does not checkpoint a generation before captureAndEmit creates its session', () => {
|
||||
for (const name of ['handleGo', 'handleInsertCreate']) {
|
||||
const body = SOURCE.match(new RegExp(`function ${name}\\(\\) \\{[\\s\\S]*?\\n \\}`))?.[0];
|
||||
assert.ok(body);
|
||||
assert.doesNotMatch(body, /sendCheckpoint\('generate_started'\)/);
|
||||
}
|
||||
});
|
||||
|
||||
for (const annotated of [false, true]) {
|
||||
for (const outcome of ['created', 'failed', 'superseded']) {
|
||||
it(`${annotated ? 'annotated' : 'plain'} generation checkpoints only its acknowledged current session (${outcome})`, async () => {
|
||||
const capture = Promise.withResolvers();
|
||||
const creation = Promise.withResolvers();
|
||||
const events = [];
|
||||
const context = {
|
||||
currentSessionId: 'session-a', state: 'GENERATING', PORT: 1234, TOKEN: 'test',
|
||||
console, Date,
|
||||
captureElementToBlob: () => capture.promise,
|
||||
showShaderOverlay() {},
|
||||
fetch: async () => ({ ok: true, json: async () => ({ path: '/annotation.png' }) }),
|
||||
sendEvent: async (payload) => { events.push(payload.type); return creation.promise; },
|
||||
sendCheckpoint: (reason) => events.push(reason),
|
||||
};
|
||||
const emit = runInNewContext(`(${CAPTURE_AND_EMIT_SOURCE})`, context);
|
||||
const pending = emit({}, { type: 'generate', id: 'session-a' }, {
|
||||
comments: annotated ? [{ text: 'change title' }] : [], strokes: [],
|
||||
}, {});
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.deepEqual(events, annotated ? [] : ['generate']);
|
||||
capture.resolve({ blob: {}, paper: 'white' });
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.deepEqual(events, ['generate'], 'capture/upload must not checkpoint before creation is acknowledged');
|
||||
if (outcome === 'superseded') context.currentSessionId = 'session-b';
|
||||
creation.resolve(outcome === 'failed' ? null : { ok: true });
|
||||
await pending;
|
||||
assert.deepEqual(events, outcome === 'created' ? ['generate', 'generate_started'] : ['generate']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
it('reports foreground poll connectivity without a background worker dependency', () => {
|
||||
assert.match(
|
||||
SOURCE,
|
||||
@@ -29,7 +70,7 @@ describe('live-browser source contracts', () => {
|
||||
);
|
||||
assert.match(
|
||||
CAPTURE_AND_EMIT_SOURCE,
|
||||
/if \(hasAnnotations\) \{[\s\S]*?basePayload\.clientSentAt = Date\.now\(\);\s*sendEvent\(screenshotPath \? \{ \.\.\.basePayload, screenshotPath \} : basePayload\);\s*\}/,
|
||||
/if \(hasAnnotations\) \{[\s\S]*?basePayload\.clientSentAt = Date\.now\(\);\s*const created = await sendEvent\(screenshotPath \? \{ \.\.\.basePayload, screenshotPath \} : basePayload\);/,
|
||||
'annotated generation should dispatch exactly after capture and upload resolve',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
MANUAL_EDIT_SYSTEM_INSTRUCTIONS,
|
||||
VARIANT_SYSTEM_INSTRUCTIONS,
|
||||
createLlmAgent,
|
||||
llmRequestSettings,
|
||||
parseManualEditResponse,
|
||||
parseVariantResponse,
|
||||
progressiveVariantGuidance,
|
||||
@@ -19,6 +20,19 @@ import {
|
||||
validateVariantVisibleCopy,
|
||||
} from './live-e2e/agents/llm-agent.mjs';
|
||||
|
||||
describe('live-e2e LLM request settings', () => {
|
||||
it('explicitly selects low-effort DeepSeek thinking for bounded JSON edit requests', () => {
|
||||
assert.deepEqual(llmRequestSettings('deepseek'), {
|
||||
thinking: { type: 'enabled' }, output_config: { effort: 'low' },
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves other providers unchanged', () => {
|
||||
assert.deepEqual(llmRequestSettings('anthropic'), {});
|
||||
assert.deepEqual(llmRequestSettings('openai'), {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('live-e2e LLM agent provider config', () => {
|
||||
it('defaults to OpenAI gpt-5.6-terra at medium reasoning effort', () => {
|
||||
const config = resolveLlmAgentConfig({}, {});
|
||||
|
||||
+10
-6
@@ -1283,17 +1283,21 @@ for (const { name, fixture } of fixtures) {
|
||||
const pickSelector = annotation.selector || fixture.runtime.pickSelector || 'h1.hero-title';
|
||||
try {
|
||||
await waitForHandshake(page);
|
||||
if (annotation.uploadDelayMs) {
|
||||
await page.route('**/annotation?*', async (route) => {
|
||||
await new Promise(resolve => setTimeout(resolve, annotation.uploadDelayMs));
|
||||
await route.continue();
|
||||
});
|
||||
}
|
||||
if (fixture.runtime.preActions) await runPreActions(page, fixture.runtime.preActions);
|
||||
await pickElement(page, pickSelector, { resetPickMode: true });
|
||||
await drawAnnotationPinAndStroke(page, {
|
||||
comment: annotation.comment || 'Make this selected element easier to scan',
|
||||
});
|
||||
await clickGo(page);
|
||||
await waitForCyclingRobust(page, 3, {
|
||||
agentMode,
|
||||
preActions: fixture.runtime.preActions,
|
||||
log: (m) => t.diagnostic(m),
|
||||
});
|
||||
// A reload would mask a checkpoint-before-creation race by adopting
|
||||
// the session again. Annotated generation must complete in this tab.
|
||||
await waitForCycling(page, 3, { timeout: agentMode === 'llm' ? 180_000 : 30_000 });
|
||||
|
||||
const generateEvent = recordedGenerateEvents.at(-1);
|
||||
await assertAnnotationUploadEvent(generateEvent);
|
||||
@@ -1302,7 +1306,7 @@ for (const { name, fixture } of fixtures) {
|
||||
|
||||
const sourceFile = await locateSessionFile(session.appRoot);
|
||||
const svelteComponentTarget = svelteComponentTargetFor(sourceFile);
|
||||
await clickNext(page);
|
||||
await cycleToVariant(page, 2, 3);
|
||||
assert.equal(await getVisibleVariant(page), 2, 'variant 2 visible after annotated generate');
|
||||
await clickAccept(page, { expectedVariant: 2 });
|
||||
await waitForBarHidden(page);
|
||||
|
||||
@@ -248,6 +248,17 @@ function resolveProvider(opts, env) {
|
||||
return 'openai';
|
||||
}
|
||||
|
||||
export function llmRequestSettings(provider) {
|
||||
// DeepSeek defaults to high-effort thinking, which can consume the entire
|
||||
// bounded response before emitting the JSON these edit tests exercise.
|
||||
// Low effort retains planning for the full live spec without inheriting
|
||||
// the provider's high-effort default.
|
||||
// https://api-docs.deepseek.com/guides/thinking_mode/
|
||||
return provider === 'deepseek'
|
||||
? { thinking: { type: 'enabled' }, output_config: { effort: 'low' } }
|
||||
: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic-SDK-shaped shim over the `ai` SDK for OpenAI models, so the
|
||||
* three text-only call sites in this file stay provider-agnostic. system
|
||||
@@ -331,6 +342,7 @@ export async function createLlmAgent(opts = {}) {
|
||||
try {
|
||||
response = await client.messages.create(
|
||||
{
|
||||
...llmRequestSettings(provider),
|
||||
model,
|
||||
temperature: 0,
|
||||
max_tokens: 16000,
|
||||
@@ -476,6 +488,7 @@ export async function createLlmAgent(opts = {}) {
|
||||
try {
|
||||
response = await client.messages.create(
|
||||
{
|
||||
...llmRequestSettings(provider),
|
||||
model,
|
||||
temperature: 0,
|
||||
max_tokens: 16000,
|
||||
@@ -605,11 +618,15 @@ export async function createLlmAgent(opts = {}) {
|
||||
].join('\n');
|
||||
|
||||
const response = await client.messages.create({
|
||||
...llmRequestSettings(provider),
|
||||
model,
|
||||
max_tokens: 4096,
|
||||
system: systemBlocks(STEER_SYSTEM_INSTRUCTIONS),
|
||||
messages: [{ role: 'user', content: userMessage }],
|
||||
});
|
||||
}, provider === 'deepseek' ? {
|
||||
maxRetries: LLM_REQUEST_MAX_RETRIES,
|
||||
timeout: MANUAL_EDIT_REQUEST_TIMEOUT_MS,
|
||||
} : {});
|
||||
|
||||
const cacheRead = response.usage?.cache_read_input_tokens ?? 0;
|
||||
const inputTokens = response.usage?.input_tokens ?? 0;
|
||||
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
clickEditCopy,
|
||||
clickExitLiveMode,
|
||||
clickGo,
|
||||
clickNext,
|
||||
clickPrev,
|
||||
cycleToVariant,
|
||||
clickSaveEdit,
|
||||
drawAnnotationPinAndStroke,
|
||||
editTextLeaf,
|
||||
@@ -40,6 +39,7 @@ import {
|
||||
waitForBarHidden,
|
||||
waitForCycling,
|
||||
waitForHandshake,
|
||||
waitForVariantSettled,
|
||||
} from './live-e2e/ui.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -281,7 +281,7 @@ async function runAnnotationGenerateFlow({ page, tmp, evidence }) {
|
||||
const generateEvent = latestJournalEvent(tmp, (event) => event.type === 'generate' && event.screenshotPath);
|
||||
await assertAnnotationUploadEvent(generateEvent);
|
||||
assert.ok(existsSync(generateEvent.screenshotPath), 'annotation screenshot file exists');
|
||||
await clickNext(page);
|
||||
await cycleTo(page, 2);
|
||||
await assertVariantCounter(page, 2, 3);
|
||||
await evidence.capture('annotation-cycle');
|
||||
await clickDiscard(page);
|
||||
@@ -514,14 +514,10 @@ async function clickPendingTrash(page) {
|
||||
}
|
||||
|
||||
async function cycleTo(page, target) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const visible = await getVisibleVariant(page);
|
||||
if (visible === target) return;
|
||||
if (visible == null) await page.waitForTimeout(250);
|
||||
else if (visible < target) await clickNext(page);
|
||||
else await clickPrev(page);
|
||||
}
|
||||
assert.equal(await getVisibleVariant(page), target, `variant ${target} visible`);
|
||||
// Component imports finish after the counter changes. Do not send the next
|
||||
// click (or reload) while the previous variant is still mounting.
|
||||
await cycleToVariant(page, target, 3);
|
||||
await waitForVariantSettled(page, target, 3);
|
||||
}
|
||||
|
||||
async function waitForVisibleCycling(page, count, { timeout }) {
|
||||
|
||||
Reference in New Issue
Block a user