Add scroll and zoom raf throttling (#8812)

* ⬆️ Update opencode and copilot deps

* 🐛 Decouple workspace-content from workspace-local to reduce scroll re-renders

Move workspace-local subscription from workspace-content* (parent) into
viewport* and viewport-classic* (children). workspace-content* now only
subscribes to the new workspace-vport derived atom, which changes only on
window resize — not on every scroll event. This prevents the sidebar,
palette and other workspace-content children from re-rendering on scroll.

* 🐛 Throttle wheel events to one state update per animation frame

Accumulate wheel event deltas in a mutable ref and flush them via
requestAnimationFrame, so that multiple wheel events between frames
produce a single state mutation instead of one per event. This prevents
the cascade of synchronous React re-renders (via useSyncExternalStore)
that can exceed the maximum update depth on rapid scrolling.

Both panning (scroll) and zoom (ctrl/mod+wheel) are throttled. Scroll
deltas are summed additively; zoom scales are compounded multiplicatively
with the latest cursor point used as the zoom center.

* ♻️ Extract schedule-zoom! and schedule-scroll! from on-mouse-wheel

* ♻️ Avoid zoom dep on on-mouse-wheel by using a ref
This commit is contained in:
Andrey Antukh
2026-03-30 11:08:33 +02:00
parent d7e0b0cf9f
commit e7e98255d9
8 changed files with 184 additions and 131 deletions

View File

@@ -234,6 +234,9 @@
(def inspect-expanded
(l/derived :inspect-expanded workspace-local))
(def workspace-vport
(l/derived :vport workspace-local))
(def vbox
(l/derived :vbox workspace-local))

View File

@@ -56,7 +56,7 @@
selected (mf/deref refs/selected-shapes)
page-id (get page :id)
{:keys [vport] :as wlocal} (mf/deref refs/workspace-local)
vport (mf/deref refs/workspace-vport)
{:keys [options-mode]} wglobal
@@ -105,7 +105,6 @@
[:> viewport*
{:file file
:page page
:wlocal wlocal
:wglobal wglobal
:selected selected
:layout layout

View File

@@ -81,10 +81,8 @@
selected))
(mf/defc viewport-classic*
[{:keys [selected wglobal wlocal layout file page palete-size]}]
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
;; that the new parameter is sent
{:keys [edit-path
[{:keys [selected wglobal layout file page palete-size]}]
(let [{:keys [edit-path
panning
selrect
transform
@@ -94,7 +92,7 @@
zoom
zoom-inverse
edition]}
wlocal
(mf/deref refs/workspace-local)
{:keys [options-mode
tooltip

View File

@@ -403,46 +403,94 @@
(kbd/alt? event)
(kbd/meta? event))))))))
(defn on-mouse-wheel [zoom]
(mf/use-callback
(mf/deps zoom)
(fn [event]
(let [event (.getBrowserEvent ^js event)
(defn- schedule-zoom!
"Accumulate a compound zoom scale and a cursor point into `state`, scheduling
a single requestAnimationFrame flush if one is not already pending. On the
next frame the accumulated scale is applied via `dw/set-zoom` and the state
is reset to its idle values."
[^js state scale pt]
(let [pending? (pos? (.-zoomRafId state))]
(set! (.-scale state) (* (.-scale state) scale))
(set! (.-zoomPt state) pt)
(when-not pending?
(set! (.-zoomRafId state)
(ts/raf
(fn []
(let [s (.-scale state)
zp (.-zoomPt state)]
(set! (.-scale state) 1)
(set! (.-zoomPt state) nil)
(set! (.-zoomRafId state) 0)
(st/emit! (dw/set-zoom zp s)))))))))
target (dom/get-target event)
mod? (kbd/mod? event)
ctrl? (kbd/ctrl? event)
(defn- schedule-scroll!
"Accumulate scroll deltas into `state`, scheduling a single
requestAnimationFrame flush if one is not already pending. On the next
frame the accumulated dx/dy are applied via `dw/update-viewport-position`
and the state is reset to its idle values."
[^js state zoom event delta-x delta-y]
(let [pending? (pos? (.-rafId state))]
(if (and (not (cfg/check-platform? :macos)) (kbd/shift? event))
;; macOS sends delta-x automatically, so on other platforms we
;; remap shift+scroll-y to horizontal panning.
(set! (.-dx state) (+ (.-dx state) (/ delta-y zoom)))
(do
(set! (.-dx state) (+ (.-dx state) (/ delta-x zoom)))
(set! (.-dy state) (+ (.-dy state) (/ delta-y zoom)))))
(when-not pending?
(set! (.-rafId state)
(ts/raf
(fn []
(let [dx (.-dx state)
dy (.-dy state)]
(set! (.-dx state) 0)
(set! (.-dy state) 0)
(set! (.-rafId state) 0)
(st/emit! (dw/update-viewport-position
{:x #(+ % dx)
:y #(+ % dy)})))))))))
picking-color? (= "pixel-overlay" (.-id target))
comments-layer? (dom/is-child? (dom/get-element "comments") target)
(defn on-mouse-wheel [zoom-ref]
(let [;; Mutable accumulator for scroll/zoom deltas, throttled to one
;; state update per animation frame. This prevents rapid wheel
;; events from causing cascading synchronous React re-renders
;; that can exceed the maximum update depth.
scroll-state (mf/use-ref #js {:dx 0 :dy 0 :rafId 0
:scale 1 :zoomPt nil :zoomRafId 0})]
(mf/use-callback
(fn [event]
(let [event (.getBrowserEvent ^js event)
raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)
target (dom/get-target event)
mod? (kbd/mod? event)
ctrl? (kbd/ctrl? event)
norm-event ^js (nw/normalize-wheel event)
picking-color? (= "pixel-overlay" (.-id target))
comments-layer? (dom/is-child? (dom/get-element "comments") target)
delta-y (.-pixelY norm-event)
delta-x (.-pixelX norm-event)
delta-zoom (+ delta-y delta-x)
raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)
scale (+ 1 (mth/abs (* scale-per-pixel delta-zoom)))
scale (if (pos? delta-zoom) (/ 1 scale) scale)]
norm-event ^js (nw/normalize-wheel event)
(when (or (uwvv/inside-viewport? target) picking-color?)
(dom/prevent-default event)
(dom/stop-propagation event)
(if (or ctrl? mod?)
(st/emit! (dw/set-zoom pt scale))
(if (and (not (cfg/check-platform? :macos)) (kbd/shift? event))
;; macos sends delta-x automatically, don't need to do it
(st/emit! (dw/update-viewport-position {:x #(+ % (/ delta-y zoom))}))
(st/emit! (dw/update-viewport-position {:x #(+ % (/ delta-x zoom))
:y #(+ % (/ delta-y zoom))})))))
delta-y (.-pixelY norm-event)
delta-x (.-pixelX norm-event)
delta-zoom (+ delta-y delta-x)
(when (and comments-layer? (or ctrl? mod?))
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (dw/set-zoom pt scale)))))))
scale (+ 1 (mth/abs (* scale-per-pixel delta-zoom)))
scale (if (pos? delta-zoom) (/ 1 scale) scale)]
(when (or (uwvv/inside-viewport? target) picking-color?)
(dom/prevent-default event)
(dom/stop-propagation event)
(if (or ctrl? mod?)
(schedule-zoom! (mf/ref-val scroll-state) scale pt)
(schedule-scroll! (mf/ref-val scroll-state) (mf/ref-val zoom-ref) event delta-x delta-y)))
(when (and comments-layer? (or ctrl? mod?))
(dom/prevent-default event)
(dom/stop-propagation event)
(schedule-zoom! (mf/ref-val scroll-state) scale pt)))))))
(defn on-drag-enter
[comp-inst-ref]

View File

@@ -44,9 +44,12 @@
(defn setup-dom-events
[zoom disable-paste-ref in-viewport-ref workspace-read-only? drawing-tool drawing-path?]
(let [on-key-down (actions/on-key-down)
(let [zoom-ref (mf/use-ref zoom)
_ (mf/with-effect [zoom]
(mf/set-ref-val! zoom-ref zoom))
on-key-down (actions/on-key-down)
on-key-up (actions/on-key-up)
on-mouse-wheel (actions/on-mouse-wheel zoom)
on-mouse-wheel (actions/on-mouse-wheel zoom-ref)
on-paste (actions/on-paste disable-paste-ref in-viewport-ref workspace-read-only?)
on-pointer-down (mf/use-fn
(mf/deps drawing-tool drawing-path?)
@@ -67,7 +70,7 @@
#(events/unlistenByKey key)))
(mf/with-layout-effect [on-key-down on-key-up on-mouse-wheel on-paste workspace-read-only?]
(mf/with-layout-effect [on-key-down on-key-up on-paste workspace-read-only?]
(let [keys [(events/listen js/document EventType.KEYDOWN on-key-down)
(events/listen js/document EventType.KEYUP on-key-up)
;; bind with passive=false to allow the event to be cancelled

View File

@@ -73,9 +73,10 @@
objects)))
(mf/defc viewport*
[{:keys [selected wglobal wlocal layout file page palete-size]}]
[{:keys [selected wglobal layout file page palete-size]}]
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
;; that the new parameter is sent
{:keys [edit-path
panning
selrect
@@ -85,17 +86,18 @@
vport
zoom
zoom-inverse
edition]} wlocal
edition]}
(mf/deref refs/workspace-local)
{:keys [options-mode
tooltip
show-distances?
picking-color?]} wglobal
picking-color?]}
wglobal
permissions (mf/use-ctx ctx/permissions)
read-only? (mf/use-ctx ctx/workspace-read-only?)
;; DEREFS
drawing (mf/deref refs/workspace-drawing)
focus (mf/deref refs/workspace-focus-selected)
wasm-modifiers (mf/deref refs/workspace-wasm-modifiers)

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"
@@ -16,9 +16,9 @@
"fmt": "./scripts/fmt"
},
"devDependencies": {
"@github/copilot": "^1.0.11",
"@github/copilot": "^1.0.12",
"@types/node": "^20.12.7",
"esbuild": "^0.27.4",
"opencode-ai": "^1.3.2"
"opencode-ai": "^1.3.7"
}
}

164
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ importers:
.:
devDependencies:
'@github/copilot':
specifier: ^1.0.11
version: 1.0.11
specifier: ^1.0.12
version: 1.0.12
'@types/node':
specifier: ^20.12.7
version: 20.19.37
@@ -18,8 +18,8 @@ importers:
specifier: ^0.27.4
version: 0.27.4
opencode-ai:
specifier: ^1.3.2
version: 1.3.2
specifier: ^1.3.7
version: 1.3.7
packages:
@@ -179,44 +179,44 @@ packages:
cpu: [x64]
os: [win32]
'@github/copilot-darwin-arm64@1.0.11':
resolution: {integrity: sha512-wdKimjtbsVeXqMqQSnGpGBPFEYHljxXNuWeH8EIJTNRgFpAsimcivsFgql3Twq4YOp0AxfsH36icG4IEen30mA==}
'@github/copilot-darwin-arm64@1.0.12':
resolution: {integrity: sha512-fjbwRIUZAH06Eyg5ZkfZXg8SVXpqI3HaFhtXZ803CZs9mfIgfOSR3URZxUnv7SIv6aI/7f6ws8RxKnPGavJ/tg==}
cpu: [arm64]
os: [darwin]
hasBin: true
'@github/copilot-darwin-x64@1.0.11':
resolution: {integrity: sha512-VeuPv8rzBVGBB8uDwMEhcHBpldoKaq26yZ5YQm+G9Ka5QIF+1DMah8ZNRMVsTeNKkb1ji9G8vcuCsaPbnG3fKg==}
'@github/copilot-darwin-x64@1.0.12':
resolution: {integrity: sha512-/tJGJEEm8kpTW/sJRNnvhMSHKIHApNun14biuIkC5CXDqVgFakbKlckn/FlIkT48eEUysc0YbEatrHIDz/8XbA==}
cpu: [x64]
os: [darwin]
hasBin: true
'@github/copilot-linux-arm64@1.0.11':
resolution: {integrity: sha512-/d8p6RlFYKj1Va2hekFIcYNMHWagcEkaxgcllUNXSyQLnmEtXUkaWtz62VKGWE+n/UMkEwCB6vI2xEwPTlUNBQ==}
'@github/copilot-linux-arm64@1.0.12':
resolution: {integrity: sha512-4977LVJi3/9Yc+ivj+VKDVtHg0kT5yqOrN8F35/jgqerx4Mdtk1pOMlWztXxLicBHN4y2V7/EY/wc0WqFW0Zvg==}
cpu: [arm64]
os: [linux]
hasBin: true
'@github/copilot-linux-x64@1.0.11':
resolution: {integrity: sha512-UujTRO3xkPFC1CybchBbCnaTEAG6JrH0etIst07JvfekMWgvRxbiCHQPpDPSzBCPiBcGu0gba0/IT+vUCORuIw==}
'@github/copilot-linux-x64@1.0.12':
resolution: {integrity: sha512-9QevJZD29PVltYDV4xHWbdN6ud/966clERL5Frh2+9D3+spaVDO1hFllzoFiEwD/M4f2GkSh7/fT3hV0LKl9Ag==}
cpu: [x64]
os: [linux]
hasBin: true
'@github/copilot-win32-arm64@1.0.11':
resolution: {integrity: sha512-EOW8HUM+EmnHEZEa+iUMl4pP1+2eZUk2XCbynYiMehwX9sidc4BxEHp2RuxADSzFPTieQEWzgjQmHWrtet8pQg==}
'@github/copilot-win32-arm64@1.0.12':
resolution: {integrity: sha512-RLAbAsLniI8vA2utgZdIsvD8slZpz1fb8l6cmIiQvDE/BwQb2zNV9VepZ+CwzYtNx9ifxBtgIwYwUJq5bxeSaQ==}
cpu: [arm64]
os: [win32]
hasBin: true
'@github/copilot-win32-x64@1.0.11':
resolution: {integrity: sha512-fKGkSNamzs3h9AbmswNvPYJBORCb2Y8CbusijU3C7fT3ohvqnHJwKo5iHhJXLOKZNOpFZgq9YKha410u9sIs6Q==}
'@github/copilot-win32-x64@1.0.12':
resolution: {integrity: sha512-4SYV09F4Sw20DAib1do26+ALZmCZrghzo+5e6IZbQOsm4B7NhBFaLpKFU+kEijfmWacLlh/at5CpGGGKlwlbcg==}
cpu: [x64]
os: [win32]
hasBin: true
'@github/copilot@1.0.11':
resolution: {integrity: sha512-cptVopko/tNKEXyBP174yBjHQBEwg6CqaKN2S0M3J+5LEB8u31bLL75ioOPd+5vubqBrA0liyTdcHeZ8UTRbmg==}
'@github/copilot@1.0.12':
resolution: {integrity: sha512-GpmoJbs1ECyLLKtY4PcFzO8Cz6GgDTOKkrzwNdkirNdfsB+o6x0OOlFyrOdNXNPII7pk9+GcpIjF87sLwWzpPQ==}
hasBin: true
'@types/node@20.19.37':
@@ -227,67 +227,67 @@ packages:
engines: {node: '>=18'}
hasBin: true
opencode-ai@1.3.2:
resolution: {integrity: sha512-InyDAXKoh+fVxWrBMJZaf1xIYpASZ2zX4O/u7nwtiYzxy/kqHySvQe9jDVrhMgbMdb4CXzACid7M2HDUa+vz2Q==}
opencode-ai@1.3.7:
resolution: {integrity: sha512-AtqTOcPvHkAF/zPGs/08/8m2DeiWADCGhT/WAJ1drGem4WdPOt45jkJLPdOCheN1gqmLxTcNV0veKFVHmbjKKQ==}
hasBin: true
opencode-darwin-arm64@1.3.2:
resolution: {integrity: sha512-/7V+J3XZGF/sCdMbEb5E3mUvuOIVvGkVjgYH1k/pnTfdGaPW/C7RgW2dU2HedXvkw4Y3CplUS+5VfA/F5kufXw==}
opencode-darwin-arm64@1.3.7:
resolution: {integrity: sha512-TRglBnrSzeR9pEFV8Z1ACqhD3r3WYl8v1y9TkgvHviTD/EXGL3Nu7f/fy3XOQprPGSLPyrlOwZXb1i9XrfTo1A==}
cpu: [arm64]
os: [darwin]
opencode-darwin-x64-baseline@1.3.2:
resolution: {integrity: sha512-jBusp8Vb1wsGKHD2AOD+Cr4qL4zSDut80Sy0CnMH8AwnSCNzaSVi2wnE/7vPrgCa44Xr0MknSE2S0wHQ5SbLaw==}
opencode-darwin-x64-baseline@1.3.7:
resolution: {integrity: sha512-YLl+peZIC1m295JNSMzM/NM5uNs2GVewZply2GV3P6WApS5JuRIaMNvzOk7jpL90yt6pur6oEwC8g0WWwP8G4A==}
cpu: [x64]
os: [darwin]
opencode-darwin-x64@1.3.2:
resolution: {integrity: sha512-J7HgNBUoDpsKHAiky18aUm4xMKmUIJqlvVMkvL9NVjwDRMuAKnbYcxpRK8O+NKzeVx29IWwX8zizUwCuqXAAlA==}
opencode-darwin-x64@1.3.7:
resolution: {integrity: sha512-Nsyh3vLAqqfVyWD4qrcyRJit+CmZZpm6IdXTk9wo1hUAE/RmYIBDz1To99ZBwA3SJB1fLrciYicMN2uq8r1XNw==}
cpu: [x64]
os: [darwin]
opencode-linux-arm64-musl@1.3.2:
resolution: {integrity: sha512-ZOB6+NvkZ19mzi6j4VAHtQDAeTXvQJqg4YtclT3wbMof8y3jn5S8vUmaXLk9d9FryEcdIoMK/oFLkcLkS1OXpA==}
opencode-linux-arm64-musl@1.3.7:
resolution: {integrity: sha512-D4gCn7oVLCc3xN0BSJOfYerCr1E1ktUkixfHQEmkoR1CLZ77Z/aHSgcm0Ln01Q+ie6MsVukvuyUQn9GEY1Dn/A==}
cpu: [arm64]
os: [linux]
opencode-linux-arm64@1.3.2:
resolution: {integrity: sha512-+q0PJ86LPitMIGf9sGXdtOcwpdiwbzdw91zXAHFe7rXZZm4Cvu9qjvt6WT/lKJqL8f6pyJs9Kbt4XJ/C20swQA==}
opencode-linux-arm64@1.3.7:
resolution: {integrity: sha512-72OnT20wIhkXMGclmw7S+d8JjIb9lx8pPIW8pkyI79+qxLTp6AuTHsmUG/qDhw3NMtVDs9efAb0C/FjLXATeAA==}
cpu: [arm64]
os: [linux]
opencode-linux-x64-baseline-musl@1.3.2:
resolution: {integrity: sha512-8XYcAqZBcUgYHfae9qoTT8erqFjd6AKTrSu60gRgzTN8cUybG4iPyAVt4GUcXCzUw44Qo++d/8I9hwpUTCoFYg==}
opencode-linux-x64-baseline-musl@1.3.7:
resolution: {integrity: sha512-DE8eqPF2benmdzUdMG+rnr0J3DtrP+x8sUzq7gecuNnU4iHo4s8Itx+gCLP978ZBdYwTkNRtNZ5BKN0ePT5KYQ==}
cpu: [x64]
os: [linux]
opencode-linux-x64-baseline@1.3.2:
resolution: {integrity: sha512-QXiF1+PJH5AOHMSTdYngOB7yWULFQKJpIbx77ZHM1MKyxiBweIrZ03nG86iXIZIVGBxEfH1CcKDU28lFGUJdNg==}
opencode-linux-x64-baseline@1.3.7:
resolution: {integrity: sha512-Aztdiq0U6H8Nww7mmARK/ZGkllTrePuyEEdzg2+0LWfUpDO5Cl/pMJ8btqVtTjfb/pLw+BU3JtYxw8oOhRkl/A==}
cpu: [x64]
os: [linux]
opencode-linux-x64-musl@1.3.2:
resolution: {integrity: sha512-o4aXgcWQidxQ6cJMsgLcCMv9MSKtLEqm3OAFm6eN7hVgHV9m7rz035BICNZ1YLltIEdQYAHXx5MGM21J4/vBdw==}
opencode-linux-x64-musl@1.3.7:
resolution: {integrity: sha512-L0ohQAbw1Ib1koawV/yJAYIGIel2zMPafbdeMXELIvpes3Sq9qIfCSRB/2ROu8gjN8P1TGnUU6Vx1F3MtJOvIA==}
cpu: [x64]
os: [linux]
opencode-linux-x64@1.3.2:
resolution: {integrity: sha512-/+OtuHr2O0/QKpBrZBnutr0ZLe+LmOx003Z99b21BgG9wsVo7gFuNpfLPVXnqcEY6D5DuWl4aEJW4+w+K0urYw==}
opencode-linux-x64@1.3.7:
resolution: {integrity: sha512-rCFXrgDLhPuHazomDgzBXGLE0wJ4VRHrIe26WCHm4iqmGu9O6ExZd612Y07/CGQm4bVBHlaalcWh7N/z6GOPkA==}
cpu: [x64]
os: [linux]
opencode-windows-arm64@1.3.2:
resolution: {integrity: sha512-3xJEjSLdk7I5Z6yyrT4LCQeK4VCj6mo/l4JdUa0zocV8wCzUtY1lSHeipDAaNr8TwD6NeTxr1iomIjuatKAFOA==}
opencode-windows-arm64@1.3.7:
resolution: {integrity: sha512-s6emZ28ORIMtKyrBKvo96q2qanRwbjPHK/rOMinZ22SW7DLzNKKf1p92JMkSni0dXXGL64jsy1se5IvELc7Mvg==}
cpu: [arm64]
os: [win32]
opencode-windows-x64-baseline@1.3.2:
resolution: {integrity: sha512-RVx9e1KBNTSWjrtfRpGIIcJDsM13OUY/lYqJXI1oj34lMOEj5G57fypjk9yBqUHBR+7tolGmewwJJU6SDgyPXQ==}
opencode-windows-x64-baseline@1.3.7:
resolution: {integrity: sha512-CGbhvn9rMXV4xEjw1lxPFbsWuOPf/xJ1AAblqKsF2VmSbqe25QG5VIf88hsJo8YmYIHz6U7tNGI4lTF1zVx9cw==}
cpu: [x64]
os: [win32]
opencode-windows-x64@1.3.2:
resolution: {integrity: sha512-pJMpptERqz8pjnW1pTQf5Ru2WbJz7P2BPM5De5gdUd10y0yUoGvY7uzyJYuGpsrCqeWrEYokERnbv8A/4V3Yaw==}
opencode-windows-x64@1.3.7:
resolution: {integrity: sha512-q7V9p10Q7BH03tnYMG4k6B1ZXLDriDMtXkkw+NW1p22F7dQ22WmaMoCWnv3d4b2vNyjMjIYuPm97q0v02QI08w==}
cpu: [x64]
os: [win32]
@@ -374,32 +374,32 @@ snapshots:
'@esbuild/win32-x64@0.27.4':
optional: true
'@github/copilot-darwin-arm64@1.0.11':
'@github/copilot-darwin-arm64@1.0.12':
optional: true
'@github/copilot-darwin-x64@1.0.11':
'@github/copilot-darwin-x64@1.0.12':
optional: true
'@github/copilot-linux-arm64@1.0.11':
'@github/copilot-linux-arm64@1.0.12':
optional: true
'@github/copilot-linux-x64@1.0.11':
'@github/copilot-linux-x64@1.0.12':
optional: true
'@github/copilot-win32-arm64@1.0.11':
'@github/copilot-win32-arm64@1.0.12':
optional: true
'@github/copilot-win32-x64@1.0.11':
'@github/copilot-win32-x64@1.0.12':
optional: true
'@github/copilot@1.0.11':
'@github/copilot@1.0.12':
optionalDependencies:
'@github/copilot-darwin-arm64': 1.0.11
'@github/copilot-darwin-x64': 1.0.11
'@github/copilot-linux-arm64': 1.0.11
'@github/copilot-linux-x64': 1.0.11
'@github/copilot-win32-arm64': 1.0.11
'@github/copilot-win32-x64': 1.0.11
'@github/copilot-darwin-arm64': 1.0.12
'@github/copilot-darwin-x64': 1.0.12
'@github/copilot-linux-arm64': 1.0.12
'@github/copilot-linux-x64': 1.0.12
'@github/copilot-win32-arm64': 1.0.12
'@github/copilot-win32-x64': 1.0.12
'@types/node@20.19.37':
dependencies:
@@ -434,55 +434,55 @@ snapshots:
'@esbuild/win32-ia32': 0.27.4
'@esbuild/win32-x64': 0.27.4
opencode-ai@1.3.2:
opencode-ai@1.3.7:
optionalDependencies:
opencode-darwin-arm64: 1.3.2
opencode-darwin-x64: 1.3.2
opencode-darwin-x64-baseline: 1.3.2
opencode-linux-arm64: 1.3.2
opencode-linux-arm64-musl: 1.3.2
opencode-linux-x64: 1.3.2
opencode-linux-x64-baseline: 1.3.2
opencode-linux-x64-baseline-musl: 1.3.2
opencode-linux-x64-musl: 1.3.2
opencode-windows-arm64: 1.3.2
opencode-windows-x64: 1.3.2
opencode-windows-x64-baseline: 1.3.2
opencode-darwin-arm64: 1.3.7
opencode-darwin-x64: 1.3.7
opencode-darwin-x64-baseline: 1.3.7
opencode-linux-arm64: 1.3.7
opencode-linux-arm64-musl: 1.3.7
opencode-linux-x64: 1.3.7
opencode-linux-x64-baseline: 1.3.7
opencode-linux-x64-baseline-musl: 1.3.7
opencode-linux-x64-musl: 1.3.7
opencode-windows-arm64: 1.3.7
opencode-windows-x64: 1.3.7
opencode-windows-x64-baseline: 1.3.7
opencode-darwin-arm64@1.3.2:
opencode-darwin-arm64@1.3.7:
optional: true
opencode-darwin-x64-baseline@1.3.2:
opencode-darwin-x64-baseline@1.3.7:
optional: true
opencode-darwin-x64@1.3.2:
opencode-darwin-x64@1.3.7:
optional: true
opencode-linux-arm64-musl@1.3.2:
opencode-linux-arm64-musl@1.3.7:
optional: true
opencode-linux-arm64@1.3.2:
opencode-linux-arm64@1.3.7:
optional: true
opencode-linux-x64-baseline-musl@1.3.2:
opencode-linux-x64-baseline-musl@1.3.7:
optional: true
opencode-linux-x64-baseline@1.3.2:
opencode-linux-x64-baseline@1.3.7:
optional: true
opencode-linux-x64-musl@1.3.2:
opencode-linux-x64-musl@1.3.7:
optional: true
opencode-linux-x64@1.3.2:
opencode-linux-x64@1.3.7:
optional: true
opencode-windows-arm64@1.3.2:
opencode-windows-arm64@1.3.7:
optional: true
opencode-windows-x64-baseline@1.3.2:
opencode-windows-x64-baseline@1.3.7:
optional: true
opencode-windows-x64@1.3.2:
opencode-windows-x64@1.3.7:
optional: true
undici-types@6.21.0: {}