quiver-cli 1.3.0 → 1.3.1

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 (120) hide show
  1. package/package.json +1 -1
  2. package/template/.agents/skills/agent-browser/SKILL.md +1 -0
  3. package/template/.agents/skills/apps/skybridge/SKILL.md +2 -0
  4. package/template/.agents/skills/data/prisma-cli/SKILL.md +25 -7
  5. package/template/.agents/skills/data/prisma-cli/references/agent-safety.md +27 -0
  6. package/template/.agents/skills/data/prisma-cli/references/complete.md +22 -0
  7. package/template/.agents/skills/data/prisma-cli/references/db-push.md +2 -0
  8. package/template/.agents/skills/data/prisma-cli/references/init.md +3 -0
  9. package/template/.agents/skills/data/prisma-cli/references/mcp.md +2 -1
  10. package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +1 -1
  11. package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +2 -0
  12. package/template/.agents/skills/data/prisma-client-api/SKILL.md +1 -1
  13. package/template/.agents/skills/data/prisma-client-api/references/constructor.md +13 -0
  14. package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +4 -0
  15. package/template/.agents/skills/design/impeccable/SKILL.md +5 -5
  16. package/template/.agents/skills/design/impeccable/reference/android.md +6 -0
  17. package/template/.agents/skills/design/impeccable/reference/animate.md +3 -0
  18. package/template/.agents/skills/design/impeccable/reference/bolder.md +3 -1
  19. package/template/.agents/skills/design/impeccable/reference/craft-floor.md +2 -0
  20. package/template/.agents/skills/design/impeccable/reference/critique.md +23 -5
  21. package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +15 -68
  22. package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +13 -12
  23. package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
  24. package/template/.agents/skills/design/impeccable/reference/doctor.md +1 -0
  25. package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
  26. package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
  27. package/template/.agents/skills/design/impeccable/reference/hooks.md +17 -11
  28. package/template/.agents/skills/design/impeccable/reference/init.md +9 -3
  29. package/template/.agents/skills/design/impeccable/reference/ios.md +6 -0
  30. package/template/.agents/skills/design/impeccable/reference/new-work.md +69 -29
  31. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
  32. package/template/.agents/skills/design/impeccable/reference/polish.md +13 -5
  33. package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
  34. package/template/.agents/skills/design/impeccable/reference/routing.md +1 -1
  35. package/template/.agents/skills/design/impeccable/reference/visualize.md +21 -22
  36. package/template/.agents/skills/design/impeccable/scripts/build-phase.mjs +1022 -0
  37. package/template/.agents/skills/design/impeccable/scripts/comp-diff.mjs +391 -0
  38. package/template/.agents/skills/design/impeccable/scripts/comp-spec.mjs +513 -0
  39. package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +297 -41
  40. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +10 -19
  41. package/template/.agents/skills/design/impeccable/scripts/context.mjs +124 -9
  42. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +279 -19
  43. package/template/.agents/skills/design/impeccable/scripts/data/font-index-failures.json +121 -0
  44. package/template/.agents/skills/design/impeccable/scripts/data/font-index.json +1 -0
  45. package/template/.agents/skills/design/impeccable/scripts/detect.mjs +9 -0
  46. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +192 -11
  47. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +10 -16
  48. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +339 -11
  49. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +1482 -722
  50. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +1 -0
  51. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +64 -2
  52. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +580 -29
  53. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +62 -7
  54. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +31 -7
  55. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +23 -22
  56. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +18 -0
  57. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +471 -370
  58. package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +474 -2
  59. package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +17 -2
  60. package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +14 -21
  61. package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +81 -48
  62. package/template/.agents/skills/design/impeccable/scripts/font-match.mjs +457 -0
  63. package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +219 -12
  64. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +93 -15
  65. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +41 -19
  66. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +507 -117
  67. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +10 -9
  68. package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +40 -1
  69. package/template/.agents/skills/design/impeccable/scripts/lib/design-parser.mjs +120 -82
  70. package/template/.agents/skills/design/impeccable/scripts/lib/font-fingerprint.mjs +564 -0
  71. package/template/.agents/skills/design/impeccable/scripts/lib/font-index.mjs +130 -0
  72. package/template/.agents/skills/design/impeccable/scripts/lib/hero-checks.mjs +246 -0
  73. package/template/.agents/skills/design/impeccable/scripts/lib/image-metrics.mjs +306 -0
  74. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +41 -59
  75. package/template/.agents/skills/design/impeccable/scripts/lib/is-generated.mjs +5 -2
  76. package/template/.agents/skills/design/impeccable/scripts/lib/live-path-globs.mjs +37 -0
  77. package/template/.agents/skills/design/impeccable/scripts/lib/open-system-browser.mjs +26 -0
  78. package/template/.agents/skills/design/impeccable/scripts/lib/png.mjs +281 -0
  79. package/template/.agents/skills/design/impeccable/scripts/lib/raster.mjs +194 -0
  80. package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +26 -19
  81. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +31 -3
  82. package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +93 -17
  83. package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +9 -11
  84. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +31 -2
  85. package/template/.agents/skills/design/impeccable/scripts/live/project-ignores.mjs +139 -0
  86. package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +10 -2
  87. package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +26 -2
  88. package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +15 -27
  89. package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +4 -25
  90. package/template/.agents/skills/design/impeccable/scripts/live/ui-surfaces.mjs +75 -0
  91. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +21 -37
  92. package/template/.agents/skills/design/impeccable/scripts/live-browser-ignores.js +242 -0
  93. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +62 -23
  94. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +68 -112
  95. package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +132 -15
  96. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +2 -42
  97. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +5 -4
  98. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +56 -19
  99. package/template/.agents/skills/design/impeccable/scripts/live.mjs +12 -37
  100. package/template/.agents/skills/design/impeccable/scripts/pin.mjs +8 -5
  101. package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +1006 -155
  102. package/template/.agents/skills/design/shadcn/rules/chat.md +26 -0
  103. package/template/.agents/skills/hono/SKILL.md +17 -3
  104. package/template/.agents/skills/integrations/langfuse/SKILL.md +8 -4
  105. package/template/.agents/skills/integrations/langfuse/references/cli.md +1 -1
  106. package/template/.agents/skills/integrations/langfuse/references/create-dataset.md +35 -0
  107. package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +9 -10
  108. package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +8 -6
  109. package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +34 -0
  110. package/template/.agents/skills/integrations/langfuse/references/setting-up-evals.md +65 -0
  111. package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +20 -40
  112. package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -30
  113. package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +3 -1
  114. package/template/.agents/skills/supabase/CHANGELOG.md +7 -0
  115. package/template/.agents/skills/supabase/SKILL.md +5 -1
  116. package/template/.agents/skills/writing/humanizer/LICENSE +21 -0
  117. package/template/.agents/skills/writing/humanizer/README.md +209 -0
  118. package/template/.agents/skills/writing/humanizer/SKILL.md +173 -338
  119. package/template/.agents/upstreams.json +21 -22
  120. package/template/.agents/skills/design/impeccable/scripts/live/ui-core.mjs +0 -180
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Canonical inventory of the Live overlay's UI surfaces: one entry per piece of
3
+ * chrome Live mounts on the user's page, with the element ids that make it up.
4
+ *
5
+ * Single source of truth, consumed by:
6
+ * - skill/scripts/live/browser-script-parts.mjs — serializes this into
7
+ * window.__IMPECCABLE_LIVE_UI_SURFACES__ in the /live.js prelude.
8
+ * - skill/scripts/live-browser.js — publishes it on
9
+ * window.__IMPECCABLE_LIVE_CHROME_CORE__ for adapters and E2E probes. That
10
+ * file is served raw and injected as a classic <script>, so it cannot
11
+ * import this module at runtime; it reads the injected global instead, the
12
+ * same path live/vocabulary.mjs already takes for the command palette.
13
+ * - the private impeccable-site repo — site/components/LiveUiGallery.astro
14
+ * and tests/live-ui-lab.test.mjs import LIVE_UI_SURFACES at build time and
15
+ * fail the site build when the Live UI lab has no snapshot for a surface
16
+ * defined here. That guard only guards if it reads this list rather than a
17
+ * copy the site keeps, so this module must stay importable from Node.
18
+ * Renaming a key or the module is a breaking change for that build; the
19
+ * list was briefly inlined into live-browser.js and the site had to parse
20
+ * it back out with a regex.
21
+ *
22
+ * Add a surface here and both the browser bundle and the site lab follow.
23
+ */
24
+
25
+ /** Id prefix every Live chrome element carries. Mirrored by PREFIX in live-browser.js. */
26
+ export const LIVE_UI_PREFIX = 'impeccable-live';
27
+
28
+ const id = (suffix) => `${LIVE_UI_PREFIX}-${suffix}`;
29
+
30
+ /**
31
+ * The mount contract every Live chrome adapter (DOM, Svelte, ...) satisfies.
32
+ * Published alongside the surfaces on __IMPECCABLE_LIVE_CHROME_CORE__.
33
+ */
34
+ export const LIVE_CHROME_MOUNT_CONTRACT = Object.freeze(['root', 'transport', 'state', 'actions']);
35
+
36
+ export const LIVE_UI_SURFACES = Object.freeze([
37
+ {
38
+ key: 'global-bottom-bar',
39
+ ids: [
40
+ id('global-bar'), id('global-bar-brand'), id('pick-toggle'), id('insert-toggle'),
41
+ id('detect-toggle'), id('detect-badge'), id('design-toggle'), id('page-chat'),
42
+ id('page-chat-input'), id('page-chat-voice'), id('page-chat-send'),
43
+ ],
44
+ },
45
+ { key: 'pending-copy-edit-dock', ids: [id('pending-dock')] },
46
+ {
47
+ key: 'element-selection-chrome',
48
+ ids: [
49
+ id('highlight'), id('tooltip'), id('bar'), id('selection-pill'), id('input'),
50
+ id('configure-voice'), id('configure-bar-tooltip'),
51
+ ],
52
+ },
53
+ { key: 'action-picker', ids: [id('picker')] },
54
+ { key: 'edit-chrome', ids: [id('edit-badge')] },
55
+ { key: 'generating-row', ids: [id('bar'), id('shader')] },
56
+ { key: 'variant-cycling-row', ids: [id('bar'), id('params-panel')] },
57
+ { key: 'variant-params-panel', ids: [id('params-panel')] },
58
+ { key: 'saving-confirmed-rows', ids: [id('bar')] },
59
+ {
60
+ key: 'insert-mode-chrome',
61
+ ids: [
62
+ id('insert-line'), id('insert-placeholder'), id('placeholder-resize'), id('insert-input'),
63
+ id('insert-voice'), id('insert-create'), id('insert-create-tooltip'),
64
+ ],
65
+ },
66
+ { key: 'annotation-chrome', ids: [id('annot'), id('annot-svg'), id('annot-pins'), id('annot-clear')] },
67
+ { key: 'design-system-panel', ids: [id('design-host')] },
68
+ { key: 'toasts-and-errors', ids: [id('toast'), id('mount-error')] },
69
+ { key: 'css-isolation-boundary', ids: [id('root')] },
70
+ ].map((surface) => Object.freeze({ ...surface, ids: Object.freeze(surface.ids) })));
71
+
72
+ /** Every id any surface owns, de-duplicated, in surface order. */
73
+ export const LIVE_UI_COMPONENT_IDS = Object.freeze([
74
+ ...new Set(LIVE_UI_SURFACES.flatMap((surface) => surface.ids)),
75
+ ]);
@@ -170,51 +170,35 @@ Output (JSON):
170
170
  }
