@satinhq/kit 0.1.0-beta.1 → 0.1.0-beta.3

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 (129) hide show
  1. package/NOTICE +2 -2
  2. package/README.md +1 -1
  3. package/dist/acknowledgements/inventory.json +5 -5
  4. package/dist/actions/RowActions.d.ts +2 -2
  5. package/dist/actions/RowActions.js +2 -2
  6. package/dist/app/deployment.js +8 -2
  7. package/dist/app/installCheck.js +4 -3
  8. package/dist/app/launchWatchdog.js +6 -1
  9. package/dist/controls/civilDate.d.ts +1 -1
  10. package/dist/controls/civilDate.js +1 -1
  11. package/dist/docs/components/file-importer.md +1 -1
  12. package/dist/docs/components/paste-button.md +1 -1
  13. package/dist/docs/components/photos-picker.md +1 -1
  14. package/dist/docs/components/share-link.md +1 -1
  15. package/dist/docs/limitations.md +1 -1
  16. package/dist/docs/push.md +3 -3
  17. package/dist/docs/reference.md +1870 -0
  18. package/dist/glass/GlassMaterialRoot.d.ts +3 -1
  19. package/dist/glass/GlassMaterialRoot.js +2 -2
  20. package/dist/glass/MaterialEffectRoot.js +5 -1
  21. package/dist/glass/errors.d.ts +3 -1
  22. package/dist/glass/errors.js +49 -3
  23. package/dist/glass/fallback/canvasOwnership.d.ts +7 -0
  24. package/dist/glass/fallback/canvasOwnership.js +23 -0
  25. package/dist/glass/fallback/material.js +3 -2
  26. package/dist/glass/fallback/regions.d.ts +1 -0
  27. package/dist/glass/fallback/regions.js +9 -12
  28. package/dist/glass/fallback/runtime.d.ts +2 -0
  29. package/dist/glass/fallback/runtime.js +15 -4
  30. package/dist/glass/lens/controlLift.d.ts +1 -1
  31. package/dist/glass/lens/controlLift.js +1 -1
  32. package/dist/glass/lens/interactivePress.d.ts +2 -2
  33. package/dist/glass/lens/interactivePress.js +2 -2
  34. package/dist/glass/lens/menu-opening-native-fits.json +1 -1
  35. package/dist/glass/lens/menuContextFit.json +1 -1
  36. package/dist/glass/lens/menuDropFit.json +1 -1
  37. package/dist/glass/lens/menuField.d.ts +2 -2
  38. package/dist/glass/lens/menuField.js +3 -3
  39. package/dist/glass/lens/menuGlyphDropFit.json +1 -1
  40. package/dist/glass/lens/menuMorphFit.json +1 -1
  41. package/dist/glass/lens/menuMorphPhoneFit.json +1 -1
  42. package/dist/glass/lens/tabAccessoryMotion.d.ts +1 -1
  43. package/dist/glass/lens/tabSearchMember.d.ts +1 -1
  44. package/dist/glass/lens/tabSearchMember.js +1 -1
  45. package/dist/glass/menuPresenter.js +1 -1
  46. package/dist/glass/regions.d.ts +1 -1
  47. package/dist/glass/regions.js +1 -1
  48. package/dist/glass/shapeTrack.d.ts +1 -1
  49. package/dist/glass/submenuMotion.d.ts +1 -1
  50. package/dist/glass/submenuMotion.js +1 -1
  51. package/dist/glass/webgpu/client.d.ts +3 -0
  52. package/dist/glass/webgpu/client.js +57 -9
  53. package/dist/glass/webgpu/entry.worker.js +3 -6
  54. package/dist/glass/webgpu/errorCode.js +3 -2
  55. package/dist/glass/webgpu/frameClock.d.ts +6 -2
  56. package/dist/glass/webgpu/frameClock.js +9 -2
  57. package/dist/glass/webgpu/frameProbe.d.ts +1 -1
  58. package/dist/glass/webgpu/frameProbe.js +3 -3
  59. package/dist/glass/webgpu/layers.d.ts +5 -1
  60. package/dist/glass/webgpu/layers.js +61 -6
  61. package/dist/glass/webgpu/minimize.js +20 -2
  62. package/dist/glass/webgpu/production.js +13 -1
  63. package/dist/glass/webgpu/raster.d.ts +18 -0
  64. package/dist/glass/webgpu/raster.js +105 -15
  65. package/dist/glass/webgpu/rasterMaterial.d.ts +4 -0
  66. package/dist/glass/webgpu/rasterMaterial.js +17 -1
  67. package/dist/glass/webgpu/regionScene.d.ts +4 -1
  68. package/dist/glass/webgpu/regionScene.js +30 -1
  69. package/dist/glass/webgpu/renderer.js +23 -7
  70. package/dist/glass/webgpu/sourcePixels.d.ts +4 -4
  71. package/dist/glass/webgpu/sourcePixels.js +48 -8
  72. package/dist/keyboard/adapter.js +2 -2
  73. package/dist/keyboard/avoidance.d.ts +1 -1
  74. package/dist/keyboard/avoidance.js +1 -1
  75. package/dist/keyboard/motion.d.ts +1 -1
  76. package/dist/keyboard/motion.js +1 -1
  77. package/dist/list-form/ListForm.d.ts +2 -2
  78. package/dist/list-form/ListForm.js +1 -1
  79. package/dist/list-form/ListItem.js +1 -1
  80. package/dist/list-form/RowLabel.d.ts +1 -1
  81. package/dist/list-form/RowLabel.js +1 -1
  82. package/dist/list-form/Slider.js +1 -1
  83. package/dist/list-form/editMotion.js +1 -1
  84. package/dist/list-form/editing.d.ts +5 -5
  85. package/dist/list-form/editing.js +1 -1
  86. package/dist/motion/animationValue.d.ts +2 -2
  87. package/dist/motion/animationValue.js +9 -10
  88. package/dist/motion/matchedGeometry.d.ts +4 -4
  89. package/dist/motion/matchedGeometry.js +7 -8
  90. package/dist/motion/springTrack.d.ts +1 -2
  91. package/dist/motion/springTrack.js +10 -5
  92. package/dist/navigation/ScreenContainer.js +1 -1
  93. package/dist/navigation/chromeCurves.js +1 -1
  94. package/dist/navigation/types.d.ts +1 -1
  95. package/dist/navigation/zoomGeometry.js +1 -1
  96. package/dist/navigation/zoomMotion.d.ts +1 -1
  97. package/dist/navigation/zoomMotion.js +2 -2
  98. package/dist/navigation/zoomTransition.js +3 -4
  99. package/dist/presentation/Presentation.d.ts +2 -2
  100. package/dist/presentation/Presentation.js +1 -1
  101. package/dist/presentation/presentationSource.js +1 -1
  102. package/dist/public/controls.d.ts +4 -4
  103. package/dist/public/controls.js +7 -7
  104. package/dist/public/glass.d.ts +3 -4
  105. package/dist/public/glass.js +1 -2
  106. package/dist/public/navigationTransition.d.ts +1 -1
  107. package/dist/public/navigationTransition.js +1 -1
  108. package/dist/public/scroll.d.ts +1 -1
  109. package/dist/public/scroll.js +1 -1
  110. package/dist/public/toolbar.js +1 -1
  111. package/dist/root/SatinRoot.js +9 -5
  112. package/dist/safe-area/SafeAreaInset.js +3 -3
  113. package/dist/safe-area/modifiers.js +2 -2
  114. package/dist/safe-area/useSafeAreaMetrics.js +2 -2
  115. package/dist/scroll/ScrollSurface.d.ts +5 -5
  116. package/dist/scroll/ScrollSurface.js +1 -1
  117. package/dist/scroll/position.js +1 -1
  118. package/dist/scroll/screenGeometry.d.ts +1 -1
  119. package/dist/scroll/screenGeometry.js +1 -1
  120. package/dist/search/SearchDrawer.js +1 -1
  121. package/dist/search/searchMotion.d.ts +1 -1
  122. package/dist/search/searchMotion.js +1 -1
  123. package/dist/search/useSearchPresentation.js +1 -1
  124. package/dist/shapes/corners.js +1 -1
  125. package/dist/styles.css +55 -34
  126. package/dist/sw/renderServiceWorker.js +12 -3
  127. package/dist/text-input/TextEditor.js +7 -3
  128. package/package.json +4 -4
  129. package/dist/docs/swiftui-reference.md +0 -1895
