@bastani/atomic 0.9.0-alpha.2 → 0.9.0-alpha.4
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/CHANGELOG.md +21 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +12 -12
- package/dist/builtin/workflows/builtin/goal-ledger.ts +2 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
- package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
- package/dist/builtin/workflows/builtin/goal-runner.ts +103 -4
- package/dist/builtin/workflows/builtin/goal-types.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.d.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.ts +14 -2
- package/dist/builtin/workflows/builtin/index.d.ts +8 -8
- package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
- package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
- package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
- package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
- package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +3 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +5 -5
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/docs/index.md +2 -2
- package/docs/quickstart.md +9 -9
- package/docs/workflows.md +816 -47
- package/package.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
- /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
|
@@ -15,8 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
import fs from 'node:fs';
|
|
17
17
|
import path from 'node:path';
|
|
18
|
-
import { isGeneratedFile } from './is-generated.mjs';
|
|
19
|
-
import { readBuffer as readManualEditsBuffer, writeBuffer as writeManualEditsBuffer } from './live
|
|
18
|
+
import { isGeneratedFile } from './lib/is-generated.mjs';
|
|
19
|
+
import { readBuffer as readManualEditsBuffer, writeBuffer as writeManualEditsBuffer } from './live/manual-edits-buffer.mjs';
|
|
20
|
+
import {
|
|
21
|
+
applyDeferredSvelteComponentAccepts,
|
|
22
|
+
findSvelteComponentManifest,
|
|
23
|
+
inlineSvelteComponentAccept,
|
|
24
|
+
removeSvelteComponentSession,
|
|
25
|
+
} from './live/svelte-component.mjs';
|
|
20
26
|
|
|
21
27
|
const EXTENSIONS = ['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro'];
|
|
22
28
|
|
|
@@ -41,6 +47,9 @@ Required:
|
|
|
41
47
|
|
|
42
48
|
Options:
|
|
43
49
|
--page-url URL Current browser page URL; scopes staged copy-edit cleanup
|
|
50
|
+
--defer-source-write
|
|
51
|
+
Deprecated compatibility flag. Svelte component accepts
|
|
52
|
+
now write the real source immediately.
|
|
44
53
|
|
|
45
54
|
Output (JSON):
|
|
46
55
|
{ handled, file, carbonize }`);
|
|
@@ -64,18 +73,67 @@ Output (JSON):
|
|
|
64
73
|
|
|
65
74
|
// Find the file containing this session's markers
|
|
66
75
|
const found = findSessionFile(id, process.cwd());
|
|
67
|
-
|
|
76
|
+
const svelteComponentManifest = found ? null : findSvelteComponentManifest(id, process.cwd());
|
|
77
|
+
|
|
78
|
+
if (!found && !svelteComponentManifest) {
|
|
68
79
|
console.log(JSON.stringify({ handled: false, error: 'Session markers not found for id: ' + id }));
|
|
69
80
|
process.exit(0);
|
|
70
81
|
}
|
|
71
82
|
|
|
83
|
+
if (svelteComponentManifest) {
|
|
84
|
+
if (isDiscard) {
|
|
85
|
+
removeSvelteComponentSession(id, process.cwd());
|
|
86
|
+
console.log(JSON.stringify({
|
|
87
|
+
handled: true,
|
|
88
|
+
file: svelteComponentManifest.sourceFile,
|
|
89
|
+
carbonize: false,
|
|
90
|
+
previewMode: 'svelte-component',
|
|
91
|
+
componentDir: svelteComponentManifest.componentDir,
|
|
92
|
+
}));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let result;
|
|
97
|
+
try {
|
|
98
|
+
result = inlineSvelteComponentAccept(
|
|
99
|
+
svelteComponentManifest,
|
|
100
|
+
variantNum,
|
|
101
|
+
paramValues,
|
|
102
|
+
process.cwd(),
|
|
103
|
+
);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
result = {
|
|
106
|
+
handled: false,
|
|
107
|
+
error: err.message,
|
|
108
|
+
file: svelteComponentManifest.sourceFile,
|
|
109
|
+
sourceFile: svelteComponentManifest.sourceFile,
|
|
110
|
+
previewMode: 'svelte-component',
|
|
111
|
+
componentDir: svelteComponentManifest.componentDir,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (result.carbonize) {
|
|
115
|
+
result.todo = 'REQUIRED before next poll: carbonize cleanup in ' + result.file + '. See reference/live.md "Required after accept".';
|
|
116
|
+
}
|
|
117
|
+
console.log(JSON.stringify({ handled: result.handled !== false, ...result }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
72
121
|
const { file: targetFile, content, lines } = found;
|
|
73
122
|
const relFile = path.relative(process.cwd(), targetFile);
|
|
123
|
+
const previewBlock = findMarkerBlock(id, lines);
|
|
124
|
+
const sourceShadowPreview = previewBlock
|
|
125
|
+
? readSourceShadowPreviewMeta(content, id)
|
|
126
|
+
: null;
|
|
127
|
+
|
|
128
|
+
if (sourceShadowPreview) {
|
|
129
|
+
console.log(JSON.stringify({
|
|
130
|
+
handled: false,
|
|
131
|
+
error: 'source_shadow_preview_deprecated',
|
|
132
|
+
hint: 'Svelte live mode now uses svelte-component injection. Re-wrap the element and regenerate variants.',
|
|
133
|
+
}));
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
74
136
|
|
|
75
|
-
// Bail if the session lives in a generated file. The agent manually wrote
|
|
76
|
-
// the wrapper there for preview, and is responsible for writing the
|
|
77
|
-
// accepted variant to true source (or cleaning up on discard). See
|
|
78
|
-
// "Handle fallback" in live.md.
|
|
79
137
|
if (isGeneratedFile(targetFile, { cwd: process.cwd() })) {
|
|
80
138
|
console.log(JSON.stringify({
|
|
81
139
|
handled: false,
|
|
@@ -207,6 +265,71 @@ function handleDiscard(id, lines, targetFile) {
|
|
|
207
265
|
// Accept
|
|
208
266
|
// ---------------------------------------------------------------------------
|
|
209
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Build carbonize stitch-in lines. JSX targets occupy a single child slot
|
|
270
|
+
* (ternary branch, return value, etc.) — the same constraint as live-wrap.
|
|
271
|
+
* When isJsx, tuck markers + <style> + variant wrapper inside one outer
|
|
272
|
+
* <div data-impeccable-carbonize> so the slot keeps a single root node.
|
|
273
|
+
*/
|
|
274
|
+
function buildCarbonizeReplacement({
|
|
275
|
+
indent,
|
|
276
|
+
commentSyntax,
|
|
277
|
+
isJsx,
|
|
278
|
+
id,
|
|
279
|
+
variantNum,
|
|
280
|
+
cssContent,
|
|
281
|
+
paramValues,
|
|
282
|
+
restored,
|
|
283
|
+
}) {
|
|
284
|
+
const lines = [];
|
|
285
|
+
if (!cssContent) {
|
|
286
|
+
lines.push(...restored);
|
|
287
|
+
return lines;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const variantStyleAttr = isJsx
|
|
291
|
+
? "style={{ display: 'contents' }}"
|
|
292
|
+
: 'style="display: contents"';
|
|
293
|
+
|
|
294
|
+
const pushCarbonizeBody = (bodyIndent) => {
|
|
295
|
+
const bodyRestored = reindentContent(restored, indent, bodyIndent + ' ');
|
|
296
|
+
lines.push(bodyIndent + commentSyntax.open + ' impeccable-carbonize-start ' + id + ' ' + commentSyntax.close);
|
|
297
|
+
lines.push(bodyIndent + '<style data-impeccable-css="' + id + '">' + (isJsx ? '{`' : ''));
|
|
298
|
+
for (const cssLine of cssContent) {
|
|
299
|
+
lines.push(bodyIndent + cssLine.trimStart());
|
|
300
|
+
}
|
|
301
|
+
lines.push(bodyIndent + (isJsx ? '`}</style>' : '</style>'));
|
|
302
|
+
if (paramValues && Object.keys(paramValues).length > 0) {
|
|
303
|
+
lines.push(
|
|
304
|
+
bodyIndent + commentSyntax.open + ' impeccable-param-values ' + id + ': ' + JSON.stringify(paramValues) + ' ' + commentSyntax.close,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
lines.push(bodyIndent + commentSyntax.open + ' impeccable-carbonize-end ' + id + ' ' + commentSyntax.close);
|
|
308
|
+
lines.push(bodyIndent + '<div data-impeccable-variant="' + variantNum + '" ' + variantStyleAttr + '>');
|
|
309
|
+
lines.push(...bodyRestored);
|
|
310
|
+
lines.push(bodyIndent + '</div>');
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (isJsx) {
|
|
314
|
+
const wrapperStyle = 'style={{ display: "contents" }}';
|
|
315
|
+
lines.push(indent + '<div data-impeccable-carbonize="' + id + '" ' + wrapperStyle + '>');
|
|
316
|
+
pushCarbonizeBody(indent + ' ');
|
|
317
|
+
lines.push(indent + '</div>');
|
|
318
|
+
} else {
|
|
319
|
+
pushCarbonizeBody(indent);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return lines;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function reindentContent(contentLines, fromIndent, toIndent) {
|
|
326
|
+
return contentLines.map((line) => {
|
|
327
|
+
if (line.trim() === '') return '';
|
|
328
|
+
if (line.startsWith(fromIndent)) return toIndent + line.slice(fromIndent.length);
|
|
329
|
+
return toIndent + line.trimStart();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
210
333
|
function handleAccept(id, variantNum, lines, targetFile, paramValues) {
|
|
211
334
|
const block = findMarkerBlock(id, lines);
|
|
212
335
|
if (!block) return { handled: false, error: 'Markers not found' };
|
|
@@ -235,45 +358,17 @@ function handleAccept(id, variantNum, lines, targetFile, paramValues) {
|
|
|
235
358
|
const hasHelperAttrs = variantText.includes('data-impeccable-variant');
|
|
236
359
|
const needsCarbonize = !!(cssContent || hasHelperAttrs);
|
|
237
360
|
|
|
238
|
-
// Build the replacement
|
|
239
361
|
const restored = deindentContent(variantContent, indent);
|
|
240
|
-
const replacement =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
replacement.push(indent + (isJsx ? '`}</style>' : '</style>'));
|
|
252
|
-
if (paramValues && Object.keys(paramValues).length > 0) {
|
|
253
|
-
// Preserve the user's knob positions for the carbonize-cleanup agent
|
|
254
|
-
// to bake into the final CSS when it collapses scoped rules.
|
|
255
|
-
replacement.push(indent + commentSyntax.open + ' impeccable-param-values ' + id + ': ' + JSON.stringify(paramValues) + ' ' + commentSyntax.close);
|
|
256
|
-
}
|
|
257
|
-
replacement.push(indent + commentSyntax.open + ' impeccable-carbonize-end ' + id + ' ' + commentSyntax.close);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Keep the `@scope ([data-impeccable-variant="N"])` selectors in the
|
|
261
|
-
// carbonize CSS block working visually by re-wrapping the accepted content
|
|
262
|
-
// in a data-impeccable-variant="N" div with `display: contents` (so layout
|
|
263
|
-
// isn't affected). The carbonize agent strips this attribute + wrapper when
|
|
264
|
-
// it moves the CSS to a proper stylesheet.
|
|
265
|
-
//
|
|
266
|
-
// Style attribute syntax has to follow the host file's flavor — JSX files
|
|
267
|
-
// need the object form, otherwise React 19 throws "Failed to set indexed
|
|
268
|
-
// property [0] on CSSStyleDeclaration" while parsing the string char-by-char.
|
|
269
|
-
if (cssContent) {
|
|
270
|
-
const styleAttr = isJsx ? "style={{ display: 'contents' }}" : 'style="display: contents"';
|
|
271
|
-
replacement.push(indent + '<div data-impeccable-variant="' + variantNum + '" ' + styleAttr + '>');
|
|
272
|
-
replacement.push(...restored);
|
|
273
|
-
replacement.push(indent + '</div>');
|
|
274
|
-
} else {
|
|
275
|
-
replacement.push(...restored);
|
|
276
|
-
}
|
|
362
|
+
const replacement = buildCarbonizeReplacement({
|
|
363
|
+
indent,
|
|
364
|
+
commentSyntax,
|
|
365
|
+
isJsx,
|
|
366
|
+
id,
|
|
367
|
+
variantNum,
|
|
368
|
+
cssContent,
|
|
369
|
+
paramValues,
|
|
370
|
+
restored,
|
|
371
|
+
});
|
|
277
372
|
|
|
278
373
|
const newLines = [
|
|
279
374
|
...lines.slice(0, replaceRange.start),
|
|
@@ -285,6 +380,34 @@ function handleAccept(id, variantNum, lines, targetFile, paramValues) {
|
|
|
285
380
|
return { carbonize: needsCarbonize, acceptedOriginalText: originalContent.join('\n') };
|
|
286
381
|
}
|
|
287
382
|
|
|
383
|
+
function readSourceShadowPreviewMeta(content, id) {
|
|
384
|
+
const escaped = escapeRegExp(id);
|
|
385
|
+
const wrapperRe = new RegExp('<[^>]+data-impeccable-variants=(["\'])' + escaped + '\\1[^>]*>');
|
|
386
|
+
const match = String(content || '').match(wrapperRe);
|
|
387
|
+
if (!match) return null;
|
|
388
|
+
const tag = match[0];
|
|
389
|
+
if (readHtmlAttr(tag, 'data-impeccable-preview') !== 'source-shadow') return null;
|
|
390
|
+
const sourceFile = readHtmlAttr(tag, 'data-impeccable-source-file');
|
|
391
|
+
const sourceStartLine = Number(readHtmlAttr(tag, 'data-impeccable-source-start'));
|
|
392
|
+
const sourceEndLine = Number(readHtmlAttr(tag, 'data-impeccable-source-end'));
|
|
393
|
+
if (!sourceFile || !Number.isFinite(sourceStartLine) || !Number.isFinite(sourceEndLine)) return null;
|
|
394
|
+
return { sourceFile, sourceStartLine, sourceEndLine };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function readHtmlAttr(tag, name) {
|
|
398
|
+
const match = String(tag || '').match(new RegExp('\\s' + escapeRegExp(name) + '\\s*=\\s*(["\'])(.*?)\\1'));
|
|
399
|
+
if (!match) return null;
|
|
400
|
+
return decodeHtmlAttr(match[2]);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function decodeHtmlAttr(value) {
|
|
404
|
+
return String(value || '')
|
|
405
|
+
.replace(/"/g, '"')
|
|
406
|
+
.replace(/</g, '<')
|
|
407
|
+
.replace(/>/g, '>')
|
|
408
|
+
.replace(/&/g, '&');
|
|
409
|
+
}
|
|
410
|
+
|
|
288
411
|
// ---------------------------------------------------------------------------
|
|
289
412
|
// Parsing helpers
|
|
290
413
|
// ---------------------------------------------------------------------------
|
|
@@ -405,16 +528,19 @@ function stripStyleAndJoin(lines, block) {
|
|
|
405
528
|
let line = lines[i];
|
|
406
529
|
|
|
407
530
|
if (!inStyle) {
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
531
|
+
// Strip any complete <style> elements on this line (self-closed or
|
|
532
|
+
// same-line-closed), including their body content.
|
|
533
|
+
line = line
|
|
534
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style[^>]*>/g, '')
|
|
535
|
+
.replace(/<style\b[^>]*\/\s*>/g, '');
|
|
536
|
+
|
|
537
|
+
// If a <style> opener remains (multi-line body starts here), strip from
|
|
538
|
+
// the opener to end-of-line and flip into skip mode.
|
|
539
|
+
const openerIdx = line.search(/<style\b/);
|
|
540
|
+
if (openerIdx !== -1) {
|
|
541
|
+
line = line.slice(0, openerIdx);
|
|
542
|
+
inStyle = true;
|
|
543
|
+
}
|
|
418
544
|
out.push(line);
|
|
419
545
|
} else {
|
|
420
546
|
// In multi-line style body; drop everything until we see </style>.
|
|
@@ -432,8 +558,7 @@ function stripStyleAndJoin(lines, block) {
|
|
|
432
558
|
/**
|
|
433
559
|
* Find the inner content of `<TAG ...attrMatch...>…</TAG>` inside `text`,
|
|
434
560
|
* handling nested same-tag elements via depth counting. `attrMatch` is a
|
|
435
|
-
* regex source fragment that must appear inside the opener tag
|
|
436
|
-
* pre-escape any dynamic (non-constant) portion with `escapeRegExp`.
|
|
561
|
+
* regex source fragment that must appear inside the opener tag.
|
|
437
562
|
* Returns the inner string (may be empty), or null if not found.
|
|
438
563
|
*/
|
|
439
564
|
function extractInnerByAttr(text, attrMatch) {
|
|
@@ -481,7 +606,7 @@ function extractOriginal(lines, block) {
|
|
|
481
606
|
*/
|
|
482
607
|
function extractVariant(lines, block, variantNum) {
|
|
483
608
|
const text = stripStyleAndJoin(lines, block);
|
|
484
|
-
const inner = extractInnerByAttr(text, 'data-impeccable-variant="' +
|
|
609
|
+
const inner = extractInnerByAttr(text, 'data-impeccable-variant="' + variantNum + '"');
|
|
485
610
|
if (inner === null) return null;
|
|
486
611
|
const result = inner.split('\n');
|
|
487
612
|
// Collapse a lone empty leading/trailing line (common after string splice).
|
|
@@ -512,7 +637,7 @@ function extractCss(lines, block, id) {
|
|
|
512
637
|
// Self-closing: nothing to carbonize.
|
|
513
638
|
if (/<style\b[^>]*\/\s*>/.test(line)) return null;
|
|
514
639
|
// Same-line open + close: extract inner text.
|
|
515
|
-
const sameLine = line.match(/<style\b[^>]*>([\s\S]*?)<\/style
|
|
640
|
+
const sameLine = line.match(/<style\b[^>]*>([\s\S]*?)<\/style[^>]*>/);
|
|
516
641
|
if (sameLine) {
|
|
517
642
|
const inner = stripJsxTemplateWrap(sameLine[1]);
|
|
518
643
|
return inner.length > 0 ? inner.split('\n') : null;
|
|
@@ -684,4 +809,4 @@ if (_running?.endsWith('live-accept.mjs') || _running?.endsWith('live-accept.mjs
|
|
|
684
809
|
acceptCli();
|
|
685
810
|
}
|
|
686
811
|
|
|
687
|
-
export { findMarkerBlock, extractOriginal, extractVariant, extractCss, deindentContent, detectCommentSyntax, scrubManualEditsAgainstFile, scrubManualEditsAgainstOriginalBlock };
|
|
812
|
+
export { findMarkerBlock, extractOriginal, extractVariant, extractCss, deindentContent, detectCommentSyntax, scrubManualEditsAgainstFile, scrubManualEditsAgainstOriginalBlock, applyDeferredSvelteComponentAccepts };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side DOM helpers for Impeccable live mode.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from live-browser.js so future browser script parts can share
|
|
5
|
+
* chrome mounting, lookup, focus, and picker helpers without depending on the
|
|
6
|
+
* full overlay UI bundle.
|
|
7
|
+
*/
|
|
8
|
+
(function (root) {
|
|
9
|
+
'use strict';
|
|
10
|
+
if (!root) return;
|
|
11
|
+
|
|
12
|
+
function createLiveBrowserDomHelpers({
|
|
13
|
+
prefix,
|
|
14
|
+
skipTags,
|
|
15
|
+
document: doc = root.document,
|
|
16
|
+
css = root.CSS,
|
|
17
|
+
crypto = root.crypto,
|
|
18
|
+
} = {}) {
|
|
19
|
+
if (!prefix) throw new Error('prefix required');
|
|
20
|
+
if (!doc) throw new Error('document required');
|
|
21
|
+
const tagsToSkip = skipTags || new Set();
|
|
22
|
+
|
|
23
|
+
function own(el) {
|
|
24
|
+
return el && (el.id?.startsWith(prefix) || el.closest?.('[id^="' + prefix + '"]'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pickable(el) {
|
|
28
|
+
if (!el || el.nodeType !== 1) return false;
|
|
29
|
+
if (tagsToSkip.has(String(el.tagName || '').toLowerCase())) return false;
|
|
30
|
+
if (own(el)) return false;
|
|
31
|
+
const r = el.getBoundingClientRect();
|
|
32
|
+
return r.width >= 20 && r.height >= 20;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function desc(el) {
|
|
36
|
+
if (!el) return '';
|
|
37
|
+
let s = el.tagName.toLowerCase();
|
|
38
|
+
if (el.id) s += '#' + el.id;
|
|
39
|
+
else if (el.classList.length) s += '.' + [...el.classList].slice(0, 2).join('.');
|
|
40
|
+
return s;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function rectIsUsableAnchor(rect) {
|
|
44
|
+
return !!rect && rect.width > 0.5 && rect.height > 0.5;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeFrozenAnchor(el) {
|
|
48
|
+
if (!el || !el.getBoundingClientRect) return null;
|
|
49
|
+
const r = el.getBoundingClientRect();
|
|
50
|
+
if (!rectIsUsableAnchor(r)) return null;
|
|
51
|
+
const rect = {
|
|
52
|
+
x: r.x, y: r.y,
|
|
53
|
+
top: r.top, left: r.left,
|
|
54
|
+
right: r.right, bottom: r.bottom,
|
|
55
|
+
width: r.width, height: r.height,
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
__impeccableFrozenAnchor: true,
|
|
59
|
+
tagName: el.tagName || 'DIV',
|
|
60
|
+
id: el.id || '',
|
|
61
|
+
classList: el.classList ? [...el.classList] : [],
|
|
62
|
+
hasAttribute: () => false,
|
|
63
|
+
getBoundingClientRect: () => rect,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function id8() {
|
|
68
|
+
if (crypto?.randomUUID) return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
|
|
69
|
+
return (Math.random().toString(16).slice(2) + Date.now().toString(16)).slice(0, 8);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cssId(id) {
|
|
73
|
+
if (css?.escape) return css.escape(id);
|
|
74
|
+
return String(id).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function liveUiRoot() {
|
|
78
|
+
const uiRoot = root.__IMPECCABLE_LIVE_UI_ROOT__;
|
|
79
|
+
if (uiRoot && typeof uiRoot.appendChild === 'function') return uiRoot;
|
|
80
|
+
return doc.body;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function uiAppend(el) {
|
|
84
|
+
liveUiRoot().appendChild(el);
|
|
85
|
+
return el;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function uiAppendStyle(styleEl) {
|
|
89
|
+
const uiRoot = liveUiRoot();
|
|
90
|
+
if (uiRoot && uiRoot !== doc.body) uiRoot.appendChild(styleEl);
|
|
91
|
+
else doc.head.appendChild(styleEl);
|
|
92
|
+
return styleEl;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function uiGetById(id) {
|
|
96
|
+
const uiRoot = liveUiRoot();
|
|
97
|
+
if (uiRoot?.getElementById) {
|
|
98
|
+
const found = uiRoot.getElementById(id);
|
|
99
|
+
if (found) return found;
|
|
100
|
+
}
|
|
101
|
+
if (uiRoot?.querySelector) {
|
|
102
|
+
const found = uiRoot.querySelector('#' + cssId(id));
|
|
103
|
+
if (found) return found;
|
|
104
|
+
}
|
|
105
|
+
return doc.getElementById(id);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function activeElementDeep() {
|
|
109
|
+
let active = doc.activeElement;
|
|
110
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
111
|
+
return active;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function defangOutsideHandlers(rootEl, { setPointerEvents = true } = {}) {
|
|
115
|
+
if (!rootEl) return;
|
|
116
|
+
if (setPointerEvents) {
|
|
117
|
+
rootEl.style.setProperty('pointer-events', 'auto', 'important');
|
|
118
|
+
}
|
|
119
|
+
const stop = (e) => e.stopPropagation();
|
|
120
|
+
rootEl.addEventListener('pointerdown', stop);
|
|
121
|
+
rootEl.addEventListener('mousedown', stop);
|
|
122
|
+
rootEl.addEventListener('focusin', stop);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
own,
|
|
127
|
+
pickable,
|
|
128
|
+
desc,
|
|
129
|
+
rectIsUsableAnchor,
|
|
130
|
+
makeFrozenAnchor,
|
|
131
|
+
id8,
|
|
132
|
+
cssId,
|
|
133
|
+
liveUiRoot,
|
|
134
|
+
uiAppend,
|
|
135
|
+
uiAppendStyle,
|
|
136
|
+
uiGetById,
|
|
137
|
+
activeElementDeep,
|
|
138
|
+
defangOutsideHandlers,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
root.__IMPECCABLE_LIVE_DOM__ = {
|
|
143
|
+
version: 1,
|
|
144
|
+
createLiveBrowserDomHelpers,
|
|
145
|
+
};
|
|
146
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|