171
171
 
172
172
  if (svelteComponentManifest) {
173
- if (isDiscard) {
174
- let result;
175
- try {
176
- result = withSourceLockSync(
177
- path.resolve(process.cwd(), svelteComponentManifest.sourceFile),
178
- 'discard:' + id,
179
- () => {
180
- removeSvelteComponentSession(id, process.cwd());
181
- return { handled: true };
182
- },
183
- { waitMs: ACCEPT_LOCK_WAIT_MS },
173
+ const { sourceFile, componentDir } = svelteComponentManifest;
174
+ const resultContext = {
175
+ file: sourceFile,
176
+ ...(isDiscard ? { carbonize: false } : { sourceFile }),
177
+ previewMode: 'svelte-component',
178
+ componentDir,
179
+ };
180
+ const runOperation = isDiscard
181
+ ? () => {
182
+ removeSvelteComponentSession(id, process.cwd());
183
+ return { handled: true, ...resultContext };
184
+ }
185
+ : () => inlineSvelteComponentAccept(
186
+ svelteComponentManifest,
187
+ variantNum,
188
+ paramValues,
189
+ process.cwd(),
184
190
  );
185
- } catch (err) {
186
- result = operationFailure(err);
187
- }
188
- emitResult({
189
- ...result,
190
- file: svelteComponentManifest.sourceFile,
191
- carbonize: false,
192
- previewMode: 'svelte-component',
193
- componentDir: svelteComponentManifest.componentDir,
194
- });
195
- return;
196
- }
197
191
 
198
192
  let result;
199
193
  try {
200
194
  result = withSourceLockSync(
201
- path.resolve(process.cwd(), svelteComponentManifest.sourceFile),
202
- 'accept:' + id,
203
- () => inlineSvelteComponentAccept(
204
- svelteComponentManifest,
205
- variantNum,
206
- paramValues,
207
- process.cwd(),
208
- ),
195
+ path.resolve(process.cwd(), sourceFile),
196
+ requestedOperation + ':' + id,
197
+ runOperation,
209
198
  { waitMs: ACCEPT_LOCK_WAIT_MS },
210
199
  );
211
200
  } catch (err) {
212
- result = operationFailure(err, {
213
- file: svelteComponentManifest.sourceFile,
214
- sourceFile: svelteComponentManifest.sourceFile,
215
- previewMode: 'svelte-component',
216
- componentDir: svelteComponentManifest.componentDir,
217
- });
201
+ result = operationFailure(err, resultContext);
218
202
  }
219
203
  if (result.carbonize) {
220
204
  result.todo = 'REQUIRED before next poll: carbonize cleanup in ' + result.file + '. See reference/live.md "Required after accept".';
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Browser-side resolution of project detector waivers for Impeccable live mode.
3
+ *
4
+ * The live server serializes `.impeccable/config.json` + `config.local.json`
5
+ * detector ignores (plus the served-root prefixes from the inject config's
6
+ * `files` globs) into `window.__IMPECCABLE_PROJECT_IGNORES__`. This part
7
+ * resolves that config against the current page's URL path when a detect scan
8
+ * starts, so the overlay suppresses the same findings the CLI and the edit
9
+ * hook do (issue #639).
10
+ *
11
+ * Mirrors filterDetectionFindings in cli/lib/impeccable-config.mjs:
12
+ * 1. `ignoreRules` suppress a rule project-wide.
13
+ * 2. `ignoreValues` entries with `value: "*"` suppress their rule in the
14
+ * files their globs name. The CLI never applies an unscoped wildcard
15
+ * (isIgnoredFindingValue returns false for it), so neither does this.
16
+ * 3. Remaining `ignoreValues` entries match on the finding's own value;
17
+ * those are forwarded as `disabledValues` for the detector bundle to
18
+ * apply where the findings are assembled.
19
+ * 4. `ignoreFiles` globs that name the page waive it wholesale: the
20
+ * resolver reports `skipScan: true` and the detector answers the scan
21
+ * with zero findings, mirroring shouldIgnoreDetectionFile in the CLI
22
+ * and the edit hook's own ignoreFiles gate.
23
+ *
24
+ * `pageFiles`, when the server could resolve it, lists the real project
25
+ * files the inject config serves. A URL that suffix-matches exactly one of
26
+ * them takes that file as its only project identity; an ambiguous or absent
27
+ * match falls back to the served-root common ancestor below.
28
+ *
29
+ * Known gap, unchanged from PR #645: framework apps inject into source files
30
+ * (src/routes/about/+page.svelte) while scans see route URLs (/about), so
31
+ * entries scoped to source or asset paths never match a page candidate and
32
+ * are dropped. That shows the finding, which is the conservative direction.
33
+ *
34
+ * Kept separate from live-browser.js so the glob and page-scope logic can be
35
+ * unit tested in Node (tests/live-browser-ignores.test.mjs) without the full
36
+ * overlay UI bundle.
37
+ */
38
+ (function (root) {
39
+ 'use strict';
40
+ if (!root) return;
41
+
42
+ // Keep in step with normalizeIgnoreRule / normalizeIgnoreValue in
43
+ // cli/lib/impeccable-config.mjs.
44
+ function normalizeIgnoreRule(rule) {
45
+ return String(rule || '').trim().toLowerCase();
46
+ }
47
+
48
+ function normalizeIgnoreValue(value) {
49
+ return String(value || '')
50
+ .trim()
51
+ .replace(/^["']|["']$/g, '')
52
+ .replace(/\+/g, ' ')
53
+ .replace(/\s+/g, ' ')
54
+ .toLowerCase();
55
+ }
56
+
57
+ // Glob -> RegExp. Supports `**`, `*`, `?`, and `{a,b}` alternation.
58
+ // Keep in step with globToRegex in cli/lib/impeccable-config.mjs.
59
+ function globToRegex(glob) {
60
+ let re = '^';
61
+ let i = 0;
62
+ while (i < glob.length) {
63
+ const c = glob[i];
64
+ if (c === '*') {
65
+ if (glob[i + 1] === '*') {
66
+ re += '.*';
67
+ i += 2;
68
+ if (glob[i] === '/') i += 1;
69
+ } else {
70
+ re += '[^/]*';
71
+ i += 1;
72
+ }
73
+ } else if (c === '?') {
74
+ re += '[^/]';
75
+ i += 1;
76
+ } else if (c === '{') {
77
+ const end = glob.indexOf('}', i);
78
+ if (end === -1) { re += '\\{'; i += 1; continue; }
79
+ const parts = glob.slice(i + 1, end).split(',').map((p) => p.replace(/[.+^$()|[\]\\]/g, '\\$&'));
80
+ re += `(?:${parts.join('|')})`;
81
+ i = end + 1;
82
+ } else if (/[.+^$()|[\]\\]/.test(c)) {
83
+ re += `\\${c}`;
84
+ i += 1;
85
+ } else {
86
+ re += c;
87
+ i += 1;
88
+ }
89
+ }
90
+ re += '$';
91
+ return new RegExp(re);
92
+ }
93
+
94
+ // The project-relative paths this page could be known as. Ignore globs are
95
+ // project-relative (prototype/foo.html) and the URL is site-relative
96
+ // (/foo.html), because a static server's root usually sits inside the
97
+ // project; `roots` carries that prefix. The server reads it from the inject
98
+ // config's own `files` globs, which already state where the served pages
99
+ // are. Do not derive it from the ignore globs: a single entry scoped to
100
+ // prototype/library/** would then lend prototype/library/ as a candidate
101
+ // prefix to every page, and that rule would suppress site-wide.
102
+ //
103
+ // Each prefixed path also contributes its slash suffixes, mirroring
104
+ // findingMatchesScopedIgnoreFile in cli/lib/impeccable-config.mjs (which
105
+ // matches globs against every path suffix of the finding's file).
106
+ //
107
+ // One live session is served by one server, so a single document root must
108
+ // sit at or above every configured page. The only prefix that can safely
109
+ // be asserted is therefore the deepest common ancestor of the glob roots.
110
+ // Treating each glob's own prefix as an identity goes wrong in both
111
+ // directions: disjoint roots (src/ and public/) invent simultaneous
112
+ // identities for one URL, so a waiver scoped to src/foo.html hides a
113
+ // finding on a page served from public/foo.html; nested roots (prototype/
114
+ // and prototype/library/, from globs at two depths in one tree) are not
115
+ // alternatives at all, and demanding a waiver match under both stops
116
+ // prototype/index.html from applying anywhere. When the globs share no
117
+ // common root, no prefix is asserted and only the URL path itself matches.
118
+ function pageCandidates(pathname, roots, pageFiles) {
119
+ let pagePath = String(pathname || '');
120
+ try {
121
+ pagePath = decodeURIComponent(pagePath);
122
+ } catch {
123
+ // Malformed percent-escape: match on the raw path rather than throwing.
124
+ }
125
+ pagePath = pagePath.replace(/^\/+/, '');
126
+ // A directory URL serves that directory's index, and the ignore globs
127
+ // name files. Without this, /news/ never matches prototype/news/index.html.
128
+ if (pagePath === '' || pagePath.endsWith('/')) pagePath += 'index.html';
129
+
130
+ const candidates = new Set();
131
+ const addSuffixes = (fullPath) => {
132
+ const parts = fullPath.split('/').filter(Boolean);
133
+ for (let i = 0; i < parts.length; i++) {
134
+ candidates.add(parts.slice(i).join('/'));
135
+ }
136
+ };
137
+ addSuffixes(pagePath);
138
+
139
+ // The served page list names the real files the inject config serves.
140
+ // A URL that suffix-matches exactly one of them has an unambiguous
141
+ // project identity; assert that identity and stop guessing from roots
142
+ // (PR #645 review: with src/ and public/ both served, /foo.html must not
143
+ // borrow src/foo.html's waivers while actually serving public/foo.html).
144
+ // Zero matches or several fall through to the common-ancestor fallback:
145
+ // ambiguity resolves toward showing the finding.
146
+ const knownPages = [];
147
+ for (const entry of Array.isArray(pageFiles) ? pageFiles : []) {
148
+ if (typeof entry !== 'string' || !entry) continue;
149
+ if (entry === pagePath || entry.endsWith('/' + pagePath)) knownPages.push(entry);
150
+ }
151
+ if (knownPages.length === 1) {
152
+ addSuffixes(knownPages[0]);
153
+ return [...candidates];
154
+ }
155
+
156
+ const prefixes = [];
157
+ for (const entry of Array.isArray(roots) ? roots : []) {
158
+ if (typeof entry !== 'string') continue;
159
+ prefixes.push(entry.split('/').filter(Boolean));
160
+ }
161
+ let common = prefixes.length > 0 ? prefixes[0] : [];
162
+ for (const segments of prefixes.slice(1)) {
163
+ let i = 0;
164
+ while (i < common.length && i < segments.length && common[i] === segments[i]) i += 1;
165
+ common = common.slice(0, i);
166
+ }
167
+
168
+ if (common.length > 0) addSuffixes(common.join('/') + '/' + pagePath);
169
+ return [...candidates];
170
+ }
171
+
172
+ function matchesScope(globs, candidates) {
173
+ return globs.some((glob) => {
174
+ let re;
175
+ try {
176
+ re = globToRegex(String(glob));
177
+ } catch {
178
+ // Malformed glob: skip it, as matchesAnyGlob does in the CLI.
179
+ return false;
180
+ }
181
+ return candidates.some((candidate) => re.test(candidate));
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Resolve the serialized project ignores for one page.
187
+ *
188
+ * @param {object} options
189
+ * @param {object} options.ignores window.__IMPECCABLE_PROJECT_IGNORES__,
190
+ * in whatever state it arrived: absent, null, or hand-edited into the
191
+ * wrong shape. Every read tolerates that and degrades to no filtering.
192
+ * @param {string} options.pathname location.pathname of the scanned page.
193
+ * @returns {{ disabledRules: string[], disabledValues: Array<{rule: string, value: string}>, skipScan: boolean }}
194
+ */
195
+ function resolveDetectIgnores({ ignores, pathname } = {}) {
196
+ const config = ignores && typeof ignores === 'object' ? ignores : {};
197
+ const asArray = (value) => (Array.isArray(value) ? value : []);
198
+ const candidates = pageCandidates(pathname, config.roots, config.pageFiles);
199
+
200
+ // detector.ignoreFiles waives whole files. When any glob names this
201
+ // page, the scan itself is skipped; rule and value lists are returned
202
+ // empty because nothing will run.
203
+ const ignoreFileGlobs = asArray(config.ignoreFiles)
204
+ .filter((glob) => typeof glob === 'string' && glob.trim());
205
+ if (ignoreFileGlobs.length > 0 && matchesScope(ignoreFileGlobs, candidates)) {
206
+ return { disabledRules: [], disabledValues: [], skipScan: true };
207
+ }
208
+
209
+ const disabledRules = new Set(
210
+ asArray(config.ignoreRules)
211
+ .filter((rule) => typeof rule === 'string')
212
+ .map(normalizeIgnoreRule)
213
+ .filter(Boolean),
214
+ );
215
+ const disabledValues = [];
216
+
217
+ for (const entry of asArray(config.ignoreValues)) {
218
+ if (!entry || typeof entry !== 'object') continue;
219
+ const rule = normalizeIgnoreRule(entry.rule);
220
+ const value = normalizeIgnoreValue(entry.value);
221
+ if (!rule || !value) continue;
222
+ const files = [
223
+ ...(typeof entry.file === 'string' && entry.file.trim() ? [entry.file.trim()] : []),
224
+ ...asArray(entry.files).filter((glob) => typeof glob === 'string' && glob.trim()),
225
+ ];
226
+ if (value === '*') {
227
+ // Wildcards suppress their rule only inside the files they name.
228
+ if (files.length > 0 && matchesScope(files, candidates)) disabledRules.add(rule);
229
+ continue;
230
+ }
231
+ if (files.length > 0 && !matchesScope(files, candidates)) continue;
232
+ disabledValues.push({ rule, value });
233
+ }
234
+
235
+ return { disabledRules: [...disabledRules], disabledValues, skipScan: false };
236
+ }
237
+
238
+ root.__IMPECCABLE_LIVE_IGNORES__ = {
239
+ version: 1,
240
+ resolveDetectIgnores,
241
+ };
242
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -97,23 +97,20 @@
97
97
  return { value: c.value, label: c.label };
98
98
  });
99
99
 
100
- const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions'];
101
- const LIVE_UI_SURFACES = [
102
- { key: 'global-bottom-bar', ids: [PREFIX + '-global-bar', PREFIX + '-global-bar-brand', PREFIX + '-pick-toggle', PREFIX + '-insert-toggle', PREFIX + '-detect-toggle', PREFIX + '-detect-badge', PREFIX + '-design-toggle', PREFIX + '-page-chat', PREFIX + '-page-chat-input', PREFIX + '-page-chat-voice', PREFIX + '-page-chat-send'] },
103
- { key: 'pending-copy-edit-dock', ids: [PREFIX + '-pending-dock'] },
104
- { key: 'element-selection-chrome', ids: [PREFIX + '-highlight', PREFIX + '-tooltip', PREFIX + '-bar', PREFIX + '-selection-pill', PREFIX + '-input', PREFIX + '-configure-voice', PREFIX + '-configure-bar-tooltip'] },
105
- { key: 'action-picker', ids: [PREFIX + '-picker'] },
106
- { key: 'edit-chrome', ids: [PREFIX + '-edit-badge'] },
107
- { key: 'generating-row', ids: [PREFIX + '-bar', PREFIX + '-shader'] },
108
- { key: 'variant-cycling-row', ids: [PREFIX + '-bar', PREFIX + '-params-panel'] },
109
- { key: 'variant-params-panel', ids: [PREFIX + '-params-panel'] },
110
- { key: 'saving-confirmed-rows', ids: [PREFIX + '-bar'] },
111
- { key: 'insert-mode-chrome', ids: [PREFIX + '-insert-line', PREFIX + '-insert-placeholder', PREFIX + '-placeholder-resize', PREFIX + '-insert-input', PREFIX + '-insert-voice', PREFIX + '-insert-create', PREFIX + '-insert-create-tooltip'] },
112
- { key: 'annotation-chrome', ids: [PREFIX + '-annot', PREFIX + '-annot-svg', PREFIX + '-annot-pins', PREFIX + '-annot-clear'] },
113
- { key: 'design-system-panel', ids: [PREFIX + '-design-host'] },
114
- { key: 'toasts-and-errors', ids: [PREFIX + '-toast', PREFIX + '-mount-error'] },
115
- { key: 'css-isolation-boundary', ids: [PREFIX + '-root'] },
116
- ];
100
+ // The Live chrome inventory (which surfaces exist, and the element ids each
101
+ // one owns) comes from the canonical source, skill/scripts/live/ui-surfaces.mjs,
102
+ // which the /live.js assembler serializes into these globals alongside the
103
+ // token/port/vocabulary. This file is served raw and injected as a classic
104
+ // script, so it cannot import that module; the private impeccable-site repo
105
+ // imports it directly to check its Live UI lab holds a snapshot for every
106
+ // surface, which only works while the list has exactly one definition.
107
+ // Add a surface in ui-surfaces.mjs, not here.
108
+ const LIVE_CHROME_MOUNT_CONTRACT = Array.isArray(window.__IMPECCABLE_LIVE_MOUNT_CONTRACT__)
109
+ ? window.__IMPECCABLE_LIVE_MOUNT_CONTRACT__
110
+ : ['root', 'transport', 'state', 'actions'];
111
+ const LIVE_UI_SURFACES = Array.isArray(window.__IMPECCABLE_LIVE_UI_SURFACES__)
112
+ ? window.__IMPECCABLE_LIVE_UI_SURFACES__
113
+ : [];
117
114
  const LIVE_UI_COMPONENT_IDS = [...new Set(LIVE_UI_SURFACES.flatMap((surface) => surface.ids))];
118
115
 
119
116
  //
@@ -3766,7 +3763,10 @@
3766
3763
  const container = copyEditContainerContext(contextElement);
3767
3764
  if (container) for (const op of ops) op.container = container;
3768
3765
  try {
3769
- const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
3766
+ // Token in the query string as well as the body: the URL token is what
3767
+ // authorizes the CORS preflight when the page runs on a non-loopback
3768
+ // dev host (ddev, Valet), since the preflight carries no request body.
3769
+ const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
3770
3770
  method: 'POST',
3771
3771
  headers: { 'Content-Type': 'application/json' },
3772
3772
  body: JSON.stringify({
@@ -4902,6 +4902,13 @@
4902
4902
  saveSession();
4903
4903
  }
4904
4904
 
4905
+ function completeParameterGenerationIfReady() {
4906
+ if (expectedVariants <= 0 || arrivedVariants < expectedVariants) return;
4907
+ if (parameterGenerationState === 'pending' || parameterGenerationState === 'loading') {
4908
+ completeParameterPublication();
4909
+ }
4910
+ }
4911
+
4905
4912
  function toggleTunePopover() {
4906
4913
  if (pendingApplyInFlight) { showManualApplyBusyToast(); return; }
4907
4914
  if (tuneOpen) { closeTunePopover(); return; }
@@ -5796,7 +5803,7 @@
5796
5803
  setLiveState('CYCLING');
5797
5804
  showOrUpdateCyclingBar();
5798
5805
  saveSession();
5799
- if (parameterGenerationState === 'loading') completeParameterPublication();
5806
+ completeParameterGenerationIfReady();
5800
5807
  return;
5801
5808
  }
5802
5809
 
@@ -5884,7 +5891,7 @@
5884
5891
  refreshParamsPanel();
5885
5892
  positionBar();
5886
5893
  saveSession();
5887
- if (parameterGenerationState === 'loading') completeParameterPublication();
5894
+ completeParameterGenerationIfReady();
5888
5895
  console.log('[impeccable] Mounted ' + arrivedVariants + ' ' + manifest.framework + ' component variants.');
5889
5896
  } catch (err) {
5890
5897
  console.error('[impeccable] Failed to mount component-preview variants:', err);
@@ -6329,7 +6336,7 @@
6329
6336
  refreshParamsPanel();
6330
6337
  positionBar();
6331
6338
  saveSession();
6332
- if (parameterGenerationState === 'loading') completeParameterPublication();
6339
+ completeParameterGenerationIfReady();
6333
6340
  console.log('[impeccable] Injected ' + arrivedVariants + ' variants from source file.');
6334
6341
  })
6335
6342
  .catch(err => {
@@ -6836,6 +6843,7 @@
6836
6843
 
6837
6844
  const expected = parseInt(wrapper.dataset.impeccableVariantCount || '0');
6838
6845
  if (expected > 0) expectedVariants = expected;
6846
+ completeParameterGenerationIfReady();
6839
6847
 
6840
6848
  if (arrivedVariants > 0) {
6841
6849
  setLiveState('CYCLING');
@@ -7150,7 +7158,10 @@
7150
7158
  console.debug('[impeccable] Dropped optional live event:', err);
7151
7159
  return null;
7152
7160
  }
7153
- const doSend = () => fetch('http://localhost:' + PORT + '/events', {
7161
+ // Token in the query string as well as the body: the URL token is what
7162
+ // authorizes the CORS preflight when the page runs on a non-loopback
7163
+ // dev host (ddev, Valet), since the preflight carries no request body.
7164
+ const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
7154
7165
  method: 'POST',
7155
7166
  headers: { 'Content-Type': 'application/json' },
7156
7167
  body: JSON.stringify(msg),
@@ -11132,10 +11143,36 @@ void main() {
11132
11143
  const scanId = String(++detectScanSeq);
11133
11144
  activeDetectScanId = scanId;
11134
11145
  pendingDetectScanId = scanId;
11146
+ // Send the project's detector waivers with the scan so the overlay
11147
+ // filters the same findings the CLI and the edit hook do (issue #639).
11148
+ // live-browser-ignores.js resolves .impeccable config for this page:
11149
+ // ignoreRules suppress outright, wildcard ignoreValues suppress their
11150
+ // rule in the files they name, ignoreFiles that name the page skip the
11151
+ // scan wholesale, and the rest match on the finding's own value inside
11152
+ // the detector. Guarded twice: a stale cached live.js without the
11153
+ // resolver part still scans, and a resolver that throws must not brick
11154
+ // the detect toggle; both degrade to an unfiltered scan.
11155
+ const ignoresApi = window.__IMPECCABLE_LIVE_IGNORES__;
11156
+ let ignores = { disabledRules: [], disabledValues: [], skipScan: false };
11157
+ if (typeof ignoresApi?.resolveDetectIgnores === 'function') {
11158
+ try {
11159
+ ignores = ignoresApi.resolveDetectIgnores({
11160
+ ignores: window.__IMPECCABLE_PROJECT_IGNORES__,
11161
+ pathname: location.pathname,
11162
+ }) || ignores;
11163
+ } catch (e) {
11164
+ ignores = { disabledRules: [], disabledValues: [], skipScan: false };
11165
+ }
11166
+ }
11135
11167
  window.postMessage({
11136
11168
  source: 'impeccable-command',
11137
11169
  action: 'scan',
11138
- config: { scanId },
11170
+ config: {
11171
+ scanId,
11172
+ disabledRules: ignores.disabledRules || [],
11173
+ disabledValues: ignores.disabledValues || [],
11174
+ skipScan: ignores.skipScan === true,
11175
+ },
11139
11176
  }, '*');
11140
11177
  }
11141
11178
 
@@ -11969,7 +12006,9 @@ void main() {
11969
12006
  rules: [
11970
12007
  ...(md.colors?.rules || []).map((r) => ({ ...r, section: 'colors' })),
11971
12008
  ...(md.typography?.rules || []).map((r) => ({ ...r, section: 'typography' })),
12009
+ ...(md.layout?.rules || []).map((r) => ({ ...r, section: 'layout' })),
11972
12010
  ...(md.elevation?.rules || []).map((r) => ({ ...r, section: 'elevation' })),
12011
+ ...(md.shapes?.rules || []).map((r) => ({ ...r, section: 'shapes' })),
11973
12012
  ],
11974
12013
  dos: md.dosDonts?.dos || [],
11975
12014
  donts: md.dosDonts?.donts || [],