@agent-native/core 0.85.6 → 0.85.7

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 (80) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/use-pinch-zoom.ts +76 -11
  5. package/corpus/core/src/collab/presence.ts +63 -3
  6. package/corpus/templates/clips/desktop/src/lib/recorder.ts +245 -77
  7. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +190 -13
  8. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +16 -4
  9. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +46 -3
  10. package/corpus/templates/design/AGENTS.md +33 -1
  11. package/corpus/templates/design/actions/apply-component-prop-edit.ts +20 -73
  12. package/corpus/templates/design/actions/apply-motion-edit.ts +29 -2
  13. package/corpus/templates/design/actions/apply-visual-edit.ts +4 -2
  14. package/corpus/templates/design/actions/connect-localhost.ts +25 -18
  15. package/corpus/templates/design/actions/export-pdf.ts +8 -1
  16. package/corpus/templates/design/actions/export-zip.ts +10 -1
  17. package/corpus/templates/design/actions/get-motion-timeline.ts +26 -20
  18. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +10 -0
  19. package/corpus/templates/design/actions/insert-asset.ts +190 -7
  20. package/corpus/templates/design/actions/open-visual-edit.ts +27 -5
  21. package/corpus/templates/design/actions/present-design-variants.ts +2 -2
  22. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +18 -14
  23. package/corpus/templates/design/actions/run-design-audit.ts +7 -4
  24. package/corpus/templates/design/actions/write-local-file.ts +47 -22
  25. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +221 -66
  26. package/corpus/templates/design/app/components/design/EditPanel.tsx +650 -237
  27. package/corpus/templates/design/app/components/design/LayersPanel.tsx +526 -127
  28. package/corpus/templates/design/app/components/design/MotionDock.tsx +234 -46
  29. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1497 -408
  30. package/corpus/templates/design/app/components/design/StatesPanel.tsx +25 -2
  31. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +640 -72
  32. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +605 -0
  33. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +50 -26
  34. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +16 -4
  35. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +90 -103
  36. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +111 -5
  37. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -2
  38. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +42 -2
  39. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +11 -2
  40. package/corpus/templates/design/app/components/design/types.ts +16 -0
  41. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +24 -6
  42. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +15 -1
  43. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +77 -0
  44. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  45. package/corpus/templates/design/app/i18n-data.ts +18 -0
  46. package/corpus/templates/design/app/lib/design-import.ts +95 -0
  47. package/corpus/templates/design/app/pages/DesignEditor.tsx +4013 -838
  48. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +6 -0
  49. package/corpus/templates/design/changelog/2026-07-03-inspector-fixes-mixed-selections-show-mixed-instead-of-wrong.md +6 -0
  50. package/corpus/templates/design/changelog/2026-07-03-keyframe-timeline-works-reliably-drag-keyframes-smoothly-pic.md +6 -0
  51. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +6 -0
  52. package/corpus/templates/design/changelog/2026-07-03-much-faster-editor-smooth-dragging-zooming-and-panel-updates.md +6 -0
  53. package/corpus/templates/design/changelog/2026-07-03-pen-tool-refinements-click-the-first-point-to-close-with-a-d.md +6 -0
  54. package/corpus/templates/design/changelog/2026-07-03-text-editing-enter-adds-a-line-break-international-ime-typin.md +6 -0
  55. package/corpus/templates/design/changelog/2026-07-03-undo-and-redo-are-dependable-across-agent-edits-screen-switc.md +6 -0
  56. package/corpus/templates/design/server/lib/design-export.ts +43 -1
  57. package/corpus/templates/design/shared/board-file.ts +25 -5
  58. package/corpus/templates/design/shared/canvas-math.ts +754 -9
  59. package/corpus/templates/design/shared/code-layer.ts +304 -26
  60. package/corpus/templates/design/shared/color-utils.ts +84 -0
  61. package/corpus/templates/design/shared/design-source-capabilities.ts +16 -7
  62. package/corpus/templates/design/shared/motion-compiler.ts +75 -31
  63. package/corpus/templates/design/shared/motion-timeline.ts +335 -0
  64. package/corpus/templates/design/shared/pen-path.ts +200 -23
  65. package/dist/client/use-pinch-zoom.d.ts.map +1 -1
  66. package/dist/client/use-pinch-zoom.js +76 -11
  67. package/dist/client/use-pinch-zoom.js.map +1 -1
  68. package/dist/collab/awareness.d.ts +2 -2
  69. package/dist/collab/awareness.d.ts.map +1 -1
  70. package/dist/collab/presence.d.ts.map +1 -1
  71. package/dist/collab/presence.js +54 -3
  72. package/dist/collab/presence.js.map +1 -1
  73. package/dist/collab/routes.d.ts +2 -2
  74. package/dist/collab/struct-routes.d.ts +1 -1
  75. package/dist/observability/routes.d.ts +3 -3
  76. package/dist/progress/routes.d.ts +1 -1
  77. package/dist/resources/handlers.d.ts +2 -2
  78. package/dist/secrets/routes.d.ts +3 -3
  79. package/dist/server/transcribe-voice.d.ts +1 -1
  80. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2046
