@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +24 -0
  8. package/dist/builtin/workflows/README.md +12 -12
  9. package/dist/builtin/workflows/builtin/goal-ledger.ts +2 -0
  10. package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
  11. package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
  12. package/dist/builtin/workflows/builtin/goal-runner.ts +103 -4
  13. package/dist/builtin/workflows/builtin/goal-types.ts +4 -0
  14. package/dist/builtin/workflows/builtin/goal.d.ts +4 -0
  15. package/dist/builtin/workflows/builtin/goal.ts +14 -2
  16. package/dist/builtin/workflows/builtin/index.d.ts +8 -8
  17. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
  18. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
  19. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
  20. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
  21. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
  22. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
  23. package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
  24. package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
  25. package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
  26. package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
  27. package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
  28. package/dist/builtin/workflows/builtin/ralph.ts +3 -1
  29. package/dist/builtin/workflows/package.json +1 -1
  30. package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
  31. package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
  32. package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
  33. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
  34. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
  35. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
  36. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
  37. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
  38. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
  39. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
  40. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  41. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  42. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
  43. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  44. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  45. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
  46. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  47. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
  48. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  49. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
  50. package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  51. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  52. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  53. package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
  54. package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  55. package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  56. package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
  57. package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  58. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  59. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  60. package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  61. package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
  62. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
  63. package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  64. package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  65. package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  66. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
  67. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
  68. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
  69. package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  70. package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
  71. package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  72. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
  73. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
  74. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
  75. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
  76. package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
  77. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
  78. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
  79. package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
  80. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
  81. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
  82. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
  83. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  84. package/dist/core/atomic-guide-command.js +5 -5
  85. package/dist/core/atomic-guide-command.js.map +1 -1
  86. package/dist/core/system-prompt.d.ts.map +1 -1
  87. package/dist/core/system-prompt.js +0 -1
  88. package/dist/core/system-prompt.js.map +1 -1
  89. package/docs/index.md +2 -2
  90. package/docs/quickstart.md +9 -9
  91. package/docs/workflows.md +816 -47
  92. package/package.json +2 -2
  93. package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  94. package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
  95. /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-manual-edits-buffer.mjs';
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
- if (!found) {
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
- if (cssContent) {
243
- replacement.push(indent + commentSyntax.open + ' impeccable-carbonize-start ' + id + ' ' + commentSyntax.close);
244
- // JSX targets need the CSS body wrapped in a template literal so that the
245
- // `{` and `}` in CSS rules don't get parsed as JSX expressions.
246
- replacement.push(indent + '<style data-impeccable-css="' + id + '">' + (isJsx ? '{`' : ''));
247
- // Re-indent CSS content to match
248
- for (const cssLine of cssContent) {
249
- replacement.push(indent + cssLine.trimStart());
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(/&quot;/g, '"')
406
+ .replace(/&lt;/g, '<')
407
+ .replace(/&gt;/g, '>')
408
+ .replace(/&amp;/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
- // An unclosed <style> opener (more non-self-closed openers than same-line
409
- // closers) starts a multi-line body that continues on following lines.
410
- const openers = (line.match(/<style\b/gi) || []).length;
411
- const selfClosed = (line.match(/<style\b[^>]*\/\s*>/gi) || []).length;
412
- const closers = (line.match(/<\/style\b/gi) || []).length;
413
- if (openers - selfClosed > closers) inStyle = true;
414
- // Remove every <style> element on this line in a single pass — self-closed
415
- // <style/>, same-line <style>…</style>, or an unclosed opener through end
416
- // of line — so the result provably cannot retain a <style fragment.
417
- line = line.replace(/<style\b(?:[^>]*\/>|[^>]*>[\s\S]*?(?:<\/style[^>]*>|$))/gi, '');
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; callers must
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="' + escapeRegExp(variantNum) + '"');
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\s*>/);
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);