quiver-cli 0.8.0 → 1.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 (200) hide show
  1. package/README.md +127 -45
  2. package/bin/quiver-cli.mjs +3 -1
  3. package/dist/cli.js +666 -436
  4. package/package.json +2 -2
  5. package/template/.agents/AGENTS.md +4 -3
  6. package/template/.agents/config.json +7 -0
  7. package/template/.agents/plugins/opencode/rtk.ts +34 -0
  8. package/template/.agents/skills/agent-browser/SKILL.md +1 -0
  9. package/template/.agents/skills/apps/skybridge/SKILL.md +4 -0
  10. package/template/.agents/skills/design/impeccable/SKILL.md +36 -118
  11. package/template/.agents/skills/design/impeccable/reference/adapt.md +1 -0
  12. package/template/.agents/skills/design/impeccable/reference/adapt.native.md +58 -0
  13. package/template/.agents/skills/design/impeccable/reference/android.md +40 -0
  14. package/template/.agents/skills/design/impeccable/reference/animate.md +73 -188
  15. package/template/.agents/skills/design/impeccable/reference/audit.md +12 -9
  16. package/template/.agents/skills/design/impeccable/reference/audit.native.md +139 -0
  17. package/template/.agents/skills/design/impeccable/reference/bolder.md +19 -101
  18. package/template/.agents/skills/design/impeccable/reference/clarify.md +59 -253
  19. package/template/.agents/skills/design/impeccable/reference/colorize.md +51 -222
  20. package/template/.agents/skills/design/impeccable/reference/craft-floor.md +42 -0
  21. package/template/.agents/skills/design/impeccable/reference/craft.md +3 -121
  22. package/template/.agents/skills/design/impeccable/reference/critique.md +44 -23
  23. package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +90 -0
  24. package/template/.agents/skills/design/impeccable/reference/degraded/documenter.md +24 -0
  25. package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +37 -0
  26. package/template/.agents/skills/design/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  27. package/template/.agents/skills/design/impeccable/reference/delight.md +47 -279
  28. package/template/.agents/skills/design/impeccable/reference/distill.md +2 -2
  29. package/template/.agents/skills/design/impeccable/reference/doctor.md +53 -0
  30. package/template/.agents/skills/design/impeccable/reference/document.md +60 -73
  31. package/template/.agents/skills/design/impeccable/reference/harden.md +1 -12
  32. package/template/.agents/skills/design/impeccable/reference/hooks.md +20 -5
  33. package/template/.agents/skills/design/impeccable/reference/init.md +72 -119
  34. package/template/.agents/skills/design/impeccable/reference/ios.md +45 -0
  35. package/template/.agents/skills/design/impeccable/reference/layout.md +54 -131
  36. package/template/.agents/skills/design/impeccable/reference/live-setup.md +102 -0
  37. package/template/.agents/skills/design/impeccable/reference/live.md +116 -511
  38. package/template/.agents/skills/design/impeccable/reference/new-work.md +105 -0
  39. package/template/.agents/skills/design/impeccable/reference/{product.md → operate.md} +6 -5
  40. package/template/.agents/skills/design/impeccable/reference/optimize.md +4 -4
  41. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -4
  42. package/template/.agents/skills/design/impeccable/reference/polish.md +68 -212
  43. package/template/.agents/skills/design/impeccable/reference/quieter.md +3 -3
  44. package/template/.agents/skills/design/impeccable/reference/routing.md +18 -0
  45. package/template/.agents/skills/design/impeccable/reference/shape.md +38 -144
  46. package/template/.agents/skills/design/impeccable/reference/typeset.md +51 -250
  47. package/template/.agents/skills/design/impeccable/reference/visualize.md +47 -0
  48. package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +1 -1
  49. package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +558 -0
  50. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +119 -10
  51. package/template/.agents/skills/design/impeccable/scripts/context.mjs +534 -45
  52. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +18 -47
  53. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
  54. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +174 -26
  55. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +233 -0
  56. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +3348 -203
  57. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
  58. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +297 -97
  59. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +187 -16
  60. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +41 -11
  61. package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +7 -1
  62. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +16 -2
  63. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +207 -38
  64. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +3082 -173
  65. package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +11 -0
  66. package/template/.agents/skills/design/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  67. package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +336 -0
  68. package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +133 -0
  69. package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +240 -0
  70. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +98 -18
  71. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +46 -6
  72. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +518 -50
  73. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +25 -8
  74. package/template/.agents/skills/design/impeccable/scripts/lib/artifact-schema.mjs +93 -0
  75. package/template/.agents/skills/design/impeccable/scripts/lib/composition-catalog.mjs +200 -0
  76. package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +357 -0
  77. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +27 -7
  78. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +17 -8
  79. package/template/.agents/skills/design/impeccable/scripts/lib/provider.mjs +5 -0
  80. package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +362 -0
  81. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +457 -0
  82. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-notice.mjs +169 -0
  83. package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +457 -0
  84. package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +151 -0
  85. package/template/.agents/skills/design/impeccable/scripts/lib/target-slug.mjs +33 -0
  86. package/template/.agents/skills/design/impeccable/scripts/lib/template-extensions.mjs +146 -0
  87. package/template/.agents/skills/design/impeccable/scripts/live/accept-css.mjs +617 -0
  88. package/template/.agents/skills/design/impeccable/scripts/live/accept-verify.mjs +60 -0
  89. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +7 -1
  90. package/template/.agents/skills/design/impeccable/scripts/live/completion.mjs +10 -1
  91. package/template/.agents/skills/design/impeccable/scripts/live/event-validation.mjs +67 -5
  92. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/astro.mjs +47 -0
  93. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/detect-utils.mjs +73 -0
  94. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/index.mjs +143 -0
  95. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/journal.mjs +197 -0
  96. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nextjs.mjs +49 -0
  97. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nuxt.mjs +161 -0
  98. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/script-src.mjs +17 -0
  99. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/static-html.mjs +26 -0
  100. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/sveltekit.mjs +71 -0
  101. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tag-strategy.mjs +247 -0
  102. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tanstack-start.mjs +70 -0
  103. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/vite-generic.mjs +42 -0
  104. package/template/.agents/skills/design/impeccable/scripts/live/generation-preflight.mjs +149 -0
  105. package/template/.agents/skills/design/impeccable/scripts/live/instructions.mjs +142 -0
  106. package/template/.agents/skills/design/impeccable/scripts/live/poll-lanes.mjs +14 -0
  107. package/template/.agents/skills/design/impeccable/scripts/live/roots.mjs +508 -0
  108. package/template/.agents/skills/design/impeccable/scripts/live/session-store.mjs +324 -50
  109. package/template/.agents/skills/design/impeccable/scripts/live/source-lock.mjs +105 -0
  110. package/template/.agents/skills/design/impeccable/scripts/live/source-search.mjs +105 -0
  111. package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +961 -0
  112. package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +588 -72
  113. package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +59 -17
  114. package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
  115. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +135 -0
  116. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +210 -68
  117. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1562 -223
  118. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
  119. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +33 -1
  120. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +175 -255
  121. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +26 -6
  122. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +6 -1
  123. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +61 -16
  124. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +39 -10
  125. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +573 -47
  126. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +17 -7
  127. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +124 -91
  128. package/template/.agents/skills/design/impeccable/scripts/live.mjs +88 -26
  129. package/template/.agents/skills/design/impeccable/scripts/palette.mjs +76 -81
  130. package/template/.agents/skills/design/impeccable/scripts/pin.mjs +18 -11
  131. package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +932 -0
  132. package/template/.agents/skills/design/impeccable/scripts/surface-brief.mjs +74 -0
  133. package/template/.agents/skills/design/shadcn/SKILL.md +46 -11
  134. package/template/.agents/skills/design/shadcn/cli.md +49 -16
  135. package/template/.agents/skills/design/shadcn/customization.md +14 -7
  136. package/template/.agents/skills/design/shadcn/evals/evals.json +30 -0
  137. package/template/.agents/skills/design/shadcn/mcp.md +27 -16
  138. package/template/.agents/skills/design/shadcn/registry.md +277 -0
  139. package/template/.agents/skills/design/shadcn/rules/chat.md +224 -0
  140. package/template/.agents/skills/design/shadcn/rules/composition.md +20 -2
  141. package/template/.agents/skills/design/shadcn/rules/styling.md +23 -0
  142. package/template/.agents/skills/find-skills/SKILL.md +2 -3
  143. package/template/.agents/skills/hono/SKILL.md +579 -0
  144. package/template/.agents/skills/integrations/langfuse/SKILL.md +10 -6
  145. package/template/.agents/skills/integrations/langfuse/references/ci-cd.md +41 -0
  146. package/template/.agents/skills/integrations/langfuse/references/cli.md +8 -0
  147. package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +15 -27
  148. package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +39 -52
  149. package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +5 -2
  150. package/template/.agents/skills/integrations/langfuse/references/prompt-engineering.md +35 -0
  151. package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +41 -196
  152. package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +3 -0
  153. package/template/.agents/skills/integrations/langfuse/references/trace-evaluator-upgrade.md +76 -0
  154. package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -0
  155. package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +73 -0
  156. package/template/.agents/skills/supabase/CHANGELOG.md +71 -0
  157. package/template/.agents/skills/supabase/SKILL.md +145 -0
  158. package/template/.agents/skills/supabase/assets/feedback-issue-template.md +17 -0
  159. package/template/.agents/skills/supabase/references/skill-feedback.md +17 -0
  160. package/template/.agents/skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
  161. package/template/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  162. package/template/.agents/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
  163. package/template/.agents/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
  164. package/template/.agents/skills/supabase-postgres-best-practices/references/_template.md +34 -0
  165. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  166. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  167. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  168. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  169. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  170. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  171. package/template/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  172. package/template/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  173. package/template/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  174. package/template/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  175. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  176. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  177. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  178. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  179. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  180. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  181. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  182. package/template/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  183. package/template/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  184. package/template/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
  185. package/template/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  186. package/template/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  187. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
  188. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  189. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  190. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  191. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  192. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  193. package/template/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  194. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  195. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
  196. package/template/.agents/upstreams.json +34 -12
  197. package/template/.agents/skills/design/impeccable/reference/brand.md +0 -108
  198. package/template/.agents/skills/design/impeccable/reference/codex.md +0 -105
  199. package/template/.agents/skills/design/impeccable/reference/interaction-design.md +0 -189
  200. package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +0 -175
