@fragments-sdk/cli 0.7.11 → 0.7.13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/cli",
3
- "version": "0.7.11",
3
+ "version": "0.7.13",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "CLI, MCP server, and dev tools for Fragments design system",
6
6
  "author": "Conan McNicholl",
@@ -15,7 +15,6 @@ import { useToast } from "./Toast.js";
15
15
 
16
16
  // Toolbar
17
17
  import { PreviewToolbar } from "./PreviewToolbar.js";
18
- import { getBackgroundStyle } from "../constants/ui.js";
19
18
 
20
19
  // Preview & Rendering
21
20
  import { PreviewArea } from "./PreviewArea.js";
@@ -65,16 +64,14 @@ export function App({ fragments }: AppProps) {
65
64
  // UI state (modals, panels, view modes)
66
65
  const { state: uiState, actions: uiActions } = useAppState();
67
66
 
68
- // View settings (zoom, background, viewport, theme)
67
+ // View settings (zoom, viewport, theme)
69
68
  const viewSettings = useViewSettings({
70
69
  initialState: {
71
70
  zoom: urlState.zoom as any,
72
- background: urlState.background as any,
73
71
  viewport: urlState.viewport as any,
74
72
  customSize: { width: urlState.customWidth, height: urlState.customHeight },
75
73
  },
76
74
  onZoomChange: (zoom) => setUrlViewSettings({ zoom }),
77
- onBackgroundChange: (bg) => setUrlViewSettings({ background: bg }),
78
75
  onViewportChange: (vp, size) => setUrlViewSettings({
79
76
  viewport: vp,
80
77
  customWidth: size?.width,
@@ -270,11 +267,6 @@ export function App({ fragments }: AppProps) {
270
267
  }
271
268
  }, [copyUrl, success, uiActions]);
272
269
 
273
- const focusSearchInput = useCallback(() => {
274
- searchInputRef.current?.focus();
275
- searchInputRef.current?.select();
276
- }, []);
277
-
278
270
  // Sorted fragment paths for keyboard navigation
279
271
  const sortedFragmentPaths = useMemo(() => {
280
272
  return [...fragments]
@@ -328,7 +320,7 @@ export function App({ fragments }: AppProps) {
328
320
  toggleResponsive: () => uiActions.setMultiViewport(!uiState.showMultiViewport),
329
321
  copyLink: handleCopyLink,
330
322
  showHelp: uiActions.toggleShortcutsHelp,
331
- openSearch: focusSearchInput,
323
+ openSearch: () => uiActions.setCommandPalette(true),
332
324
  escape: () => {
333
325
  if (document.activeElement === searchInputRef.current) {
334
326
  if (searchQuery) {
@@ -391,6 +383,7 @@ export function App({ fragments }: AppProps) {
391
383
  activeFragment && !uiState.showHealthDashboard ? (
392
384
  <TopToolbar
393
385
  fragment={activeFragment}
386
+ viewSettings={viewSettings}
394
387
  uiState={uiState}
395
388
  uiActions={uiActions}
396
389
  figmaUrl={figmaUrl}
@@ -455,19 +448,6 @@ export function App({ fragments }: AppProps) {
455
448
  <div id="preview-layout" style={{ display: 'flex', height: '100%', flexDirection: panelDock === "bottom" ? 'column' : 'row' }}>
456
449
  {/* Main Content Area */}
457
450
  <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, minHeight: 0 }}>
458
- <PreviewControlsBar
459
- zoom={viewSettings.zoom}
460
- background={viewSettings.background}
461
- onZoomChange={viewSettings.setZoom}
462
- onBackgroundChange={viewSettings.setBackground}
463
- showMatrixView={uiState.showMatrixView}
464
- showMultiViewport={uiState.showMultiViewport}
465
- panelOpen={uiState.panelOpen}
466
- onToggleMatrix={() => uiActions.setMatrixView(!uiState.showMatrixView)}
467
- onToggleMultiViewport={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
468
- onTogglePanel={uiActions.togglePanel}
469
- />
470
-
471
451
  {/* Preview Area */}
472
452
  <div
473
453
  id="preview-canvas"
@@ -475,7 +455,6 @@ export function App({ fragments }: AppProps) {
475
455
  flex: 1,
476
456
  overflow: 'auto',
477
457
  position: 'relative',
478
- ...(uiState.showMatrixView ? {} : getBackgroundStyle(viewSettings.background)),
479
458
  }}
480
459
  >
481
460
  {variantCount === 0 ? (
@@ -487,7 +466,6 @@ export function App({ fragments }: AppProps) {
487
466
  variants={variants}
488
467
  focusedVariantIndex={safeVariantIndex}
489
468
  zoom={viewSettings.zoom}
490
- background={viewSettings.background}
491
469
  viewport={viewSettings.viewport}
492
470
  customSize={viewSettings.customSize}
493
471
  previewTheme={resolvedTheme}
@@ -505,7 +483,6 @@ export function App({ fragments }: AppProps) {
505
483
  variant={activeVariant}
506
484
  variants={variants}
507
485
  zoom={viewSettings.zoom}
508
- background={viewSettings.background}
509
486
  viewport={viewSettings.viewport}
510
487
  customSize={viewSettings.customSize}
511
488
  previewTheme={resolvedTheme}
@@ -571,6 +548,7 @@ export function App({ fragments }: AppProps) {
571
548
  // Top Toolbar Component
572
549
  interface TopToolbarProps {
573
550
  fragment: { path: string; fragment: FragmentDefinition };
551
+ viewSettings: ReturnType<typeof useViewSettings>;
574
552
  uiState: ReturnType<typeof useAppState>['state'];
575
553
  uiActions: ReturnType<typeof useAppState>['actions'];
576
554
  figmaUrl?: string;
@@ -614,7 +592,6 @@ function HeaderSearch({ value, onChange, inputRef }: HeaderSearchProps) {
614
592
  placeholder="Search components"
615
593
  aria-label="Search components"
616
594
  size="sm"
617
- shortcut="⌘K"
618
595
  style={{ width: '240px' }}
619
596
  />
620
597
  </Header.Search>
@@ -723,11 +700,12 @@ function PreviewAside({
723
700
  </a>
724
701
  {variants.map((variant, index) => {
725
702
  const active = index === focusedVariantIndex;
703
+ const anchorId = getVariantSectionId(fragment.meta.name, variant.name);
726
704
 
727
705
  return (
728
706
  <a
729
707
  key={variant.name}
730
- href="#preview-canvas"
708
+ href={`#${anchorId}`}
731
709
  style={getLinkStyle(active)}
732
710
  onClick={(event) => {
733
711
  event.preventDefault();
@@ -769,6 +747,7 @@ function PreviewAside({
769
747
 
770
748
  function TopToolbar({
771
749
  fragment,
750
+ viewSettings,
772
751
  uiState,
773
752
  uiActions,
774
753
  figmaUrl,
@@ -790,6 +769,48 @@ function TopToolbar({
790
769
  <HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
791
770
  <Header.Spacer />
792
771
  <Header.Actions>
772
+ <PreviewToolbar
773
+ zoom={viewSettings.zoom}
774
+ onZoomChange={viewSettings.setZoom}
775
+ />
776
+ <Separator orientation="vertical" style={{ height: '16px' }} />
777
+ <Tooltip content={uiState.showMatrixView ? "Disable matrix view" : "Enable matrix view"}>
778
+ <Button
779
+ variant="ghost"
780
+ size="sm"
781
+ aria-pressed={uiState.showMatrixView}
782
+ aria-label="Toggle matrix view"
783
+ onClick={() => uiActions.setMatrixView(!uiState.showMatrixView)}
784
+ style={uiState.showMatrixView ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
785
+ >
786
+ <GridFour size={16} />
787
+ </Button>
788
+ </Tooltip>
789
+ <Tooltip content={uiState.showMultiViewport ? "Disable responsive view" : "Enable responsive view"}>
790
+ <Button
791
+ variant="ghost"
792
+ size="sm"
793
+ aria-pressed={uiState.showMultiViewport}
794
+ aria-label="Toggle responsive view"
795
+ onClick={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
796
+ style={uiState.showMultiViewport ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
797
+ >
798
+ <DeviceMobile size={16} />
799
+ </Button>
800
+ </Tooltip>
801
+ <Tooltip content={uiState.panelOpen ? "Hide addons panel" : "Show addons panel"}>
802
+ <Button
803
+ variant="ghost"
804
+ size="sm"
805
+ aria-pressed={uiState.panelOpen}
806
+ aria-label="Toggle addons panel"
807
+ onClick={uiActions.togglePanel}
808
+ style={uiState.panelOpen ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
809
+ >
810
+ <Rows size={16} />
811
+ </Button>
812
+ </Tooltip>
813
+ <Separator orientation="vertical" style={{ height: '16px' }} />
793
814
  {figmaUrl && (
794
815
  <>
795
816
  <Tooltip content={uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"}>
@@ -856,93 +877,12 @@ function getVariantSectionId(componentName: string, variantName: string): string
856
877
  return `preview-${normalizeAnchorSegment(componentName)}-${normalizeAnchorSegment(variantName)}`;
857
878
  }
858
879
 
859
- interface PreviewControlsBarProps {
860
- zoom: ReturnType<typeof useViewSettings>["zoom"];
861
- background: ReturnType<typeof useViewSettings>["background"];
862
- onZoomChange: ReturnType<typeof useViewSettings>["setZoom"];
863
- onBackgroundChange: ReturnType<typeof useViewSettings>["setBackground"];
864
- showMatrixView: boolean;
865
- showMultiViewport: boolean;
866
- panelOpen: boolean;
867
- onToggleMatrix: () => void;
868
- onToggleMultiViewport: () => void;
869
- onTogglePanel: () => void;
870
- }
871
-
872
- function PreviewControlsBar({
873
- zoom,
874
- background,
875
- onZoomChange,
876
- onBackgroundChange,
877
- showMatrixView,
878
- showMultiViewport,
879
- panelOpen,
880
- onToggleMatrix,
881
- onToggleMultiViewport,
882
- onTogglePanel,
883
- }: PreviewControlsBarProps) {
884
- const toggleButtonStyle = (active: boolean): CSSProperties => (
885
- active ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}
886
- );
887
-
888
- return (
889
- <div style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
890
- <Stack direction="row" gap="sm" align="center" justify="end">
891
- <PreviewToolbar
892
- zoom={zoom}
893
- background={background}
894
- onZoomChange={onZoomChange}
895
- onBackgroundChange={onBackgroundChange}
896
- />
897
- <Separator orientation="vertical" style={{ height: '16px' }} />
898
- <Tooltip content={showMatrixView ? "Disable matrix view" : "Enable matrix view"}>
899
- <Button
900
- variant="ghost"
901
- size="sm"
902
- aria-pressed={showMatrixView}
903
- aria-label="Toggle matrix view"
904
- onClick={onToggleMatrix}
905
- style={toggleButtonStyle(showMatrixView)}
906
- >
907
- <GridFour size={16} />
908
- </Button>
909
- </Tooltip>
910
- <Tooltip content={showMultiViewport ? "Disable responsive view" : "Enable responsive view"}>
911
- <Button
912
- variant="ghost"
913
- size="sm"
914
- aria-pressed={showMultiViewport}
915
- aria-label="Toggle responsive view"
916
- onClick={onToggleMultiViewport}
917
- style={toggleButtonStyle(showMultiViewport)}
918
- >
919
- <DeviceMobile size={16} />
920
- </Button>
921
- </Tooltip>
922
- <Tooltip content={panelOpen ? "Hide addons panel" : "Show addons panel"}>
923
- <Button
924
- variant="ghost"
925
- size="sm"
926
- aria-pressed={panelOpen}
927
- aria-label="Toggle addons panel"
928
- onClick={onTogglePanel}
929
- style={toggleButtonStyle(panelOpen)}
930
- >
931
- <Rows size={16} />
932
- </Button>
933
- </Tooltip>
934
- </Stack>
935
- </div>
936
- );
937
- }
938
-
939
880
  interface AllVariantsPreviewProps {
940
881
  componentName: string;
941
882
  fragmentPath: string;
942
883
  variants: FragmentVariant[];
943
884
  focusedVariantIndex: number;
944
885
  zoom: ReturnType<typeof useViewSettings>["zoom"];
945
- background: ReturnType<typeof useViewSettings>["background"];
946
886
  viewport: ReturnType<typeof useViewSettings>["viewport"];
947
887
  customSize: ReturnType<typeof useViewSettings>["customSize"];
948
888
  previewTheme: ReturnType<typeof useTheme>["resolvedTheme"];
@@ -960,7 +900,6 @@ function AllVariantsPreview({
960
900
  variants,
961
901
  focusedVariantIndex,
962
902
  zoom,
963
- background,
964
903
  viewport,
965
904
  customSize,
966
905
  previewTheme,
@@ -999,7 +938,6 @@ function AllVariantsPreview({
999
938
  variant={variant}
1000
939
  variants={variants}
1001
940
  zoom={zoom}
1002
- background={background}
1003
941
  viewport={viewport}
1004
942
  customSize={customSize}
1005
943
  previewTheme={previewTheme}
@@ -336,10 +336,16 @@ export function CommandPalette({
336
336
  function fuzzyScore(text: string, query: string): number {
337
337
  if (!query) return 1;
338
338
 
339
+ // Require substring match for short queries to avoid scattered single-char noise
340
+ if (query.length <= 3 && !text.includes(query)) {
341
+ return 0;
342
+ }
343
+
339
344
  let score = 0;
340
345
  let queryIndex = 0;
341
346
  let consecutiveBonus = 0;
342
347
  let lastMatchIndex = -2;
348
+ let maxGap = 0;
343
349
 
344
350
  for (let i = 0; i < text.length && queryIndex < query.length; i++) {
345
351
  if (text[i] === query[queryIndex]) {
@@ -351,6 +357,11 @@ function fuzzyScore(text: string, query: string): number {
351
357
  score += consecutiveBonus;
352
358
  } else {
353
359
  consecutiveBonus = 0;
360
+ // Track max gap between matches
361
+ if (lastMatchIndex >= 0) {
362
+ const gap = i - lastMatchIndex;
363
+ if (gap > maxGap) maxGap = gap;
364
+ }
354
365
  }
355
366
 
356
367
  // Bonus for matching at word start
@@ -366,6 +377,11 @@ function fuzzyScore(text: string, query: string): number {
366
377
  // Only return score if all query characters were found
367
378
  if (queryIndex < query.length) return 0;
368
379
 
380
+ // Penalize large gaps between matched characters
381
+ if (maxGap > 5) {
382
+ score -= maxGap;
383
+ }
384
+
369
385
  // Bonus for shorter results (more relevant)
370
386
  score += Math.max(0, 20 - text.length);
371
387
 
@@ -375,5 +391,5 @@ function fuzzyScore(text: string, query: string): number {
375
391
  // Bonus for starts with
376
392
  if (text.startsWith(query)) score += 30;
377
393
 
378
- return score;
394
+ return Math.max(score, 0) || 0;
379
395
  }
@@ -162,91 +162,88 @@ export const IsolatedPreviewFrame = memo(function IsolatedPreviewFrame({
162
162
  onMouseLeave={() => setIsHovered(false)}
163
163
  >
164
164
  {/* Skeleton loading overlay (initial load) */}
165
- <div
166
- style={{
167
- position: 'absolute',
168
- inset: 0,
169
- zIndex: 10,
170
- transition: 'opacity 150ms',
171
- opacity: showSkeleton ? 1 : 0,
172
- pointerEvents: showSkeleton ? 'auto' : 'none',
173
- background: 'rgba(255, 255, 255, 0.95)',
174
- }}
175
- >
176
- <PreviewSkeleton />
177
- </div>
165
+ {showSkeleton && (
166
+ <div
167
+ style={{
168
+ position: 'absolute',
169
+ inset: 0,
170
+ zIndex: 10,
171
+ background: 'var(--bg-primary, rgba(255, 255, 255, 0.95))',
172
+ }}
173
+ >
174
+ <PreviewSkeleton />
175
+ </div>
176
+ )}
178
177
 
179
178
  {/* Spinner overlay (subsequent renders) */}
180
- <div
181
- style={{
182
- position: 'absolute',
183
- inset: 0,
184
- zIndex: 10,
185
- display: 'flex',
186
- alignItems: 'center',
187
- justifyContent: 'center',
188
- transition: 'opacity 150ms',
189
- opacity: showSpinner ? 1 : 0,
190
- pointerEvents: showSpinner ? 'auto' : 'none',
191
- background: 'rgba(255, 255, 255, 0.8)',
192
- }}
193
- >
194
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#6b7280', fontSize: 14 }}>
195
- <LoadingSpinner />
196
- <span>Rendering...</span>
179
+ {showSpinner && (
180
+ <div
181
+ style={{
182
+ position: 'absolute',
183
+ inset: 0,
184
+ zIndex: 10,
185
+ display: 'flex',
186
+ alignItems: 'center',
187
+ justifyContent: 'center',
188
+ background: 'color-mix(in srgb, var(--bg-primary, white) 80%, transparent)',
189
+ }}
190
+ >
191
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-tertiary, #6b7280)', fontSize: 14 }}>
192
+ <LoadingSpinner />
193
+ <span>Rendering...</span>
194
+ </div>
197
195
  </div>
198
- </div>
196
+ )}
199
197
 
200
198
  {/* Error overlay */}
201
- <div
202
- style={{
203
- position: 'absolute',
204
- inset: 0,
205
- zIndex: 10,
206
- display: 'flex',
207
- alignItems: 'center',
208
- justifyContent: 'center',
209
- padding: '16px',
210
- transition: 'opacity 150ms',
211
- opacity: frameError && !isLoading ? 1 : 0,
212
- pointerEvents: frameError && !isLoading ? 'auto' : 'none',
213
- background: 'rgba(254, 242, 242, 0.95)',
214
- }}
215
- >
199
+ {frameError && !isLoading && (
216
200
  <div
217
201
  style={{
218
- background: 'white',
219
- border: '1px solid #fecaca',
220
- borderRadius: 8,
221
- padding: 16,
222
- maxWidth: 400,
202
+ position: 'absolute',
203
+ inset: 0,
204
+ zIndex: 10,
205
+ display: 'flex',
206
+ alignItems: 'center',
207
+ justifyContent: 'center',
208
+ padding: '16px',
209
+ background: 'rgba(254, 242, 242, 0.95)',
223
210
  }}
224
211
  >
225
- <div style={{ color: '#dc2626', fontWeight: 500, marginBottom: 8 }}>
226
- Preview Error
227
- </div>
228
- <div style={{ color: '#991b1b', fontSize: 13, marginBottom: retryCount < MAX_RETRIES ? 12 : 0 }}>
229
- {frameError}
212
+ <div
213
+ style={{
214
+ background: 'white',
215
+ border: '1px solid #fecaca',
216
+ borderRadius: 8,
217
+ padding: 16,
218
+ maxWidth: 400,
219
+ }}
220
+ >
221
+ <div style={{ color: '#dc2626', fontWeight: 500, marginBottom: 8 }}>
222
+ Preview Error
223
+ </div>
224
+ <div style={{ color: '#991b1b', fontSize: 13, marginBottom: retryCount < MAX_RETRIES ? 12 : 0 }}>
225
+ {frameError}
226
+ </div>
227
+ {retryCount < MAX_RETRIES && (
228
+ <button
229
+ onClick={handleRetry}
230
+ style={{
231
+ padding: '6px 12px',
232
+ fontSize: 13,
233
+ fontWeight: 500,
234
+ color: 'white',
235
+ background: '#dc2626',
236
+ border: 'none',
237
+ borderRadius: 6,
238
+ cursor: 'pointer',
239
+ }}
240
+ >
241
+ Retry ({MAX_RETRIES - retryCount} remaining)
242
+ </button>
243
+ )}
230
244
  </div>
231
- {retryCount < MAX_RETRIES && (
232
- <button
233
- onClick={handleRetry}
234
- style={{
235
- padding: '6px 12px',
236
- fontSize: 13,
237
- fontWeight: 500,
238
- color: 'white',
239
- background: '#dc2626',
240
- border: 'none',
241
- borderRadius: 6,
242
- cursor: 'pointer',
243
- }}
244
- >
245
- Retry ({MAX_RETRIES - retryCount} remaining)
246
- </button>
247
- )}
248
245
  </div>
249
- </div>
246
+ )}
250
247
 
251
248
  {/* The iframe */}
252
249
  <iframe
@@ -1,7 +1,7 @@
1
1
  import { useMemo, useEffect, useState } from "react";
2
2
  import type { FragmentDefinition } from "../../core/index.js";
3
3
  import { VariantRenderer } from "./VariantRenderer.js";
4
- import { getBackgroundStyle, type BackgroundOption, type ZoomLevel } from "../constants/ui.js";
4
+ import { type ZoomLevel } from "../constants/ui.js";
5
5
 
6
6
  interface IsolatedRenderProps {
7
7
  fragments: Array<{ path: string; fragment: FragmentDefinition }>;
@@ -10,7 +10,7 @@ interface IsolatedRenderProps {
10
10
  /**
11
11
  * Isolated render component for screenshot capture and standalone viewing.
12
12
  * Renders a single variant with minimal UI for visual testing.
13
- * URL params: ?isolated=true&component=Name&variant=VariantName&zoom=100&bg=white&theme=light
13
+ * URL params: ?isolated=true&component=Name&variant=VariantName&zoom=100&theme=light
14
14
  */
15
15
  export function IsolatedRender({ fragments }: IsolatedRenderProps) {
16
16
  const [ready, setReady] = useState(false);
@@ -19,15 +19,11 @@ export function IsolatedRender({ fragments }: IsolatedRenderProps) {
19
19
  const params = useMemo(() => {
20
20
  const searchParams = new URLSearchParams(window.location.search);
21
21
  const zoomParam = parseInt(searchParams.get("zoom") || "100", 10);
22
- const bgParam = searchParams.get("bg") || "white";
23
22
  return {
24
23
  component: searchParams.get("component"),
25
24
  variant: searchParams.get("variant"),
26
25
  theme: searchParams.get("theme") || "light",
27
26
  zoom: [50, 75, 100, 150, 200].includes(zoomParam) ? zoomParam as ZoomLevel : 100 as ZoomLevel,
28
- background: ["white", "black", "checkerboard", "transparent"].includes(bgParam)
29
- ? bgParam as BackgroundOption
30
- : "white" as BackgroundOption,
31
27
  };
32
28
  }, []);
33
29
 
@@ -101,7 +97,7 @@ export function IsolatedRender({ fragments }: IsolatedRenderProps) {
101
97
  display: 'flex',
102
98
  alignItems: 'center',
103
99
  justifyContent: 'center',
104
- ...getBackgroundStyle(params.background),
100
+ backgroundColor: 'var(--bg-primary)',
105
101
  }}
106
102
  >
107
103
  <div
@@ -14,16 +14,26 @@ function fuzzyMatch(text: string, pattern: string): FuzzyMatch | null {
14
14
  const textLower = text.toLowerCase();
15
15
  const patternLower = pattern.toLowerCase();
16
16
 
17
+ // Require substring match for short queries (<=3 chars) to avoid scattered single-char noise
18
+ if (patternLower.length <= 3 && !textLower.includes(patternLower)) {
19
+ return null;
20
+ }
21
+
17
22
  const indices: number[] = [];
18
23
  let patternIdx = 0;
19
24
  let score = 0;
20
25
  let consecutiveBonus = 0;
26
+ let maxGap = 0;
21
27
 
22
28
  for (let i = 0; i < textLower.length && patternIdx < patternLower.length; i++) {
23
29
  if (textLower[i] === patternLower[patternIdx]) {
24
30
  indices.push(i);
25
- if (indices.length > 1 && indices[indices.length - 2] === i - 1) {
26
- consecutiveBonus += 5;
31
+ if (indices.length > 1) {
32
+ const gap = i - indices[indices.length - 2];
33
+ if (gap === 1) {
34
+ consecutiveBonus += 5;
35
+ }
36
+ if (gap > maxGap) maxGap = gap;
27
37
  }
28
38
  if (i === 0 || text[i - 1] === ' ' || text[i - 1] === '-' || text[i - 1] === '_') {
29
39
  score += 10;
@@ -36,9 +46,19 @@ function fuzzyMatch(text: string, pattern: string): FuzzyMatch | null {
36
46
  return null;
37
47
  }
38
48
 
49
+ // Penalize large gaps between matched characters
50
+ if (maxGap > 5) {
51
+ score -= maxGap * 2;
52
+ }
53
+
39
54
  score += consecutiveBonus;
40
55
  score += (patternLower.length / textLower.length) * 20;
41
56
 
57
+ // Reject very low scores (scattered single-char matches)
58
+ if (score <= 0 && patternLower.length > 1) {
59
+ return null;
60
+ }
61
+
42
62
  return { score, indices };
43
63
  }
44
64
 
@@ -364,7 +384,7 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
364
384
  <Sidebar.Footer>
365
385
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
366
386
  <Text size="xs" color="tertiary">
367
- {isFilterActive ? `${flatItems.length} / ${fragments.length}` : fragments.length} components
387
+ {isFilterActive || searchResults ? `${flatItems.length} of ${fragments.length}` : fragments.length} components
368
388
  </Text>
369
389
  <Sidebar.CollapseToggle />
370
390
  </div>
@@ -10,7 +10,6 @@ import { useState, type ReactNode } from "react";
10
10
  import { ErrorBoundary } from "./ErrorBoundary.js";
11
11
  import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
12
12
  import { ChevronDownIcon } from "./Icons.js";
13
- import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
14
13
 
15
14
  interface ViewportConfig {
16
15
  name: string;
@@ -50,8 +49,6 @@ interface MultiViewportPreviewProps {
50
49
  renderContent: () => ReactNode;
51
50
  /** Preview theme */
52
51
  previewTheme: "light" | "dark";
53
- /** Background option for preview */
54
- background: BackgroundOption;
55
52
  /** Base zoom level (used for scaling if needed) */
56
53
  zoom: number;
57
54
  /** Whether to use iframe isolation */
@@ -64,7 +61,6 @@ export function MultiViewportPreview({
64
61
  variantName,
65
62
  renderContent,
66
63
  previewTheme,
67
- background,
68
64
  zoom,
69
65
  useIframeIsolation = true,
70
66
  }: MultiViewportPreviewProps) {
@@ -207,7 +203,6 @@ export function MultiViewportPreview({
207
203
  variantName={variantName}
208
204
  renderContent={renderContent}
209
205
  previewTheme={previewTheme}
210
- background={background}
211
206
  useIframeIsolation={useIframeIsolation}
212
207
  />
213
208
  ))}
@@ -224,7 +219,6 @@ interface ViewportPanelProps {
224
219
  variantName: string;
225
220
  renderContent: () => ReactNode;
226
221
  previewTheme: "light" | "dark";
227
- background: BackgroundOption;
228
222
  useIframeIsolation: boolean;
229
223
  }
230
224
 
@@ -235,7 +229,6 @@ function ViewportPanel({
235
229
  variantName,
236
230
  renderContent,
237
231
  previewTheme,
238
- background,
239
232
  useIframeIsolation,
240
233
  }: ViewportPanelProps) {
241
234
  if (viewport.type === "desktop") {
@@ -245,7 +238,6 @@ function ViewportPanel({
245
238
  height={viewport.height}
246
239
  label={viewport.name}
247
240
  previewTheme={previewTheme}
248
- background={background}
249
241
  componentName={componentName}
250
242
  fragmentPath={fragmentPath}
251
243
  variantName={variantName}
@@ -262,7 +254,6 @@ function ViewportPanel({
262
254
  height={viewport.height}
263
255
  label={viewport.name}
264
256
  previewTheme={previewTheme}
265
- background={background}
266
257
  componentName={componentName}
267
258
  fragmentPath={fragmentPath}
268
259
  variantName={variantName}
@@ -278,7 +269,6 @@ interface DeviceMockupProps {
278
269
  height: number;
279
270
  label: string;
280
271
  previewTheme: "light" | "dark";
281
- background: BackgroundOption;
282
272
  componentName: string;
283
273
  fragmentPath: string;
284
274
  variantName: string;
@@ -292,7 +282,6 @@ function DeviceMockup({
292
282
  height,
293
283
  label,
294
284
  previewTheme,
295
- background,
296
285
  componentName,
297
286
  fragmentPath,
298
287
  variantName,
@@ -302,7 +291,6 @@ function DeviceMockup({
302
291
  const isMobile = type === "mobile";
303
292
  const frameWidth = width + 24; // Add bezel width
304
293
  const screenHeight = height;
305
- const backgroundStyle = getBackgroundStyle(background);
306
294
 
307
295
  return (
308
296
  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
@@ -479,7 +467,6 @@ interface DesktopMockupProps {
479
467
  height: number;
480
468
  label: string;
481
469
  previewTheme: "light" | "dark";
482
- background: BackgroundOption;
483
470
  componentName: string;
484
471
  fragmentPath: string;
485
472
  variantName: string;
@@ -492,15 +479,12 @@ function DesktopMockup({
492
479
  height,
493
480
  label,
494
481
  previewTheme,
495
- background,
496
482
  componentName,
497
483
  fragmentPath,
498
484
  variantName,
499
485
  renderContent,
500
486
  useIframeIsolation,
501
487
  }: DesktopMockupProps) {
502
- const backgroundStyle = getBackgroundStyle(background);
503
-
504
488
  return (
505
489
  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
506
490
  {/* Label */}
@@ -568,7 +552,6 @@ function DesktopMockup({
568
552
  overflow: 'hidden',
569
553
  width: `${width}px`,
570
554
  height: `${height}px`,
571
- ...backgroundStyle,
572
555
  }}
573
556
  data-theme={previewTheme}
574
557
  >
@@ -13,7 +13,7 @@ import { FigmaEmbed } from './FigmaEmbed.js';
13
13
  import { VariantMatrix } from './VariantMatrix.js';
14
14
  import { MultiViewportPreview } from './MultiViewportPreview.js';
15
15
  import { IsolatedPreviewFrame } from './IsolatedPreviewFrame.js';
16
- import { getBackgroundStyle, getViewportWidth, type ZoomLevel, type BackgroundOption, type ViewportPreset, type ViewportSize } from '../constants/ui.js';
16
+ import { getViewportWidth, type ZoomLevel, type ViewportPreset, type ViewportSize } from '../constants/ui.js';
17
17
  import type { PreviewTheme } from '../hooks/useViewSettings.js';
18
18
 
19
19
  interface PreviewAreaProps {
@@ -25,7 +25,6 @@ interface PreviewAreaProps {
25
25
 
26
26
  // View settings
27
27
  zoom: ZoomLevel;
28
- background: BackgroundOption;
29
28
  viewport: ViewportPreset;
30
29
  customSize: ViewportSize;
31
30
  previewTheme: PreviewTheme;
@@ -151,11 +150,10 @@ const DeviceMockup = memo(function DeviceMockup({ type, width, children }: Devic
151
150
  interface PreviewContentProps {
152
151
  zoom: ZoomLevel;
153
152
  previewTheme: PreviewTheme;
154
- background: BackgroundOption;
155
153
  children: ReactNode;
156
154
  }
157
155
 
158
- const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, background, children }: PreviewContentProps) {
156
+ const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, children }: PreviewContentProps) {
159
157
  return (
160
158
  <div
161
159
  data-preview-container="true"
@@ -164,7 +162,6 @@ const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, backgr
164
162
  width: '100%',
165
163
  height: '100%',
166
164
  overflow: 'auto',
167
- backgroundColor: background === 'transparent' ? 'transparent' : undefined,
168
165
  }}
169
166
  >
170
167
  <div
@@ -188,7 +185,6 @@ export function PreviewArea({
188
185
  variant,
189
186
  variants,
190
187
  zoom,
191
- background,
192
188
  viewport,
193
189
  customSize,
194
190
  previewTheme,
@@ -212,7 +208,6 @@ export function PreviewArea({
212
208
  fragmentPath={fragmentPath}
213
209
  zoom={zoom}
214
210
  previewTheme={previewTheme}
215
- background={background}
216
211
  useIframeIsolation={useIframeIsolation}
217
212
  onSelectVariant={(index) => {
218
213
  onSelectVariant(index);
@@ -230,7 +225,6 @@ export function PreviewArea({
230
225
  variantName={variant.name}
231
226
  renderContent={renderContent}
232
227
  previewTheme={previewTheme}
233
- background={background}
234
228
  zoom={zoom}
235
229
  useIframeIsolation={useIframeIsolation}
236
230
  />
@@ -240,7 +234,6 @@ export function PreviewArea({
240
234
  const viewportWidth = getViewportWidth(viewport, customSize);
241
235
  const viewportHeight = viewport === 'custom' ? customSize.height : null;
242
236
  const isDevice = viewport === 'tablet' || viewport === 'mobile';
243
- const backgroundStyle = getBackgroundStyle(background);
244
237
 
245
238
  // Device mockup view (tablet/mobile)
246
239
  if (isDevice && viewportWidth && variant) {
@@ -252,7 +245,7 @@ export function PreviewArea({
252
245
  <div style={{ display: 'flex', gap: '16px', flex: 1 }}>
253
246
  <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
254
247
  <div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-tertiary)', marginBottom: '8px', textAlign: 'center' }}>Rendered</div>
255
- <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', ...backgroundStyle }}>
248
+ <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
256
249
  <DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
257
250
  {useIframeIsolation ? (
258
251
  <IsolatedPreviewFrame
@@ -264,7 +257,7 @@ export function PreviewArea({
264
257
  previewKey={previewKey}
265
258
  />
266
259
  ) : (
267
- <PreviewContent zoom={zoom} previewTheme={previewTheme} background={background}>
260
+ <PreviewContent zoom={zoom} previewTheme={previewTheme}>
268
261
  <ErrorBoundary key={previewKey} componentName={componentName} onRetry={onRetry}>
269
262
  {renderContent()}
270
263
  </ErrorBoundary>
@@ -285,7 +278,6 @@ export function PreviewArea({
285
278
  borderRadius: '8px',
286
279
  border: '1px solid var(--border)',
287
280
  overflow: 'hidden',
288
- ...backgroundStyle,
289
281
  }}
290
282
  />
291
283
  </div>
@@ -307,7 +299,7 @@ export function PreviewArea({
307
299
  previewKey={previewKey}
308
300
  />
309
301
  ) : (
310
- <PreviewContent zoom={zoom} previewTheme={previewTheme} background={background}>
302
+ <PreviewContent zoom={zoom} previewTheme={previewTheme}>
311
303
  <ErrorBoundary key={previewKey} componentName={componentName} onRetry={onRetry}>
312
304
  {renderContent()}
313
305
  </ErrorBoundary>
@@ -331,7 +323,6 @@ export function PreviewArea({
331
323
  borderRadius: '8px',
332
324
  border: '1px solid var(--border)',
333
325
  overflow: 'auto',
334
- ...backgroundStyle,
335
326
  }}
336
327
  >
337
328
  {useIframeIsolation ? (
@@ -383,7 +374,6 @@ export function PreviewArea({
383
374
  borderRadius: '8px',
384
375
  border: '1px solid var(--border)',
385
376
  overflow: 'hidden',
386
- ...backgroundStyle,
387
377
  }}
388
378
  />
389
379
  </div>
@@ -1,85 +1,19 @@
1
1
  import { useEffect, useCallback } from 'react';
2
- import { Button, Menu, Stack, Separator } from '@fragments-sdk/ui';
3
- import {
4
- ZOOM_LEVELS,
5
- type ZoomLevel,
6
- type BackgroundOption,
7
- } from '../constants/ui.js';
2
+ import { Button, Menu, Stack } from '@fragments-sdk/ui';
3
+ import { ZOOM_LEVELS, type ZoomLevel } from '../constants/ui.js';
8
4
  import { ZoomIcon, ChevronDownIcon } from './Icons.js';
9
5
 
10
6
  // Re-export types for consumers
11
- export type { ZoomLevel, BackgroundOption };
12
- export { getBackgroundStyle } from '../constants/ui.js';
13
-
14
- // Background options with display metadata
15
- const BACKGROUND_OPTIONS_UI: { value: BackgroundOption; label: string }[] = [
16
- { value: 'white', label: 'White' },
17
- { value: 'black', label: 'Black' },
18
- { value: 'checkerboard', label: 'Checkerboard' },
19
- { value: 'transparent', label: 'Transparent' },
20
- ];
21
-
22
- function BackgroundSwatch({ background }: { background: BackgroundOption }) {
23
- const baseStyle = {
24
- width: '14px',
25
- height: '14px',
26
- borderRadius: '4px',
27
- border: '1px solid var(--border)',
28
- flexShrink: 0,
29
- };
30
-
31
- if (background === 'white') {
32
- return <span aria-hidden="true" style={{ ...baseStyle, backgroundColor: '#ffffff' }} />;
33
- }
34
-
35
- if (background === 'black') {
36
- return <span aria-hidden="true" style={{ ...baseStyle, backgroundColor: '#171717', borderColor: '#2a2a2a' }} />;
37
- }
38
-
39
- if (background === 'checkerboard') {
40
- return (
41
- <span
42
- aria-hidden="true"
43
- style={{
44
- ...baseStyle,
45
- backgroundColor: '#ffffff',
46
- backgroundImage: `
47
- linear-gradient(45deg, #d4d4d8 25%, transparent 25%),
48
- linear-gradient(-45deg, #d4d4d8 25%, transparent 25%),
49
- linear-gradient(45deg, transparent 75%, #d4d4d8 75%),
50
- linear-gradient(-45deg, transparent 75%, #d4d4d8 75%)
51
- `,
52
- backgroundSize: '8px 8px',
53
- backgroundPosition: '0 0, 0 4px, 4px -4px, -4px 0',
54
- }}
55
- />
56
- );
57
- }
58
-
59
- return (
60
- <span
61
- aria-hidden="true"
62
- style={{
63
- ...baseStyle,
64
- backgroundColor: 'transparent',
65
- backgroundImage: 'linear-gradient(135deg, transparent 42%, var(--text-tertiary) 43%, var(--text-tertiary) 57%, transparent 58%)',
66
- }}
67
- />
68
- );
69
- }
7
+ export type { ZoomLevel };
70
8
 
71
9
  interface PreviewToolbarProps {
72
10
  zoom: ZoomLevel;
73
- background: BackgroundOption;
74
11
  onZoomChange: (zoom: ZoomLevel) => void;
75
- onBackgroundChange: (bg: BackgroundOption) => void;
76
12
  }
77
13
 
78
14
  export function PreviewToolbar({
79
15
  zoom,
80
- background,
81
16
  onZoomChange,
82
- onBackgroundChange,
83
17
  }: PreviewToolbarProps) {
84
18
  // Keyboard shortcuts for zoom
85
19
  const handleKeyDown = useCallback((e: KeyboardEvent) => {
@@ -114,7 +48,6 @@ export function PreviewToolbar({
114
48
 
115
49
  return (
116
50
  <Stack direction="row" gap="sm" align="center">
117
- {/* Zoom dropdown */}
118
51
  <Menu>
119
52
  <Menu.Trigger asChild>
120
53
  <Button variant="ghost" size="sm" title="Zoom level (+/-/0)">
@@ -142,39 +75,6 @@ export function PreviewToolbar({
142
75
  </Menu.RadioGroup>
143
76
  </Menu.Content>
144
77
  </Menu>
145
-
146
- {/* Divider */}
147
- <Separator orientation="vertical" />
148
-
149
- {/* Background selector */}
150
- <Menu>
151
- <Menu.Trigger asChild>
152
- <Button variant="ghost" size="sm" title="Background color">
153
- <Stack direction="row" gap="xs" align="center">
154
- <BackgroundSwatch background={background} />
155
- <span>{BACKGROUND_OPTIONS_UI.find(o => o.value === background)?.label}</span>
156
- <span style={{ display: 'inline-flex', width: '12px', height: '12px' }}>
157
- <ChevronDownIcon />
158
- </span>
159
- </Stack>
160
- </Button>
161
- </Menu.Trigger>
162
- <Menu.Content side="bottom" align="start">
163
- <Menu.RadioGroup
164
- value={background}
165
- onValueChange={(value: string) => onBackgroundChange(value as BackgroundOption)}
166
- >
167
- {BACKGROUND_OPTIONS_UI.map((option) => (
168
- <Menu.RadioItem key={option.value} value={option.value}>
169
- <Stack direction="row" gap="sm" align="center">
170
- <BackgroundSwatch background={option.value} />
171
- {option.label}
172
- </Stack>
173
- </Menu.RadioItem>
174
- ))}
175
- </Menu.RadioGroup>
176
- </Menu.Content>
177
- </Menu>
178
78
  </Stack>
179
79
  );
180
80
  }
@@ -17,7 +17,6 @@ import { ErrorBoundary } from "./ErrorBoundary.js";
17
17
  import { StoryRenderer, LoaderIndicator } from "./StoryRenderer.js";
18
18
  import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
19
19
  import { ChevronDownIcon } from "./Icons.js";
20
- import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
21
20
 
22
21
  interface VariantMatrixProps {
23
22
  /** All variants to display */
@@ -30,8 +29,6 @@ interface VariantMatrixProps {
30
29
  zoom: number;
31
30
  /** Preview theme */
32
31
  previewTheme: "light" | "dark";
33
- /** Background option */
34
- background: BackgroundOption;
35
32
  /** Whether to use iframe isolation */
36
33
  useIframeIsolation?: boolean;
37
34
  /** Callback when a variant is clicked to focus on it */
@@ -63,7 +60,6 @@ export function VariantMatrix({
63
60
  fragmentPath,
64
61
  zoom,
65
62
  previewTheme,
66
- background,
67
63
  useIframeIsolation = true,
68
64
  onSelectVariant,
69
65
  }: VariantMatrixProps) {
@@ -208,7 +204,6 @@ export function VariantMatrix({
208
204
  scale={effectiveScale}
209
205
  minHeight={gridConfig.minHeight}
210
206
  previewTheme={previewTheme}
211
- background={background}
212
207
  useIframeIsolation={useIframeIsolation}
213
208
  isHovered={hoveredIndex === index}
214
209
  onHover={() => setHoveredIndex(index)}
@@ -240,7 +235,6 @@ export function VariantMatrix({
240
235
  scale={effectiveScale}
241
236
  minHeight={gridConfig.minHeight}
242
237
  previewTheme={previewTheme}
243
- background={background}
244
238
  useIframeIsolation={useIframeIsolation}
245
239
  isHovered={hoveredIndex === index}
246
240
  onHover={() => setHoveredIndex(index)}
@@ -263,7 +257,6 @@ interface VariantCardProps {
263
257
  scale: number;
264
258
  minHeight: string;
265
259
  previewTheme: "light" | "dark";
266
- background: BackgroundOption;
267
260
  useIframeIsolation: boolean;
268
261
  isHovered: boolean;
269
262
  onHover: () => void;
@@ -279,15 +272,12 @@ function VariantCard({
279
272
  scale,
280
273
  minHeight,
281
274
  previewTheme,
282
- background,
283
275
  useIframeIsolation,
284
276
  isHovered,
285
277
  onHover,
286
278
  onLeave,
287
279
  onClick,
288
280
  }: VariantCardProps) {
289
- const backgroundStyle = getBackgroundStyle(background);
290
-
291
281
  return (
292
282
  <div
293
283
  style={{
@@ -373,7 +363,6 @@ function VariantCard({
373
363
  display: 'flex',
374
364
  alignItems: 'center',
375
365
  justifyContent: 'center',
376
- ...backgroundStyle,
377
366
  }}
378
367
  >
379
368
  {useIframeIsolation ? (
@@ -102,12 +102,6 @@ export function getRelationshipConfig(relationship: string) {
102
102
  return RELATIONSHIP_CONFIG[relationship as RelationshipType] || RELATIONSHIP_CONFIG.related;
103
103
  }
104
104
 
105
- /**
106
- * Preview background options.
107
- */
108
- export const BACKGROUND_OPTIONS = ['white', 'black', 'checkerboard', 'transparent'] as const;
109
- export type BackgroundOption = (typeof BACKGROUND_OPTIONS)[number];
110
-
111
105
  /**
112
106
  * Zoom level options.
113
107
  */
@@ -140,34 +134,6 @@ export function getViewportWidth(viewport: ViewportPreset, customSize: ViewportS
140
134
  return VIEWPORT_PRESETS[viewport]?.width ?? null;
141
135
  }
142
136
 
143
- /**
144
- * Get CSS background style for preview pane.
145
- */
146
- export function getBackgroundStyle(bg: BackgroundOption): React.CSSProperties {
147
- switch (bg) {
148
- case 'white':
149
- return { backgroundColor: '#ffffff' };
150
- case 'black':
151
- return { backgroundColor: '#000000' };
152
- case 'transparent':
153
- return { backgroundColor: 'transparent' };
154
- case 'checkerboard':
155
- return {
156
- backgroundColor: '#ffffff',
157
- backgroundImage: `
158
- linear-gradient(45deg, #e5e5e5 25%, transparent 25%),
159
- linear-gradient(-45deg, #e5e5e5 25%, transparent 25%),
160
- linear-gradient(45deg, transparent 75%, #e5e5e5 75%),
161
- linear-gradient(-45deg, transparent 75%, #e5e5e5 75%)
162
- `,
163
- backgroundSize: '16px 16px',
164
- backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0px',
165
- };
166
- default:
167
- return { backgroundColor: '#ffffff' };
168
- }
169
- }
170
-
171
137
  /**
172
138
  * Storage keys for localStorage persistence.
173
139
  */
@@ -262,7 +262,7 @@ export const SHORTCUTS = [
262
262
  { keys: ["p"], description: "Toggle addons panel" },
263
263
  { keys: ["m"], description: "Matrix view" },
264
264
  { keys: ["v"], description: "Responsive view" },
265
- { keys: ["/", "⌘K"], description: "Search" },
265
+ { keys: ["/", "⌘K"], description: "Command palette" },
266
266
  { keys: ["?"], description: "Show shortcuts" },
267
267
  { keys: ["Esc"], description: "Close / Clear" },
268
268
  ];
@@ -5,9 +5,9 @@
5
5
  * - Component path
6
6
  * - Variant index/name
7
7
  * - Edited props
8
- * - View settings (zoom, background, viewport)
8
+ * - View settings (zoom, viewport)
9
9
  *
10
- * URL format: #/ComponentName/VariantName?props=base64&zoom=100&bg=white
10
+ * URL format: #/ComponentName/VariantName?props=base64&zoom=100
11
11
  */
12
12
 
13
13
  import { useState, useEffect, useCallback, useMemo } from "react";
@@ -21,7 +21,6 @@ export interface UrlState {
21
21
  props: Record<string, unknown>;
22
22
  /** View settings */
23
23
  zoom: number;
24
- background: string;
25
24
  viewport: string;
26
25
  customWidth: number | null;
27
26
  customHeight: number | null;
@@ -32,7 +31,6 @@ const DEFAULT_STATE: UrlState = {
32
31
  variant: null,
33
32
  props: {},
34
33
  zoom: 100,
35
- background: "transparent",
36
34
  viewport: "responsive",
37
35
  customWidth: null,
38
36
  customHeight: null,
@@ -99,11 +97,6 @@ function parseUrl(): UrlState {
99
97
  }
100
98
  }
101
99
 
102
- const bg = params.get("bg");
103
- if (bg && ["white", "black", "checkerboard", "transparent"].includes(bg)) {
104
- state.background = bg;
105
- }
106
-
107
100
  const viewport = params.get("viewport");
108
101
  if (viewport && ["responsive", "desktop", "tablet", "mobile", "custom"].includes(viewport)) {
109
102
  state.viewport = viewport;
@@ -155,10 +148,6 @@ function buildUrl(state: Partial<UrlState>): string {
155
148
  params.set("zoom", String(state.zoom));
156
149
  }
157
150
 
158
- if (state.background && state.background !== "transparent") {
159
- params.set("bg", state.background);
160
- }
161
-
162
151
  if (state.viewport && state.viewport !== "responsive") {
163
152
  params.set("viewport", state.viewport);
164
153
  }
@@ -249,7 +238,6 @@ export function useUrlState() {
249
238
  const setViewSettings = useCallback(
250
239
  (settings: {
251
240
  zoom?: number;
252
- background?: string;
253
241
  viewport?: string;
254
242
  customWidth?: number | null;
255
243
  customHeight?: number | null;
@@ -1,17 +1,16 @@
1
1
  /**
2
2
  * View settings state management.
3
- * Handles zoom, background, viewport, and theme settings with URL sync.
3
+ * Handles zoom, viewport, and theme settings with URL sync.
4
4
  */
5
5
 
6
6
  import { useReducer, useCallback, useMemo } from 'react';
7
- import type { ZoomLevel, BackgroundOption, ViewportPreset, ViewportSize } from '../constants/ui.js';
7
+ import type { ZoomLevel, ViewportPreset, ViewportSize } from '../constants/ui.js';
8
8
 
9
9
  // Preview theme type (for canvas theming)
10
10
  export type PreviewTheme = 'light' | 'dark';
11
11
 
12
12
  interface ViewSettings {
13
13
  zoom: ZoomLevel;
14
- background: BackgroundOption;
15
14
  viewport: ViewportPreset;
16
15
  customSize: ViewportSize;
17
16
  previewTheme: PreviewTheme;
@@ -19,7 +18,6 @@ interface ViewSettings {
19
18
 
20
19
  type ViewSettingsAction =
21
20
  | { type: 'SET_ZOOM'; payload: ZoomLevel }
22
- | { type: 'SET_BACKGROUND'; payload: BackgroundOption }
23
21
  | { type: 'SET_VIEWPORT'; payload: ViewportPreset }
24
22
  | { type: 'SET_CUSTOM_SIZE'; payload: ViewportSize }
25
23
  | { type: 'SET_PREVIEW_THEME'; payload: PreviewTheme }
@@ -28,7 +26,6 @@ type ViewSettingsAction =
28
26
 
29
27
  interface ViewSettingsInit {
30
28
  zoom?: ZoomLevel;
31
- background?: BackgroundOption;
32
29
  viewport?: ViewportPreset;
33
30
  customSize?: ViewportSize;
34
31
  previewTheme?: PreviewTheme;
@@ -37,7 +34,6 @@ interface ViewSettingsInit {
37
34
  function createInitialState(init?: ViewSettingsInit): ViewSettings {
38
35
  return {
39
36
  zoom: init?.zoom ?? 100,
40
- background: init?.background ?? 'transparent',
41
37
  viewport: init?.viewport ?? 'responsive',
42
38
  customSize: init?.customSize ?? { width: null, height: null },
43
39
  previewTheme: init?.previewTheme ?? 'light',
@@ -48,8 +44,6 @@ function viewSettingsReducer(state: ViewSettings, action: ViewSettingsAction): V
48
44
  switch (action.type) {
49
45
  case 'SET_ZOOM':
50
46
  return { ...state, zoom: action.payload };
51
- case 'SET_BACKGROUND':
52
- return { ...state, background: action.payload };
53
47
  case 'SET_VIEWPORT':
54
48
  return { ...state, viewport: action.payload };
55
49
  case 'SET_CUSTOM_SIZE':
@@ -68,12 +62,11 @@ function viewSettingsReducer(state: ViewSettings, action: ViewSettingsAction): V
68
62
  interface UseViewSettingsOptions {
69
63
  initialState?: ViewSettingsInit;
70
64
  onZoomChange?: (zoom: ZoomLevel) => void;
71
- onBackgroundChange?: (bg: BackgroundOption) => void;
72
65
  onViewportChange?: (viewport: ViewportPreset, customSize?: ViewportSize) => void;
73
66
  }
74
67
 
75
68
  export function useViewSettings(options: UseViewSettingsOptions = {}) {
76
- const { initialState: init, onZoomChange, onBackgroundChange, onViewportChange } = options;
69
+ const { initialState: init, onZoomChange, onViewportChange } = options;
77
70
 
78
71
  const [state, dispatch] = useReducer(
79
72
  viewSettingsReducer,
@@ -86,11 +79,6 @@ export function useViewSettings(options: UseViewSettingsOptions = {}) {
86
79
  onZoomChange?.(zoom);
87
80
  }, [onZoomChange]);
88
81
 
89
- const setBackground = useCallback((bg: BackgroundOption) => {
90
- dispatch({ type: 'SET_BACKGROUND', payload: bg });
91
- onBackgroundChange?.(bg);
92
- }, [onBackgroundChange]);
93
-
94
82
  const setViewport = useCallback((viewport: ViewportPreset) => {
95
83
  dispatch({ type: 'SET_VIEWPORT', payload: viewport });
96
84
  onViewportChange?.(viewport);
@@ -111,12 +99,11 @@ export function useViewSettings(options: UseViewSettingsOptions = {}) {
111
99
 
112
100
  const actions = useMemo(() => ({
113
101
  setZoom,
114
- setBackground,
115
102
  setViewport,
116
103
  setCustomSize,
117
104
  setPreviewTheme,
118
105
  toggleTheme,
119
- }), [setZoom, setBackground, setViewport, setCustomSize, setPreviewTheme, toggleTheme]);
106
+ }), [setZoom, setViewport, setCustomSize, setPreviewTheme, toggleTheme]);
120
107
 
121
108
  return { ...state, ...actions };
122
109
  }