@evertonferreira96/agents 0.0.0-stage → 0.1.0

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 (258) hide show
  1. package/.claude/agents/api.md +45 -0
  2. package/.claude/agents/app.md +41 -0
  3. package/.claude/agents/architect.md +49 -0
  4. package/.claude/agents/infra.md +27 -0
  5. package/.claude/agents/product.md +58 -0
  6. package/.claude/agents/quick.md +33 -0
  7. package/.claude/agents/security.md +39 -0
  8. package/.claude/agents/ui.md +54 -0
  9. package/.claude/agents/verifier.md +56 -0
  10. package/.claude/agents/web.md +41 -0
  11. package/.claude/hooks/config-update-check.mjs +14 -0
  12. package/.claude/hooks/context-budget.mjs +51 -0
  13. package/.claude/hooks/graphify-update.sh +16 -0
  14. package/.claude/hooks/guard-bash.sh +64 -0
  15. package/.claude/hooks/orchestrator-guard.sh +49 -0
  16. package/.claude/hooks/post-compact.sh +26 -0
  17. package/.claude/hooks/readonly-guard.sh +54 -0
  18. package/.claude/hooks/stop-verify.sh +52 -0
  19. package/.claude/rules/api.md +21 -0
  20. package/.claude/rules/db.md +9 -0
  21. package/.claude/rules/delivery-diagram.md +16 -0
  22. package/.claude/rules/git-workflow.md +34 -0
  23. package/.claude/rules/graphify.md +3 -0
  24. package/.claude/rules/mobile.md +22 -0
  25. package/.claude/rules/tickets.md +9 -0
  26. package/.claude/rules/ui.md +14 -0
  27. package/.claude/rules/web.md +27 -0
  28. package/.claude/settings.json +114 -0
  29. package/.claude/skills/animate/RECIPES.md +324 -0
  30. package/.claude/skills/animate/SKILL.md +208 -0
  31. package/.claude/skills/animate/agents/openai.yaml +5 -0
  32. package/.claude/skills/animate-expo/RECIPES.md +385 -0
  33. package/.claude/skills/animate-expo/SKILL.md +264 -0
  34. package/.claude/skills/animate-expo/agents/openai.yaml +5 -0
  35. package/.claude/skills/animation-performance/SKILL.md +223 -0
  36. package/.claude/skills/animation-performance/agents/openai.yaml +5 -0
  37. package/.claude/skills/apple-design/SKILL.md +291 -0
  38. package/.claude/skills/apple-design/agents/openai.yaml +5 -0
  39. package/.claude/skills/arch-api/SKILL.md +291 -0
  40. package/.claude/skills/arch-api/agents/openai.yaml +3 -0
  41. package/.claude/skills/arch-api/references/common.md +174 -0
  42. package/.claude/skills/arch-api/references/contract.md +289 -0
  43. package/.claude/skills/arch-api/references/domain.md +301 -0
  44. package/.claude/skills/arch-api/references/endpoints.md +184 -0
  45. package/.claude/skills/arch-api/references/review.md +228 -0
  46. package/.claude/skills/arch-api/references/security.md +151 -0
  47. package/.claude/skills/arch-app/SKILL.md +278 -0
  48. package/.claude/skills/arch-app/agents/openai.yaml +3 -0
  49. package/.claude/skills/arch-app/references/navigation-session.md +257 -0
  50. package/.claude/skills/arch-app/references/review.md +155 -0
  51. package/.claude/skills/arch-app/references/screens.md +214 -0
  52. package/.claude/skills/arch-app/references/services.md +301 -0
  53. package/.claude/skills/arch-app/references/ui-mobile.md +124 -0
  54. package/.claude/skills/arch-db/SKILL.md +181 -0
  55. package/.claude/skills/arch-db/agents/openai.yaml +3 -0
  56. package/.claude/skills/arch-db/references/indexes-queries.md +251 -0
  57. package/.claude/skills/arch-db/references/migrations.md +191 -0
  58. package/.claude/skills/arch-db/references/modeling.md +214 -0
  59. package/.claude/skills/arch-db/references/mysql.md +262 -0
  60. package/.claude/skills/arch-db/references/pooling-ops.md +112 -0
  61. package/.claude/skills/arch-db/references/review.md +143 -0
  62. package/.claude/skills/arch-db/references/transactions-locks.md +141 -0
  63. package/.claude/skills/arch-web/SKILL.md +307 -0
  64. package/.claude/skills/arch-web/agents/openai.yaml +3 -0
  65. package/.claude/skills/arch-web/references/auth-session-env.md +165 -0
  66. package/.claude/skills/arch-web/references/blocks.md +198 -0
  67. package/.claude/skills/arch-web/references/cache.md +124 -0
  68. package/.claude/skills/arch-web/references/review.md +142 -0
  69. package/.claude/skills/arch-web/references/services.md +297 -0
  70. package/.claude/skills/arch-web/references/tests.md +159 -0
  71. package/.claude/skills/arch-web/references/ui-forms.md +143 -0
  72. package/.claude/skills/break-ui/CATALOG.md +135 -0
  73. package/.claude/skills/break-ui/SKILL.md +176 -0
  74. package/.claude/skills/break-ui/agents/openai.yaml +5 -0
  75. package/.claude/skills/brief/SKILL.md +91 -0
  76. package/.claude/skills/brief/agents/openai.yaml +3 -0
  77. package/.claude/skills/diagnosing-bugs/SKILL.md +138 -0
  78. package/.claude/skills/diagnosing-bugs/agents/openai.yaml +3 -0
  79. package/.claude/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  80. package/.claude/skills/emil-design-eng/SKILL.md +675 -0
  81. package/.claude/skills/emil-design-eng/agents/openai.yaml +5 -0
  82. package/.claude/skills/find-animation-opportunities/SKILL.md +141 -0
  83. package/.claude/skills/find-animation-opportunities/agents/openai.yaml +5 -0
  84. package/.claude/skills/graphify/.graphify_version +1 -0
  85. package/.claude/skills/graphify/SKILL.md +742 -0
  86. package/.claude/skills/graphify/agents/openai.yaml +3 -0
  87. package/.claude/skills/graphify/references/add-watch.md +56 -0
  88. package/.claude/skills/graphify/references/exports.md +91 -0
  89. package/.claude/skills/graphify/references/extraction-spec.md +70 -0
  90. package/.claude/skills/graphify/references/github-and-merge.md +46 -0
  91. package/.claude/skills/graphify/references/hooks.md +33 -0
  92. package/.claude/skills/graphify/references/query.md +317 -0
  93. package/.claude/skills/graphify/references/transcribe.md +52 -0
  94. package/.claude/skills/graphify/references/update.md +221 -0
  95. package/.claude/skills/grill-me/SKILL.md +7 -0
  96. package/.claude/skills/grill-me/agents/openai.yaml +5 -0
  97. package/.claude/skills/grill-with-docs/SKILL.md +64 -0
  98. package/.claude/skills/grill-with-docs/agents/openai.yaml +5 -0
  99. package/.claude/skills/grilling/SKILL.md +30 -0
  100. package/.claude/skills/grilling/agents/openai.yaml +3 -0
  101. package/.claude/skills/handoff/SKILL.md +28 -0
  102. package/.claude/skills/handoff/agents/openai.yaml +5 -0
  103. package/.claude/skills/impeccable/SKILL.md +89 -0
  104. package/.claude/skills/impeccable/agents/openai.yaml +5 -0
  105. package/.claude/skills/impeccable/reference/adapt.md +318 -0
  106. package/.claude/skills/impeccable/reference/adapt.native.md +58 -0
  107. package/.claude/skills/impeccable/reference/android.md +46 -0
  108. package/.claude/skills/impeccable/reference/animate.md +89 -0
  109. package/.claude/skills/impeccable/reference/audit.md +137 -0
  110. package/.claude/skills/impeccable/reference/audit.native.md +139 -0
  111. package/.claude/skills/impeccable/reference/bolder.md +33 -0
  112. package/.claude/skills/impeccable/reference/clarify.md +94 -0
  113. package/.claude/skills/impeccable/reference/colorize.md +86 -0
  114. package/.claude/skills/impeccable/reference/component-review.md +63 -0
  115. package/.claude/skills/impeccable/reference/craft-floor.md +44 -0
  116. package/.claude/skills/impeccable/reference/craft.md +5 -0
  117. package/.claude/skills/impeccable/reference/critique.md +809 -0
  118. package/.claude/skills/impeccable/reference/degraded/asset-producer.md +42 -0
  119. package/.claude/skills/impeccable/reference/degraded/documenter.md +24 -0
  120. package/.claude/skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
  121. package/.claude/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  122. package/.claude/skills/impeccable/reference/delight.md +70 -0
  123. package/.claude/skills/impeccable/reference/distill.md +111 -0
  124. package/.claude/skills/impeccable/reference/doctor.md +54 -0
  125. package/.claude/skills/impeccable/reference/document.md +416 -0
  126. package/.claude/skills/impeccable/reference/extract.md +69 -0
  127. package/.claude/skills/impeccable/reference/generate.md +101 -0
  128. package/.claude/skills/impeccable/reference/harden.md +345 -0
  129. package/.claude/skills/impeccable/reference/hooks.md +113 -0
  130. package/.claude/skills/impeccable/reference/init.md +131 -0
  131. package/.claude/skills/impeccable/reference/ios.md +51 -0
  132. package/.claude/skills/impeccable/reference/layout.md +84 -0
  133. package/.claude/skills/impeccable/reference/live-setup.md +104 -0
  134. package/.claude/skills/impeccable/reference/live.md +325 -0
  135. package/.claude/skills/impeccable/reference/mode-operate.md +21 -0
  136. package/.claude/skills/impeccable/reference/mode-persuade.md +19 -0
  137. package/.claude/skills/impeccable/reference/mode-read.md +21 -0
  138. package/.claude/skills/impeccable/reference/new-work.md +154 -0
  139. package/.claude/skills/impeccable/reference/onboard.md +234 -0
  140. package/.claude/skills/impeccable/reference/operate.md +61 -0
  141. package/.claude/skills/impeccable/reference/optimize.md +258 -0
  142. package/.claude/skills/impeccable/reference/overdrive.md +127 -0
  143. package/.claude/skills/impeccable/reference/polish.md +105 -0
  144. package/.claude/skills/impeccable/reference/quieter.md +99 -0
  145. package/.claude/skills/impeccable/reference/region-map.md +26 -0
  146. package/.claude/skills/impeccable/reference/routing.md +24 -0
  147. package/.claude/skills/impeccable/reference/shape.md +59 -0
  148. package/.claude/skills/impeccable/reference/typeset.md +80 -0
  149. package/.claude/skills/impeccable/reference/visualize.md +47 -0
  150. package/.claude/skills/impeccable/scripts/VERSION +1 -0
  151. package/.claude/skills/impeccable/scripts/command-metadata.json +98 -0
  152. package/.claude/skills/impeccable/scripts/data/font-index-failures.json +121 -0
  153. package/.claude/skills/impeccable/scripts/data/font-index.json +1 -0
  154. package/.claude/skills/impeccable/scripts/impeccable +206 -0
  155. package/.claude/skills/impeccable/scripts/impeccable.cmd +214 -0
  156. package/.claude/skills/impeccable/scripts/live-browser-dom.js +281 -0
  157. package/.claude/skills/impeccable/scripts/live-browser-ignores.js +242 -0
  158. package/.claude/skills/impeccable/scripts/live-browser-session.js +148 -0
  159. package/.claude/skills/impeccable/scripts/live-browser.js +13532 -0
  160. package/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  161. package/.claude/skills/improve-animations/AUDIT.md +115 -0
  162. package/.claude/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
  163. package/.claude/skills/improve-animations/SKILL.md +110 -0
  164. package/.claude/skills/improve-animations/agents/openai.yaml +5 -0
  165. package/.claude/skills/mobile-native/SKILL.md +311 -0
  166. package/.claude/skills/mobile-native/agents/openai.yaml +3 -0
  167. package/.claude/skills/orchestrate/SKILL.md +181 -0
  168. package/.claude/skills/orchestrate/agents/openai.yaml +3 -0
  169. package/.claude/skills/pick-ui-library/SKILL.md +85 -0
  170. package/.claude/skills/pick-ui-library/agents/openai.yaml +5 -0
  171. package/.claude/skills/ponytail/SKILL.md +52 -0
  172. package/.claude/skills/ponytail/agents/openai.yaml +3 -0
  173. package/.claude/skills/ponytail-audit/SKILL.md +103 -0
  174. package/.claude/skills/ponytail-audit/agents/openai.yaml +3 -0
  175. package/.claude/skills/ponytail-debt/SKILL.md +44 -0
  176. package/.claude/skills/ponytail-debt/agents/openai.yaml +3 -0
  177. package/.claude/skills/ponytail-review/SKILL.md +101 -0
  178. package/.claude/skills/ponytail-review/agents/openai.yaml +3 -0
  179. package/.claude/skills/postgres-best-practices/SKILL.md +42 -0
  180. package/.claude/skills/postgres-best-practices/agents/openai.yaml +3 -0
  181. package/.claude/skills/postgres-best-practices/references/backup-restore.md +372 -0
  182. package/.claude/skills/postgres-best-practices/references/bulk-loading.md +315 -0
  183. package/.claude/skills/postgres-best-practices/references/connection-pooling.md +310 -0
  184. package/.claude/skills/postgres-best-practices/references/hot-standby.md +221 -0
  185. package/.claude/skills/postgres-best-practices/references/indexing.md +318 -0
  186. package/.claude/skills/postgres-best-practices/references/logical-replication.md +610 -0
  187. package/.claude/skills/postgres-best-practices/references/major-version-upgrades.md +334 -0
  188. package/.claude/skills/postgres-best-practices/references/performance-diagnostics.md +553 -0
  189. package/.claude/skills/postgres-best-practices/references/query-optimization.md +294 -0
  190. package/.claude/skills/postgres-best-practices/references/query-patterns.md +566 -0
  191. package/.claude/skills/postgres-best-practices/references/schema-design.md +362 -0
  192. package/.claude/skills/postgres-best-practices/references/security-roles.md +489 -0
  193. package/.claude/skills/postgres-best-practices/references/transaction-isolation.md +344 -0
  194. package/.claude/skills/project-docs/SKILL.md +330 -0
  195. package/.claude/skills/project-docs/agents/openai.yaml +3 -0
  196. package/.claude/skills/prototype/PICKER.md +197 -0
  197. package/.claude/skills/prototype/SKILL.md +98 -0
  198. package/.claude/skills/prototype/agents/openai.yaml +5 -0
  199. package/.claude/skills/retro/SKILL.md +43 -0
  200. package/.claude/skills/retro/agents/openai.yaml +5 -0
  201. package/.claude/skills/review/SKILL.md +74 -0
  202. package/.claude/skills/review/agents/openai.yaml +3 -0
  203. package/.claude/skills/review/references/smells.md +23 -0
  204. package/.claude/skills/review-animations/SKILL.md +120 -0
  205. package/.claude/skills/review-animations/STANDARDS.md +187 -0
  206. package/.claude/skills/review-animations/agents/openai.yaml +5 -0
  207. package/.claude/skills/tdd/SKILL.md +38 -0
  208. package/.claude/skills/tdd/agents/openai.yaml +3 -0
  209. package/.claude/skills/tdd/mocking.md +59 -0
  210. package/.claude/skills/tdd/tests.md +77 -0
  211. package/.claude/skills/triage/SKILL.md +58 -0
  212. package/.claude/skills/triage/agents/openai.yaml +3 -0
  213. package/.claude/skills/ui-ux-pro-max/SKILL.md +378 -0
  214. package/.claude/skills/ui-ux-pro-max/agents/openai.yaml +5 -0
  215. package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  216. package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  217. package/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
  218. package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  219. package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  220. package/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  221. package/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  222. package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  223. package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  224. package/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  225. package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  226. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  227. package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  228. package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  229. package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  230. package/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  231. package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  232. package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  233. package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  234. package/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
  235. package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  236. package/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  237. package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  238. package/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  239. package/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
  240. package/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  241. package/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
  242. package/.claude/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
  243. package/.claude/skills/writing-for-agents/SKILL.md +81 -0
  244. package/.claude/skills/writing-for-agents/agents/openai.yaml +3 -0
  245. package/.worktreeinclude +3 -0
  246. package/AGENTS.md +44 -0
  247. package/README.md +14 -2
  248. package/VERSION +1 -0
  249. package/bin/agents.mjs +45 -0
  250. package/docs/adr/README.md +31 -0
  251. package/docs/brief-template.md +3 -0
  252. package/docs/plans/README.md +73 -0
  253. package/package.json +46 -4
  254. package/scripts/bootstrap.mjs +188 -0
  255. package/scripts/check-catalog.mjs +43 -0
  256. package/scripts/skills-update.mjs +51 -0
  257. package/skills-lock.json +269 -0
  258. package/templates/CLAUDE.project.md +41 -0