31
- - template files: 5041
31
+ - template files: 5050
@@ -1,5 +1,11 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.85.7
4
+
5
+ ### Patch Changes
6
+
7
+ - b2e1b6c: Fix presence local-echo re-renders and coalesce pinch-zoom updates through requestAnimationFrame. `usePresence` no longer re-derives and re-renders subscribers when an awareness "change" event only reflects the local client's own state (e.g. cursor/selection echoes), and `usePinchZoom` now batches wheel and touch pinch zoom updates to at most one state update per animation frame instead of one per input event. When multiple wheel events land in the same animation frame, the cursor-anchored scroll compensation now accumulates across all of them (matching the pre-batching, one-event-at-a-time result) instead of anchoring later events in the burst against the stale pre-burst scroll position.
8
+
3
9
  ## 0.85.6
4
10
 
5
11
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.85.6",
3
+ "version": "0.85.7",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -55,11 +55,57 @@ export function usePinchZoom({
55
55
 
56
56
  const clamp = (n: number) => Math.max(min, Math.min(max, n));
57
57
 
58
+ // rAF coalescing: multiple wheel/pointermove events can fire per frame
59
+ // (trackpad pinch and touch pinch both deliver many events between
60
+ // paints). Instead of calling setZoom() synchronously per event — which
61
+ // schedules a React re-render per event — stash the latest pending zoom
62
+ // (and its cursor-anchored scroll delta) in a ref and flush once per
63
+ // animation frame with the last-wins value. This preserves the exact
64
+ // zoom-to-cursor math; it just applies it at most once per frame.
65
+ //
66
+ // Within a burst the DOM's real scrollLeft/scrollTop do NOT move until
67
+ // flush() runs, so per-event math must not read them directly — every
68
+ // event after the first in the same frame would anchor against the
69
+ // pre-burst scroll position instead of where the (not-yet-committed)
70
+ // previous events in the burst would have scrolled to. Track a simulated
71
+ // running scroll position (`simScrollLeft`/`simScrollTop`, seeded from the
72
+ // real scroll position when a new burst starts) and use that as the
73
+ // anchor base, so each event's math composes exactly as if the prior
74
+ // events in the burst had already been applied — matching the
75
+ // pre-coalescing, one-setZoom-per-event behavior.
76
+ let pendingZoom: number | null = null;
77
+ let pendingScrollDelta: { dx: number; dy: number } | null = null;
78
+ let simScrollLeft = 0;
79
+ let simScrollTop = 0;
80
+ let rafId: number | null = null;
81
+
82
+ const flush = () => {
83
+ rafId = null;
84
+ if (pendingZoom === null) return;
85
+ const nextZoom = pendingZoom;
86
+ const scrollDelta = pendingScrollDelta;
87
+ pendingZoom = null;
88
+ pendingScrollDelta = null;
89
+ setZoomRef.current(nextZoom);
90
+ if (scrollDelta) {
91
+ container.scrollLeft += scrollDelta.dx;
92
+ container.scrollTop += scrollDelta.dy;
93
+ }
94
+ };
95
+
96
+ const scheduleFlush = () => {
97
+ if (rafId !== null) return;
98
+ rafId = requestAnimationFrame(flush);
99
+ };
100
+
58
101
  const handleWheel = (e: WheelEvent) => {
59
102
  if (!(e.ctrlKey || e.metaKey)) return;
60
103
  e.preventDefault();
61
104
 
62
- const currentZoom = zoomRef.current;
105
+ // Use the latest not-yet-applied zoom (if a flush is pending) so rapid
106
+ // wheel events within the same frame compound correctly instead of
107
+ // each computing off the last-committed React state.
108
+ const currentZoom = pendingZoom ?? zoomRef.current;
63
109
  const clampedDelta = Math.max(-50, Math.min(50, e.deltaY));
64
110
  const factor = Math.exp(-clampedDelta * 0.01);
65
111
  const nextZoom = clamp(currentZoom * factor);
@@ -67,20 +113,33 @@ export function usePinchZoom({
67
113
  if (nextZoom === currentZoom) return;
68
114
 
69
115
  if (zoomToCursor) {
116
+ // Starting a new burst (nothing pending yet): seed the simulated
117
+ // scroll position from the container's real, currently-committed
118
+ // scroll offset.
119
+ if (pendingScrollDelta === null) {
120
+ simScrollLeft = container.scrollLeft;
121
+ simScrollTop = container.scrollTop;
122
+ }
70
123
  const rect = container.getBoundingClientRect();
71
- const cx = e.clientX - rect.left + container.scrollLeft;
72
- const cy = e.clientY - rect.top + container.scrollTop;
124
+ const cx = e.clientX - rect.left + simScrollLeft;
125
+ const cy = e.clientY - rect.top + simScrollTop;
73
126
  const ratio = nextZoom / currentZoom;
74
127
  const dx = cx * (ratio - 1);
75
128
  const dy = cy * (ratio - 1);
76
- setZoomRef.current(nextZoom);
77
- requestAnimationFrame(() => {
78
- container.scrollLeft += dx;
79
- container.scrollTop += dy;
80
- });
129
+ // Advance the simulated scroll position so the next event in this
130
+ // same burst anchors against where this event would have left it.
131
+ simScrollLeft += dx;
132
+ simScrollTop += dy;
133
+ pendingZoom = nextZoom;
134
+ const prevDelta = pendingScrollDelta;
135
+ pendingScrollDelta = {
136
+ dx: (prevDelta?.dx ?? 0) + dx,
137
+ dy: (prevDelta?.dy ?? 0) + dy,
138
+ };
81
139
  } else {
82
- setZoomRef.current(nextZoom);
140
+ pendingZoom = nextZoom;
83
141
  }
142
+ scheduleFlush();
84
143
  };
85
144
 
86
145
  const activePointers = new Map<number, { x: number; y: number }>();
@@ -105,8 +164,11 @@ export function usePinchZoom({
105
164
  const [p1, p2] = Array.from(activePointers.values());
106
165
  const distance = Math.hypot(p2.x - p1.x, p2.y - p1.y);
107
166
  const nextZoom = clamp(initialZoom * (distance / initialDistance));
108
- if (nextZoom !== zoomRef.current) {
109
- setZoomRef.current(nextZoom);
167
+ if (nextZoom !== (pendingZoom ?? zoomRef.current)) {
168
+ // Touch pinch has no cursor-anchoring math, so last-wins is simply
169
+ // the newest zoom value — no scroll delta to accumulate.
170
+ pendingZoom = nextZoom;
171
+ scheduleFlush();
110
172
  }
111
173
  e.preventDefault();
112
174
  }
@@ -132,6 +194,9 @@ export function usePinchZoom({
132
194
  container.removeEventListener("pointermove", handlePointerMove);
133
195
  container.removeEventListener("pointerup", handlePointerEnd);
134
196
  container.removeEventListener("pointercancel", handlePointerEnd);
197
+ if (rafId !== null) cancelAnimationFrame(rafId);
198
+ pendingZoom = null;
199
+ pendingScrollDelta = null;
135
200
  };
136
201
  }, [containerRef, enabled, min, max, zoomToCursor]);
137
202
  }
@@ -63,6 +63,40 @@ export function usePresence(
63
63
  return;
64
64
  }
65
65
 
66
+ // Keep the last derived snapshot so genuinely no-op change events (e.g. a
67
+ // local-only awareness field flip that doesn't affect any remote entry)
68
+ // can bail out without triggering a subscriber re-render.
69
+ let lastOthers: OtherPresence[] = [];
70
+
71
+ function shallowEqualOthers(
72
+ a: readonly OtherPresence[],
73
+ b: readonly OtherPresence[],
74
+ ): boolean {
75
+ if (a === b) return true;
76
+ if (a.length !== b.length) return false;
77
+ for (let i = 0; i < a.length; i += 1) {
78
+ const left = a[i]!;
79
+ const right = b[i]!;
80
+ if (left === right) continue;
81
+ if (
82
+ left.clientId !== right.clientId ||
83
+ left.isAgent !== right.isAgent ||
84
+ left.user.name !== right.user.name ||
85
+ left.user.email !== right.user.email ||
86
+ left.user.color !== right.user.color
87
+ ) {
88
+ return false;
89
+ }
90
+ // Compare presence payloads with a stable JSON.stringify — presence
91
+ // fields (cursor/selection/viewport) are small JSON-safe records, so
92
+ // this is cheap and avoids a re-render when nothing actually changed.
93
+ if (JSON.stringify(left.presence) !== JSON.stringify(right.presence)) {
94
+ return false;
95
+ }
96
+ }
97
+ return true;
98
+ }
99
+
66
100
  function derive(): OtherPresence[] {
67
101
  const result: OtherPresence[] = [];
68
102
  awareness!.getStates().forEach((state, clientId) => {
@@ -100,12 +134,38 @@ export function usePresence(
100
134
  return result;
101
135
  }
102
136
 
103
- function onAwarenessChange() {
104
- setOthers(derive());
137
+ function onAwarenessChange(changes?: {
138
+ added: number[];
139
+ updated: number[];
140
+ removed: number[];
141
+ }) {
142
+ // The awareness "change" event fires for local-only state edits too
143
+ // (e.g. this hook's own setPresence() calls, or the doc's
144
+ // activeFileId/visible fields). When every changed client id is the
145
+ // local client, the derived `others` array (which excludes the local
146
+ // client) cannot have changed, so skip the re-render entirely.
147
+ if (changes) {
148
+ const changedIds = [
149
+ ...changes.added,
150
+ ...changes.updated,
151
+ ...changes.removed,
152
+ ];
153
+ if (
154
+ changedIds.length > 0 &&
155
+ changedIds.every((id) => id === localClientId)
156
+ ) {
157
+ return;
158
+ }
159
+ }
160
+ const next = derive();
161
+ if (shallowEqualOthers(lastOthers, next)) return;
162
+ lastOthers = next;
163
+ setOthers(next);
105
164
  }
106
165
 
107
166
  // Derive immediately.
108
- setOthers(derive());
167
+ lastOthers = derive();
168
+ setOthers(lastOthers);
109
169
  awareness.on("change", onAwarenessChange);
110
170
  return () => {
111
171
  awareness.off("change", onAwarenessChange);