@tutti-os/workbench-surface 0.0.263 → 0.0.265

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4326,6 +4326,139 @@ function createWorkbenchGenieNodeVisibilityStore() {
4326
4326
  };
4327
4327
  }
4328
4328
 
4329
+ // src/preview/workbenchNodePreviewRuntime.ts
4330
+ var defaultMaxEntries = 96;
4331
+ function createWorkbenchNodePreviewRuntime(input = {}) {
4332
+ const maxEntries = input.maxEntries ?? defaultMaxEntries;
4333
+ const previewByIdentity = /* @__PURE__ */ new Map();
4334
+ const latestPreviewByNodeId = /* @__PURE__ */ new Map();
4335
+ const pendingByIdentity = /* @__PURE__ */ new Map();
4336
+ const activeIdentityByNodeId = /* @__PURE__ */ new Map();
4337
+ const writeIdentity = (identity, previewImageUrl) => {
4338
+ previewByIdentity.delete(identity);
4339
+ previewByIdentity.set(identity, previewImageUrl);
4340
+ trimOldest(previewByIdentity, maxEntries);
4341
+ };
4342
+ const writeLatest = (nodeId, identity, previewImageUrl) => {
4343
+ latestPreviewByNodeId.delete(nodeId);
4344
+ latestPreviewByNodeId.set(nodeId, { identity, previewImageUrl });
4345
+ trimOldest(latestPreviewByNodeId, maxEntries);
4346
+ };
4347
+ return {
4348
+ async ensure(request) {
4349
+ activeIdentityByNodeId.set(request.nodeId, request.identity);
4350
+ const cached = previewByIdentity.get(request.identity);
4351
+ if (cached) {
4352
+ writeLatest(request.nodeId, request.identity, cached);
4353
+ return cached;
4354
+ }
4355
+ const pending = pendingByIdentity.get(request.identity);
4356
+ if (pending) {
4357
+ return pending;
4358
+ }
4359
+ const operation = (async () => {
4360
+ const persistedPreview = await invokePreviewAdapter(
4361
+ request.readPersisted
4362
+ );
4363
+ if (persistedPreview) {
4364
+ writeIdentity(request.identity, persistedPreview);
4365
+ if (activeIdentityByNodeId.get(request.nodeId) === request.identity) {
4366
+ writeLatest(request.nodeId, request.identity, persistedPreview);
4367
+ }
4368
+ return persistedPreview;
4369
+ }
4370
+ const capturedPreview = await invokePreviewAdapter(request.capture);
4371
+ if (!capturedPreview) {
4372
+ return null;
4373
+ }
4374
+ writeIdentity(request.identity, capturedPreview);
4375
+ const isCurrentGeneration = activeIdentityByNodeId.get(request.nodeId) === request.identity;
4376
+ if (isCurrentGeneration) {
4377
+ writeLatest(request.nodeId, request.identity, capturedPreview);
4378
+ }
4379
+ if (isCurrentGeneration && request.writePersisted) {
4380
+ invokePreviewPersistence(request.writePersisted, capturedPreview);
4381
+ }
4382
+ return capturedPreview;
4383
+ })();
4384
+ pendingByIdentity.set(request.identity, operation);
4385
+ try {
4386
+ return await operation;
4387
+ } finally {
4388
+ if (pendingByIdentity.get(request.identity) === operation) {
4389
+ pendingByIdentity.delete(request.identity);
4390
+ }
4391
+ }
4392
+ },
4393
+ read(identity) {
4394
+ const previewImageUrl = previewByIdentity.get(identity) ?? null;
4395
+ if (previewImageUrl) {
4396
+ previewByIdentity.delete(identity);
4397
+ previewByIdentity.set(identity, previewImageUrl);
4398
+ }
4399
+ return previewImageUrl;
4400
+ },
4401
+ readLatest(nodeId) {
4402
+ const latest = latestPreviewByNodeId.get(nodeId) ?? null;
4403
+ if (latest) {
4404
+ latestPreviewByNodeId.delete(nodeId);
4405
+ latestPreviewByNodeId.set(nodeId, latest);
4406
+ }
4407
+ return latest?.previewImageUrl ?? null;
4408
+ },
4409
+ write({ identity, nodeId, previewImageUrl }) {
4410
+ if (!previewImageUrl) {
4411
+ return;
4412
+ }
4413
+ activeIdentityByNodeId.set(nodeId, identity);
4414
+ writeIdentity(identity, previewImageUrl);
4415
+ writeLatest(nodeId, identity, previewImageUrl);
4416
+ }
4417
+ };
4418
+ }
4419
+ async function invokePreviewAdapter(adapter) {
4420
+ if (!adapter) {
4421
+ return null;
4422
+ }
4423
+ try {
4424
+ return await adapter();
4425
+ } catch {
4426
+ return null;
4427
+ }
4428
+ }
4429
+ function invokePreviewPersistence(persist, previewImageUrl) {
4430
+ try {
4431
+ void Promise.resolve(persist(previewImageUrl)).catch(() => void 0);
4432
+ } catch {
4433
+ }
4434
+ }
4435
+ function trimOldest(entries, maxEntries) {
4436
+ while (entries.size > maxEntries) {
4437
+ const oldestKey = entries.keys().next().value;
4438
+ if (oldestKey === void 0) {
4439
+ return;
4440
+ }
4441
+ entries.delete(oldestKey);
4442
+ }
4443
+ }
4444
+ var workbenchNodePreviewRuntime = createWorkbenchNodePreviewRuntime();
4445
+ function workbenchNodePreviewIdentity(nodeId) {
4446
+ return `node:${nodeId}`;
4447
+ }
4448
+ function workbenchDockPreviewIdentity(key) {
4449
+ return JSON.stringify([
4450
+ key.workspaceId,
4451
+ key.typeId,
4452
+ key.instanceId,
4453
+ key.instanceKey ?? null,
4454
+ key.nodeId,
4455
+ key.revision ?? null
4456
+ ]);
4457
+ }
4458
+ function workbenchMinimizedDockPreviewIdentity(key, minimizedAtUnixMs) {
4459
+ return `${workbenchDockPreviewIdentity(key)}:${minimizedAtUnixMs ?? "unknown"}`;
4460
+ }
4461
+
4329
4462
  // src/react/workbenchGenieScheduling.ts