@@ -1,9 +1,18 @@
1
+ // A preview whose variants live in component modules rather than in the user's
2
+ // source. These leave no markers in the real file, so a failed accept gives the
3
+ // agent nothing to hand-edit and must be reported as a failure rather than
4
+ // reference/live.md's manual-cleanup handoff. Kept as a set: any future
5
+ // component-module preview mode belongs here the day it lands.
6
+ const PREVIEW_MODES_WITHOUT_SOURCE_MARKERS = new Set([
7
+ 'svelte-component',
8
+ ]);
9
+
1
10
  export function completionTypeForAcceptResult(eventType, acceptResult) {
2
11
  if (eventType === 'discard') return acceptResult?.handled === true ? 'discarded' : 'error';
3
12
  if (acceptResult?.handled === true && acceptResult?.carbonize === true) return 'agent_done';
4
13
  if (acceptResult?.handled === true) return 'complete';
5
14
  if (acceptResult?.mode === 'error') return 'error';
6
- if (eventType === 'accept' && acceptResult?.previewMode === 'svelte-component') return 'error';
15
+ if (eventType === 'accept' && PREVIEW_MODES_WITHOUT_SOURCE_MARKERS.has(acceptResult?.previewMode)) return 'error';
7
16
  return 'agent_done';
8
17
  }
