@1agh/maude 0.58.3 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/apps/studio/annotations-layer.tsx +49 -15
  2. package/apps/studio/bin/_import-asset.mjs +18 -0
  3. package/apps/studio/bin/_import-figma.mjs +868 -214
  4. package/apps/studio/bin/_perf-probe-safari.mjs +332 -0
  5. package/apps/studio/bin/_perf-probe.mjs +228 -0
  6. package/apps/studio/bin/_perf-shared.mjs +345 -0
  7. package/apps/studio/bin/_video-playwright.mjs +17 -4
  8. package/apps/studio/bin/import-figma.sh +10 -1
  9. package/apps/studio/bin/perf.sh +228 -0
  10. package/apps/studio/bin/smoke.sh +49 -5
  11. package/apps/studio/canvas-lib.tsx +148 -6
  12. package/apps/studio/client/app.jsx +152 -37
  13. package/apps/studio/client/panels/SyncPanel.jsx +229 -0
  14. package/apps/studio/client/panels/TimelinePanel.jsx +29 -1
  15. package/apps/studio/client/panels/timeline-comp-target.js +101 -0
  16. package/apps/studio/client/styles/3-shell-maude.css +30 -0
  17. package/apps/studio/client/styles/4-components.css +4 -4
  18. package/apps/studio/dist/client.bundle.js +772 -772
  19. package/apps/studio/dist/styles.css +1 -1
  20. package/apps/studio/exporters/video-encode-lib.ts +8 -5
  21. package/apps/studio/exporters/video.ts +10 -0
  22. package/apps/studio/figma/assets.test.ts +92 -0
  23. package/apps/studio/figma/assets.ts +63 -9
  24. package/apps/studio/figma/codegen-client.test.ts +276 -0
  25. package/apps/studio/figma/codegen-client.ts +509 -0
  26. package/apps/studio/figma/codegen-fonts.test.ts +103 -0
  27. package/apps/studio/figma/codegen-fonts.ts +195 -0
  28. package/apps/studio/figma/codegen-values.test.ts +179 -0
  29. package/apps/studio/figma/codegen-values.ts +270 -0
  30. package/apps/studio/figma/endpoints.ts +73 -0
  31. package/apps/studio/figma/fig-decode.test.ts +702 -0
  32. package/apps/studio/figma/fig-decode.ts +617 -0
  33. package/apps/studio/figma/fig-kiwi.ts +410 -0
  34. package/apps/studio/figma/fig-zip.ts +270 -0
  35. package/apps/studio/figma/from-codegen.test.ts +408 -0
  36. package/apps/studio/figma/from-codegen.ts +1103 -0
  37. package/apps/studio/figma/sanitize.test.ts +69 -0
  38. package/apps/studio/figma/sanitize.ts +139 -47
  39. package/apps/studio/figma/tailwind-map.test.ts +142 -0
  40. package/apps/studio/figma/tailwind-map.ts +545 -0
  41. package/apps/studio/figma/to-render.ts +25 -3
  42. package/apps/studio/figma/types.ts +6 -1
  43. package/apps/studio/http.ts +47 -0
  44. package/apps/studio/sync/asset-push.ts +346 -38
  45. package/apps/studio/sync/connection-state.ts +71 -3
  46. package/apps/studio/sync/index.ts +10 -1
  47. package/apps/studio/sync/status.ts +18 -0
  48. package/apps/studio/test/canvas-origin-gate.test.ts +4 -0
  49. package/apps/studio/test/figma-explode.test.ts +438 -0
  50. package/apps/studio/test/fixtures/perf-canvas.mjs +201 -0
  51. package/apps/studio/test/import-figma.test.ts +192 -4
  52. package/apps/studio/test/sync-asset-push.test.ts +490 -47
  53. package/apps/studio/test/sync-connection-state.test.ts +66 -0
  54. package/apps/studio/test/sync-panel-surface.test.ts +90 -0
  55. package/apps/studio/test/sync-status.test.ts +28 -0
  56. package/apps/studio/test/timeline-comp-target.test.ts +139 -0
  57. package/apps/studio/test/video-comp.test.ts +81 -1
  58. package/apps/studio/test/video-encode-lib.test.ts +63 -0
  59. package/apps/studio/use-artboard-drag.tsx +37 -3
  60. package/apps/studio/video-comp.tsx +51 -0
  61. package/apps/studio/whats-new.json +71 -0
  62. package/cli/commands/design.mjs +7 -0
  63. package/cli/commands/kg.mjs +8 -1
  64. package/cli/commands/kg.test.mjs +24 -0
  65. package/cli/lib/figma-codegen-reachability.test.mjs +104 -0
  66. package/cli/lib/figma-import-controls.test.mjs +70 -0
  67. package/package.json +8 -8