4330
4463
  function startCachedWorkbenchGenieRestore({
4331
4464
  launch,
@@ -4452,14 +4585,12 @@ var minimizedDockSlotEnterAnimationMs = 720;
4452
4585
  var dockAnchorResolveMaxAnimationFrames = 3;
4453
4586
  var dockPreviewMaxWidth = 260;
4454
4587
  var dockPreviewMaxHeight = 170;
4455
- var dockPreviewImageCacheMaxEntries = 96;
4456
4588
  var minimizedGenieTextureCacheMaxBytes = 64 * 1024 * 1024;
4457
4589
  var minimizedGenieTextureCacheMaxEntries = 8;
4458
4590
  var inlineImageResourceCacheMaxEntries = 160;
4459
4591
  var inlineImageResizeTargetCacheMaxEntries = 4;
4460
4592
  var dockAnchorFallbackSizePx = 43.2;
4461
4593
  var genieInlineImageMaxDevicePixelRatio = 2;
4462
- var dockPreviewImageByNodeID = /* @__PURE__ */ new Map();
4463
4594
  var inlineImageResourceByUrl = /* @__PURE__ */ new Map();
4464
4595
  var resizedInlineImageResourceByUrl = /* @__PURE__ */ new Map();
4465
4596
  function resolveWorkbenchCaptureElement(windowElement) {
@@ -4813,42 +4944,11 @@ function resolveDockAnchorViewportRect(element) {
4813
4944
  };
4814
4945
  }
4815
4946
  function writeCachedWorkbenchNodePreviewImage(nodeID, previewImageUrl) {
4816
- if (previewImageUrl) {
4817
- dockPreviewImageByNodeID.delete(nodeID);
4818
- dockPreviewImageByNodeID.set(nodeID, previewImageUrl);
4819
- while (dockPreviewImageByNodeID.size > dockPreviewImageCacheMaxEntries) {
4820
- const oldestNodeID = dockPreviewImageByNodeID.keys().next().value;
4821
- if (typeof oldestNodeID !== "string") {
4822
- break;
4823
- }
4824
- dockPreviewImageByNodeID.delete(oldestNodeID);
4825
- }
4826
- }
4827
- }
4828
- function readCachedWorkbenchNodePreviewImage(nodeID) {
4829
- return dockPreviewImageByNodeID.get(nodeID) ?? null;
4830
- }
4831
- async function captureWorkbenchNodePreviewImage(nodeID, options = {}) {
4832
- if (!options.bypassCache) {
4833
- const cachedPreviewImageUrl = readCachedWorkbenchNodePreviewImage(nodeID);
4834
- if (cachedPreviewImageUrl) {
4835
- return cachedPreviewImageUrl;
4836
- }
4837
- }
4838
- const windowElement = Array.from(
4839
- document.querySelectorAll("[data-workbench-window-id]")
4840
- ).find((candidate) => candidate.dataset.workbenchWindowId === nodeID) ?? null;
4841
- const captureTarget = windowElement ? resolveWorkbenchCaptureElement(windowElement) : null;
4842
- if (!captureTarget) {
4843
- return null;
4844
- }
4845
- const texture = await captureElementTexture(captureTarget, {
4846
- maxHeight: dockPreviewMaxHeight,
4847
- maxWidth: dockPreviewMaxWidth
4848
- }).catch(() => null);
4849
- const previewImageUrl = texture ? createDockPreviewDataUrl(texture.canvas) : null;
4850
- writeCachedWorkbenchNodePreviewImage(nodeID, previewImageUrl);
4851
- return previewImageUrl;
4947
+ workbenchNodePreviewRuntime.write({
4948
+ identity: workbenchNodePreviewIdentity(nodeID),
4949
+ nodeId: nodeID,
4950
+ previewImageUrl
4951
+ });
4852
4952
  }
4853
4953
  async function captureProvidedWorkbenchNodePreviewImageForNode(node, input = {}) {
4854
4954
  const previewImageUrl = await Promise.resolve(
@@ -9008,11 +9108,11 @@ import { Component, memo as memo3, useCallback as useCallback14, useMemo as useM
9008
9108
  // src/host/WorkbenchHostDock.tsx
9009
9109
  import {
9010
9110
  useCallback as useCallback13,
9011
- useEffect as useEffect12,
9111
+ useEffect as useEffect13,
9012
9112
  useLayoutEffect as useLayoutEffect7,
9013
9113
  useMemo as useMemo10,
9014
9114
  useRef as useRef11,
9015
- useState as useState10
9115
+ useState as useState11
9016
9116
  } from "react";
9017
9117
  import {
9018
9118
  ArrowLeftIcon,
@@ -9022,6 +9122,141 @@ import {
9022
9122
  ChevronUpIcon
9023
9123
  } from "@tutti-os/ui-system";
9024
9124
 
9125
+ // src/host/useWorkbenchMinimizedDockPreview.ts
9126
+ import { useEffect as useEffect7, useState as useState6 } from "react";
9127
+ function useWorkbenchMinimizedDockPreview({
9128
+ capturePreview,
9129
+ deferPreview,
9130
+ dockPreviewCache,
9131
+ node,
9132
+ providePreview,
9133
+ workspaceId
9134
+ }) {
9135
+ const [componentPreview, setComponentPreview] = useState6(void 0);
9136
+ const [previewImageUrl, setPreviewImageUrl] = useState6(
9137
+ () => deferPreview ? null : workbenchNodePreviewRuntime.readLatest(node.id)
9138
+ );
9139
+ useEffect7(() => {
9140
+ if (deferPreview || !providePreview || componentPreview !== void 0 || previewImageUrl) {
9141
+ return void 0;
9142
+ }
9143
+ return scheduleDeferredPreview(() => {
9144
+ setComponentPreview(providePreview(node) ?? null);
9145
+ });
9146
+ }, [
9147
+ componentPreview,
9148
+ deferPreview,
9149
+ node.data.instanceId,
9150
+ node.data.instanceKey,
9151
+ node.data.typeId,
9152
+ node.id,
9153
+ node.minimizedAtUnixMs,
9154
+ previewImageUrl,
9155
+ providePreview
9156
+ ]);
9157
+ useEffect7(() => {
9158
+ if (deferPreview) {
9159
+ return void 0;
9160
+ }
9161
+ let active = true;
9162
+ const cachedPreview = readWorkbenchMinimizedDockPreviewImage(node.id);
9163
+ setPreviewImageUrl(cachedPreview);
9164
+ void resolveWorkbenchMinimizedDockPreviewImage({
9165
+ capturePreview: capturePreview ? () => Promise.resolve(capturePreview(node)) : void 0,
9166
+ dockPreviewCache,
9167
+ node,
9168
+ workspaceId
9169
+ }).then((nextPreview) => {
9170
+ if (active && nextPreview) {
9171
+ setPreviewImageUrl(nextPreview);
9172
+ }
9173
+ });
9174
+ return () => {
9175
+ active = false;
9176
+ };
9177
+ }, [
9178
+ capturePreview,
9179
+ deferPreview,
9180
+ dockPreviewCache,
9181
+ node.data.instanceId,
9182
+ node.data.instanceKey,
9183
+ node.data.typeId,
9184
+ node.id,
9185
+ node.minimizedAtUnixMs,
9186
+ workspaceId
9187
+ ]);
9188
+ return { componentPreview, previewImageUrl };
9189
+ }
9190
+ function readWorkbenchMinimizedDockPreviewImage(nodeId) {
9191
+ return workbenchNodePreviewRuntime.readLatest(nodeId);
9192
+ }
9193
+ function resolveWorkbenchMinimizedDockPreviewRevision(node) {
9194
+ return String(node.minimizedAtUnixMs);
9195
+ }
9196
+ function resolveWorkbenchMinimizedDockPreviewImage({
9197
+ capturePreview,
9198
+ dockPreviewCache,
9199
+ node,
9200
+ workspaceId
9201
+ }) {
9202
+ const cacheKey = {
9203
+ instanceId: node.data.instanceId,
9204
+ instanceKey: node.data.instanceKey ?? null,
9205
+ nodeId: node.id,
9206
+ revision: resolveWorkbenchMinimizedDockPreviewRevision(node),
9207
+ typeId: node.data.typeId,
9208
+ workspaceId
9209
+ };
9210
+ const genieCacheKey = {
9211
+ ...cacheKey,
9212
+ revision: void 0
9213
+ };
9214
+ return workbenchNodePreviewRuntime.ensure({
9215
+ capture: capturePreview,
9216
+ identity: workbenchMinimizedDockPreviewIdentity(
9217
+ cacheKey,
9218
+ node.minimizedAtUnixMs
9219
+ ),
9220
+ nodeId: node.id,
9221
+ readPersisted: dockPreviewCache ? async () => await dockPreviewCache.read(cacheKey) ?? dockPreviewCache.read(genieCacheKey) : void 0,
9222
+ writePersisted: dockPreviewCache ? (previewImageUrl) => dockPreviewCache.write({ key: cacheKey, previewImageUrl }) : void 0
9223
+ });
9224
+ }
9225
+ function scheduleDeferredPreview(run) {
9226
+ let cancelled = false;
9227
+ let frameId = null;
9228
+ let idleId = null;
9229
+ let timeoutId = null;
9230
+ const invoke = () => {
9231
+ if (!cancelled) {
9232
+ run();
9233
+ }
9234
+ };
9235
+ const scheduler = globalThis;
9236
+ if (typeof scheduler.requestIdleCallback === "function") {
9237
+ idleId = scheduler.requestIdleCallback(invoke, { timeout: 250 });
9238
+ } else if (typeof scheduler.requestAnimationFrame === "function") {
9239
+ frameId = scheduler.requestAnimationFrame(() => {
9240
+ frameId = null;
9241
+ timeoutId = globalThis.setTimeout(invoke, 0);
9242
+ });
9243
+ } else {
9244
+ timeoutId = globalThis.setTimeout(invoke, 0);
9245
+ }
9246
+ return () => {
9247
+ cancelled = true;
9248
+ if (idleId !== null && typeof scheduler.cancelIdleCallback === "function") {
9249
+ scheduler.cancelIdleCallback(idleId);
9250
+ }
9251
+ if (frameId !== null && typeof scheduler.cancelAnimationFrame === "function") {
9252
+ scheduler.cancelAnimationFrame(frameId);
9253
+ }
9254
+ if (timeoutId !== null) {
9255
+ globalThis.clearTimeout(timeoutId);
9256
+ }
9257
+ };
9258
+ }
9259
+
9025
9260
  // src/host/dockEntries.ts
9026
9261
  function matchWorkbenchDockEntryNode(entry, node) {
9027
9262
  if (typeof node.data.dockEntryId === "string") {
@@ -9149,7 +9384,7 @@ function isWorkbenchDockEntryBlocked(entry) {
9149
9384
  }
9150
9385
 
9151
9386
  // src/host/dockMagnification.ts
9152
- import { useCallback as useCallback10, useEffect as useEffect7, useRef as useRef6 } from "react";
9387
+ import { useCallback as useCallback10, useEffect as useEffect8, useRef as useRef6 } from "react";
9153
9388
 
9154
9389
  // src/host/dockMagnificationBounds.ts
9155
9390
  var DOCK_MAGNIFICATION_CROSS_AXIS_PADDING = 8;
@@ -9832,7 +10067,7 @@ function useDockMagnification({
9832
10067
  );
9833
10068
  handleGlobalPointerMoveRef.current = handlePointerMove;
9834
10069
  handleGlobalPointerCancelRef.current = handleGlobalPointerCancel;
9835
- useEffect7(() => {
10070
+ useEffect8(() => {
9836
10071
  if (typeof document === "undefined") {
9837
10072
  return;
9838
10073
  }
@@ -9962,7 +10197,7 @@ function useDockMagnification({
9962
10197
  entryRampStartedAtRef.current = null;
9963
10198
  setMagnifyActive(false);
9964
10199
  }, [setMagnifyActive, slotRefs, stopAnimation, stopGlobalPointerTracking]);
9965
- useEffect7(
10200
+ useEffect8(
9966
10201
  () => () => {
9967
10202
  resetMagnification();
9968
10203
  },
@@ -10158,7 +10393,7 @@ function orderWorkbenchMinimizedDockNodes(input) {
10158
10393
  }
10159
10394
 
10160
10395
  // src/host/minimizedDockStackPromotion.ts
10161
- import { useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
10396
+ import { useEffect as useEffect9, useRef as useRef7, useState as useState7 } from "react";
10162
10397
  var minimizedDockStackPromotionDurationMs = 520;
10163
10398
  function detectMinimizedDockStackPromotion(previous, next) {
10164
10399
  const previousVisibleNodeIds = new Set(
@@ -10180,9 +10415,9 @@ function detectMinimizedDockStackPromotion(previous, next) {
10180
10415
  function useMinimizedDockStackPromotion(slots) {
10181
10416
  const previousSlotsRef = useRef7(slots);
10182
10417
  const promotionTimerRef = useRef7(null);
10183
- const [promotedNodeId, setPromotedNodeId] = useState6(null);
10184
- const [stackDispatching, setStackDispatching] = useState6(false);
10185
- useEffect8(() => {
10418
+ const [promotedNodeId, setPromotedNodeId] = useState7(null);
10419
+ const [stackDispatching, setStackDispatching] = useState7(false);
10420
+ useEffect9(() => {
10186
10421
  const promotedNodeId2 = detectMinimizedDockStackPromotion(
10187
10422
  previousSlotsRef.current,
10188
10423
  slots
@@ -10248,10 +10483,10 @@ function resolveWorkbenchMinimizedDockRestoreIntent(input) {
10248
10483
  // src/host/WorkbenchHostDockPopup.tsx
10249
10484
  import {
10250
10485
  useCallback as useCallback12,
10251
- useEffect as useEffect11,
10486
+ useEffect as useEffect12,
10252
10487
  useLayoutEffect as useLayoutEffect6,
10253
10488
  useRef as useRef10,
10254
- useState as useState9
10489
+ useState as useState10
10255
10490
  } from "react";
10256
10491
  import { createPortal as createPortal3 } from "react-dom";
10257
10492
  import { FileCreateIcon as FileCreateIcon2, cn as cn2 } from "@tutti-os/ui-system";
@@ -10405,9 +10640,9 @@ function resolveDockPopupVerticalClampOffsetPx(input) {
10405
10640
  import {
10406
10641
  forwardRef,
10407
10642
  useCallback as useCallback11,
10408
- useEffect as useEffect9,
10643
+ useEffect as useEffect10,
10409
10644
  useRef as useRef8,
10410
- useState as useState7
10645
+ useState as useState8
10411
10646
  } from "react";
10412
10647
  import {
10413
10648
  Button as Button2,
@@ -10638,9 +10873,9 @@ var WorkbenchHostDockPopupCard = forwardRef(function WorkbenchHostDockPopupCard2
10638
10873
  }, ref) {
10639
10874
  const title = item.title?.trim() || item.node.title;
10640
10875
  const isMinimizedStack = variant === "minimized-stack";
10641
- const [isLaunching, setIsLaunching] = useState7(false);
10876
+ const [isLaunching, setIsLaunching] = useState8(false);
10642
10877
  const launchTimerRef = useRef8(null);
10643
- useEffect9(
10878
+ useEffect10(
10644
10879
  () => () => {
10645
10880
  if (launchTimerRef.current !== null) {
10646
10881
  clearTimeout(launchTimerRef.current);
@@ -10812,7 +11047,7 @@ function WorkbenchHostDockPopupCardLabel({ title }) {
10812
11047
  }
10813
11048
 
10814
11049
  // src/host/useWorkbenchHostDockPopupPreviewCapture.ts
10815
- import { useEffect as useEffect10, useLayoutEffect as useLayoutEffect5, useRef as useRef9, useState as useState8 } from "react";
11050
+ import { useEffect as useEffect11, useLayoutEffect as useLayoutEffect5, useRef as useRef9, useState as useState9 } from "react";
10816
11051
  var dockPopupPreviewCacheMaxEntries = 64;
10817
11052
  var dockPopupPreviewByMemoryKey = /* @__PURE__ */ new Map();
10818
11053
  var pendingDockPopupPreviewCaptureKeys = /* @__PURE__ */ new Set();
@@ -10826,7 +11061,7 @@ function useWorkbenchHostDockPopupPreviewCapture(input) {
10826
11061
  resolveDockPreviewCacheKey: resolveDockPreviewCacheKey2
10827
11062
  } = input;
10828
11063
  const hasCapturePreview = Boolean(capturePreview);
10829
- const [capturedPreviewByMemoryKey, setCapturedPreviewByMemoryKey] = useState8({});
11064
+ const [capturedPreviewByMemoryKey, setCapturedPreviewByMemoryKey] = useState9({});
10830
11065
  const capturedPreviewByMemoryKeyRef = useRef9(capturedPreviewByMemoryKey);
10831
11066
  const capturePreviewRef = useRef9(capturePreview);
10832
11067
  const debugDiagnosticsRef = useRef9(debugDiagnostics);
@@ -10855,13 +11090,13 @@ function useWorkbenchHostDockPopupPreviewCapture(input) {
10855
11090
  previewCaptureItemStateKeys
10856
11091
  );
10857
11092
  });
10858
- useEffect10(() => {
11093
+ useEffect11(() => {
10859
11094
  isMountedRef.current = true;
10860
11095
  return () => {
10861
11096
  isMountedRef.current = false;
10862
11097
  };
10863
11098
  }, []);
10864
- useEffect10(() => {
11099
+ useEffect11(() => {
10865
11100
  if (!capturePreviewRef.current || isContextMenu) {
10866
11101
  return;
10867
11102
  }
@@ -11280,9 +11515,9 @@ function WorkbenchHostDockPopup({
11280
11515
  );
11281
11516
  const popupRootRef = useRef10(null);
11282
11517
  const minimizedStackViewportRef = useRef10(null);
11283
- const [pointer, setPointer] = useState9(null);
11284
- const [minimizedStackScrollOffset, setMinimizedStackScrollOffset] = useState9(0);
11285
- const [verticalClampOffsetPx, setVerticalClampOffsetPx] = useState9(0);
11518
+ const [pointer, setPointer] = useState10(null);
11519
+ const [minimizedStackScrollOffset, setMinimizedStackScrollOffset] = useState10(0);
11520
+ const [verticalClampOffsetPx, setVerticalClampOffsetPx] = useState10(0);
11286
11521
  const { previewStateFor } = useWorkbenchHostDockPopupPreviewCapture({
11287
11522
  capturePreview,
11288
11523
  debugDiagnostics,
@@ -11360,7 +11595,7 @@ function WorkbenchHostDockPopup({
11360
11595
  } : {}
11361
11596
  };
11362
11597
  const popupDiagnosticKey = items.map((item) => item.node.id).join("|");
11363
- useEffect11(() => {
11598
+ useEffect12(() => {
11364
11599
  logWorkbenchDockPopupDebug("dock.popup.rendered", debugDiagnostics, {
11365
11600
  hasCapturePreview: Boolean(capturePreview),
11366
11601
  itemCount: popupDiagnosticKey ? popupDiagnosticKey.split("|").length : 0,
@@ -11442,7 +11677,7 @@ function WorkbenchHostDockPopup({
11442
11677
  cardRefCallbacksRef.current.set(nodeId, callback);
11443
11678
  return callback;
11444
11679
  }, []);
11445
- useEffect11(() => {
11680
+ useEffect12(() => {
11446
11681
  if (!isLeftMinimizedStack) {
11447
11682
  return;
11448
11683
  }
@@ -11454,7 +11689,7 @@ function WorkbenchHostDockPopup({
11454
11689
  document.body.removeAttribute("data-desktop-dock-minimized-stack-open");
11455
11690
  };
11456
11691
  }, [isLeftMinimizedStack]);
11457
- useEffect11(() => {
11692
+ useEffect12(() => {
11458
11693
  const handlePointerDown = (event) => {
11459
11694
  if (!(event.target instanceof Element)) {
11460
11695
  onClose();
@@ -11479,13 +11714,13 @@ function WorkbenchHostDockPopup({
11479
11714
  window.removeEventListener("keydown", handleKeyDown);
11480
11715
  };
11481
11716
  }, [onClose]);
11482
- useEffect11(() => {
11717
+ useEffect12(() => {
11483
11718
  if (!isMinimizedStack) {
11484
11719
  return;
11485
11720
  }
11486
11721
  setMinimizedStackScrollOffset(initialMinimizedStackScrollOffset);
11487
11722
  }, [initialMinimizedStackScrollOffset, isMinimizedStack, items.length]);
11488
- useEffect11(() => {
11723
+ useEffect12(() => {
11489
11724
  if (!isMinimizedStack) {
11490
11725
  return;
11491
11726
  }
@@ -11756,27 +11991,27 @@ function WorkbenchHostDock({
11756
11991
  const attentionTimeouts = useRef11(
11757
11992
  /* @__PURE__ */ new Map()
11758
11993
  );
11759
- const [activeAttentionEntryIds, setActiveAttentionEntryIds] = useState10(/* @__PURE__ */ new Set());
11760
- const [activePopup, setActivePopup] = useState10(null);
11761
- const [activeHoverPanel, setActiveHoverPanel] = useState10(null);
11762
- const [activeLabelTooltip, setActiveLabelTooltip] = useState10(null);
11763
- const [activeMinimizedStackPopup, setActiveMinimizedStackPopup] = useState10(null);
11764
- const [pendingActionKeys, setPendingActionKeys] = useState10(
11994
+ const [activeAttentionEntryIds, setActiveAttentionEntryIds] = useState11(/* @__PURE__ */ new Set());
11995
+ const [activePopup, setActivePopup] = useState11(null);
11996
+ const [activeHoverPanel, setActiveHoverPanel] = useState11(null);
11997
+ const [activeLabelTooltip, setActiveLabelTooltip] = useState11(null);
11998
+ const [activeMinimizedStackPopup, setActiveMinimizedStackPopup] = useState11(null);
11999
+ const [pendingActionKeys, setPendingActionKeys] = useState11(
11765
12000
  () => /* @__PURE__ */ new Set()
11766
12001
  );
11767
- const [dockFrameSize, setDockFrameSize] = useState10(null);
12002
+ const [dockFrameSize, setDockFrameSize] = useState11(null);
11768
12003
  const { triggerDockBounce } = useDockBounce(slotRefs);
11769
- const [dockScrollState, setDockScrollState] = useState10(() => ({
12004
+ const [dockScrollState, setDockScrollState] = useState11(() => ({
11770
12005
  canScrollBackward: false,
11771
12006
  canScrollForward: false,
11772
12007
  hasOverflow: false
11773
12008
  }));
11774
- const [dockStateRevision, setDockStateRevision] = useState10(0);
11775
- const [externalStateRevision, setExternalStateRevision] = useState10(0);
12009
+ const [dockStateRevision, setDockStateRevision] = useState11(0);
12010
+ const [externalStateRevision, setExternalStateRevision] = useState11(0);
11776
12011
  const [
11777
12012
  collapsingMinimizedLaunchAnchorKeys,
11778
12013
  setCollapsingMinimizedLaunchAnchorKeys
11779
- ] = useState10(() => /* @__PURE__ */ new Set());
12014
+ ] = useState11(() => /* @__PURE__ */ new Set());
11780
12015
  const collapsingMinimizedLaunchTimerRef = useRef11(
11781
12016
  /* @__PURE__ */ new Map()
11782
12017
  );
@@ -11803,7 +12038,7 @@ function WorkbenchHostDock({
11803
12038
  labelTooltipOpenTimerRef.current = null;
11804
12039
  labelTooltipScheduledPointRef.current = null;
11805
12040
  }, []);
11806
- useEffect12(
12041
+ useEffect13(
11807
12042
  () => () => {
11808
12043
  clearHoverPanelCloseTimer();
11809
12044
  clearHoverPanelOpenTimer();
@@ -11897,7 +12132,7 @@ function WorkbenchHostDock({
11897
12132
  pendingDockStateRefreshRef.current = false;
11898
12133
  setDockStateRevision((revision) => revision + 1);
11899
12134
  }, []);
11900
- useEffect12(() => {
12135
+ useEffect13(() => {
11901
12136
  if (!dockStateSource) {
11902
12137
  return void 0;
11903
12138
  }
@@ -12003,10 +12238,10 @@ function WorkbenchHostDock({
12003
12238
  }
12004
12239
  dockMeasureRef.current?.removeAttribute("data-dock-hover-panel-open");
12005
12240
  }, []);
12006
- useEffect12(() => {
12241
+ useEffect13(() => {
12007
12242
  activeHoverPanelRef.current = activeHoverPanel;
12008
12243
  }, [activeHoverPanel]);
12009
- useEffect12(() => {
12244
+ useEffect13(() => {
12010
12245
  activeLabelTooltipRef.current = activeLabelTooltip;
12011
12246
  }, [activeLabelTooltip]);
12012
12247
  const closeLabelTooltipImmediate = useCallback13(
@@ -12478,7 +12713,7 @@ function WorkbenchHostDock({
12478
12713
  })()
12479
12714
  )
12480
12715
  );
12481
- useEffect12(() => {
12716
+ useEffect13(() => {
12482
12717
  const shouldSubscribe = activePopup !== null || hasMinimizedPreviewCapture;
12483
12718
  if (!shouldSubscribe || !externalStateSource?.subscribeNodeState) {
12484
12719
  return void 0;
@@ -12503,7 +12738,7 @@ function WorkbenchHostDock({
12503
12738
  hasMinimizedPreviewCapture,
12504
12739
  workspaceId
12505
12740
  ]);
12506
- useEffect12(() => {
12741
+ useEffect13(() => {
12507
12742
  const nextAttentionIds = /* @__PURE__ */ new Set();
12508
12743
  for (const entry of renderedDockEntries) {
12509
12744
  const nextToken = entry.attentionToken ?? null;
@@ -12544,7 +12779,7 @@ function WorkbenchHostDock({
12544
12779
  );
12545
12780
  }
12546
12781
  }, [renderedDockEntries]);
12547
- useEffect12(
12782
+ useEffect13(
12548
12783
  () => () => {
12549
12784
  for (const timeout of attentionTimeouts.current.values()) {
12550
12785
  globalThis.clearTimeout(timeout);
@@ -13613,7 +13848,12 @@ function WorkbenchHostDock({
13613
13848
  placement: dockPlacement,
13614
13849
  debugDiagnostics,
13615
13850
  capturePreview: async (item) => {
13616
- const src = await captureMinimizedNodePreview(item.node);
13851
+ const src = await resolveWorkbenchMinimizedDockPreviewImage({
13852
+ capturePreview: () => captureMinimizedNodePreview(item.node),
13853
+ dockPreviewCache,
13854
+ node: item.node,
13855
+ workspaceId
13856
+ });
13617
13857
  return src ? { kind: "image", src } : null;
13618
13858
  },
13619
13859
  dockPreviewCache,
@@ -13632,10 +13872,10 @@ function WorkbenchHostDock({
13632
13872
  isMinimized: true,
13633
13873
  node,
13634
13874
  preview: minimizedDock?.kind === "component" ? provideMinimizedNodePreview(node) ?? null : minimizedDock?.kind === "snapshot" && minimizedDock.capturePreview ? null : (() => {
13635
- const previewImageUrl = readCachedWorkbenchNodePreviewImage(node.id);
13875
+ const previewImageUrl = readWorkbenchMinimizedDockPreviewImage(node.id);
13636
13876
  return previewImageUrl ? { kind: "image", src: previewImageUrl } : null;
13637
13877
  })(),
13638
- previewRevision: null,
13878
+ previewRevision: resolveWorkbenchMinimizedDockPreviewRevision(node),
13639
13879
  subtitle: node.data.instanceKey ?? node.data.instanceId,
13640
13880
  title: node.title
13641
13881
  };
@@ -13706,115 +13946,14 @@ function WorkbenchHostDockMinimizedNodePreview({
13706
13946
  providePreview,
13707
13947
  workspaceId
13708
13948
  }) {
13709
- const [componentPreview, setComponentPreview] = useState10(void 0);
13710
- const [previewImageUrl, setPreviewImageUrl] = useState10(
13711
- () => deferPreview ? null : readCachedWorkbenchNodePreviewImage(node.id)
13712
- );
13713
- useEffect12(() => {
13714
- if (deferPreview || !providePreview || componentPreview !== void 0 || previewImageUrl) {
13715
- return void 0;
13716
- }
13717
- let cancelled = false;
13718
- let frameId = null;
13719
- let idleId = null;
13720
- let timeoutId = null;
13721
- const setDeferredComponentPreview = () => {
13722
- if (cancelled) {
13723
- return;
13724
- }
13725
- setComponentPreview(providePreview(node) ?? null);
13726
- };
13727
- const scheduler = globalThis;
13728
- if (typeof scheduler.requestIdleCallback === "function") {
13729
- idleId = scheduler.requestIdleCallback(setDeferredComponentPreview, {
13730
- timeout: 250
13731
- });
13732
- } else if (typeof scheduler.requestAnimationFrame === "function") {
13733
- frameId = scheduler.requestAnimationFrame(() => {
13734
- frameId = null;
13735
- timeoutId = globalThis.setTimeout(setDeferredComponentPreview, 0);
13736
- });
13737
- } else {
13738
- timeoutId = globalThis.setTimeout(setDeferredComponentPreview, 0);
13739
- }
13740
- return () => {
13741
- cancelled = true;
13742
- if (idleId !== null && typeof scheduler.cancelIdleCallback === "function") {
13743
- scheduler.cancelIdleCallback(idleId);
13744
- }
13745
- if (frameId !== null && typeof scheduler.cancelAnimationFrame === "function") {
13746
- scheduler.cancelAnimationFrame(frameId);
13747
- }
13748
- if (timeoutId !== null) {
13749
- globalThis.clearTimeout(timeoutId);
13750
- }
13751
- };
13752
- }, [
13753
- componentPreview,
13754
- deferPreview,
13755
- node.data.instanceId,
13756
- node.data.instanceKey,
13757
- node.data.typeId,
13758
- node.id,
13759
- node.minimizedAtUnixMs,
13760
- previewImageUrl,
13761
- providePreview
13762
- ]);
13763
- useEffect12(() => {
13764
- if (deferPreview) {
13765
- return void 0;
13766
- }
13767
- let cancelled = false;
13768
- const cachedPreviewImageUrl = readCachedWorkbenchNodePreviewImage(node.id);
13769
- setPreviewImageUrl(cachedPreviewImageUrl);
13770
- const cacheKey = resolveDockPreviewCacheKey(workspaceId, node);
13771
- if (!cachedPreviewImageUrl && dockPreviewCache) {
13772
- void dockPreviewCache.read(cacheKey).catch(() => null).then((persistedPreview) => {
13773
- if (cancelled || !persistedPreview) {
13774
- return;
13775
- }
13776
- writeCachedWorkbenchNodePreviewImage(node.id, persistedPreview);
13777
- setPreviewImageUrl(persistedPreview);
13778
- });
13779
- }
13780
- if (capturePreview) {
13781
- void Promise.resolve(capturePreview(node)).catch(() => null).then((nextPreview) => {
13782
- if (cancelled || !nextPreview) {
13783
- return;
13784
- }
13785
- writeCachedWorkbenchNodePreviewImage(node.id, nextPreview);
13786
- dockPreviewCache?.write({
13787
- key: cacheKey,
13788
- previewImageUrl: nextPreview
13789
- });
13790
- setPreviewImageUrl(nextPreview);
13791
- });
13792
- }
13793
- if (cachedPreviewImageUrl || node.isMinimized || capturePreview) {
13794
- return () => {
13795
- cancelled = true;
13796
- };
13797
- }
13798
- void captureWorkbenchNodePreviewImage(node.id).then((nextPreview) => {
13799
- if (!cancelled) {
13800
- setPreviewImageUrl(nextPreview);
13801
- }
13802
- });
13803
- return () => {
13804
- cancelled = true;
13805
- };
13806
- }, [
13949
+ const { componentPreview, previewImageUrl } = useWorkbenchMinimizedDockPreview({
13807
13950
  capturePreview,
13808
13951
  deferPreview,
13809
13952
  dockPreviewCache,
13810
- node.data.instanceId,
13811
- node.data.instanceKey,
13812
- node.data.typeId,
13813
- node.id,
13814
- node.minimizedAtUnixMs,
13953
+ node,
13815
13954
  providePreview,
13816
13955
  workspaceId
13817
- ]);
13956
+ });
13818
13957
  if (deferPreview) {
13819
13958
  return renderMinimizedDockPreviewPlaceholder(className);
13820
13959
  }
@@ -13895,7 +14034,7 @@ function WorkbenchHostDockFrozenComponentPreview({
13895
14034
  preview
13896
14035
  }) {
13897
14036
  const sourceRef = useRef11(null);
13898
- const [frozenMarkup, setFrozenMarkup] = useState10(null);
14037
+ const [frozenMarkup, setFrozenMarkup] = useState11(null);
13899
14038
  useLayoutEffect7(() => {
13900
14039
  if (frozenMarkup !== null) {
13901
14040
  return;
@@ -14295,7 +14434,7 @@ function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
14295
14434
  latestItemsByKey.current = new Map(items.map((item) => [item.key, item]));
14296
14435
  shouldAnimateMinimizedDockEnterRef.current = shouldAnimateMinimizedDockEnter;
14297
14436
  const itemKeys = items.map((item) => item.key).join("\0");
14298
- const [presentItems, setPresentItems] = useState10(
14437
+ const [presentItems, setPresentItems] = useState11(
14299
14438
  () => items.map((item) => ({
14300
14439
  item,
14301
14440
  key: item.key,
@@ -14303,7 +14442,7 @@ function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
14303
14442
  }))
14304
14443
  );
14305
14444
  const initialized = useRef11(false);
14306
- useEffect12(() => {
14445
+ useEffect13(() => {
14307
14446
  let nextSettleMs = dockPresenceAnimationMs;
14308
14447
  setPresentItems((current) => {
14309
14448
  const filteredItems = resolveDockPresenceItems({
@@ -14341,7 +14480,7 @@ function useDockWallpaperTones({
14341
14480
  elementRefs,
14342
14481
  itemKeys
14343
14482
  }) {
14344
- const [tones, setTones] = useState10(
14483
+ const [tones, setTones] = useState11(
14345
14484
  () => /* @__PURE__ */ new Map()
14346
14485
  );
14347
14486
  useLayoutEffect7(() => {
@@ -14546,7 +14685,7 @@ function useDockBounce(slotRefs) {
14546
14685
  },
14547
14686
  [slotRefs]
14548
14687
  );
14549
- useEffect12(
14688
+ useEffect13(
14550
14689
  () => () => {
14551
14690
  for (const anchorKey of timeoutsRef.current.keys()) {
14552
14691
  clearDockBounce(anchorKey);
@@ -15624,6 +15763,186 @@ function WorkbenchHost({
15624
15763
  // src/host/activations.ts
15625
15764
  var workbenchFocusInputActivationType = "focus-input";
15626
15765
 
15766
+ // src/preview/createWorkbenchNodePreviewCaptureAdapter.ts
15767
+ var defaultCaptureTimeoutMs = 2500;
15768
+ function createWorkbenchNodePreviewCaptureAdapter(options) {
15769
+ const {
15770
+ captureRect,
15771
+ diagnostics,
15772
+ maxHeight,
15773
+ maxWidth,
15774
+ resolveDiagnosticContext,
15775
+ timeoutMs = defaultCaptureTimeoutMs
15776
+ } = options;
15777
+ return async (node) => {
15778
+ const diagnosticContext = resolveDiagnosticContext?.(node) ?? {};
15779
+ const emit = (diagnostic) => {
15780
+ emitDiagnostic(diagnostics, {
15781
+ ...diagnostic,
15782
+ details: { ...diagnosticContext, ...diagnostic.details }
15783
+ });
15784
+ };
15785
+ emit({
15786
+ details: {
15787
+ documentVisibilityState: document.visibilityState,
15788
+ isMinimized: node.isMinimized,
15789
+ nodeId: node.id
15790
+ },
15791
+ event: "capture_requested",
15792
+ level: "debug"
15793
+ });
15794
+ const skip = resolveCaptureTarget(node);
15795
+ if (skip.status === "skipped") {
15796
+ emit({
15797
+ details: {
15798
+ ...skip.details,
15799
+ nodeId: node.id,
15800
+ reason: skip.reason
15801
+ },
15802
+ event: "capture_skipped",
15803
+ level: skip.level
15804
+ });
15805
+ return null;
15806
+ }
15807
+ const rect = skip.target.getBoundingClientRect();
15808
+ if (!isUsableCaptureRect(rect)) {
15809
+ emit({
15810
+ details: {
15811
+ nodeId: node.id,
15812
+ reason: "capture_rect_invalid",
15813
+ rect: captureRectDetails(rect),
15814
+ viewport: { height: window.innerHeight, width: window.innerWidth }
15815
+ },
15816
+ event: "capture_skipped",
15817
+ level: "warn"
15818
+ });
15819
+ return null;
15820
+ }
15821
+ const input = {
15822
+ maxHeight,
15823
+ maxWidth,
15824
+ nodeId: node.id,
15825
+ rect: captureRectDetails(rect)
15826
+ };
15827
+ emit({
15828
+ details: { ...input, timeoutMs },
15829
+ event: "capture_started",
15830
+ level: "info"
15831
+ });
15832
+ const startedAt = performance.now();
15833
+ const capturePromise = Promise.resolve().then(() => captureRect(input));
15834
+ const outcome = await resolveCaptureOutcome(capturePromise, timeoutMs);
15835
+ if (outcome.status === "timed_out") {
15836
+ emit({
15837
+ details: {
15838
+ durationMs: Math.round(performance.now() - startedAt),
15839
+ nodeId: node.id,
15840
+ timeoutMs
15841
+ },
15842
+ event: "capture_timed_out",
15843
+ level: "warn"
15844
+ });
15845
+ return null;
15846
+ }
15847
+ if (outcome.status === "failed") {
15848
+ emit({
15849
+ details: {
15850
+ durationMs: Math.round(performance.now() - startedAt),
15851
+ error: serializeError(outcome.error),
15852
+ nodeId: node.id
15853
+ },
15854
+ event: "capture_failed",
15855
+ level: "warn"
15856
+ });
15857
+ return null;
15858
+ }
15859
+ emit({
15860
+ details: {
15861
+ dockPreviewImageUrlLength: outcome.previewImages?.dockPreviewImageUrl?.length ?? 0,
15862
+ durationMs: Math.round(performance.now() - startedAt),
15863
+ genieImageUrlLength: outcome.previewImages?.genieImageUrl?.length ?? 0,
15864
+ hasResult: outcome.previewImages !== null,
15865
+ nodeId: node.id
15866
+ },
15867
+ event: "capture_resolved",
15868
+ level: outcome.previewImages ? "info" : "warn"
15869
+ });
15870
+ return outcome.previewImages;
15871
+ };
15872
+ }
15873
+ function resolveCaptureTarget(node) {
15874
+ if (node.isMinimized) {
15875
+ return { level: "debug", reason: "node_minimized", status: "skipped" };
15876
+ }
15877
+ if (document.visibilityState !== "visible") {
15878
+ return {
15879
+ details: { documentVisibilityState: document.visibilityState },
15880
+ level: "debug",
15881
+ reason: "document_not_visible",
15882
+ status: "skipped"
15883
+ };
15884
+ }
15885
+ const windowElement = Array.from(
15886
+ document.querySelectorAll("[data-workbench-window-id]")
15887
+ ).find((candidate) => candidate.dataset.workbenchWindowId === node.id) ?? null;
15888
+ if (!windowElement) {
15889
+ return {
15890
+ level: "warn",
15891
+ reason: "window_element_missing",
15892
+ status: "skipped"
15893
+ };
15894
+ }
15895
+ if (windowElement.dataset.focused !== "true") {
15896
+ return {
15897
+ details: { focused: windowElement.dataset.focused ?? null },
15898
+ level: "debug",
15899
+ reason: "window_not_focused",
15900
+ status: "skipped"
15901
+ };
15902
+ }
15903
+ return {
15904
+ status: "ready",
15905
+ target: windowElement.querySelector(
15906
+ '[data-workbench-window-capture="true"]'
15907
+ ) ?? windowElement.querySelector(".workbench-window") ?? windowElement
15908
+ };
15909
+ }
15910
+ function isUsableCaptureRect(rect) {
15911
+ return Number.isFinite(rect.left) && Number.isFinite(rect.top) && Number.isFinite(rect.width) && Number.isFinite(rect.height) && rect.width > 0 && rect.height > 0 && rect.left >= 0 && rect.top >= 0 && rect.left + rect.width <= window.innerWidth && rect.top + rect.height <= window.innerHeight;
15912
+ }
15913
+ function captureRectDetails(rect) {
15914
+ return {
15915
+ height: rect.height,
15916
+ width: rect.width,
15917
+ x: rect.left,
15918
+ y: rect.top
15919
+ };
15920
+ }
15921
+ async function resolveCaptureOutcome(capturePromise, timeoutMs) {
15922
+ let timeout;
15923
+ const settledCapture = capturePromise.then(
15924
+ (previewImages) => ({ previewImages, status: "resolved" }),
15925
+ (error) => ({ error, status: "failed" })
15926
+ );
15927
+ const timeoutOutcome = new Promise((resolve) => {
15928
+ timeout = setTimeout(() => resolve({ status: "timed_out" }), timeoutMs);
15929
+ });
15930
+ return Promise.race([settledCapture, timeoutOutcome]).finally(() => {
15931
+ if (timeout) {
15932
+ clearTimeout(timeout);
15933
+ }
15934
+ });
15935
+ }
15936
+ function emitDiagnostic(diagnostics, diagnostic) {
15937
+ try {
15938
+ diagnostics?.(diagnostic);
15939
+ } catch {
15940
+ }
15941
+ }
15942
+ function serializeError(error) {
15943
+ return error instanceof Error ? error.message : String(error);
15944
+ }
15945
+
15627
15946
  // src/react/WorkbenchDockComponentPreviewFrame.tsx
15628
15947
  import { jsx as jsx17 } from "react/jsx-runtime";
15629
15948
  function WorkbenchDockComponentPreviewFrame({
@@ -15849,6 +16168,7 @@ export {
15849
16168
  createWorkbenchHostProjectedNodeId,
15850
16169
  createWorkbenchNode,
15851
16170
  createWorkbenchNodeFromSnapshot,
16171
+ createWorkbenchNodePreviewCaptureAdapter,
15852
16172
  createWorkbenchSnapshotFromState,
15853
16173
  createWorkbenchSnapshotLayoutBasis,
15854
16174
  createWorkbenchStateFromSnapshot,