9
18
 
@@ -5,17 +5,26 @@
5
5
 
6
6
  import { canCreateInsert } from './insert-ui.mjs';
7
7
 
8
- // The accepted visual action values come from the canonical vocabulary so the
9
- // validator, the picker UI, and the marketing demo never drift. Imported (not
10
- // just re-exported) so it is also in scope for the validators below.
11
- import { VISUAL_ACTIONS } from './vocabulary.mjs';
12
- export { VISUAL_ACTIONS };
8
+ // The accepted protocol values come from the canonical vocabulary so the
9
+ // validator, the store, the server, and the picker UI never drift. Imported
10
+ // (not just re-exported) so they are also in scope for the validators below.
11
+ import { AGENT_PHASES, CLIENT_EVENT_TYPES, VISUAL_ACTIONS } from './vocabulary.mjs';
12
+ export { AGENT_PHASES, CLIENT_EVENT_TYPES, VISUAL_ACTIONS };
13
+
14
+ const AGENT_PHASE_SET = new Set(AGENT_PHASES);
13
15
 
14
16
  const ID_PATTERN = /^[0-9a-f]{8}$/;
15
17
  const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/;
16
18
  const INSERT_POSITIONS = new Set(['before', 'after']);
17
19
  const FORBIDDEN_MANUAL_EDIT_TEXT_CHARS = ['<', '{', '}', '`'];
18
20
 
21
+ // Mount acknowledgements carry a module URL and a raw exception message from
22
+ // the page. Both are attacker-adjacent (any script on the page can POST them
23
+ // with the token it can already read), so they are length-capped before they
24
+ // reach the journal.
25
+ export const MOUNT_URL_MAX_LENGTH = 2000;
26
+ export const MOUNT_ERROR_MAX_LENGTH = 1000;
27
+
19
28
  function isValidId(v) { return typeof v === 'string' && ID_PATTERN.test(v); }
20
29
  function isValidVariantId(v) { return typeof v === 'string' && VARIANT_ID_PATTERN.test(v); }
21
30
 
@@ -92,6 +101,36 @@ function validateManualEditEvent(msg, label) {
92
101
  return null;
93
102
  }
94
103
 