@@ -154,7 +154,13 @@ import {
154
154
  } from './annotations-snap.ts';
155
155
  import { arrowPrimitives, type SvgPrimitive } from './canvas-arrowheads.ts';
156
156
  import { IconLineThick, IconLineThin } from './canvas-icons.tsx';
157
- import { useViewportControllerContext, useWorldRefContext } from './canvas-lib.tsx';
157
+ import {
158
+ countRender,
159
+ getLiveViewport,
160
+ useLiveViewport,
161
+ useViewportControllerContext,
162
+ useWorldRefContext,
163
+ } from './canvas-lib.tsx';
158
164
  import { buildAnnotationStrokesRecord } from './commands/annotation-strokes-command.ts';
159
165
  import { ensureMenuStyles as ensureCtxMenuStyles } from './context-menu.tsx';
160
166
  import { crossedDragThreshold, type Tool } from './input-router.tsx';
@@ -948,6 +954,7 @@ function scaleStrokeInGroup(
948
954
  // Component
949
955
 
950
956
  export function AnnotationsLayer() {
957
+ countRender('annotationRenders');
951
958
  ensureAnnotStyles();
952
959
  const { tool, setTool, sticky, tools, shapeKind } = useToolMode();
953
960
  const theme = useCanvasChromeTheme();
@@ -1011,8 +1018,22 @@ export function AnnotationsLayer() {
1011
1018
  // endpoint free). Tracked here because endStroke (pointerup) carries no
1012
1019
  // modifier state of its own.
1013
1020
  const cmdHeldRef = useRef(false);
1014
- const vpRef = useRef(vp);
1015
- vpRef.current = vp;
1021
+ const vpFallbackRef = useRef(vp);
1022
+ vpFallbackRef.current = vp;
1023
+ // Reads the LIVE viewport, not the published one. Publishing is settle-only
1024
+ // (gesture-static React), so mirroring the published value here would freeze
1025
+ // this ref for the whole gesture and every zoom-scaled threshold computed
1026
+ // from it — bind distance, snap tolerance, hit-test slop, sticker placement —
1027
+ // would silently use the pre-gesture zoom. Shaped as a ref so the ~40 existing
1028
+ // `vpRef.current` call sites keep working unchanged.
1029
+ const vpRef = useMemo(
1030
+ () => ({
1031
+ get current() {
1032
+ return getLiveViewport() ?? vpFallbackRef.current;
1033
+ },
1034
+ }),
1035
+ []
1036
+ );
1016
1037
  const visibilityCtx = useAnnotationsVisibility();
1017
1038
  const chrome = useChromeVisibility();
1018
1039
  // Presentation Mode hides annotations without mutating the user's own
@@ -1655,7 +1676,9 @@ export function AnnotationsLayer() {
1655
1676
 
1656
1677
  const screenToWorld = useCallback(
1657
1678
  (cx: number, cy: number): [number, number] => {
1658
- const v = vp ?? { x: 0, y: 0, zoom: 1 };
1679
+ // Live: this runs at event time (drop/placement), where the published
1680
+ // viewport can be a whole gesture behind.
1681
+ const v = vpRef.current ?? { x: 0, y: 0, zoom: 1 };
1659
1682
  const z = v.zoom || 1;
1660
1683
  return [(cx - v.x) / z, (cy - v.y) / z];
1661
1684
  },
@@ -1826,7 +1849,7 @@ export function AnnotationsLayer() {
1826
1849
 
1827
1850
  const eraseAt = useCallback(
1828
1851
  (wx: number, wy: number) => {
1829
- const zoom = vp?.zoom || 1;
1852
+ const zoom = vpRef.current?.zoom || 1;
1830
1853
  const tol = 8 / zoom;
1831
1854
  const prev = strokesRef.current;
1832
1855
  for (let i = prev.length - 1; i >= 0; i--) {
@@ -4750,8 +4773,15 @@ function AddTextHint({ strokes, hintId }: { strokes: readonly Stroke[]; hintId:
4750
4773
  * marquee/drag handler yields).
4751
4774
  */
4752
4775
  function ConnectorDots({ stroke }: { stroke: Stroke }) {
4753
- const controller = useViewportControllerContext();
4754
- const zoom = controller?.viewport?.zoom || 1;
4776
+ // LIVE zoom, deliberately not the published one. These are the counter-scaled
4777
+ // chrome elements (connector dots, selection bbox, section-label chips): they
4778
+ // must hold a constant SCREEN size while the world scales. Publishing is
4779
+ // settle-only since the gesture-static change, so reading published zoom here
4780
+ // would let this chrome grow and shrink with the world through a pinch and
4781
+ // then jump back at settle — the visible fidelity-pop a previous LOD attempt
4782
+ // was reverted for. These components render only for selected/hovered
4783
+ // strokes, so tracking the gesture frame-by-frame stays cheap.
4784
+ const zoom = useLiveViewport().zoom || 1;
4755
4785
  if (!isBindable(stroke)) return null;
4756
4786
  const center = strokeCenter(stroke);
4757
4787
  if (!center) return null;
@@ -4942,8 +4972,9 @@ function SelectionHalo({
4942
4972
  anchorsById: Map<string, AnchorHost>;
4943
4973
  multi: boolean;
4944
4974
  }) {
4945
- const controller = useViewportControllerContext();
4946
- const zoom = controller?.viewport?.zoom || 1;
4975
+ // Counter-scaled chrome — must hold a constant screen size while the world
4976
+ // scales, so it needs the live zoom, not the settle-cadence published one.
4977
+ const zoom = useLiveViewport().zoom || 1;
4947
4978
  const bbox = strokeBBox(stroke, anchorsById);
4948
4979
  if (!bbox) return null;
4949
4980
  // T17 + post-Wave-2 fix — annotation halo idioms:
@@ -4990,8 +5021,9 @@ function AnnotGroupBbox({
4990
5021
  selectedStrokes: readonly Stroke[];
4991
5022
  anchorsById: Map<string, AnchorHost>;
4992
5023
  }) {
4993
- const controller = useViewportControllerContext();
4994
- const zoom = controller?.viewport?.zoom || 1;
5024
+ // Counter-scaled chrome — must hold a constant screen size while the world
5025
+ // scales, so it needs the live zoom, not the settle-cadence published one.
5026
+ const zoom = useLiveViewport().zoom || 1;
4995
5027
  if (selectedStrokes.length < 2) return null;
4996
5028
  let xMin = Number.POSITIVE_INFINITY;
4997
5029
  let yMin = Number.POSITIVE_INFINITY;
@@ -5225,8 +5257,9 @@ function SectionLabelChip({
5225
5257
  y: number;
5226
5258
  hitMode: 'visiblePainted' | 'none';
5227
5259
  }) {
5228
- const controller = useViewportControllerContext();
5229
- const zoom = controller?.viewport?.zoom || 1;
5260
+ // Counter-scaled chrome — must hold a constant screen size while the world
5261
+ // scales, so it needs the live zoom, not the settle-cadence published one.
5262
+ const zoom = useLiveViewport().zoom || 1;
5230
5263
  const fontSize = SECTION_LABEL_FONT / zoom;
5231
5264
  const chipH = SECTION_LABEL_H / zoom;
5232
5265
  const gap = 4 / zoom;
@@ -5279,8 +5312,9 @@ function SectionTitleEditor({
5279
5312
  onCommit: (text: string, fmt?: EditorFmt) => void;
5280
5313
  onCancel: () => void;
5281
5314
  }) {
5282
- const controller = useViewportControllerContext();
5283
- const zoom = controller?.viewport?.zoom || 1;
5315
+ // Counter-scaled chrome — must hold a constant screen size while the world
5316
+ // scales, so it needs the live zoom, not the settle-cadence published one.
5317
+ const zoom = useLiveViewport().zoom || 1;
5284
5318
  const fontSize = SECTION_LABEL_FONT / zoom;
5285
5319
  const chipH = SECTION_LABEL_H / zoom;
5286
5320
  const gap = 4 / zoom;
@@ -203,7 +203,25 @@ const ALLOWED_PLAIN_ATTRS = new Set([
203
203
  'stop-color',
204
204
  'stop-opacity',
205
205
  'patternUnits',
206
+ // Required by spec for pattern CONTENT, and we were stripping it. A
207
+ // `<pattern>` whose children are authored in the 0..1 unit box — which is what
208
+ // `<use transform="scale(0.00097 0.00075)">` (1/1028 × 1/1331) means — is read
209
+ // in user space without it, so its content collapses to about a pixel.
210
+ //
211
+ // HONEST SCOPE: this is an allowlist bug, NOT the cause of Figma's raster
212
+ // fills failing to appear. That was chased here first and the diagnosis was
213
+ // wrong: Figma's OWN UNTOUCHED export, fetched straight from `/v1/images` and
214
+ // loaded directly, does not paint its `<pattern>`/`<use>`/`<image
215
+ // xlink:href="data:…">` chain either. Allowing this attribute fixes nothing
216
+ // user-visible; it just stops us corrupting a pattern that would otherwise
217
+ // have worked. Same enum-valued, no-URL, no-script risk class as
218
+ // `patternUnits` directly above.
219
+ 'patternContentUnits',
206
220
  'patternTransform',
221
+ // Presentational text metrics. Their absence is not fatal the way the pattern
222
+ // one is, but it silently drifts imported type away from the source.
223
+ 'letter-spacing',
224
+ 'xml:space',
207
225
  'version',
208
226
  ]);
209
227