inkbridge 0.1.0-beta.2 → 0.1.0-beta.21

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 (178) hide show
  1. package/README.md +108 -25
  2. package/bin/inkbridge.mjs +354 -83
  3. package/code.js +40 -11802
  4. package/manifest.json +1 -0
  5. package/package.json +74 -23
  6. package/scanner/adapter-utils-regression.ts +159 -0
  7. package/scanner/aspect-percent-position-regression.ts +237 -0
  8. package/scanner/aspect-ratio-regression.ts +90 -0
  9. package/scanner/blob-placement-regression.ts +2 -2
  10. package/scanner/block-cache-regression.ts +195 -0
  11. package/scanner/bundle-size-regression.ts +50 -0
  12. package/scanner/child-sizing-matrix-regression.ts +303 -0
  13. package/scanner/cli.ts +342 -13
  14. package/scanner/component-scanner.ts +2108 -174
  15. package/scanner/component-sections-regression.ts +198 -0
  16. package/scanner/compound-classes-lookup-regression.ts +163 -0
  17. package/scanner/css-token-reader-regression.ts +7 -6
  18. package/scanner/css-token-reader.ts +152 -31
  19. package/scanner/cva-jsx-child-fallback-regression.ts +98 -0
  20. package/scanner/cva-master-icon-regression.ts +315 -0
  21. package/scanner/data-attr-prop-alias-regression.ts +129 -0
  22. package/scanner/explicit-size-root-regression.ts +102 -0
  23. package/scanner/font-family-extract-regression.ts +113 -0
  24. package/scanner/font-style-resolver-regression.ts +1 -1
  25. package/scanner/framework-adapter-shadcn-regression.ts +480 -0
  26. package/scanner/full-width-matrix-regression.ts +338 -0
  27. package/scanner/grid-cols-extraction-regression.ts +110 -0
  28. package/scanner/image-src-collector-regression.ts +204 -0
  29. package/scanner/inline-flex-regression.ts +235 -0
  30. package/scanner/input-range-regression.ts +217 -0
  31. package/scanner/instance-rendering-regression.ts +224 -0
  32. package/scanner/jsx-prop-unresolved-regression.ts +178 -0
  33. package/scanner/jsx-text-regression.ts +178 -0
  34. package/scanner/layout-alignment-regression.ts +108 -0
  35. package/scanner/layout-flex-regression.ts +90 -0
  36. package/scanner/layout-mode-regression.ts +71 -0
  37. package/scanner/layout-sizing-regression.ts +227 -0
  38. package/scanner/layout-spacing-regression.ts +135 -0
  39. package/scanner/local-const-className-regression.ts +331 -0
  40. package/scanner/percent-position-regression.ts +105 -0
  41. package/scanner/provider-cascade-regression.ts +224 -0
  42. package/scanner/provider-flatten-regression.ts +235 -0
  43. package/scanner/radial-gradient-regression.ts +1 -1
  44. package/scanner/render-prop-parser-regression.ts +161 -0
  45. package/scanner/ring-utility-regression.ts +153 -0
  46. package/scanner/sandbox-spread-regression.ts +125 -0
  47. package/scanner/selection-pressed-regression.ts +241 -0
  48. package/scanner/size-full-normalization-regression.ts +127 -0
  49. package/scanner/state-classification-regression.ts +175 -0
  50. package/scanner/story-diagnostics-regression.ts +216 -0
  51. package/scanner/story-dimensioning-regression.ts +298 -0
  52. package/scanner/story-render-strategy-regression.ts +205 -0
  53. package/scanner/stretch-to-parent-width-regression.ts +147 -0
  54. package/scanner/svg-fill-parent-regression.ts +98 -0
  55. package/scanner/svg-group-inheritance-regression.ts +166 -0
  56. package/scanner/svg-marker-inline-regression.ts +211 -0
  57. package/scanner/svg-marker-regression.ts +116 -0
  58. package/scanner/tailwind-parser.ts +46 -4
  59. package/scanner/text-resize-matrix-regression.ts +173 -0
  60. package/scanner/transform-math-regression.ts +1 -1
  61. package/scanner/types.ts +26 -2
  62. package/src/cache/frame-cache.ts +150 -0
  63. package/src/cache/index.ts +2 -0
  64. package/src/{component-defs.ts → components/component-defs.ts} +25 -10
  65. package/src/{component-gen.ts → components/component-gen.ts} +43 -116
  66. package/src/components/component-instance.ts +386 -0
  67. package/src/components/component-library.ts +44 -0
  68. package/src/components/component-lookup.ts +161 -0
  69. package/src/components/index.ts +7 -0
  70. package/src/components/scanner-types.ts +39 -0
  71. package/src/components/symbol-instance-policy.ts +312 -0
  72. package/src/design-system/block-cache.ts +130 -0
  73. package/src/design-system/component-sections.ts +107 -0
  74. package/src/design-system/cva-inference.ts +187 -0
  75. package/src/design-system/cva-master.ts +427 -0
  76. package/src/design-system/cva-utils.ts +29 -0
  77. package/src/design-system/design-system.ts +334 -0
  78. package/src/design-system/frame-stabilizers.ts +191 -0
  79. package/src/design-system/frame-utils.ts +46 -0
  80. package/src/design-system/generated-node.ts +84 -0
  81. package/src/design-system/icon-rendering.ts +229 -0
  82. package/src/design-system/index.ts +13 -0
  83. package/src/design-system/instance-rendering.ts +307 -0
  84. package/src/design-system/master-shared.ts +133 -0
  85. package/src/design-system/node-helpers.ts +237 -0
  86. package/src/design-system/node-variants.ts +196 -0
  87. package/src/design-system/non-cva-master.ts +104 -0
  88. package/src/design-system/portal-handling.ts +138 -0
  89. package/src/design-system/preview-builder.ts +738 -0
  90. package/src/{render-context.ts → design-system/render-context.ts} +32 -6
  91. package/src/design-system/render-prop-parser.ts +50 -0
  92. package/src/design-system/responsive-resolver.ts +180 -0
  93. package/src/design-system/selectable-state.ts +157 -0
  94. package/src/design-system/state-master.ts +267 -0
  95. package/src/design-system/state-utils.ts +15 -0
  96. package/src/design-system/story-builder-context.ts +40 -0
  97. package/src/design-system/story-builder.ts +1322 -0
  98. package/src/design-system/story-diagnostics.ts +80 -0
  99. package/src/design-system/story-dimensioning.ts +272 -0
  100. package/src/design-system/story-frames.ts +400 -0
  101. package/src/design-system/story-instance.ts +333 -0
  102. package/src/{story-layout.ts → design-system/story-layout.ts} +2 -2
  103. package/src/design-system/story-render-strategy.ts +150 -0
  104. package/src/design-system/story-tree-search.ts +110 -0
  105. package/src/design-system/symbol-fallback.ts +89 -0
  106. package/src/design-system/symbol-source.ts +172 -0
  107. package/src/design-system/table-helpers.ts +56 -0
  108. package/src/design-system/tag-predicates.ts +99 -0
  109. package/src/design-system/theme-context.ts +52 -0
  110. package/src/design-system/typography.ts +100 -0
  111. package/src/design-system/ui-builder.ts +2676 -0
  112. package/src/{clip-path-decorative.ts → effects/clip-path-decorative.ts} +11 -11
  113. package/src/effects/icon-builder.ts +1074 -0
  114. package/src/effects/index.ts +5 -0
  115. package/src/effects/portal-panel.ts +369 -0
  116. package/src/{radial-gradient.ts → effects/radial-gradient.ts} +1 -1
  117. package/src/framework-adapters/index.ts +47 -0
  118. package/src/framework-adapters/shadcn.ts +541 -0
  119. package/src/{github.ts → github/github.ts} +46 -21
  120. package/src/github/index.ts +1 -0
  121. package/src/layout/deferred-layout.ts +1556 -0
  122. package/src/layout/index.ts +24 -0
  123. package/src/layout/layout-parser.ts +375 -0
  124. package/src/{layout-utils.ts → layout/layout-utils.ts} +23 -17
  125. package/src/layout/parser/alignment.ts +54 -0
  126. package/src/layout/parser/flex.ts +59 -0
  127. package/src/layout/parser/index.ts +65 -0
  128. package/src/layout/parser/ir.ts +80 -0
  129. package/src/layout/parser/layout-mode.ts +57 -0
  130. package/src/layout/parser/sizing.ts +241 -0
  131. package/src/layout/parser/spacing-scale.ts +78 -0
  132. package/src/layout/parser/spacing.ts +134 -0
  133. package/src/layout/ring-utils.ts +120 -0
  134. package/src/layout/size-utils.ts +143 -0
  135. package/src/layout/text-resize-decision.ts +51 -0
  136. package/src/{width-solver.ts → layout/width-solver.ts} +168 -37
  137. package/src/main.ts +444 -162
  138. package/src/{config.ts → plugin/config.ts} +12 -12
  139. package/src/{dev-server.ts → plugin/dev-server.ts} +3 -3
  140. package/src/plugin/image-src-collector.ts +52 -0
  141. package/src/plugin/index.ts +3 -0
  142. package/src/plugin/packs/index.ts +2 -0
  143. package/src/{pack-provider.ts → plugin/packs/pack-provider.ts} +12 -12
  144. package/src/{packs.ts → plugin/packs/packs.ts} +22 -17
  145. package/src/render-engine-version.ts +2 -0
  146. package/src/tailwind/adapter-utils.ts +137 -0
  147. package/src/{class-utils.ts → tailwind/class-utils.ts} +33 -6
  148. package/src/tailwind/index.ts +8 -0
  149. package/src/tailwind/jsx-utils.ts +319 -0
  150. package/src/{node-ir.ts → tailwind/node-ir.ts} +208 -19
  151. package/src/{responsive-analyzer.ts → tailwind/responsive-analyzer.ts} +32 -2
  152. package/src/{state-analyzer.ts → tailwind/state-analyzer.ts} +71 -5
  153. package/src/{tailwind.ts → tailwind/tailwind.ts} +423 -674
  154. package/src/{utility-resolver.ts → tailwind/utility-resolver.ts} +27 -6
  155. package/src/{font-style-resolver.ts → text/font-style-resolver.ts} +0 -2
  156. package/src/text/index.ts +4 -0
  157. package/src/{inline-text.ts → text/inline-text.ts} +13 -13
  158. package/src/{text-builder.ts → text/text-builder.ts} +24 -7
  159. package/src/{text-line.ts → text/text-line.ts} +2 -2
  160. package/src/{change-detection.ts → tokens/change-detection.ts} +12 -12
  161. package/src/{color-resolver.ts → tokens/color-resolver.ts} +1 -6
  162. package/src/{colors.ts → tokens/colors.ts} +13 -6
  163. package/src/tokens/index.ts +6 -0
  164. package/src/{token-source.ts → tokens/token-source.ts} +4 -1
  165. package/src/{tokens.ts → tokens/tokens.ts} +116 -20
  166. package/src/{variables.ts → tokens/variables.ts} +447 -102
  167. package/templates/patch-tokens-route.ts +25 -6
  168. package/templates/scan-components-route.ts +26 -5
  169. package/ui.html +485 -37
  170. package/src/component-lookup.ts +0 -82
  171. package/src/design-system.ts +0 -59
  172. package/src/icon-builder.ts +0 -607
  173. package/src/layout-parser.ts +0 -667
  174. package/src/story-builder.ts +0 -1706
  175. package/src/ui-builder.ts +0 -1996
  176. /package/src/{image-cache.ts → cache/image-cache.ts} +0 -0
  177. /package/src/{blob-placement.ts → effects/blob-placement.ts} +0 -0
  178. /package/src/{transform-math.ts → tailwind/transform-math.ts} +0 -0