104
+ function isValidMountVariant(value) {
105
+ return Number.isInteger(value) && value >= 1 && value <= 999;
106
+ }
107
+
108
+ /**
109
+ * Mount acknowledgements are the browser's answer to "did the thing you
110
+ * published actually render". They are validated strictly because the render
111
+ * truth in the session snapshot is built from them: a malformed ack that slid
112
+ * through would report a variant as mounted that never was.
113
+ */
114
+ function validateMountAck(msg) {
115
+ if (!isValidId(msg.id)) return 'variant_mounted: missing or malformed id';
116
+ if (!isValidMountVariant(msg.variant)) return 'variant_mounted: variant must be an integer 1-999';
117
+ if (msg.url !== undefined) {
118
+ if (typeof msg.url !== 'string') return 'variant_mounted: url must be string';
119
+ if (msg.url.length > MOUNT_URL_MAX_LENGTH) return 'variant_mounted: url too long';
120
+ }
121
+ return null;
122
+ }
123
+
124
+ function validateMountFailure(msg) {
125
+ if (!isValidId(msg.id)) return 'variant_mount_failed: missing or malformed id';
126
+ if (!isValidMountVariant(msg.variant)) return 'variant_mount_failed: variant must be an integer 1-999';
127
+ if (typeof msg.url !== 'string' || !msg.url.trim()) return 'variant_mount_failed: url required';
128
+ if (msg.url.length > MOUNT_URL_MAX_LENGTH) return 'variant_mount_failed: url too long';
129
+ if (typeof msg.error !== 'string' || !msg.error.trim()) return 'variant_mount_failed: error required';
130
+ if (msg.error.length > MOUNT_ERROR_MAX_LENGTH) return 'variant_mount_failed: error too long';
131
+ return null;
132
+ }
133
+
95
134
  export function validateEvent(msg) {
96
135
  if (!msg || typeof msg !== 'object' || !msg.type) return 'Missing or invalid message';
97
136
  switch (msg.type) {
@@ -118,6 +157,23 @@ export function validateEvent(msg) {
118
157
  return 'checkpoint: paramValues must be an object';
119
158
  }
120
159
  return null;
160
+ case 'agent_phase':
161
+ if (!isValidId(msg.id)) return 'agent_phase: missing or malformed id';
162
+ if (typeof msg.phase !== 'string' || !msg.phase) return 'agent_phase: missing phase';
163
+ // The enum, not a shape pattern. A phase the browser cannot rank is a
164
+ // phase the progress bar cannot show, so accepting an arbitrary
165
+ // lowercase word only defers the failure to the UI.
166
+ if (!AGENT_PHASE_SET.has(msg.phase)) {
167
+ return 'agent_phase: unknown phase ' + msg.phase + ' (expected one of ' + AGENT_PHASES.join(', ') + ')';
168
+ }
169
+ if (msg.durationMs !== undefined && (!Number.isFinite(msg.durationMs) || msg.durationMs < 0)) {
170
+ return 'agent_phase: durationMs must be a non-negative number';
171
+ }
172
+ return null;
173
+ case 'variant_mounted':
174
+ return validateMountAck(msg);
175
+ case 'variant_mount_failed':
176
+ return validateMountFailure(msg);
121
177
  case 'exit':
122
178
  return null;
123
179
  case 'prefetch':
@@ -131,6 +187,12 @@ export function validateEvent(msg) {
131
187
  if (msg.message.length > 4000) return 'steer: message too long';
132
188
  if (msg.pageUrl !== undefined && typeof msg.pageUrl !== 'string') return 'steer: pageUrl must be string';
133
189
  return null;
190
+ case 'carbonize_cleanup':
191
+ if (!isValidId(msg.id)) return 'carbonize_cleanup: missing or malformed id';
192
+ if (!isValidId(msg.sessionId)) return 'carbonize_cleanup: missing or malformed sessionId';
193
+ if (!msg.file || typeof msg.file !== 'string') return 'carbonize_cleanup: missing file';
194
+ if (!isValidVariantId(String(msg.variantId))) return 'carbonize_cleanup: missing or malformed variantId';
195
+ return null;
134
196
  default:
135
197
  return 'Unknown event type: ' + msg.type;
136
198
  }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Astro registry entry.
3
+ *
4
+ * Astro takes the generic tag strategy, with two Astro-specific values that
5
+ * used to sit as inline `endsWith('.astro')` branches in live-inject.mjs and
6
+ * live-wrap.mjs:
7
+ *
8
+ * injectScriptAttrs Astro processes <script> tags by default and rewrites
9
+ * src to its own bundled URL; is:inline opts out.
10
+ * styleMode Astro scopes component styles, which strips preview CSS
11
+ * off the generated variant wrappers, so preview rules are
12
+ * authored global and prefixed instead of @scope'd.
13
+ */
14
+
15
+ import { findConfigFile, hasAnyDependency, literalConfigFiles } from './detect-utils.mjs';
16
+
17
+ const ASTRO_CONFIG_RE = /^astro\.config\.(?:js|mjs|cjs|ts|mts|cts)$/;
18
+
19
+ export function detectAstroProject(cwd = process.cwd(), config = null) {
20
+ const configFile = findConfigFile(cwd, ASTRO_CONFIG_RE);
21
+ if (configFile) return { configFile, via: 'config' };
22
+ if (hasAnyDependency(cwd, ['astro'])) return { configFile: null, via: 'package' };
23
+ // A tree of .astro entry templates with no astro.config still belongs to
24
+ // Astro; the configured injection target names it.
25
+ const entry = literalConfigFiles(cwd, config).find((rel) => rel.endsWith('.astro'));
26
+ if (entry) return { configFile: null, via: 'config-files', entry };
27
+ return null;
28
+ }
29
+
30
+ export const astro = {
31
+ name: 'astro',
32
+
33
+ detect(cwd, config) {
34
+ return detectAstroProject(cwd, config);
35
+ },
36
+
37
+ inject: { kind: 'tag' },
38
+
39
+ source: {
40
+ extensions: ['.astro'],
41
+ preview: 'source',
42
+ styleMode: 'astro-global-prefixed',
43
+ styleTag: '<style is:inline data-impeccable-css="SESSION_ID">',
44
+ commentSyntax: 'html',
45
+ injectScriptAttrs: 'is:inline ',
46
+ },
47
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Small read-only probes the framework entries share.
3
+ *
4
+ * Every helper here is cheap and failure-tolerant: detection runs on every
5
+ * inject, against project trees that may be half-installed, so a missing or
6
+ * malformed file means "not this framework", never a throw.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+
12
+ /** Merged dependency names from package.json, or an empty object. */
13
+ export function readPackageDeps(cwd) {
14
+ const file = path.join(cwd, 'package.json');
15
+ try {
16
+ const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));
17
+ return {
18
+ ...(pkg.dependencies || {}),
19
+ ...(pkg.devDependencies || {}),
20
+ ...(pkg.peerDependencies || {}),
21
+ };
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+
27
+ export function hasAnyDependency(cwd, names) {
28
+ const deps = readPackageDeps(cwd);
29
+ return names.some((name) => Boolean(deps[name]));
30
+ }
31
+
32
+ /** First top-level file name matching `re`, or null. */
33
+ export function findConfigFile(cwd, re) {
34
+ try {
35
+ return fs.readdirSync(cwd, { withFileTypes: true })
36
+ .find((entry) => entry.isFile() && re.test(entry.name))
37
+ ?.name ?? null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ export function fileExists(cwd, rel) {
44
+ try {
45
+ return fs.existsSync(path.join(cwd, rel));
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ export function firstExistingFile(cwd, candidates) {
52
+ for (const rel of candidates) {
53
+ if (fileExists(cwd, rel)) return rel;
54
+ }
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Literal (non-glob) entries of `config.files` that exist on disk. Several
60
+ * detectors read the configured injection target as a signal, which is how the
61
+ * bare fixtures — a tree of `.astro` files with no astro.config — still resolve
62
+ * to the framework that authored them.
63
+ */
64
+ export function literalConfigFiles(cwd, config) {
65
+ const files = Array.isArray(config?.files) ? config.files : [];
66
+ const out = [];
67
+ for (const rel of files) {
68
+ if (typeof rel !== 'string' || rel.includes('*') || rel.includes('?')) continue;
69
+ const normalized = rel.split(path.sep).join('/');
70
+ if (fileExists(cwd, normalized)) out.push(normalized);
71
+ }
72
+ return out;
73
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The live-mode framework registry.
3
+ *
4
+ * Before this existed, framework knowledge was smeared across live-inject.mjs
5
+ * (detection order, the Nuxt adapter, the Astro `is:inline` branch), the two
6
+ * adapter modules, and live-wrap.mjs (which extension gets component preview,
7
+ * which gets Astro's global-prefixed CSS, which gets JSX comments). Adding or
8
+ * fixing a framework meant reading all of them.
9
+ *
10
+ * One entry per framework now declares everything the live scripts need:
11
+ *
12
+ * name stable identifier; also the `adapter` value in inject JSON.
13
+ * detect (cwd, config) → falsy when this is not the project, otherwise
14
+ * a truthy project descriptor that apply/remove/artifacts read.
15
+ * Order in FRAMEWORKS is priority order; first truthy wins.
16
+ * inject { kind: 'adapter', apply, remove, ignorePatterns, artifacts,
17
+ * unpatch } for frameworks that server-render their document
18
+ * shell, or { kind: 'tag' } for the generic marker-wrapped
19
+ * <script src> block.
20
+ * source how live-wrap treats files this framework authors:
21
+ * extensions, preview ('source' | 'component'), styleMode,
22
+ * styleTag, commentSyntax, injectScriptAttrs. Anything omitted
23
+ * falls back to SOURCE_TRAIT_DEFAULTS.
24
+ *
25
+ * Two rules hold the thing together:
26
+ *
27
+ * 1. **Detection order is injection priority.** SvelteKit → Nuxt → TanStack
28
+ * Start → Astro → Next → Vite → static HTML, exactly the order
29
+ * live-inject.mjs used to hard-code. static-html always matches, so
30
+ * resolveFramework never returns null.
31
+ * 2. **Source traits resolve by file extension, not by project.** A SvelteKit
32
+ * project's injection target is `src/app.html`; a Vite app can contain
33
+ * `.astro` partials. live-wrap has always keyed these off the target file,
34
+ * and resolveSourceTraits keeps it that way. Several entries may claim the
35
+ * same extension (`.tsx` belongs to three); when they do, the values must
36
+ * agree, which tests/live-frameworks.test.mjs asserts.
37
+ */
38
+
39
+ import path from 'node:path';
40
+
41
+ import { sveltekit } from './sveltekit.mjs';
42
+ import { nuxt } from './nuxt.mjs';
43
+ import { tanstackStart } from './tanstack-start.mjs';
44
+ import { astro } from './astro.mjs';
45
+ import { nextjs } from './nextjs.mjs';
46
+ import { viteGeneric } from './vite-generic.mjs';
47
+ import { staticHtml } from './static-html.mjs';
48
+ import { TAG_PATCH_MARKERS, unpatchTagFile } from './tag-strategy.mjs';
49
+
50
+ /** Priority order. Do not reorder without re-reading rule 1 above. */
51
+ export const FRAMEWORKS = Object.freeze([
52
+ sveltekit,
53
+ nuxt,
54
+ tanstackStart,
55
+ astro,
56
+ nextjs,
57
+ viteGeneric,
58
+ staticHtml,
59
+ ]);
60
+
61
+ export const PREVIEW_MODES = Object.freeze(['source', 'component']);
62
+ export const STYLE_MODES = Object.freeze(['scoped', 'astro-global-prefixed']);
63
+ export const COMMENT_SYNTAXES = Object.freeze(['html', 'jsx']);
64
+ export const INJECT_KINDS = Object.freeze(['adapter', 'tag']);
65
+
66
+ export const SOURCE_TRAIT_DEFAULTS = Object.freeze({
67
+ preview: 'source',
68
+ styleMode: 'scoped',
69
+ styleTag: '<style data-impeccable-css="SESSION_ID">',
70
+ commentSyntax: 'html',
71
+ injectScriptAttrs: '',
72
+ });
73
+
74
+ /** The patch kind the generic tag strategy records in the journal. */
75
+ export const TAG_PATCH_KIND = 'live-tag';
76
+
77
+ /**
78
+ * Undo functions keyed by the `patch` value an artifact carries. Built from
79
+ * the entries so a new adapter registers its own undo alongside its apply.
80
+ */
81
+ export const PATCH_UNDOERS = Object.freeze(Object.assign(
82
+ { [TAG_PATCH_KIND]: unpatchTagFile },
83
+ ...FRAMEWORKS.map((framework) => framework.inject.unpatch || {}),
84
+ ));
85
+
86
+ /**
87
+ * First entry whose detect() matches. Returns { framework, project } where
88
+ * project is the detector's descriptor (adapters read it; tag frameworks
89
+ * mostly ignore it).
90
+ */
91
+ export function resolveFramework(cwd = process.cwd(), config = null) {
92
+ for (const framework of FRAMEWORKS) {
93
+ const project = framework.detect(cwd, config);
94
+ if (project) return { framework, project };
95
+ }
96
+ // Unreachable while static-html stays terminal, but a caller that reorders
97
+ // the array should get a diagnosable null rather than a silent tag inject.
98
+ return null;
99
+ }
100
+
101
+ /**
102
+ * Source-authoring traits for one file, merged over SOURCE_TRAIT_DEFAULTS.
103
+ * `framework` names the entry that claimed the extension, or null.
104
+ */
105
+ export function resolveSourceTraits(filePath) {
106
+ const ext = path.extname(String(filePath || '')).toLowerCase();
107
+ for (const framework of FRAMEWORKS) {
108
+ const source = framework.source;
109
+ if (!source || !source.extensions.includes(ext)) continue;
110
+ const { extensions, ...traits } = source;
111
+ return { framework: framework.name, ...SOURCE_TRAIT_DEFAULTS, ...traits };
112
+ }
113
+ return { framework: null, ...SOURCE_TRAIT_DEFAULTS };
114
+ }
115
+
116
+ /**
117
+ * Extra gitignore patterns the resolved framework needs beyond the static
118
+ * LIVE_IGNORE_PATTERNS list (paths that depend on a detected srcDir or file
119
+ * extension and so cannot be written down ahead of time).
120
+ */
121
+ export function frameworkIgnorePatterns(resolved) {
122
+ const fn = resolved?.framework?.inject?.ignorePatterns;
123
+ return typeof fn === 'function' ? (fn(resolved.project) || []) : [];
124
+ }
125
+
126
+ /**
127
+ * The files this injection will create or patch, in journal-artifact form.
128
+ * Adapters declare their own; the tag strategy patches exactly the resolved
129
+ * config files.
130
+ */
131
+ export function describeInjectArtifacts(resolved, { cwd = process.cwd(), files = [] } = {}) {
132
+ if (!resolved) return [];
133
+ const { framework, project } = resolved;
134
+ if (framework.inject.kind === 'adapter') {
135
+ return (framework.inject.artifacts?.({ cwd, project }) || []).filter((a) => a && a.path);
136
+ }
137
+ return files.map((file) => ({
138
+ kind: 'patched',
139
+ path: file,
140
+ patch: TAG_PATCH_KIND,
141
+ markers: [...TAG_PATCH_MARKERS],
142
+ }));
143
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Crash-safe injection journal.
3
+ *
4
+ * Injection writes into the user's source tree: generated components, a Nuxt
5
+ * client plugin, marker blocks inside a layout, a patched CSP meta tag. The
6
+ * clean path removes all of it on stop. The unclean paths do not:
7
+ *
8
+ * - the dev server is SIGKILLed, so `--remove` never runs;
9
+ * - the project changes shape between start and stop (a nuxt.config appears,
10
+ * a package.json is edited), so detection resolves a different framework
11
+ * and the old framework's artifacts are nobody's business;
12
+ * - stop runs from a different directory than start did.
13
+ *
14
+ * So every inject records what it wrote to `.impeccable/live/inject-journal.json`
15
+ * before the next one runs, and both inject and `--remove` reconcile that
16
+ * record against the tree.
17
+ *
18
+ * **The journal is a claim of ownership, not a to-do list.** Healing an
19
+ * artifact only ever removes what still carries our marker; a generated file
20
+ * the user has since replaced, or a layout they have since un-patched by hand,
21
+ * is dropped from the journal untouched.
22
+ *
23
+ * **Path resolution is appRoot-relative.** Live entry scripts chdir onto the
24
+ * roots manifest (`enterLiveRoot`) before doing anything, so a journal written
25
+ * by a session started in the app root is found by a stop issued from any
26
+ * directory inside the repo.
27
+ */
28
+
29
+ import fs from 'node:fs';
30
+ import path from 'node:path';
31
+ import { PATCH_UNDOERS } from './index.mjs';
32
+
33
+ export const INJECT_JOURNAL_VERSION = 1;
34
+ export const INJECT_JOURNAL_RELPATH = '.impeccable/live/inject-journal.json';
35
+
36
+ export function injectJournalPath(cwd = process.cwd()) {
37
+ return path.join(cwd, ...INJECT_JOURNAL_RELPATH.split('/'));
38
+ }
39
+
40
+ export function readInjectJournal(cwd = process.cwd()) {
41
+ const file = injectJournalPath(cwd);
42
+ let raw;
43
+ try {
44
+ raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
45
+ } catch {
46
+ return null;
47
+ }
48
+ if (!raw || typeof raw !== 'object' || !Array.isArray(raw.artifacts)) return null;
49
+ return raw;
50
+ }
51
+
52
+ export function clearInjectJournal(cwd = process.cwd()) {
53
+ try { fs.unlinkSync(injectJournalPath(cwd)); } catch { /* already gone */ }
54
+ }
55
+
56
+ function writeInjectJournal(cwd, journal) {
57
+ const file = injectJournalPath(cwd);
58
+ fs.mkdirSync(path.dirname(file), { recursive: true });
59
+ fs.writeFileSync(file, JSON.stringify(journal, null, 2) + '\n', 'utf-8');
60
+ return file;
61
+ }
62
+
63
+ /**
64
+ * Record the artifacts an injection just wrote. Replaces any previous record:
65
+ * callers heal first (see healInjectJournal), so nothing survivable is lost.
66
+ */
67
+ export function recordInjection(cwd = process.cwd(), { framework, port, artifacts = [] } = {}) {
68
+ if (!artifacts.length) {
69
+ clearInjectJournal(cwd);
70
+ return null;
71
+ }
72
+ return writeInjectJournal(cwd, {
73
+ version: INJECT_JOURNAL_VERSION,
74
+ appRoot: path.resolve(cwd),
75
+ framework: framework || null,
76
+ port: Number.isFinite(Number(port)) ? Number(port) : null,
77
+ pid: process.pid,
78
+ recordedAt: new Date().toISOString(),
79
+ artifacts,
80
+ });
81
+ }
82
+
83
+ function normalizeRel(cwd, rel) {
84
+ return path.resolve(cwd, String(rel || '')).split(path.sep).join('/');
85
+ }
86
+
87
+ function readIfPresent(abs) {
88
+ try {
89
+ return fs.readFileSync(abs, 'utf-8');
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ function pruneEmptyDirs(dir, stopDir) {
96
+ let current = path.resolve(dir);
97
+ const stop = path.resolve(stopDir);
98
+ while (current !== stop && current.startsWith(stop + path.sep)) {
99
+ try {
100
+ if (fs.readdirSync(current).length > 0) return;
101
+ fs.rmdirSync(current);
102
+ } catch {
103
+ return;
104
+ }
105
+ current = path.dirname(current);
106
+ }
107
+ }
108
+
109
+ function insideProject(cwd, abs) {
110
+ const rel = path.relative(path.resolve(cwd), path.resolve(abs));
111
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
112
+ }
113
+
114
+ function healArtifact(cwd, artifact, undoers) {
115
+ const abs = path.resolve(cwd, artifact.path);
116
+ // The journal is a project-local file, i.e. attacker-writable input in a
117
+ // cloned repo. Never touch anything outside the project tree, whatever the
118
+ // journal claims to own.
119
+ if (!insideProject(cwd, abs)) return { path: artifact.path, action: 'refused_outside_project' };
120
+ const content = readIfPresent(abs);
121
+ if (content === null) return { path: artifact.path, action: 'absent' };
122
+
123
+ if (artifact.kind === 'created') {
124
+ // Only reclaim a generated file that still carries our marker; a created
125
+ // artifact with no marker at all is unverifiable and stays untouched.
126
+ if (!artifact.marker || !content.includes(artifact.marker)) {
127
+ return { path: artifact.path, action: 'disowned' };
128
+ }
129
+ try { fs.rmSync(abs, { force: true }); } catch { return null; }
130
+ if (artifact.pruneTo !== undefined) {
131
+ const pruneRoot = path.resolve(cwd, artifact.pruneTo || '.');
132
+ if (insideProject(cwd, pruneRoot) || pruneRoot === path.resolve(cwd)) {
133
+ pruneEmptyDirs(path.dirname(abs), pruneRoot);
134
+ }
135
+ }
136
+ return { path: artifact.path, action: 'removed' };
137
+ }
138
+
139
+ if (artifact.kind === 'patched') {
140
+ const markers = Array.isArray(artifact.markers) ? artifact.markers : [];
141
+ // No marker left means the patch is already gone; never run an undo over
142
+ // a file we no longer recognize (the undoers normalize whitespace).
143
+ if (markers.length && !markers.some((marker) => content.includes(marker))) {
144
+ return { path: artifact.path, action: 'disowned' };
145
+ }
146
+ const undo = undoers[artifact.patch];
147
+ if (typeof undo !== 'function') return null;
148
+ const next = undo(content);
149
+ if (next === content) return { path: artifact.path, action: 'disowned' };
150
+ try { fs.writeFileSync(abs, next, 'utf-8'); } catch { return null; }
151
+ return { path: artifact.path, action: 'unpatched' };
152
+ }
153
+
154
+ return null;
155
+ }
156
+
157
+ /**
158
+ * Reconcile the journal against the tree.
159
+ *
160
+ * `keep` is the set of paths the current operation legitimately owns — the
161
+ * artifacts an inject is about to (re)write. Everything else in the journal is
162
+ * an orphan of a session that is gone, and gets healed. This keeps a repeat
163
+ * inject byte-idempotent: the artifacts it is about to rewrite are kept, not
164
+ * torn down and rebuilt.
165
+ *
166
+ * Returns `{ healed, kept }`. `healed` lists only artifacts whose file was
167
+ * actually changed or removed, so callers can stay silent when nothing was
168
+ * orphaned. Idempotent: a second call finds an empty journal.
169
+ */
170
+ export function healInjectJournal(cwd = process.cwd(), { keep = [], undoers = PATCH_UNDOERS } = {}) {
171
+ const journal = readInjectJournal(cwd);
172
+ if (!journal) return { healed: [], kept: [] };
173
+
174
+ const keepSet = new Set(keep.map((rel) => normalizeRel(cwd, rel)));
175
+ const healed = [];
176
+ const kept = [];
177
+
178
+ for (const artifact of journal.artifacts) {
179
+ if (!artifact || typeof artifact.path !== 'string') continue;
180
+ if (keepSet.has(normalizeRel(cwd, artifact.path))) {
181
+ kept.push(artifact);
182
+ continue;
183
+ }
184
+ const outcome = healArtifact(cwd, artifact, undoers);
185
+ if (outcome && (outcome.action === 'removed' || outcome.action === 'unpatched')) {
186
+ healed.push(outcome);
187
+ }
188
+ }
189
+
190
+ if (kept.length) {
191
+ writeInjectJournal(cwd, { ...journal, artifacts: kept });
192
+ } else {
193
+ clearInjectJournal(cwd);
194
+ }
195
+
196
+ return { healed, kept };
197
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Next.js registry entry.
3
+ *
4
+ * Next takes the generic tag strategy: the App Router's root layout renders
5
+ * `<html>…<body>` in JSX, so the marker-wrapped script block goes in there
6
+ * verbatim. Nothing about injection differs from a plain Vite app, which is
7
+ * why live-inject.mjs never had a Next branch. The entry exists so the
8
+ * registry can name what it is looking at.
9
+ */
10
+
11
+ import { fileExists, findConfigFile, hasAnyDependency } from './detect-utils.mjs';
12
+
13
+ const NEXT_CONFIG_RE = /^next\.config\.(?:js|mjs|cjs|ts|mts|cts)$/;
14
+
15
+ const ROUTER_ENTRY_CANDIDATES = [
16
+ 'app/layout.tsx', 'app/layout.jsx', 'app/layout.ts', 'app/layout.js',
17
+ 'src/app/layout.tsx', 'src/app/layout.jsx', 'src/app/layout.ts', 'src/app/layout.js',
18
+ 'pages/_app.tsx', 'pages/_app.jsx', 'pages/_app.ts', 'pages/_app.js',
19
+ 'pages/_document.tsx', 'pages/_document.jsx',
20
+ 'src/pages/_app.tsx', 'src/pages/_app.jsx',
21
+ ];
22
+
23
+ export function detectNextProject(cwd = process.cwd()) {
24
+ const configFile = findConfigFile(cwd, NEXT_CONFIG_RE);
25
+ if (configFile) return { configFile, via: 'config' };
26
+ if (hasAnyDependency(cwd, ['next'])) return { configFile: null, via: 'package' };
27
+ // Next's file conventions are distinctive enough to stand alone: a root
28
+ // `app/layout.*` or `pages/_app.*` is not a shape other bundlers produce.
29
+ const entry = ROUTER_ENTRY_CANDIDATES.find((rel) => fileExists(cwd, rel));
30
+ if (entry) return { configFile: null, via: 'router-entry', entry };
31
+ return null;
32
+ }
33
+
34
+ export const nextjs = {
35
+ name: 'nextjs',
36
+
37
+ detect(cwd) {
38
+ return detectNextProject(cwd);
39
+ },
40
+
41
+ inject: { kind: 'tag' },
42
+
43
+ source: {
44
+ extensions: ['.tsx', '.jsx'],
45
+ preview: 'source',
46
+ styleMode: 'scoped',
47
+ commentSyntax: 'jsx',
48
+ },
49
+ };