@@ -6,7 +6,7 @@ import { IgnoresSafeAreaContext } from './context.js';
6
6
  import { createContext, useContext, useLayoutEffect, useRef, useMemo, useCallback, } from 'react';
7
7
  /** Whether an enclosing SafeAreaInset already owns the bottom boundary (keyboard, home indicator). */
8
8
  export const BottomInsetContext = createContext({ consumed: false });
9
- /** SwiftUI's system spacing between content and a safe-area inset (`spacing: nil`). */
9
+ /** Default spacing between content and a safe-area inset. */
10
10
  const SYSTEM_INSET_SPACING_PX = 8;
11
11
  const ACCESSORY_INSET_SCOPE = { consumed: true };
12
12
  /**
@@ -41,7 +41,7 @@ function BottomInset({ content, spacing, children, ...props }) {
41
41
  geometry.publish((parent.geometry?.value ?? 0) + measuredHeight.current + spacing + extension);
42
42
  }, [geometry, parent.geometry, spacing]);
43
43
  // `IgnoresSafeArea regions="keyboard"` around the inset: the boundary stays above the home
44
- // indicator while the keyboard is up, as SwiftUI's `ignoresSafeArea(.keyboard)`.
44
+ // indicator while the keyboard is up `ignoresSafeArea(.keyboard)`.
45
45
  const keyboardIgnored = useContext(IgnoresSafeAreaContext).keyboard;
46
46
  const inset = useRef(null);
47
47
  const accessory = useRef(null);
@@ -112,7 +112,7 @@ function BottomInset({ content, spacing, children, ...props }) {
112
112
  * native adds it to the scroll view's adjusted inset (iOS 27 native reference `inset-top`: 116 + 44.33 + 8);
113
113
  * - leading/trailing: the accessory is a column of the safe area's height and the content's frame
114
114
  * gives up its width plus `spacing` (`inset-leading`: strip 0–60, cards from 68).
115
- * A background drawn by the accessory's root view reaches the screen's top edge, as a SwiftUI
115
+ * A background drawn by the accessory's root view reaches the screen's top edge. An accessory
116
116
  * `background` ignores the safe area by default (`inset-top`: the yellow fills 0–160.33).
117
117
  */