package/ui.html CHANGED
@@ -171,6 +171,50 @@
171
171
  text-decoration: underline;
172
172
  }
173
173
 
174
+ /* Pre-flight view */
175
+ .preflight-group-header {
176
+ font-size: 10px;
177
+ font-weight: 600;
178
+ color: #555;
179
+ text-transform: uppercase;
180
+ letter-spacing: 0.5px;
181
+ padding: 6px 12px 4px;
182
+ background: #f9f9f9;
183
+ border-bottom: 1px solid #eee;
184
+ position: sticky;
185
+ top: 0;
186
+ }
187
+ .preflight-item {
188
+ padding: 6px 12px;
189
+ border-bottom: 1px solid #eee;
190
+ display: flex;
191
+ align-items: center;
192
+ gap: 8px;
193
+ }
194
+ .preflight-item:last-child { border-bottom: none; }
195
+ .preflight-item input[type="checkbox"] { width: auto; flex-shrink: 0; margin: 0; }
196
+ .preflight-item label { flex: 1; margin-bottom: 0; cursor: pointer; font-size: 11px; color: #333; }
197
+ .preflight-badge {
198
+ font-size: 9px;
199
+ padding: 2px 6px;
200
+ border-radius: 10px;
201
+ white-space: nowrap;
202
+ flex-shrink: 0;
203
+ }
204
+ .preflight-badge.new { background: #e8f5e9; color: #18a058; }
205
+ .preflight-badge.changed { background: #fff3e0; color: #f57c00; }
206
+ .preflight-badge.unchanged { background: #f5f5f5; color: #999; }
207
+ .preflight-empty {
208
+ font-size: 12px;
209
+ color: #555;
210
+ background: #f8f8f8;
211
+ border: 1px solid #ececec;
212
+ border-radius: 8px;
213
+ padding: 12px;
214
+ line-height: 1.5;
215
+ margin-bottom: 8px;
216
+ }
217
+
174
218
  /* Offline view */
175
219
  .offline-icon {
176
220
  font-size: 36px;
@@ -192,9 +236,136 @@
192
236
  margin-right: 6px;
193
237
  user-select: none;
194
238
  }
239
+
240
+ /* Brand watermark: a faint bridge at the bottom of every view. During the
241
+ loading view it becomes more visible and the drop from the logo falls
242
+ onto it on repeat. */
243
+ .bridge-watermark-svg {
244
+ display: block;
245
+ opacity: 0.022;
246
+ transition: opacity 0.4s ease;
247
+ }
248
+ body.is-loading .bridge-watermark-svg { opacity: 0.12; }
249
+
250
+ .loading-drop {
251
+ position: fixed;
252
+ bottom: 58px;
253
+ left: 50%;
254
+ width: 26px;
255
+ height: 39px;
256
+ margin-left: -13px;
257
+ pointer-events: none;
258
+ z-index: 10;
259
+ opacity: 0;
260
+ /* Squash pivots on the drop's bottom tip so the top compresses down
261
+ onto the bridge on impact. */
262
+ transform-origin: 50% 100%;
263
+ animation: drop-fall 1.8s linear infinite;
264
+ }
265
+ .loading-drop svg { width: 100%; height: 100%; display: block; }
266
+
267
+ /* Status text shown above the drop while components load.
268
+ Positioned roughly midway between the top of the panel and the
269
+ drop's starting point so the drop appears to fall from below it.
270
+ Two lines: a sticky phase header and a swapping detail line. */
271
+ .loading-status {
272
+ position: fixed;
273
+ top: 60px;
274
+ left: 50%;
275
+ /* Fixed width + centered position keeps both lines anchored at
276
+ the same x regardless of text length — text inside is
277
+ left-aligned so the phase label doesn't dance horizontally as
278
+ the detail line below changes length. */
279
+ width: 280px;
280
+ margin-left: -140px;
281
+ text-align: left;
282
+ z-index: 11;
283
+ pointer-events: none;
284
+ opacity: 0;
285
+ transition: opacity 0.2s ease;
286
+ display: flex;
287
+ flex-direction: column;
288
+ gap: 2px;
289
+ }
290
+ .loading-status.is-visible { opacity: 1; }
291
+ .loading-status__phase {
292
+ font-size: 12px;
293
+ line-height: 1.4;
294
+ color: #555;
295
+ font-weight: 600;
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 4px;
299
+ }
300
+ .loading-status__phase.is-empty { display: none; }
301
+ /* Three text-dot spans with cumulative opacity reveal so the
302
+ header reads "Building components.", "..", "...", "" then
303
+ loops. Each dot has its own keyframes (`steps(1)` timing, no
304
+ interpolation) so dots appear/disappear discretely. The dots
305
+ container is always 3ch wide so the surrounding layout never
306
+ shifts as dots toggle on and off. */
307
+ .loading-status__dots {
308
+ display: inline-block;
309
+ width: 3ch;
310
+ text-align: left;
311
+ }
312
+ .loading-status__dot {
313
+ opacity: 0;
314
+ }
315
+ .loading-status__dot:nth-child(1) {
316
+ animation: loading-status-dot-1 1.5s steps(1) infinite;
317
+ }
318
+ .loading-status__dot:nth-child(2) {
319
+ animation: loading-status-dot-2 1.5s steps(1) infinite;
320
+ }
321
+ .loading-status__dot:nth-child(3) {
322
+ animation: loading-status-dot-3 1.5s steps(1) infinite;
323
+ }
324
+ @keyframes loading-status-dot-1 {
325
+ 0%, 74.99% { opacity: 1; }
326
+ 75%, 100% { opacity: 0; }
327
+ }
328
+ @keyframes loading-status-dot-2 {
329
+ 0%, 24.99% { opacity: 0; }
330
+ 25%, 74.99% { opacity: 1; }
331
+ 75%, 100% { opacity: 0; }
332
+ }
333
+ @keyframes loading-status-dot-3 {
334
+ 0%, 49.99% { opacity: 0; }
335
+ 50%, 74.99% { opacity: 1; }
336
+ 75%, 100% { opacity: 0; }
337
+ }
338
+ .loading-status__detail {
339
+ font-size: 11px;
340
+ line-height: 1.4;
341
+ color: #888;
342
+ }
343
+ .loading-status__detail:empty { display: none; }
344
+ @keyframes drop-fall {
345
+ /* Single linear-motion fall from 0% → 55% (no intermediate transform
346
+ keyframes, so interpolation stays perfectly linear — no apparent
347
+ ease-in/out at the start or middle). Drop appears at full opacity
348
+ immediately; on impact (55%→65%) it compresses and fades together;
349
+ the tail holds invisible so the next cycle has breathing room. */
350
+ 0% { transform: translateY(-300px) scaleY(1); opacity: 0.30; }
351
+ 55% { transform: translateY(0) scaleY(1); opacity: 0.30; }
352
+ 65% { transform: translateY(0) scaleY(0.55); opacity: 0; }
353
+ 100% { transform: translateY(0) scaleY(0.55); opacity: 0; }
354
+ }
195
355
  </style>
196
356
  </head>
197
357
  <body>
358
+ <!-- Brand watermark: bridge element full-width at bottom -->
359
+ <div style="position:fixed;bottom:0;left:0;right:0;height:78px;overflow:hidden;pointer-events:none;z-index:9;">
360
+ <svg class="bridge-watermark-svg" width="100%" height="100%" viewBox="-70 168 400 140" preserveAspectRatio="xMidYMin slice" fill="none" xmlns="http://www.w3.org/2000/svg">
361
+ <path d="M-60.786 294C-57.327 242 27.215 188 131 188C234.786 188 319.327 242 322.786 294" stroke="#000" stroke-width="28" stroke-linecap="butt"/>
362
+ <rect x="32" y="207" width="20.5" height="101" fill="#000"/>
363
+ <rect x="91" y="198" width="20.5" height="110" fill="#000"/>
364
+ <rect x="151" y="198" width="20.5" height="110" fill="#000"/>
365
+ <rect x="210" y="207" width="20.5" height="101" fill="#000"/>
366
+ </svg>
367
+ </div>
368
+
198
369
  <!-- Push View -->
199
370
  <div id="pushView">
200
371
  <h2>Push to Code</h2>
@@ -205,7 +376,7 @@
205
376
  </div>
206
377
  <div id="tokenSourceInfoPush" class="repo-display" style="display:none;padding:6px 8px;">
207
378
  Last scan source: <strong id="tokenSourceLabelPush"></strong><br>
208
- Configured mode: <strong id="configuredTokenSourceModePush">auto</strong>
379
+ Configured mode: <strong id="configuredTokenSourceModePush">css</strong>
209
380
  </div>
210
381
 
211
382
  <div class="field">
@@ -250,7 +421,7 @@
250
421
 
251
422
  <div id="tokenSourceInfoSettings" class="repo-display" style="display:none;padding:6px 8px;">
252
423
  Last scan source: <strong id="tokenSourceLabelSettings"></strong><br>
253
- Configured mode: <strong id="configuredTokenSourceModeSettings">auto</strong>
424
+ Configured mode: <strong id="configuredTokenSourceModeSettings">css</strong>
254
425
  </div>
255
426
 
256
427
  <div class="field">
@@ -271,11 +442,10 @@
271
442
  <div class="field">
272
443
  <label>Token Source Mode</label>
273
444
  <select id="settingsTokenSourceMode">
274
- <option value="auto">auto (prefer CSS)</option>
275
- <option value="css">css (force CSS only)</option>
276
- <option value="dtcg">dtcg (force DTCG only)</option>
445
+ <option value="css">css (auto-discover CSS, fallback to DTCG)</option>
446
+ <option value="dtcg">dtcg (force DTCG file only)</option>
277
447
  </select>
278
- <p class="hint">`auto` prefers CSS and falls back to DTCG, then embedded tokens.</p>
448
+ <p class="hint">CSS mode auto-discovers your globals.css and falls back to DTCG if not found. Use DTCG to force the legacy token file.</p>
279
449
  </div>
280
450
 
281
451
  <div class="field" id="tokenPathField">
@@ -291,25 +461,25 @@
291
461
  </div>
292
462
 
293
463
  <div class="field" id="syncDtcgOnPushField">
294
- <label style="display:flex;align-items:center;gap:8px;">
295
- <input type="checkbox" id="settingsSyncDtcgOnPush">
296
- Also update DTCG on Push to Code
297
- </label>
464
+ <div style="display:flex;align-items:center;gap:8px;">
465
+ <input type="checkbox" id="settingsSyncDtcgOnPush" style="width:auto;flex-shrink:0;">
466
+ <label for="settingsSyncDtcgOnPush" style="display:inline;margin:0;cursor:pointer;">Also update DTCG on Push to Code</label>
467
+ </div>
298
468
  <p class="hint">When enabled, the plugin also commits <code>tokens.dtcg.json</code> as a generated artifact.</p>
299
469
  </div>
300
470
 
301
471
  <div class="field">
302
- <label style="display:flex;align-items:center;gap:8px;">
303
- <input type="checkbox" id="settingsAllowNewTokensFromFigma">
304
- Allow New Tokens from Figma
305
- </label>
472
+ <div style="display:flex;align-items:center;gap:8px;">
473
+ <input type="checkbox" id="settingsAllowNewTokensFromFigma" style="width:auto;flex-shrink:0;">
474
+ <label for="settingsAllowNewTokensFromFigma" style="display:inline;margin:0;cursor:pointer;">Allow New Tokens from Figma</label>
475
+ </div>
306
476
  <p class="hint">Disabled by default. When enabled, new token keys can be added to code on push.</p>
307
477
  </div>
308
478
 
309
- <div class="field">
479
+ <div class="field" id="newTokenPrefixesField" style="display:none;">
310
480
  <label>New Token Prefixes (optional)</label>
311
481
  <input type="text" id="settingsNewTokenPrefixes" placeholder="chart-, brand-">
312
- <p class="hint">Comma-separated. If empty, all new keys are allowed. If set, only matching prefixes are allowed.</p>
482
+ <p class="hint">Comma-separated. Only tokens with these prefixes can be added. Leave empty to allow all new tokens.</p>
313
483
  </div>
314
484
 
315
485
  <div class="divider"></div>
@@ -355,26 +525,75 @@
355
525
  <div id="settingsStatus" class="status"></div>
356
526
  </div>
357
527
 
528
+ <!-- Loading View — the bottom-of-body bridge watermark stays visible
529
+ (CSS fades it up during loading) and the drop from the logo falls
530
+ onto it on repeat via a CSS keyframe animation. The status text
531
+ above the drop is updated via the 'status' postMessage. -->
532
+ <div id="loadingView" style="display:none;min-height:200px;">
533
+ <div id="loadingStatus" class="loading-status" aria-live="polite">
534
+ <div id="loadingStatusPhaseRow" class="loading-status__phase is-empty">
535
+ <span id="loadingStatusPhase" class="loading-status__phase-text"></span>
536
+ <span class="loading-status__dots" aria-hidden="true"><span class="loading-status__dot">.</span><span class="loading-status__dot">.</span><span class="loading-status__dot">.</span></span>
537
+ </div>
538
+ <div id="loadingStatusDetail" class="loading-status__detail"></div>
539
+ </div>
540
+ <div class="loading-drop" aria-hidden="true">
541
+ <svg viewBox="0 0 92 138" xmlns="http://www.w3.org/2000/svg">
542
+ <g transform="translate(-85, -39)" fill="#000">
543
+ <path d="M131 39.2646C134.289 44.7223 137.921 50.4461 141.631 56.2715C147.191 65.0012 152.943 73.9894 158.166 82.9082C163.392 91.8314 168.010 100.554 171.312 108.708C174.632 116.908 176.489 124.214 176.489 130.379C176.489 156.002 155.978 176.673 130.800 176.673C105.622 176.673 85.1105 156.002 85.1105 130.379C85.1105 124.289 87.1502 116.839 90.7628 108.368C94.3465 99.965 99.3047 90.9483 104.768 81.8164C110.238 72.6724 116.092 63.61 121.514 54.9707C124.938 49.5147 128.194 44.2166 131 39.2646Z"/>
544
+ </g>
545
+ </svg>
546
+ </div>
547
+ </div>
548
+
358
549
  <!-- Offline View -->
359
550
  <div id="offlineView" style="display:none;">
360
551
  <div class="offline-icon">⚡</div>
361
552
  <h2 style="text-align:center;margin:0 0 8px;">Dev server not running</h2>
362
553
  <p style="font-size:12px;color:#666;text-align:center;margin:0 0 12px;line-height:1.5;">
363
- The plugin needs your local dev server to scan components.
554
+ The plugin scans components from your local Next.js dev server.
364
555
  </p>
365
556
 
557
+ <p style="font-size:11px;color:#888;margin:0 0 6px;line-height:1.5;">
558
+ In your project root:
559
+ </p>
366
560
  <div class="command-block">
367
- <span class="prompt">$</span>pnpm figma:dev
561
+ <span class="prompt">$</span>pnpm inkbridge:dev
368
562
  </div>
369
563
 
370
564
  <p style="font-size:11px;color:#888;margin:0 0 16px;line-height:1.5;">
371
- This starts Next.js and the component scanner. Once it's running, click Retry.
565
+ Once it's running, click Retry. First time? See the
566
+ <a href="https://inkbridge.ink/docs/getting-started" target="_blank" style="color:#18a058;">setup guide</a>
567
+ — you may need to run <code style="background:#eee;padding:0 4px;border-radius:3px;">pnpm exec inkbridge setup</code> first.
372
568
  </p>
373
569
 
374
570
  <button id="retryBtn">Retry</button>
375
571
  <div id="retryStatus" class="status"></div>
376
572
  </div>
377
573
 
574
+ <!-- Pre-flight View -->
575
+ <div id="preflightView" style="display:none;">
576
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
577
+ <h2 style="margin:0;">Create Design System</h2>
578
+ </div>
579
+ <p id="preflightSummary" style="font-size:11px;color:#666;margin:0 0 10px;"></p>
580
+
581
+ <div id="preflightActions" class="select-actions">
582
+ <a onclick="selectAllPreflight()">Select all</a>
583
+ <a onclick="selectNonePreflight()">Select none</a>
584
+ <a id="preflightToggleUnchanged" style="display:none;"></a>
585
+ </div>
586
+
587
+ <div id="preflightNoChanges" class="preflight-empty" style="display:none;">
588
+ No component changes detected. You can close this dialog, or regenerate all components anyway.
589
+ </div>
590
+
591
+ <div id="preflightList" class="change-list" style="max-height:300px;overflow-y:auto;"></div>
592
+
593
+ <button id="preflightGenerateBtn" style="margin-top:12px;">Generate</button>
594
+ <button class="secondary" id="preflightCancelBtn" style="margin-top:4px;">Cancel</button>
595
+ </div>
596
+
378
597
  <!-- Upgrade View -->
379
598
  <div id="upgradeView" style="display:none;">
380
599
  <h2>Pro Feature</h2>
@@ -410,7 +629,7 @@
410
629
  </div>
411
630
  <div id="tokenSourceInfoSync" class="repo-display" style="display:none;padding:6px 8px;">
412
631
  Last scan source: <strong id="tokenSourceLabelSync"></strong><br>
413
- Configured mode: <strong id="configuredTokenSourceModeSync">auto</strong>
632
+ Configured mode: <strong id="configuredTokenSourceModeSync">css</strong>
414
633
  </div>
415
634
 
416
635
  <p style="font-size: 11px; color: #666; margin-bottom: 12px;">
@@ -451,6 +670,22 @@
451
670
  var upgradeView = document.getElementById('upgradeView');
452
671
  var overviewView = document.getElementById('overviewView');
453
672
  var offlineView = document.getElementById('offlineView');
673
+ var loadingView = document.getElementById('loadingView');
674
+ var loadingStatusEl = document.getElementById('loadingStatus');
675
+ var loadingStatusPhaseRowEl = document.getElementById('loadingStatusPhaseRow');
676
+ var loadingStatusPhaseEl = document.getElementById('loadingStatusPhase');
677
+ var loadingStatusDetailEl = document.getElementById('loadingStatusDetail');
678
+ // Latch the phase so a detail-only update doesn't blank the header.
679
+ // setStatus(string) and clearStatus() reset both sides.
680
+ var loadingStatusCurrentPhase = '';
681
+ var preflightView = document.getElementById('preflightView');
682
+ var preflightActionsEl = document.getElementById('preflightActions');
683
+ var preflightListEl = document.getElementById('preflightList');
684
+ var preflightSummaryEl = document.getElementById('preflightSummary');
685
+ var preflightNoChangesEl = document.getElementById('preflightNoChanges');
686
+ var preflightToggleUnchangedEl = document.getElementById('preflightToggleUnchanged');
687
+ var preflightGenerateBtn = document.getElementById('preflightGenerateBtn');
688
+ var preflightCancelBtn = document.getElementById('preflightCancelBtn');
454
689
  var retryBtn = document.getElementById('retryBtn');
455
690
  var retryStatusEl = document.getElementById('retryStatus');
456
691
  var overviewPushBtn = document.getElementById('overviewPushBtn');
@@ -531,6 +766,7 @@
531
766
  var syncDtcgOnPushField = document.getElementById('syncDtcgOnPushField');
532
767
  var settingsAllowNewTokensFromFigma = document.getElementById('settingsAllowNewTokensFromFigma');
533
768
  var settingsNewTokenPrefixes = document.getElementById('settingsNewTokenPrefixes');
769
+ var newTokenPrefixesField = document.getElementById('newTokenPrefixesField');
534
770
  var settingsToken = document.getElementById('settingsToken');
535
771
  var settingsProjectName = document.getElementById('settingsProjectName');
536
772
  var settingsLicenseKey = document.getElementById('settingsLicenseKey');
@@ -538,13 +774,20 @@
538
774
  // Current detected changes
539
775
  var detectedChanges = { tokens: true, components: [] };
540
776
  var lastTokenSourceInfo = null;
541
- var configuredTokenSourceMode = 'auto';
777
+ var configuredTokenSourceMode = 'css';
778
+ var preflightItems = [];
779
+ var preflightExcludedSet = {};
780
+ var preflightHasActionable = false;
781
+ var preflightShowUnchanged = false;
542
782
 
543
783
  function resizeToContent() {
544
784
  // Wait for layout to settle before measuring
545
785
  setTimeout(function() {
546
786
  var h = document.body.scrollHeight;
547
- parent.postMessage({ pluginMessage: { type: 'resize', height: h } }, '*');
787
+ // Pass the current viewport width so the plugin's resize handler
788
+ // doesn't snap the panel to a different width than showUI set.
789
+ var w = window.innerWidth;
790
+ parent.postMessage({ pluginMessage: { type: 'resize', height: h, width: w } }, '*');
548
791
  }, 50);
549
792
  }
550
793
 
@@ -580,25 +823,20 @@
580
823
  }
581
824
 
582
825
  function normalizeConfiguredMode(mode) {
583
- if (mode === 'css' || mode === 'dtcg' || mode === 'auto') return mode;
584
- return 'auto';
826
+ if (mode === 'dtcg') return 'dtcg';
827
+ return 'css';
585
828
  }
586
829
 
587
830
  function applyTokenSourceModeVisibility(mode) {
588
831
  var normalized = normalizeConfiguredMode(mode);
589
- if (normalized === 'css') {
590
- tokenPathField.style.display = 'none';
591
- cssTokenPathField.style.display = 'block';
592
- syncDtcgOnPushField.style.display = 'block';
593
- return;
594
- }
595
832
  if (normalized === 'dtcg') {
596
833
  tokenPathField.style.display = 'block';
597
834
  cssTokenPathField.style.display = 'none';
598
835
  syncDtcgOnPushField.style.display = 'none';
599
836
  return;
600
837
  }
601
- tokenPathField.style.display = 'block';
838
+ // css mode
839
+ tokenPathField.style.display = 'none';
602
840
  cssTokenPathField.style.display = 'block';
603
841
  syncDtcgOnPushField.style.display = 'block';
604
842
  }
@@ -619,6 +857,156 @@
619
857
  configuredTokenSourceModeSync.textContent = configured;
620
858
  }
621
859
 
860
+ function buildPreflightExcludedFromUI() {
861
+ var excludedMap = {};
862
+
863
+ // When actionable items exist, unchanged items are treated as excluded by default
864
+ // unless explicitly shown and checked.
865
+ if (preflightHasActionable && !preflightShowUnchanged) {
866
+ for (var i = 0; i < preflightItems.length; i++) {
867
+ if (preflightItems[i].status === 'unchanged') excludedMap[preflightItems[i].name] = true;
868
+ }
869
+ }
870
+
871
+ var boxes = preflightListEl.querySelectorAll('input[type="checkbox"]');
872
+ for (var bi = 0; bi < boxes.length; bi++) {
873
+ var box = boxes[bi];
874
+ if (!box.checked) excludedMap[box.value] = true;
875
+ else delete excludedMap[box.value];
876
+ }
877
+
878
+ var excluded = [];
879
+ for (var key in excludedMap) {
880
+ if (Object.prototype.hasOwnProperty.call(excludedMap, key)) excluded.push(key);
881
+ }
882
+ return excluded;
883
+ }
884
+
885
+ function renderPreflight() {
886
+ var nNew = 0, nChanged = 0, nUnchanged = 0;
887
+ for (var i = 0; i < preflightItems.length; i++) {
888
+ if (preflightItems[i].status === 'new') nNew++;
889
+ else if (preflightItems[i].status === 'changed') nChanged++;
890
+ else nUnchanged++;
891
+ }
892
+ preflightHasActionable = (nNew + nChanged) > 0;
893
+
894
+ var summaryParts = [preflightItems.length + ' components'];
895
+ if (nNew > 0) summaryParts.push(nNew + ' new');
896
+ if (nChanged > 0) summaryParts.push(nChanged + ' changed');
897
+ if (nUnchanged > 0) summaryParts.push(nUnchanged + ' unchanged');
898
+ preflightSummaryEl.textContent = summaryParts.join(' · ');
899
+
900
+ if (!preflightHasActionable) {
901
+ preflightActionsEl.style.display = 'none';
902
+ preflightNoChangesEl.style.display = 'block';
903
+ preflightListEl.style.display = 'none';
904
+ preflightListEl.innerHTML = '';
905
+ preflightGenerateBtn.textContent = 'Generate all anyway';
906
+ preflightGenerateBtn.disabled = false;
907
+ return;
908
+ }
909
+
910
+ preflightNoChangesEl.style.display = 'none';
911
+ preflightActionsEl.style.display = 'flex';
912
+ preflightListEl.style.display = 'block';
913
+ preflightGenerateBtn.textContent = 'Generate selected';
914
+ preflightGenerateBtn.disabled = false;
915
+
916
+ if (nUnchanged > 0) {
917
+ preflightToggleUnchangedEl.style.display = 'inline';
918
+ preflightToggleUnchangedEl.textContent = preflightShowUnchanged
919
+ ? 'Hide unchanged (' + nUnchanged + ')'
920
+ : 'Show unchanged (' + nUnchanged + ')';
921
+ } else {
922
+ preflightToggleUnchangedEl.style.display = 'none';
923
+ }
924
+
925
+ var itemsToRender = [];
926
+ for (var ri = 0; ri < preflightItems.length; ri++) {
927
+ var item = preflightItems[ri];
928
+ if (!preflightShowUnchanged && item.status === 'unchanged') continue;
929
+ itemsToRender.push(item);
930
+ }
931
+
932
+ var groups = {};
933
+ var groupOrder = [];
934
+ for (var ii = 0; ii < itemsToRender.length; ii++) {
935
+ var item = itemsToRender[ii];
936
+ if (!groups[item.section]) {
937
+ groups[item.section] = { title: item.sectionTitle, items: [] };
938
+ groupOrder.push(item.section);
939
+ }
940
+ groups[item.section].items.push(item);
941
+ }
942
+
943
+ preflightListEl.innerHTML = '';
944
+ for (var gi = 0; gi < groupOrder.length; gi++) {
945
+ var sectionKey = groupOrder[gi];
946
+ var group = groups[sectionKey];
947
+
948
+ var headerEl = document.createElement('div');
949
+ headerEl.className = 'preflight-group-header';
950
+ headerEl.textContent = group.title;
951
+ preflightListEl.appendChild(headerEl);
952
+
953
+ for (var ci = 0; ci < group.items.length; ci++) {
954
+ var comp = group.items[ci];
955
+ var row = document.createElement('div');
956
+ row.className = 'preflight-item';
957
+
958
+ var cb = document.createElement('input');
959
+ cb.type = 'checkbox';
960
+ cb.id = 'pf_' + comp.name;
961
+ cb.value = comp.name;
962
+
963
+ // Actionable items default to checked unless explicitly excluded.
964
+ // Unchanged items default to unchecked (non-actionable).
965
+ if (comp.status === 'unchanged') cb.checked = false;
966
+ else cb.checked = !preflightExcludedSet[comp.name];
967
+
968
+ var lbl = document.createElement('label');
969
+ lbl.htmlFor = 'pf_' + comp.name;
970
+ lbl.textContent = comp.displayName || comp.name;
971
+
972
+ var badge = document.createElement('span');
973
+ badge.className = 'preflight-badge ' + comp.status;
974
+ badge.textContent = comp.status;
975
+
976
+ row.appendChild(cb);
977
+ row.appendChild(lbl);
978
+ row.appendChild(badge);
979
+ preflightListEl.appendChild(row);
980
+ }
981
+ }
982
+ }
983
+
984
+ function selectAllPreflight() {
985
+ var boxes = preflightListEl.querySelectorAll('input[type="checkbox"]');
986
+ for (var i = 0; i < boxes.length; i++) boxes[i].checked = true;
987
+ }
988
+
989
+ function selectNonePreflight() {
990
+ var boxes = preflightListEl.querySelectorAll('input[type="checkbox"]');
991
+ for (var i = 0; i < boxes.length; i++) boxes[i].checked = false;
992
+ }
993
+
994
+ preflightGenerateBtn.onclick = function() {
995
+ var excluded = buildPreflightExcludedFromUI();
996
+ preflightGenerateBtn.disabled = true;
997
+ preflightGenerateBtn.textContent = 'Generating...';
998
+ parent.postMessage({ pluginMessage: { type: 'confirm-preflight', excluded: excluded } }, '*');
999
+ };
1000
+
1001
+ preflightCancelBtn.onclick = function() {
1002
+ parent.postMessage({ pluginMessage: { type: 'cancel-preflight' } }, '*');
1003
+ };
1004
+ preflightToggleUnchangedEl.onclick = function() {
1005
+ preflightShowUnchanged = !preflightShowUnchanged;
1006
+ renderPreflight();
1007
+ resizeToContent();
1008
+ };
1009
+
622
1010
  function selectAllChanges() {
623
1011
  var checkboxes = changeListEl.querySelectorAll('input[type="checkbox"]');
624
1012
  checkboxes.forEach(function(cb) { cb.checked = true; });
@@ -705,9 +1093,9 @@
705
1093
  // Dev server fetch - runs in the UI iframe which has network access
706
1094
  // (code.js sandbox cannot make fetch calls)
707
1095
  var DEV_SERVER_ENDPOINTS = [
708
- 'http://localhost:4000/api/figma/scan-components',
709
- 'http://localhost:3000/api/figma/scan-components',
710
- 'http://localhost:5173/api/figma/scan-components',
1096
+ 'http://localhost:3000/api/inkbridge/scan-components',
1097
+ 'http://localhost:4000/api/inkbridge/scan-components',
1098
+ 'http://localhost:5173/api/inkbridge/scan-components',
711
1099
  ];
712
1100
 
713
1101
  var DEV_SERVER_TIMEOUT_MS = 15000;
@@ -867,7 +1255,7 @@
867
1255
  // UI-side license validation (code.js sandbox has no network)
868
1256
  if (msg.type === 'validate-license') {
869
1257
  var licenseResult = { tier: 'free', valid: false };
870
- var validatePorts = ['4000', '3000', '5173'];
1258
+ var validatePorts = ['3000', '4000', '5173'];
871
1259
  for (var vp = 0; vp < validatePorts.length; vp++) {
872
1260
  try {
873
1261
  var vUrl = 'http://localhost:' + validatePorts[vp] + '/api/plugin/validate?key=' + encodeURIComponent(msg.key);
@@ -930,6 +1318,48 @@
930
1318
  return;
931
1319
  }
932
1320
 
1321
+ if (msg.type === 'show-preflight') {
1322
+ pushView.style.display = 'none';
1323
+ configView.style.display = 'none';
1324
+ settingsView.style.display = 'none';
1325
+ syncView.style.display = 'none';
1326
+ upgradeView.style.display = 'none';
1327
+ overviewView.style.display = 'none';
1328
+ offlineView.style.display = 'none';
1329
+ loadingView.style.display = 'none';
1330
+ document.body.classList.remove('is-loading');
1331
+ preflightView.style.display = 'block';
1332
+
1333
+ preflightItems = Array.isArray(msg.items) ? msg.items : [];
1334
+ var excluded = Array.isArray(msg.excluded) ? msg.excluded : [];
1335
+ preflightExcludedSet = {};
1336
+ for (var ei = 0; ei < excluded.length; ei++) preflightExcludedSet[excluded[ei]] = true;
1337
+ preflightShowUnchanged = false;
1338
+ renderPreflight();
1339
+ resizeToContent();
1340
+ }
1341
+
1342
+ if (msg.type === 'status') {
1343
+ // { type: 'status', phase?: '…', detail?: '…' } — structured.
1344
+ // Phase is sticky: only updated when the message includes a
1345
+ // `phase` key. Detail is updated independently so per-section
1346
+ // emits during a build can swap the sub-line without clobbering
1347
+ // the bold phase header above. CSS animates trailing dots on
1348
+ // the phase element, so callers don't need to bake in '…'.
1349
+ if (typeof msg.phase === 'string') {
1350
+ loadingStatusCurrentPhase = msg.phase;
1351
+ loadingStatusPhaseEl.textContent = msg.phase;
1352
+ // Hide the whole phase row (text + dots) when phase is empty,
1353
+ // otherwise the dots animation would dangle alone.
1354
+ loadingStatusPhaseRowEl.classList.toggle('is-empty', msg.phase.length === 0);
1355
+ }
1356
+ if (typeof msg.detail === 'string') {
1357
+ loadingStatusDetailEl.textContent = msg.detail;
1358
+ }
1359
+ var anyText = loadingStatusCurrentPhase.length > 0 || loadingStatusDetailEl.textContent.length > 0;
1360
+ loadingStatusEl.classList.toggle('is-visible', anyText);
1361
+ }
1362
+
933
1363
  if (msg.type === 'show-view') {
934
1364
  pushView.style.display = msg.view === 'push' ? 'block' : 'none';
935
1365
  configView.style.display = msg.view === 'configure' ? 'block' : 'none';
@@ -938,6 +1368,19 @@
938
1368
  upgradeView.style.display = msg.view === 'upgrade' ? 'block' : 'none';
939
1369
  overviewView.style.display = msg.view === 'overview' ? 'block' : 'none';
940
1370
  offlineView.style.display = msg.view === 'offline' ? 'block' : 'none';
1371
+ loadingView.style.display = msg.view === 'loading' ? 'block' : 'none';
1372
+ document.body.classList.toggle('is-loading', msg.view === 'loading');
1373
+ // Always reset the status text on a view switch — including
1374
+ // entering the loading view. Otherwise a stale message from the
1375
+ // previous run lingers until the first new setStatus arrives,
1376
+ // and if the previous run ended on the loading view the next
1377
+ // open shows the old text immediately.
1378
+ loadingStatusPhaseEl.textContent = '';
1379
+ loadingStatusDetailEl.textContent = '';
1380
+ loadingStatusCurrentPhase = '';
1381
+ loadingStatusPhaseRowEl.classList.add('is-empty');
1382
+ loadingStatusEl.classList.remove('is-visible');
1383
+ preflightView.style.display = 'none';
941
1384
  // Reset retry button state when navigating away from/to offline view
942
1385
  if (msg.view === 'offline') {
943
1386
  retryBtn.disabled = false;
@@ -980,7 +1423,8 @@
980
1423
  settingsCssTokenPath.value = config.cssTokenPath || '';
981
1424
  settingsSyncDtcgOnPush.checked = config.syncDtcgOnPush === true;
982
1425
  settingsAllowNewTokensFromFigma.checked = config.allowNewTokensFromFigma === true;
983
- settingsNewTokenPrefixes.value = Array.isArray(config.newTokenPrefixes) ? config.newTokenPrefixes.join(', ') : '';
1426
+ settingsNewTokenPrefixes.value = Array.isArray(config.newTokenPrefixes) ? config.newTokenPrefixes.join(', ') : (typeof config.newTokenPrefixes === 'string' ? config.newTokenPrefixes : '');
1427
+ newTokenPrefixesField.style.display = config.allowNewTokensFromFigma === true ? 'block' : 'none';
984
1428
  settingsProjectName.value = config.projectName || '';
985
1429
  configuredTokenSourceMode = tokenSourceMode;
986
1430
  applyTokenSourceModeVisibility(tokenSourceMode);
@@ -1197,7 +1641,7 @@
1197
1641
  repo: repo,
1198
1642
  baseBranch: settingsBranch.value.trim() || 'main',
1199
1643
  tokenPath: settingsTokenPath.value.trim() || 'design-tokens/tokens.dtcg.json',
1200
- tokenSourceMode: settingsTokenSourceMode.value || 'auto',
1644
+ tokenSourceMode: settingsTokenSourceMode.value || 'css',
1201
1645
  cssTokenPath: settingsCssTokenPath.value.trim(),
1202
1646
  syncDtcgOnPush: settingsSyncDtcgOnPush.checked === true,
1203
1647
  allowNewTokensFromFigma: settingsAllowNewTokensFromFigma.checked === true,
@@ -1217,6 +1661,10 @@
1217
1661
  applyTokenSourceModeVisibility(configuredTokenSourceMode);
1218
1662
  setTokenSourceInfo(lastTokenSourceInfo);
1219
1663
  };
1664
+
1665
+ settingsAllowNewTokensFromFigma.onchange = function() {
1666
+ newTokenPrefixesField.style.display = settingsAllowNewTokensFromFigma.checked ? 'block' : 'none';
1667
+ };
1220
1668
  </script>
1221
1669
  </body>
1222
1670
  </html>