@@ -0,0 +1,281 @@
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 hasFrameworkHmrOwnership(el) {
68
+ for (let node = el; node; node = node.parentElement) {
69
+ let keys = [];
70
+ try { keys = Object.getOwnPropertyNames(node); } catch {}
71
+ if (keys.some((key) => (
72
+ key.startsWith('__reactFiber$')
73
+ || key.startsWith('__reactProps$')
74
+ || key.startsWith('__reactContainer$')
75
+ || key === '_reactRootContainer'
76
+ || key === '__vueParentComponent'
77
+ || key === '__vue_app__'
78
+ || key === '__vnode'
79
+ || key === '__svelte_meta'
80
+ ))) {
81
+ return true;
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+
87
+ function id8() {
88
+ if (crypto?.randomUUID) return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
89
+ return (Math.random().toString(16).slice(2) + Date.now().toString(16)).slice(0, 8);
90
+ }
91
+
92
+ function cssId(id) {
93
+ if (css?.escape) return css.escape(id);
94
+ return String(id).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
95
+ }
96
+
97
+ function liveUiRoot() {
98
+ const uiRoot = root.__IMPECCABLE_LIVE_UI_ROOT__;
99
+ if (uiRoot && typeof uiRoot.appendChild === 'function') return uiRoot;
100
+ return doc.body;
101
+ }
102
+
103
+ // A modal <dialog> (showModal) paints in the top layer and makes every
104
+ // node outside its subtree inert, so chrome on <body> can be neither seen
105
+ // nor clicked while one is open, and no z-index reaches it (issue #879).
106
+ // While a modal is open the chrome parks inside the topmost one, in one
107
+ // popover: inside the dialog it is not inert, and a popover shown after
108
+ // the dialog paints above it. One popover for all of it, because
109
+ // top-layer entries stack by show order, which would drop the z-index
110
+ // order between the roots. The detect overlay (browser-bundle/
111
+ // 40-overlay.js) mounts its outlines here too: it finds the host by this
112
+ // id and places its outlines again on the event of the same name, fired
113
+ // on every move.
114
+ const topLayerHost = doc.createElement('div');
115
+ topLayerHost.id = prefix + '-top-layer';
116
+ // Same box as the SvelteKit shadow host: 0x0, fixed, chrome overflows it.
117
+ // `all: initial` also clears the popover UA box.
118
+ for (const [name, value] of Object.entries({
119
+ all: 'initial', position: 'fixed', top: '0', left: '0', width: '0', height: '0', overflow: 'visible',
120
+ })) topLayerHost.style.setProperty(name, value, 'important');
121
+ const pageRoots = new WeakSet(); // chrome nodes mounted on the page itself
122
+ const openModals = []; // in the order they opened; last is topmost
123
+
124
+ // Modals that were open before watching began have no readable top-layer
125
+ // order, but the topmost one's backdrop covers the viewport, so a hit
126
+ // test lands in it. Raise that one to the top of the stack.
127
+ function raiseHitModal() {
128
+ const i = openModals.indexOf(doc.elementFromPoint(0, 0)?.closest('dialog:modal'));
129
+ if (i !== -1) openModals.push(...openModals.splice(i, 1));
130
+ }
131
+
132
+ function syncTopLayerHost(records = []) {
133
+ for (const { type, target, oldValue } of records) {
134
+ if (type !== 'attributes' || oldValue !== null || !target.matches('dialog:modal')) continue;
135
+ // Opened, or closed and reopened in one task (the observer sees only
136
+ // the end state): either way it is back on top of the top layer, so
137
+ // a host shown inside it earlier has to be shown again. A write to an
138
+ // `open` that was already set (oldValue not null) moved nothing.
139
+ const i = openModals.indexOf(target);
140
+ if (i !== -1) openModals.splice(i, 1);
141
+ openModals.push(target);
142
+ if (topLayerHost.parentNode === target) topLayerHost.remove();
143
+ }
144
+ // A closed dialog, or one removed from the document while open, stops
145
+ // matching :modal. Whichever is now on top may predate the watch.
146
+ const before = openModals.length;
147
+ for (let i = openModals.length - 1; i >= 0; i--) {
148
+ if (!openModals[i].matches('dialog:modal')) openModals.splice(i, 1);
149
+ }
150
+ if (openModals.length < before && openModals.length > 1) raiseHitModal();
151
+ const modal = openModals[openModals.length - 1] || null;
152
+ if (topLayerHost.parentNode === modal) return;
153
+ if (modal) {
154
+ modal.appendChild(topLayerHost);
155
+ for (const el of [...doc.body.children]) if (pageRoots.has(el)) topLayerHost.appendChild(el);
156
+ topLayerHost.showPopover();
157
+ } else {
158
+ doc.body.append(...topLayerHost.childNodes);
159
+ topLayerHost.remove();
160
+ }
161
+ doc.dispatchEvent(new Event(topLayerHost.id));
162
+ }
163
+
164
+ // Returns the function that stops watching and puts the chrome back.
165
+ function watchModalDialogs() {
166
+ if (typeof topLayerHost.showPopover !== 'function') return () => {};
167
+ topLayerHost.popover = 'manual';
168
+ // Parked in the page's dialog, chrome clicks would bubble into its own
169
+ // handlers, such as a click-outside-the-box close. Live listens in capture.
170
+ topLayerHost.addEventListener('click', (e) => e.stopPropagation());
171
+ openModals.push(...doc.querySelectorAll('dialog:modal'));
172
+ raiseHitModal();
173
+ const observer = new MutationObserver(syncTopLayerHost);
174
+ observer.observe(doc, { subtree: true, childList: true, attributes: true, attributeOldValue: true, attributeFilter: ['open'] });
175
+ syncTopLayerHost();
176
+ return () => {
177
+ observer.disconnect();
178
+ openModals.length = 0;
179
+ syncTopLayerHost();
180
+ };
181
+ }
182
+
183
+ // A copy of a page element without the chrome parked inside it, which a
184
+ // picked modal dialog contains.
185
+ function cloneWithoutChrome(el) {
186
+ const clone = el.cloneNode(true);
187
+ if (el.contains(topLayerHost)) clone.querySelector('#' + cssId(topLayerHost.id)).remove();
188
+ return clone;
189
+ }
190
+
191
+ // Mount a chrome node on the page itself: <body>, or the top-layer host
192
+ // while the chrome is parked in a modal.
193
+ function uiAppendToPage(el) {
194
+ pageRoots.add(el);
195
+ (topLayerHost.parentNode ? topLayerHost : doc.body).appendChild(el);
196
+ return el;
197
+ }
198
+
199
+ function uiAppend(el) {
200
+ const uiRoot = liveUiRoot();
201
+ if (uiRoot === doc.body) return uiAppendToPage(el);
202
+ // An adapter shadow root: its host is the node that sits on the page.
203
+ if (uiRoot.host) pageRoots.add(uiRoot.host);
204
+ uiRoot.appendChild(el);
205
+ return el;
206
+ }
207
+
208
+ function uiAppendStyle(styleEl) {
209
+ const uiRoot = liveUiRoot();
210
+ if (uiRoot && uiRoot !== doc.body) uiRoot.appendChild(styleEl);
211
+ else doc.head.appendChild(styleEl);
212
+ return styleEl;
213
+ }
214
+
215
+ function uiGetById(id) {
216
+ const uiRoot = liveUiRoot();
217
+ if (uiRoot?.getElementById) {
218
+ const found = uiRoot.getElementById(id);
219
+ if (found) return found;
220
+ }
221
+ if (uiRoot?.querySelector) {
222
+ const found = uiRoot.querySelector('#' + cssId(id));
223
+ if (found) return found;
224
+ }
225
+ return doc.getElementById(id);
226
+ }
227
+
228
+ function activeElementDeep() {
229
+ let active = doc.activeElement;
230
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
231
+ return active;
232
+ }
233
+
234
+ function defangOutsideHandlers(rootEl, { setPointerEvents = true } = {}) {
235
+ if (!rootEl) return;
236
+ if (setPointerEvents) {
237
+ rootEl.style.setProperty('pointer-events', 'auto', 'important');
238
+ }
239
+ const stop = (e) => e.stopPropagation();
240
+ rootEl.addEventListener('pointerdown', stop);
241
+ rootEl.addEventListener('mousedown', stop);
242
+ rootEl.addEventListener('focusin', stop);
243
+ }
244
+
245
+ // The half of the defang that cannot sit on the chrome: a focus trap (Radix /
246
+ // Reka FocusScope) also hears the page's own focusout as focus leaves its
247
+ // modal for our chrome, and pulls it back. Register on the document in
248
+ // capture, ahead of the trap. The page's own focusout handlers miss that
249
+ // one transition too; blur still fires.
250
+ function stopFocusOutIntoChrome(e) {
251
+ if (own(e.relatedTarget)) e.stopPropagation();
252
+ }
253
+
254
+ return {
255
+ own,
256
+ pickable,
257
+ desc,
258
+ rectIsUsableAnchor,
259
+ makeFrozenAnchor,
260
+ hasFrameworkHmrOwnership,
261
+ id8,
262
+ cssId,
263
+ liveUiRoot,
264
+ uiAppend,
265
+ uiAppendToPage,
266
+ topLayerHost,
267
+ watchModalDialogs,
268
+ cloneWithoutChrome,
269
+ uiAppendStyle,
270
+ uiGetById,
271
+ activeElementDeep,
272
+ defangOutsideHandlers,
273
+ stopFocusOutIntoChrome,
274
+ };
275
+ }
276
+
277
+ root.__IMPECCABLE_LIVE_DOM__ = {
278
+ version: 1,
279
+ createLiveBrowserDomHelpers,
280
+ };
281
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -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);
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Browser-side durable session helpers for Impeccable live mode.
3
+ *
4
+ * Kept separate from live-browser.js so recovery state can be tested without
5
+ * booting the full overlay UI. Served before live-browser.js and attached to
6
+ * window.__IMPECCABLE_LIVE_SESSION__.
7
+ */
8
+ (function (root) {
9
+ 'use strict';
10
+
11
+ function createLiveBrowserSessionState({ prefix, storage, idFactory }) {
12
+ if (!prefix) throw new Error('prefix required');
13
+ const store = storage || root.localStorage;
14
+ const makeId = idFactory || function () { return Math.random().toString(16).slice(2, 10); };
15
+ const sessionKey = prefix + '-session';
16
+ const handledKey = sessionKey + '-handled';
17
+ const scrollKey = sessionKey + '-scroll';
18
+ let checkpointRevision = 0;
19
+ const owner = makeId();
20
+
21
+ function safeRead(key) {
22
+ try { return store.getItem(key); } catch { return null; }
23
+ }
24
+
25
+ function safeWrite(key, value) {
26
+ try { store.setItem(key, value); } catch { /* quota exceeded or private mode */ }
27
+ }
28
+
29
+ function safeRemove(key) {
30
+ try { store.removeItem(key); } catch { /* unavailable storage */ }
31
+ }
32
+
33
+ function loadSession() {
34
+ try {
35
+ const raw = safeRead(sessionKey);
36
+ if (!raw) return null;
37
+ const parsed = JSON.parse(raw);
38
+ if (Number.isInteger(parsed.checkpointRevision)) {
39
+ checkpointRevision = Math.max(checkpointRevision, parsed.checkpointRevision);
40
+ }
41
+ return parsed;
42
+ } catch { return null; }
43
+ }
44
+
45
+ function saveSession(session) {
46
+ if (!session || !session.id) return;
47
+ // Adopt a revision another tab stored so this write does not lower it.
48
+ // localStorage has no compare-and-set, so a save from another tab that
49
+ // lands between this read and the write below can still be overwritten.
50
+ loadSession();
51
+ const payload = {
52
+ ...session,
53
+ checkpointRevision,
54
+ };
55
+ safeWrite(sessionKey, JSON.stringify(payload));
56
+ }
57
+
58
+ function clearSession() {
59
+ safeRemove(sessionKey);
60
+ }
61
+
62
+ function nextCheckpointRevision() {
63
+ const existing = loadSession();
64
+ checkpointRevision += 1;
65
+ if (existing?.id) saveSession(existing);
66
+ return checkpointRevision;
67
+ }
68
+
69
+ function seedCheckpointRevision(value) {
70
+ if (Number.isInteger(value)) checkpointRevision = Math.max(checkpointRevision, value);
71
+ return checkpointRevision;
72
+ }
73
+
74
+ function currentCheckpointRevision() {
75
+ return checkpointRevision;
76
+ }
77
+
78
+ function readHandledIds() {
79
+ const raw = safeRead(handledKey);
80
+ if (!raw) return [];
81
+ try {
82
+ const parsed = JSON.parse(raw);
83
+ if (Array.isArray(parsed)) {
84
+ return parsed.filter(id => typeof id === 'string' && id);
85
+ }
86
+ if (typeof parsed === 'string' && parsed) return [parsed];
87
+ } catch { /* legacy values were stored as a plain session id */ }
88
+ return [raw];
89
+ }
90
+
91
+ function markHandled(id) {
92
+ if (!id) return;
93
+ const ids = readHandledIds().filter(existing => existing !== id);
94
+ ids.push(id);
95
+ safeWrite(handledKey, JSON.stringify(ids.slice(-8)));
96
+ }
97
+
98
+ function isHandled(id) {
99
+ return !!id && readHandledIds().includes(id);
100
+ }
101
+
102
+ function clearHandled(id) {
103
+ if (!id) {
104
+ safeRemove(handledKey);
105
+ return;
106
+ }
107
+ const remaining = readHandledIds().filter(existing => existing !== id);
108
+ if (remaining.length > 0) safeWrite(handledKey, JSON.stringify(remaining));
109
+ else safeRemove(handledKey);
110
+ }
111
+
112
+ function writeScrollY(y) {
113
+ safeWrite(scrollKey, String(y));
114
+ }
115
+
116
+ function readScrollY() {
117
+ const raw = safeRead(scrollKey);
118
+ if (raw == null) return null;
119
+ const n = parseFloat(raw);
120
+ return isFinite(n) ? n : null;
121
+ }
122
+
123
+ function clearScrollY() {
124
+ safeRemove(scrollKey);
125
+ }
126
+
127
+ return {
128
+ owner,
129
+ sessionKey,
130
+ handledKey,
131
+ scrollKey,
132
+ saveSession,
133
+ loadSession,
134
+ clearSession,
135
+ nextCheckpointRevision,
136
+ seedCheckpointRevision,
137
+ currentCheckpointRevision,
138
+ markHandled,
139
+ isHandled,
140
+ clearHandled,
141
+ writeScrollY,
142
+ readScrollY,
143
+ clearScrollY,
144
+ };
145
+ }
146
+
147
+ root.__IMPECCABLE_LIVE_SESSION__ = { createLiveBrowserSessionState };
148
+ })(typeof window !== 'undefined' ? window : globalThis);