118
118
  function EdgeInset({ edge, content, spacing, children, ...props }) {
@@ -3,7 +3,7 @@ import { startsScreenContent } from '../scroll/screenGeometry.js';
3
3
  import { SafeAreaInset } from './SafeAreaInset.js';
4
4
  import { ContentMarginsContext, IgnoresSafeAreaContext, NO_LENGTHS, SafeAreaPaddingContext, } from './context.js';
5
5
  import { useContext, useLayoutEffect, useRef } from 'react';
6
- /** SwiftUI's default padding amount (`padding()` and `safeAreaPadding()` with no length). */
6
+ /** Default padding used when no length is supplied. */
7
7
  const SYSTEM_PADDING_PX = 16;
8
8
  function edgesOf(set) {
9
9
  const edges = new Set();
@@ -68,7 +68,7 @@ export function IgnoresSafeArea({ regions = 'all', edges = 'all', children, ...p
68
68
  const container = regions === 'keyboard' ? new Set() : set;
69
69
  const keyboard = regions !== 'container' && set.has('bottom');
70
70
  const element = useRef(null);
71
- // Only a view that reaches the safe edge extends past it, as SwiftUI's does.
71
+ // Only a view that reaches the safe edge extends past it.
72
72
  useLayoutEffect(() => {
73
73
  const node = element.current;
74
74
  if (!node)
@@ -60,7 +60,7 @@ function readInsets(element) {
60
60
  };
61
61
  }
62
62
  // Native home-indicator boundaries, in points. Phone: Foundation 00, iPhone 17 Pro Max,
63
- // iOS 27 portrait. Pad: a SwiftUI .safeAreaInset(edge: .bottom) composer on iPad Pro
63
+ // iOS 27 portrait. Pad: a .safeAreaInset(edge: .bottom) composer on iPad Pro
64
64
  // 12.9-inch, iPadOS 27, which keeps 25 pt in portrait and in landscape.
65
65
  const PHONE_HOME_INDICATOR_INSET = 34;
66
66
  const PAD_HOME_INDICATOR_INSET = 25;
@@ -72,7 +72,7 @@ const PAD_FITTED_STATUS_BAND = 32;
72
72
  * With viewport-fit auto/contain, the system takes the status band outside the viewport,
73
73
  * but content still reaches the screen bottom and every env() inset reads zero. Without
74
74
  * this boundary, bottom bars and composers sit in the home-indicator band: on iPad a
75
- * composer sat 25 pt lower than the same SwiftUI layout, its trailing edge in the corner
75
+ * composer sat 25 pt lower than the same layout, its trailing edge in the corner
76
76
  * that carries the window-resize grabber. Device identity comes from the shared
77
77
  * resolver, never from screen dimensions.
78
78
  */
@@ -28,15 +28,15 @@ export type ScrollSurfaceProps = Omit<HTMLAttributes<HTMLDivElement>, 'id'> & Sc
28
28
  /**
29
29
  * Whether this is the screen's primary scroll view, which gets the large title, the bar's
30
30
  * content inset and the scroll edge effect. By default the outermost vertical scroll view at the
31
- * top of the screen's content is primary, as in SwiftUI.
31
+ * top of the screen's content is primary.
32
32
  */
33
33
  primary?: boolean;
34
- /** SwiftUI `scrollEdgeEffectHidden(_:for: .top)`: no scroll edge effect under the bar. */
34
+ /** Hide the scroll edge effect under the top bar. */
35
35
  scrollEdgeEffectHidden?: ScrollEdgeEffectHidden;
36
36
  /** Soft/automatic select the existing top treatment. Hard is explicitly unsupported. */
37
37
  scrollEdgeEffectStyle?: ScrollEdgeEffectStyleSelection;
38
38
  }>;
39
- /** SwiftUI `ScrollView`: a named scroll surface whose position is retained by the framework. */
39
+ /** A named scroll surface whose position is retained by the framework. */
40
40
  export declare const ScrollSurface: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "id"> & Readonly<{
41
41
  defaultScrollAnchor?: 'top' | 'bottom';
42
42
  maintainVisibleContentPosition?: boolean;
@@ -52,10 +52,10 @@ export declare const ScrollSurface: import("react").ForwardRefExoticComponent<Om
52
52
  /**
53
53
  * Whether this is the screen's primary scroll view, which gets the large title, the bar's
54
54
  * content inset and the scroll edge effect. By default the outermost vertical scroll view at the
55
- * top of the screen's content is primary, as in SwiftUI.
55
+ * top of the screen's content is primary.
56
56
  */
57
57
  primary?: boolean;
58
- /** SwiftUI `scrollEdgeEffectHidden(_:for: .top)`: no scroll edge effect under the bar. */
58
+ /** Hide the scroll edge effect under the top bar. */
59
59
  scrollEdgeEffectHidden?: ScrollEdgeEffectHidden;
60
60
  /** Soft/automatic select the existing top treatment. Hard is explicitly unsupported. */
61
61
  scrollEdgeEffectStyle?: ScrollEdgeEffectStyleSelection;
@@ -27,7 +27,7 @@ export const ListNavigationContext = createContext(undefined);
27
27
  /** A scroll view's content is inside its safe area: modifiers above it stop at its frame. */
28
28
  const NO_IGNORED = { container: new Set(), keyboard: false };
29
29
  const cssLength = (value) => typeof value === 'number' ? `${value}px` : (value ?? '0px');
30
- /** SwiftUI `ScrollView`: a named scroll surface whose position is retained by the framework. */
30
+ /** A named scroll surface whose position is retained by the framework. */
31
31
  export const ScrollSurface = forwardRef(function ScrollSurface({ axis = 'vertical', chain = false, children, primary, scrollEdgeEffectHidden, scrollEdgeEffectStyle, refreshable, scrollDismissesKeyboard = 'never', id, defaultScrollAnchor, maintainVisibleContentPosition, scrollPosition, onScrollGeometryChange, ...props }, forwardedRef) {
32
32
  const refresh = refreshable;
33
33
  if (axis !== 'vertical' &&
@@ -84,7 +84,7 @@ export function createScrollPositionController(surface, content, targets, option
84
84
  pending = undefined;
85
85
  const targetTop = top(target);
86
86
  const height = target.getBoundingClientRect().height;
87
- // Targets align within the content insets, as SwiftUI's do: a screen's primary scroll view
87
+ // Targets align within the content insets, within the owned scroll region: a screen's primary scroll view
88
88
  // reaches under the bar and the bottom bars, and its padding is the bar's and the bottom inset
89
89
  // chain's content inset. Other scroll views only clear the enclosing bottom insets' accessories.
90
90
  const style = getComputedStyle(surface);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Whether a scroll view starts its screen's content, as a SwiftUI scroll view that reaches the
2
+ * Whether a scroll view starts its screen's content, as a scroll view that reaches the
3
3
  * top safe-area edge does. Only such a scroll view extends under the navigation bar and takes the
4
4
  * bar's content inset; one placed below other content keeps its own frame, like a Form under a
5
5
  * header in a VStack.
@@ -11,7 +11,7 @@ function occupiesFlow(element) {
11
11
  return position !== 'absolute' && position !== 'fixed';
12
12
  }
13
13
  /**
14
- * Whether a scroll view starts its screen's content, as a SwiftUI scroll view that reaches the
14
+ * Whether a scroll view starts its screen's content, as a scroll view that reaches the
15
15
  * top safe-area edge does. Only such a scroll view extends under the navigation bar and takes the
16
16
  * bar's content inset; one placed below other content keeps its own frame, like a Form under a
17
17
  * header in a VStack.
@@ -258,7 +258,7 @@ function pinProgress(drawer) {
258
258
  }
259
259
  /* How far the resting field has turned from its grey fill to glass. The field is glass from
260
260
  * scroll 0 only when the drawer is fixed in the bar: an explicit `navigationBarDrawer` placement
261
- * (SwiftUI's `displayMode: .always`) under an inline or inlineLarge title (native reference reference and reference
261
+ * with persistent visibility under an inline or inlineLarge title (native reference reference and reference
262
262
  * B/H, iOS 27.0: light 251 with a darker rim, dark 32 with a light rim). Under a large title the
263
263
  * field belongs to the content header, and an automatic placement's drawer scrolls away with the
264
264
  * content; both are a grey fill until content scrolls under the field (reference A/C/D/G: light 228-239,
@@ -3,7 +3,7 @@ import { sampledKeyframes, springTrack, type SpringParameters, type SpringTrack
3
3
  export { sampledKeyframes, springTrack, type SpringTrack };
4
4
  /**
5
5
  * Native `.searchable` presentation and dismissal, measured from the presentation layer of a
6
- * stock SwiftUI drawer (iOS 27.0, 15 focus/close trials in light and dark,
6
+ * native search drawer (iOS 27.0, 15 focus/close trials in light and dark,
7
7
  * every frame).
8
8
  *
9
9
  * - The field's position and width, the list content, the close button and the field's grey fill
@@ -3,7 +3,7 @@ import { sampledKeyframes, springMotion, springTrack, } from '../motion/springTr
3
3
  export { sampledKeyframes, springTrack };
4
4
  /**
5
5
  * Native `.searchable` presentation and dismissal, measured from the presentation layer of a
6
- * stock SwiftUI drawer (iOS 27.0, 15 focus/close trials in light and dark,
6
+ * native search drawer (iOS 27.0, 15 focus/close trials in light and dark,
7
7
  * every frame).
8
8
  *
9
9
  * - The field's position and width, the list content, the close button and the field's grey fill
@@ -3,7 +3,7 @@ import { focusFromFramework } from '../focus/programmaticFocus.js';
3
3
  import { validateSearchPresentation } from './searchable.js';
4
4
  /*
5
5
  * The state of one `searchable` field, whatever draws it: presentation (owned or controlled by
6
- * `isPresented:`), text changes, submission and suggestion completion, as SwiftUI's search
6
+ * `isPresented:`), text changes, submission and suggestion completion search
7
7
  * controller. The bottom toolbar field uses it (SearchToolbarField.tsx); the navigation bar drawer
8
8
  * keeps its own copy until reference adopts this hook (spec notes, reference construction update).
9
9
  */
@@ -1,4 +1,4 @@
1
- /** Measured from public SwiftUI RoundedRectangle paths on iOS 27.0. The
1
+ /** Continuous rounded corners measured on iOS 27.0. The
2
2
  * straight-edge approach contracts independently on each axis for short boxes;
3
3
  * clamping the entire continuous corner to half-height changes its curvature. */
4
4
  const extent = 1.5286649466;
package/dist/styles.css CHANGED
@@ -922,7 +922,7 @@ body[data-satin-document='true'],
922
922
  gap: 8px;
923
923
  margin-top: 16px;
924
924
  }
925
- /* SwiftUI ContentUnavailableView fixture: 40.5pt symbol, 20pt label gap,
925
+ /* ContentUnavailableView geometry: 40.5pt symbol, 20pt label gap,
926
926
  * 3pt description gap and 30pt to the 15pt action label. Keep its 44pt hit area. */
927
927
  [data-satin-offline='full-screen'] {
928
928
  padding: 20px;
@@ -2972,7 +2972,7 @@ body[data-satin-document='true'],
2972
2972
  [data-satin-toolbar-item] {
2973
2973
  display: contents;
2974
2974
  }
2975
- /* SwiftUI ToolbarSpacer: a fixed spacer leaves the standard 12 pt between two items' glass
2975
+ /* ToolbarSpacer: a fixed spacer leaves the standard 12 pt between two items' glass
2976
2976
  (iOS 27.0 native reference: 44 pt circles at 320 and 376; bottom bar 48 pt circles at 166 and 226). */
2977
2977
  [data-satin-toolbar-spacer='fixed'] {
2978
2978
  width: 12px;
@@ -3349,14 +3349,14 @@ body[data-satin-document='true'],
3349
3349
  --satin-section-header-size: 17px;
3350
3350
  --satin-section-header-line-height: 20.333333px;
3351
3351
  --satin-section-header-weight: 600;
3352
- /* SwiftUI secondary foreground is half-opacity primary over the cell fill. */
3352
+ /* Secondary foreground uses half-opacity primary over the cell fill. */
3353
3353
  --satin-list-secondary: rgb(0 0 0 / 50%);
3354
3354
  }
3355
3355
  [data-satin-list] {
3356
3356
  width: 100%;
3357
3357
  color: var(--satin-list-label, #000);
3358
3358
  }
3359
- /* A List is flexible like SwiftUI's: in a stack it takes the remaining space (reference nested-form pair). */
3359
+ /* A List is flexible: in a stack it takes the remaining space (reference nested-form pair). */
3360
3360
  :where([data-satin-list]) {
3361
3361
  --satin-list-row-min-height: var(--satin-settings-root-row-height, 52px);
3362
3362
  flex-grow: 1;
@@ -3452,7 +3452,7 @@ body[data-satin-document='true'],
3452
3452
  [data-satin-list='plain'] [data-satin-section-rows='true'] {
3453
3453
  border-radius: 0;
3454
3454
  }
3455
- /* A grouped List paints systemGroupedBackground wherever it is placed, as SwiftUI's does, including a
3455
+ /* A grouped List paints systemGroupedBackground wherever it is placed, including a
3456
3456
  Form nested below other content (reference nested-form pairs). The status band samples it. */
3457
3457
  [data-satin-list='insetGrouped']:not(:where([data-satin-scroll-content-background='hidden'])),
3458
3458
  [data-satin-list='grouped']:not(:where([data-satin-scroll-content-background='hidden'])) {
@@ -3465,7 +3465,7 @@ body[data-satin-document='true'],
3465
3465
  background: #000;
3466
3466
  }
3467
3467
  /* Inside a sheet the sheet's own material is the one surface (native iOS 27 native reference, iPhone Form
3468
- sheet): a grouped List paints no background of its own. At medium the sheet is Liquid Glass and
3468
+ sheet): a grouped List paints no background of its own. At medium the sheet is glass and
3469
3469
  its cells are a grey fill on it (118 118 128: light 216 on 231 at 12%, dark 61 on 46 at 22%),
3470
3470
  separators 195 / 89 and off switch tracks 138 / 112 on them; at large it is opaque and elevated,
3471
3471
  cells 255 on 242 (light) and 44 on 28 (dark), separators 232 / 64, off tracks 197 / 101. All of
@@ -3510,7 +3510,7 @@ body[data-satin-document='true'],
3510
3510
  }
3511
3511
  /* The sheet's own surface is the backdrop of the glass nested in it (glass/presentationFrame.ts):
3512
3512
  the opaque large-detent tone above (242 light, 28 dark). The renderer has no glass-on-glass
3513
- source, so nested glass on a medium (Liquid Glass) sheet samples this tone too. */
3513
+ source, so nested glass on a medium (glass) sheet samples this tone too. */
3514
3514
  [data-satin-presentation='sheet'] {
3515
3515
  --satin-presentation-backdrop: rgb(242 242 247);
3516
3516
  }
@@ -3643,11 +3643,11 @@ body[data-satin-document='true'],
3643
3643
  border-radius: inherit;
3644
3644
  }
3645
3645
  /* The shared row box above clears row fills; a ListRow keeps its cell or listRowBackground, as a
3646
- SwiftUI row does in every list style (reference rows pair). */
3646
+ row does in every list style (reference rows pair). */
3647
3647
  [data-satin-list-row-options]:not(:has([data-satin-row], [data-satin-field-style='row'])) {
3648
3648
  background: var(--satin-row-background, var(--satin-list-cell, #fff));
3649
3649
  }
3650
- /* Explicit insets size the row to its content: SwiftUI's defaultMinListRowHeight (44 pt) bounds the
3650
+ /* Explicit insets size the row to its content: the minimum row height of 44 pt bounds the
3651
3651
  content inside the insets. iOS 27 native reference rows: a 36 pt capsule with 4 pt insets is a 52 pt row
3652
3652
  (capsule centred, 8 pt above and below); with zero insets it is 44 pt (reference search rows). */
3653
3653
  [data-satin-list-row-options][data-satin-row-insets='explicit']:not(
@@ -3730,6 +3730,12 @@ body[data-satin-document='true'],
3730
3730
  flex: 1;
3731
3731
  overflow-wrap: anywhere;
3732
3732
  }
3733
+ /* The sheet Form reference measures plain-text Stepper labels only: 72 1/3 and
3734
+ 94 1/3 pt for two/three lines. Do not apply its extra inset to composed List
3735
+ labels or RowLabel stacks; those retain their independently measured pitch. */
3736
+ .satin-stepper-row > [data-satin-row-label] {
3737
+ padding-block: 10.166667px;
3738
+ }
3733
3739
  [data-satin-row-trailing='true'] {
3734
3740
  display: flex;
3735
3741
  align-items: center;
@@ -4008,11 +4014,15 @@ body[data-satin-document='true'],
4008
4014
  inset: 6px 0;
4009
4015
  z-index: -1;
4010
4016
  border-radius: 18px;
4011
- background: var(--satin-list-stepper, #eeeef0);
4017
+ background: var(--satin-list-stepper, #eeeeef);
4012
4018
  pointer-events: none;
4013
4019
  }
4014
4020
  [data-satin-stepper='true'] button {
4015
4021
  position: relative;
4022
+ display: flex;
4023
+ align-items: center;
4024
+ justify-content: center;
4025
+ text-align: center;
4016
4026
  width: 50%;
4017
4027
  min-width: 0;
4018
4028
  min-height: 44px;
@@ -4037,7 +4047,8 @@ body[data-satin-document='true'],
4037
4047
  top: 10px;
4038
4048
  bottom: 10px;
4039
4049
  width: 1px;
4040
- background: var(--satin-list-separator, #c6c6c8);
4050
+ /* Native divider uses label at 30% over the control, independent of list separators. */
4051
+ background: var(--satin-placeholder-text);
4041
4052
  pointer-events: none;
4042
4053
  }
4043
4054
  [data-satin-stepper='true'] button:disabled {
@@ -4146,7 +4157,7 @@ body[data-satin-document='true'],
4146
4157
  --satin-list-menu: #2c2c2e;
4147
4158
  --satin-list-toggle-off: #5a5a5e;
4148
4159
  --satin-list-toggle-on: #30d158;
4149
- --satin-list-stepper: #2c2c2e;
4160
+ --satin-list-stepper: #3b3b3d;
4150
4161
  --satin-list-segment-selected: #636366;
4151
4162
  }
4152
4163
 
@@ -4323,6 +4334,16 @@ body[data-satin-document='true'],
4323
4334
  }
4324
4335
  /* A Form editor's first line sits 23 pt below the cell top and its text 5 pt inside the row
4325
4336
  inset (UITextView line-fragment padding); each further line adds 22 pt. Stock iOS 27 pairs. */
4337
+ /* Form controls share the 20 pt content inset, including multiline editors. */
4338
+ .satin-text-editor[data-satin-field-style='row'] {
4339
+ padding-inline: 20px;
4340
+ }
4341
+ /* A vertical TextField uses the stock multiline row pitch (22 pt per line + 28 1/3 pt).
4342
+ TextEditor retains its separate UITextView content padding below. */
4343
+ .satin-text-editor[data-satin-field-style='row']:has(> [data-satin-field-axis='vertical']) {
4344
+ min-height: 52px;
4345
+ padding-block: 14.166667px;
4346
+ }
4326
4347
  [data-satin-field-style='row'] > [data-satin-text-editor] {
4327
4348
  padding: 12px 5px;
4328
4349
  }
@@ -4868,7 +4889,7 @@ html[data-satin-appearance='dark'] .satin-sheet-grabber > span {
4868
4889
  );
4869
4890
  }
4870
4891
  /* reference: the screen owns scrolling geometry. A screen's primary scroll view (ScrollSurface, elected
4871
- like SwiftUI's: the outermost vertical scroll view that starts the screen's content) extends under
4892
+ the outermost vertical scroll view that starts the screen's content) extends under
4872
4893
  the bar and takes the bar's content inset and large title. Native iOS 27, iPhone 17 Pro Max:
4873
4894
  inline safe top 54 pt below the status band, large 106 pt (54 + a 52 pt title row). */
4874
4895
  :where([data-satin-scroll-navigation]) {
@@ -4928,7 +4949,7 @@ html[data-satin-appearance='dark'] .satin-sheet-grabber > span {
4928
4949
  [data-satin-navigation-chrome][data-satin-navigation-bar-hidden='true'] * {
4929
4950
  pointer-events: none;
4930
4951
  }
4931
- /* inlineLarge (SwiftUI toolbarTitleDisplayMode): the large title stands in the bar row itself
4952
+ /* inlineLarge title layout: the large title stands in the bar row itself
4932
4953
  (native reference header-styles r1: frame x20 y63.667 h40.667, 34 pt bold, the owner's Apple Music rest
4933
4954
  pixels) and gives way to the inline title within the first 5 pt of scroll (NavigationChrome). */
4934
4955
  [data-satin-navigation-title-display='inlineLarge'] [data-satin-navigation-title-content] > strong {
@@ -4971,7 +4992,7 @@ html[data-satin-appearance='dark'] .satin-sheet-grabber > span {
4971
4992
  letter-spacing: 0;
4972
4993
  }
4973
4994
  /* Without a primary scroll view the screen's content sits in the safe area below the bar, as a
4974
- SwiftUI VStack does; a large title stands in the bar's title row. */
4995
+ VStack does; a large title stands in the bar's title row. */
4975
4996
  [data-satin-navigation-motion-layer='true'] {
4976
4997
  box-sizing: border-box;
4977
4998
  }
@@ -5101,7 +5122,7 @@ html[data-satin-appearance='dark'] [data-satin-scroll-indicator-thumb='true'] {
5101
5122
  letter-spacing: 0;
5102
5123
  }
5103
5124
 
5104
- /* Stock SwiftUI iOS 27 Form, same-content rest/scrolled fit, DPR 3.
5125
+ /* Native controls iOS 27 Form, same-content rest/scrolled fit, DPR 3.
5105
5126
  Source and residuals: measured-reference-full-bleed-notes.md. These are material
5106
5127
  measurements, not spring values; installed cross-content parity is separate. */
5107
5128
  /* Separate stacking contexts keep WebKit backdrop sampling below both the
@@ -5656,7 +5677,7 @@ html[data-satin-appearance='dark'] [data-satin-navigation-chrome] {
5656
5677
  position: relative;
5657
5678
  top: calc(0.333333px * var(--satin-text-scale, 1));
5658
5679
  }
5659
- /* Stock SwiftUI List allocates a 64 pt row around the 34 pt bordered control. */
5680
+ /* Native controls List allocates a 64 pt row around the 34 pt bordered control. */
5660
5681
  [data-satin-section-item]:has(> [data-satin-button-style='borderedProminent']),
5661
5682
  [data-satin-section-item]:has(> [data-satin-button-style='bordered']),
5662
5683
  [data-satin-section-item]:has(
@@ -5853,7 +5874,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
5853
5874
  }
5854
5875
  /* A plain glyph (a Menu labelled with a bare symbol) fades back in as its menu's drop dissolves
5855
5876
  into it, measured from the close: hidden to +240 ms, 0.3 at +256, 0.62 at +290, 0.85 at
5856
- +325 and whole by +400 ms (stock SwiftUI, iOS 27; menuGlyphDropFit.json). The animation
5877
+ +325 and whole by +400 ms (native controls, iOS 27; menuGlyphDropFit.json). The animation
5857
5878
  outranks the emerged state's opacity while the drop is still dissolving. */
5858
5879
  [data-satin-menu-glyph-return='true'] {
5859
5880
  animation: satin-menu-glyph-return 400ms linear both;
@@ -5987,7 +6008,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
5987
6008
  [data-satin-row]:has([data-satin-row-label-stack]) {
5988
6009
  padding-block: 5px;
5989
6010
  }
5990
- /* SwiftUI LabeledContent moves its value below the label when their intrinsic
6011
+ /* LabeledContent moves its value below the label when their intrinsic
5991
6012
  widths do not fit. Growing the label fills the first line; an unshrunk label
5992
6013
  makes flex wrapping move the value to the leading edge of the next line. */
5993
6014
  [data-satin-row-value-content] {
@@ -6024,13 +6045,15 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
6024
6045
  padding-block: 14.166667px;
6025
6046
  }
6026
6047
 
6027
- /* Layout frames, rather than glyph ink bounds, match SwiftUI Text exports. */
6048
+ /* Text measurement uses layout frames rather than glyph ink bounds. */
6028
6049
  [data-satin-row-text] {
6029
6050
  display: inline-block;
6051
+ /* Stock row labels retain authored line breaks, including Stepper labels. */
6052
+ white-space: pre-wrap;
6030
6053
  max-width: 100%;
6031
6054
  vertical-align: top;
6032
6055
  /* Installed 3x reference: the 17 pt WebKit baseline is one device pixel
6033
- * above SwiftUI. The correction stays one device pixel at each supported DPR. */
6056
+ * above the measured baseline. The correction stays one device pixel at each supported DPR. */
6034
6057
  position: relative;
6035
6058
  top: var(--satin-device-pixel, 1px);
6036
6059
  }
@@ -6067,7 +6090,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
6067
6090
  line-height: calc(22px * var(--satin-text-scale, 1));
6068
6091
  }
6069
6092
 
6070
- /* SwiftUI SecureField exports a 21 pt editor, while TextField exports 22 pt.
6093
+ /* SecureField has a 21 pt editor, while TextField exports 22 pt.
6071
6094
  * The enclosing native row retains its 52 pt minimum and centers either editor. */
6072
6095
  [data-satin-section-item]
6073
6096
  [data-satin-field-style='row'][data-satin-label-placement='leading']
@@ -6444,8 +6467,8 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
6444
6467
  pointer-events: none;
6445
6468
  }
6446
6469
 
6447
- /* A row's own fill (a ListRow's cell or listRowBackground) follows the card's corners, as a SwiftUI
6448
- row's background does in an inset-grouped section (reference rows pair: square corners otherwise). */
6470
+ /* A row's own fill (a ListRow's cell or listRowBackground) follows the card corners
6471
+ in an inset-grouped section. Other row backgrounds keep square corners. */
6449
6472
  [data-satin-section-rows='true'] > [data-satin-section-item][data-satin-first-row] {
6450
6473
  border-start-start-radius: inherit;
6451
6474
  border-start-end-radius: inherit;
@@ -6610,7 +6633,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
6610
6633
  font: inherit;
6611
6634
  line-height: normal;
6612
6635
  }
6613
- /* Stock regular SwiftUI content allocates a 20 1/3 pt line box. */
6636
+ /* Stock regular content allocates a 20 1/3 pt line box. */
6614
6637
  [data-satin-button-style='plain'],
6615
6638
  [data-satin-button-style='borderless'] {
6616
6639
  line-height: calc(20.333333px * var(--satin-text-scale, 1));
@@ -6764,7 +6787,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
6764
6787
  --satin-control-held-ink: inherit;
6765
6788
  --satin-control-held-ink-opacity: 0.4;
6766
6789
  }
6767
- /* Omitted SwiftUI tint uses a neutral bordered fill; an explicit/inherited
6790
+ /* Omitted tint uses a neutral bordered fill; an explicit/inherited
6768
6791
  author tint owns the colored recipe above. Toggle-button selection stays tinted. */
6769
6792
  [data-satin-button-style='bordered']:not([data-satin-control-tinted='true']):not(
6770
6793
  [data-satin-toggle-style]
@@ -6918,7 +6941,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
6918
6941
  }
6919
6942
 
6920
6943
  /* The bottom safe-area chain (SafeAreaInset, TabView): tab bar, bottom accessory, app insets and
6921
- the keyboard, as in SwiftUI.
6944
+ the keyboard.
6922
6945
  - The root boundary sits above the keyboard. The keyboard reaches it on the keyboard's own
6923
6946
  measured curve (keyboard/motion.ts), started as far into that curve as the system keyboard
6924
6947
  already is when WebKit reports it (--satin-keyboard-phase), so the composer and the thread
@@ -7086,7 +7109,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
7086
7109
  --satin-inset-band: calc(var(--satin-safe-top, 0px) + var(--satin-inset-parent-top));
7087
7110
  margin-top: var(--satin-inset-band);
7088
7111
  }
7089
- /* SwiftUI's `background` ignores the safe area: the accessory's root background reaches the
7112
+ /* The accessory background extends beyond its safe area: the accessory's root background reaches the
7090
7113
  screen's top edge, under the bar (`inset-top`: yellow 0–160.33). */
7091
7114
  :where([data-satin-inset-accessory='top'] > *) {
7092
7115
  position: relative;
@@ -7582,9 +7605,7 @@ html[data-satin-appearance='dark'] [data-satin-tab-sidebar-dim] {
7582
7605
  right: 69px;
7583
7606
  }
7584
7607
 
7585
- /* reference searchable: the navigation bar drawer (search/SearchDrawer.tsx). Geometry from the stock
7586
- SwiftUI `.searchable(placement: .navigationBarDrawer(displayMode: .always)).searchScopes`
7587
- reference on iPhone 17 Pro Max, iOS 27.0: at rest the
7608
+ /* reference searchable: the navigation bar drawer (search/SearchDrawer.tsx). Measured on iPhone 17 Pro Max, iOS 27.0: at rest the
7588
7609
  field is [20,168,400,44] under a large title and [20,116,400,44] under an inline one; presented,
7589
7610
  the field is [20,62,345,44], the close button [376,62,44,44] and the scope bar [20,116,400,36].
7590
7611
  The drawer is 54 pt tall (field + 10), 90 pt with a presented scope bar, which ends at 152;
@@ -8409,7 +8430,7 @@ html[data-satin-appearance='dark'] [data-satin-tab-sidebar-dim] {
8409
8430
  display: none;
8410
8431
  }
8411
8432
 
8412
- /* A vertical lazy stack accepts the available cross-axis proposal, as SwiftUI does. */
8433
+ /* A vertical lazy stack accepts the available cross-axis proposal. */
8413
8434
  [data-satin-content='vstack'][data-satin-lazy-stack='vertical'],
8414
8435
  [data-satin-content='frame'] > [data-satin-content='vstack']:has(> [data-satin-content='divider']),
8415
8436
  [data-satin-content='frame']
@@ -8655,7 +8676,7 @@ html[data-satin-appearance='dark'] [data-satin-tab-sidebar-dim] {
8655
8676
  opacity: 0;
8656
8677
  }
8657
8678
 
8658
- /* List edit mode (reference). Native geometry from the stock SwiftUI native reference (`native list editing fixture`, iPhone 17
8679
+ /* List edit mode (reference). Native geometry from the measurement fixture (`native list editing fixture`, iPhone 17
8659
8680
  Pro Max, 3x stills): each leading control sits in a 40 pt column, centred 32 pt (then 72 pt)
8660
8681
  from the row's edge; the 21.33 pt delete circle carries a 10.67 x 1.67 pt bar; the 21.67 pt
8661
8682
  selection circle has a 1.67 pt tertiary-label ring, or the tint with a white check; the 21 x 10.67
@@ -8889,7 +8910,7 @@ html:is([data-satin-viewport-fit='auto'], [data-satin-viewport-fit='contain'])
8889
8910
  height: max(100%, var(--satin-inline-title-content-height, 0px));
8890
8911
  }
8891
8912
 
8892
- /* SwiftUI overlay(alignment:content:): the modified view owns the frame. The overlay consumes
8913
+ /* An overlay leaves its modified view in control of the frame. The overlay consumes
8893
8914
  the inset store directly, including the system extension behind bottom accessories. */
8894
8915
  [data-satin-overlay='true'] {
8895
8916
  position: relative;
@@ -39,7 +39,8 @@ self.addEventListener('install', (event) => {
39
39
  await reportProgress()
40
40
  const activeDownloads = new Set()
41
41
  let failureReport
42
- const download = async (entry) => {
42
+ const download = async (entry, attempt = 0) => {
43
+ let received = 0
43
44
  let networkFailure = true
44
45
  let stalled = false
45
46
  const controller = new AbortController()
@@ -76,7 +77,7 @@ self.addEventListener('install', (event) => {
76
77
  progress.activity++
77
78
  await reportProgress()
78
79
  if (installFailure) throw new Error('Installation cancelled')
79
- networkFailure = false
80
+ networkFailure = response.status >= 500 || response.status === 408 || response.status === 429
80
81
  if (!response.ok || response.type === 'opaque') throw new Error('HTTP ' + response.status)
81
82
  if (response.redirected) throw new Error('Precache resource redirected')
82
83
  const mime = (response.headers.get('content-type') || '').split(';')[0].trim().toLowerCase()
@@ -86,7 +87,6 @@ self.addEventListener('install', (event) => {
86
87
  networkFailure = true
87
88
  reader = response.body?.getReader()
88
89
  const chunks = []
89
- let received = 0
90
90
  if (reader) {
91
91
  while (true) {
92
92
  const { done, value } = await Promise.race([reader.read(), idleFailure])
@@ -112,6 +112,15 @@ self.addEventListener('install', (event) => {
112
112
  if (installFailure) throw new Error('Installation cancelled')
113
113
  await cache.put(request, new Response(blob, { status: response.status, statusText: response.statusText, headers: response.headers }))
114
114
  } catch (error) {
115
+ if (!installFailure && (networkFailure || stalled) && attempt < 2) {
116
+ clearTimeout(idleTimer)
117
+ activeDownloads.delete(cancelDownload)
118
+ // Retransmitted bytes are not extra installed bytes.
119
+ progress.loaded -= received
120
+ await new Promise(resolve => setTimeout(resolve, 250 * 2 ** attempt))
121
+ if (installFailure) throw error
122
+ return download(entry, attempt + 1)
123
+ }
115
124
  if (!installFailure) {
116
125
  installFailure = { type: 'SATIN_INSTALL_FAILURE', buildId: BUILD_ID, url: entry.url,
117
126
  reason: error instanceof Error ? error.message : String(error), networkFailure, stalled }
@@ -24,7 +24,11 @@ export const TextEditor = forwardRef(function TextEditor({ value, onChange, labe
24
24
  // Only a scroll surface bounds growth. A content-sized parent is the editor's own height,
25
25
  // so reading it as a limit would stop the editor from ever growing.
26
26
  const containerHeight = surface?.clientHeight ?? Infinity;
27
- const visibleHeight = keyboard.viewportHeight > 0
27
+ // A sheet enters from outside the viewport. Its transformed screen position is
28
+ // not an editing limit: transforms do not deliver a ResizeObserver notification
29
+ // when the sheet settles. Only intersect screen geometry for an open keyboard.
30
+ const keyboardVisible = keyboard.height > 0 && keyboard.viewportHeight > 0;
31
+ const visibleHeight = keyboardVisible
28
32
  ? keyboard.viewportHeight -
29
33
  Math.max(0, (surface?.getBoundingClientRect().top ?? 0) - keyboard.offsetTop)
30
34
  : containerHeight;
@@ -32,7 +36,7 @@ export const TextEditor = forwardRef(function TextEditor({ value, onChange, labe
32
36
  // to measure it: use an isolated, non-focusable copy with the same text layout instead.
33
37
  const available = maxLines === undefined
34
38
  ? Math.min(containerHeight, visibleHeight) - 24
35
- : Math.min(Math.max(minLines, maxLines) * lineHeight + padding, keyboard.viewportHeight > 0 ? visibleHeight - 24 : Infinity);
39
+ : Math.min(Math.max(minLines, maxLines) * lineHeight + padding, keyboardVisible ? visibleHeight - 24 : Infinity);
36
40
  const height = editorHeight(measureEditorContentHeight(element), minLines, lineHeight, padding, available);
37
41
  if (element.style.height === `${height}px`)
38
42
  return;
@@ -65,7 +69,7 @@ export const TextEditor = forwardRef(function TextEditor({ value, onChange, labe
65
69
  observer.disconnect();
66
70
  element.removeEventListener('input', resize);
67
71
  };
68
- }, [value, minLines, maxLines, keyboard.viewportHeight, keyboard.offsetTop]);
72
+ }, [value, minLines, maxLines, keyboard.height, keyboard.viewportHeight, keyboard.offsetTop]);
69
73
  useLayoutEffect(() => {
70
74
  if (autoFocus && ref.current)
71
75
  focusFromFramework(ref.current, { preventScroll: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@satinhq/kit",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.3",
4
4
  "type": "module",
5
5
  "description": "Opinionated single-target framework for agent-generated personal iOS Home Screen apps.",
6
6
  "main": "./dist/index.js",
@@ -96,7 +96,7 @@
96
96
  "repository": {
97
97
  "type": "git",
98
98
  "url": "git+https://github.com/satinhq/satin.git",
99
- "directory": "packages/pwakit"
99
+ "directory": "packages/kit"
100
100
  },
101
101
  "engines": {
102
102
  "node": "^22.12.0 || >=24.0.0"
@@ -111,8 +111,8 @@
111
111
  "scripts": {
112
112
  "build": "node scripts/build.mjs",
113
113
  "typecheck": "tsc -p tsconfig.json --pretty false",
114
- "lint": "oxlint && node ../pwakit-lint/scripts/check-webgpu-worker.mjs src/glass/webgpu/entry.worker.ts && node ../pwakit-lint/scripts/check-sidebar-artwork.mjs src/navigation/SidebarSymbol.tsx && node ../pwakit-lint/scripts/check-modal-lifecycle.mjs src",
115
- "test": "vitest run && node scripts/check-readme-examples.mjs && node scripts/check-public-exports.mjs && node scripts/api-report.mjs && node --test scripts/api-graph.node-test.mjs scripts/public-types.node-test.mjs scripts/swiftui-reference.node-test.mjs scripts/path-spaces.node-test.mjs",
114
+ "lint": "oxlint && node ../lint/scripts/check-webgpu-worker.mjs src/glass/webgpu/entry.worker.ts && node ../lint/scripts/check-sidebar-artwork.mjs src/navigation/SidebarSymbol.tsx && node ../lint/scripts/check-modal-lifecycle.mjs src",
115
+ "test": "vitest run && node scripts/check-readme-examples.mjs && node scripts/check-public-exports.mjs && node scripts/api-report.mjs && node --test scripts/api-graph.node-test.mjs scripts/public-types.node-test.mjs scripts/api-reference.node-test.mjs scripts/path-spaces.node-test.mjs",
116
116
  "api:write": "node scripts/api-report.mjs --write",
117
117
  "api:check": "node scripts/api-report.mjs"
118
118
  }