@tutti-os/workbench-surface 0.0.21 → 0.0.22

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
@@ -2687,9 +2687,11 @@ function cloneMeaningfulGenieElement(source, rootRect) {
2687
2687
  function renderGenieScanlines(context, viewportWidth, viewportHeight, frame) {
2688
2688
  const { direction, dockPoint, texture, textureRect } = frame;
2689
2689
  const progress = clampGenieProgress(frame.progress);
2690
- const textureWidth = Math.max(1, Math.round(textureRect.width));
2691
- const textureHeight = Math.max(1, Math.round(textureRect.height));
2692
- const scanlineStride = resolveGenieScanlineStride(textureHeight);
2690
+ const sourceWidth = Math.max(1, texture.width);
2691
+ const sourceHeight = Math.max(1, texture.height);
2692
+ const destinationWidth = Math.max(1, textureRect.width);
2693
+ const destinationHeight = Math.max(1, textureRect.height);
2694
+ const scanlineStride = resolveGenieScanlineStride(sourceHeight);
2693
2695
  const dirtyRect = resolveGenieDirtyRect({
2694
2696
  dockPoint,
2695
2697
  textureRect,
@@ -2702,10 +2704,10 @@ function renderGenieScanlines(context, viewportWidth, viewportHeight, frame) {
2702
2704
  dirtyRect.width,
2703
2705
  dirtyRect.height
2704
2706
  );
2705
- for (let y = 0; y < textureHeight; y += scanlineStride) {
2706
- const sourceHeight = Math.min(scanlineStride, textureHeight - y);
2707
- const sourceMidY = y + sourceHeight / 2;
2708
- const rowProgress = sourceMidY / textureHeight;
2707
+ for (let y = 0; y < sourceHeight; y += scanlineStride) {
2708
+ const sourceSliceHeight = Math.min(scanlineStride, sourceHeight - y);
2709
+ const sourceMidY = y + sourceSliceHeight / 2;
2710
+ const rowProgress = sourceMidY / sourceHeight;
2709
2711
  const horizontalProgress = resolveGenieRowProgress({
2710
2712
  direction,
2711
2713
  progress,
@@ -2715,28 +2717,30 @@ function renderGenieScanlines(context, viewportWidth, viewportHeight, frame) {
2715
2717
  const horizontalEase = easeInOutCubic(horizontalProgress);
2716
2718
  const left = direction === "minimize" ? lerpGenieValue(textureRect.left, dockPoint.x, horizontalEase) : lerpGenieValue(dockPoint.x, textureRect.left, horizontalEase);
2717
2719
  const right = direction === "minimize" ? lerpGenieValue(
2718
- textureRect.left + textureWidth,
2720
+ textureRect.left + destinationWidth,
2719
2721
  dockPoint.x,
2720
2722
  horizontalEase
2721
2723
  ) : lerpGenieValue(
2722
2724
  dockPoint.x,
2723
- textureRect.left + textureWidth,
2725
+ textureRect.left + destinationWidth,
2724
2726
  horizontalEase
2725
2727
  );
2728
+ const destinationSourceTop = y / sourceHeight * destinationHeight;
2729
+ const destinationSourceBottom = (y + sourceSliceHeight) / sourceHeight * destinationHeight;
2726
2730
  const targetTop = resolveGenieRowTargetY({
2727
2731
  direction,
2728
2732
  dockPoint,
2729
2733
  progress,
2730
- sourceY: y,
2731
- textureHeight,
2734
+ sourceY: destinationSourceTop,
2735
+ textureHeight: destinationHeight,
2732
2736
  textureRect
2733
2737
  });
2734
2738
  const targetBottom = resolveGenieRowTargetY({
2735
2739
  direction,
2736
2740
  dockPoint,
2737
2741
  progress,
2738
- sourceY: y + sourceHeight,
2739
- textureHeight,
2742
+ sourceY: destinationSourceBottom,
2743
+ textureHeight: destinationHeight,
2740
2744
  textureRect
2741
2745
  });
2742
2746
  const targetY = Math.min(targetTop, targetBottom) - 0.5;
@@ -2749,8 +2753,8 @@ function renderGenieScanlines(context, viewportWidth, viewportHeight, frame) {
2749
2753
  texture,
2750
2754
  0,
2751
2755
  y,
2752
- textureWidth,
2753
- sourceHeight,
2756
+ sourceWidth,
2757
+ sourceSliceHeight,
2754
2758
  left,
2755
2759
  targetY,
2756
2760
  rowWidth,
@@ -2782,12 +2786,14 @@ function renderGenieScanlines(context, viewportWidth, viewportHeight, frame) {
2782
2786
  }
2783
2787
 
2784
2788
  // src/react/useWorkbenchGenieAnimation.tsx
2785
- import { jsx as jsx7 } from "react/jsx-runtime";
2789
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2786
2790
  var genieDurationMs = 400;
2787
2791
  var scaleMinimizeDurationMs = 220;
2788
2792
  var genieMaxDevicePixelRatio = 2;
2789
2793
  var genieSnapshotScale = 1;
2794
+ var renderedGeniePreviewHeaderOffsetPx = 40;
2790
2795
  var minimizedDockSlotEnterAnimationMs = 720;
2796
+ var dockAnchorResolveMaxAnimationFrames = 3;
2791
2797
  var dockPreviewMaxWidth = 260;
2792
2798
  var dockPreviewMaxHeight = 170;
2793
2799
  var dockPreviewImageCacheMaxEntries = 96;
@@ -2809,6 +2815,37 @@ function waitForNextAnimationFrame() {
2809
2815
  function isFocusedWorkbenchNode(controller, nodeID) {
2810
2816
  return controller.getSnapshot().nodeStack.at(-1) === nodeID;
2811
2817
  }
2818
+ function isEmptyGeniePreview(preview) {
2819
+ return preview === null || preview === void 0 || preview === false;
2820
+ }
2821
+ function logWorkbenchGenieDiagnostic(debugDiagnostics, event, details, level = "info") {
2822
+ if (!debugDiagnostics?.isEnabled() || !debugDiagnostics.log) {
2823
+ return;
2824
+ }
2825
+ void Promise.resolve(
2826
+ debugDiagnostics.log({
2827
+ details,
2828
+ event,
2829
+ level,
2830
+ source: "workbench-genie"
2831
+ })
2832
+ ).catch(() => void 0);
2833
+ }
2834
+ function describeGenieNode(node) {
2835
+ if (!node) {
2836
+ return {};
2837
+ }
2838
+ const data = node.data && typeof node.data === "object" ? node.data : {};
2839
+ return {
2840
+ displayMode: node.displayMode,
2841
+ instanceId: typeof data.instanceId === "string" ? data.instanceId : null,
2842
+ isMinimized: node.isMinimized,
2843
+ minimizedAtUnixMs: node.minimizedAtUnixMs,
2844
+ nodeId: node.id,
2845
+ title: node.title,
2846
+ typeId: typeof data.typeId === "string" ? data.typeId : null
2847
+ };
2848
+ }
2812
2849
  function shouldReduceMotion() {
2813
2850
  return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
2814
2851
  }
@@ -2974,6 +3011,25 @@ function prepareElementTextureCapture(element) {
2974
3011
  rect: windowRect
2975
3012
  };
2976
3013
  }
3014
+ function prepareRenderedGeniePreviewCloneForTexture(clone, textureRect) {
3015
+ clone.style.width = `${textureRect.width}px`;
3016
+ clone.style.height = `${textureRect.height}px`;
3017
+ const previewElement = clone.querySelector(
3018
+ ".workbench-genie-preview-capture__preview"
3019
+ );
3020
+ if (!previewElement) {
3021
+ return;
3022
+ }
3023
+ previewElement.style.display = "block";
3024
+ previewElement.style.width = `${textureRect.width}px`;
3025
+ previewElement.style.height = `${textureRect.height}px`;
3026
+ previewElement.style.padding = "0";
3027
+ previewElement.style.border = "0";
3028
+ previewElement.style.borderRadius = "0";
3029
+ previewElement.style.background = "var(--background-panel)";
3030
+ previewElement.style.boxShadow = "none";
3031
+ previewElement.style.transform = `translateY(${renderedGeniePreviewHeaderOffsetPx}px)`;
3032
+ }
2977
3033
  async function renderPreparedElementTexture({
2978
3034
  clone,
2979
3035
  images,
@@ -2992,6 +3048,8 @@ async function renderPreparedElementTexture({
2992
3048
  if (!context) {
2993
3049
  return null;
2994
3050
  }
3051
+ context.imageSmoothingEnabled = true;
3052
+ context.imageSmoothingQuality = "high";
2995
3053
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
2996
3054
  return { canvas, rect };
2997
3055
  }
@@ -3018,6 +3076,28 @@ function createDockPreviewDataUrl(canvas) {
3018
3076
  context.drawImage(canvas, 0, 0, output.width, output.height);
3019
3077
  return output.toDataURL("image/png");
3020
3078
  }
3079
+ async function renderPreviewImageTexture({
3080
+ previewImageUrl,
3081
+ rect
3082
+ }) {
3083
+ if (!isUsableGenieRect(rect)) {
3084
+ return null;
3085
+ }
3086
+ const image = new Image();
3087
+ image.src = previewImageUrl;
3088
+ await image.decode();
3089
+ const canvas = document.createElement("canvas");
3090
+ canvas.width = Math.max(1, Math.round(rect.width * genieSnapshotScale));
3091
+ canvas.height = Math.max(1, Math.round(rect.height * genieSnapshotScale));
3092
+ const context = canvas.getContext("2d");
3093
+ if (!context) {
3094
+ return null;
3095
+ }
3096
+ context.imageSmoothingEnabled = true;
3097
+ context.imageSmoothingQuality = "high";
3098
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
3099
+ return { canvas, rect };
3100
+ }
3021
3101
  function readDockAnchorFallbackSize(element) {
3022
3102
  const cssValue = window.getComputedStyle(element).getPropertyValue("--desktop-dock-size");
3023
3103
  const parsed = Number.parseFloat(cssValue);
@@ -3123,8 +3203,10 @@ function createGenieSvgTexture(element, rect) {
3123
3203
  function useWorkbenchGenieAnimation({
3124
3204
  captureNodePreviewImage,
3125
3205
  controller,
3206
+ debugDiagnostics,
3126
3207
  dockPreviewCache,
3127
3208
  minimizeAnimation = "genie",
3209
+ renderNodeGeniePreview,
3128
3210
  resolveDockAnchorKey,
3129
3211
  resolveDockPreviewCacheKey: resolveDockPreviewCacheKey2,
3130
3212
  shouldCaptureNodePreviewImage
@@ -3138,11 +3220,18 @@ function useWorkbenchGenieAnimation({
3138
3220
  const minimizedDockEnterAnimationTimersRef = useRef4(
3139
3221
  /* @__PURE__ */ new Map()
3140
3222
  );
3223
+ const minimizedGenieTextureByNodeIDRef = useRef4(
3224
+ /* @__PURE__ */ new Map()
3225
+ );
3226
+ const renderedPreviewCaptureIDRef = useRef4(0);
3227
+ const renderedPreviewCaptureElementRef = useRef4(null);
3228
+ const pendingRenderedPreviewCaptureRef = useRef4(null);
3141
3229
  const [isCanvasActive, setIsCanvasActive] = useState4(false);
3142
3230
  const [genieHiddenNodeIDs, setGenieHiddenNodeIDs] = useState4(
3143
3231
  () => /* @__PURE__ */ new Set()
3144
3232
  );
3145
3233
  const [pendingMinimizedNode, setPendingMinimizedNode] = useState4(null);
3234
+ const [pendingRenderedPreviewCapture, setPendingRenderedPreviewCapture] = useState4(null);
3146
3235
  const registerDockAnchor = useCallback6(
3147
3236
  (anchorKey, element) => {
3148
3237
  if (element) {
@@ -3155,8 +3244,25 @@ function useWorkbenchGenieAnimation({
3155
3244
  );
3156
3245
  const resolveDockAnchorRect = useCallback6((anchorKey) => {
3157
3246
  const element = dockAnchorElementsRef.current.get(anchorKey) ?? null;
3158
- return element ? resolveDockAnchorViewportRect(element) : null;
3247
+ if (!element) {
3248
+ return null;
3249
+ }
3250
+ return resolveDockAnchorViewportRect(element);
3159
3251
  }, []);
3252
+ const resolveDockAnchorRectAfterRender = useCallback6(
3253
+ async (anchorKey, isCurrent) => {
3254
+ let dockRect = resolveDockAnchorRect(anchorKey);
3255
+ for (let frame = 0; (!dockRect || !isUsableGenieRect(dockRect)) && frame < dockAnchorResolveMaxAnimationFrames; frame += 1) {
3256
+ await waitForNextAnimationFrame();
3257
+ if (!isCurrent()) {
3258
+ return null;
3259
+ }
3260
+ dockRect = resolveDockAnchorRect(anchorKey);
3261
+ }
3262
+ return dockRect;
3263
+ },
3264
+ [resolveDockAnchorRect]
3265
+ );
3160
3266
  const resolveNodeElement = useCallback6((nodeID) => {
3161
3267
  return Array.from(
3162
3268
  document.querySelectorAll("[data-workbench-window-id]")
@@ -3200,6 +3306,36 @@ function useWorkbenchGenieAnimation({
3200
3306
  const shouldAnimateMinimizedDockEnter = useCallback6((nodeID) => {
3201
3307
  return minimizedDockEnterAnimationNodeIdsRef.current.has(nodeID);
3202
3308
  }, []);
3309
+ const readMinimizedGenieTexture = useCallback6((nodeID) => {
3310
+ return minimizedGenieTextureByNodeIDRef.current.get(nodeID) ?? null;
3311
+ }, []);
3312
+ const writeMinimizedGenieTexture = useCallback6(
3313
+ (nodeID, texture) => {
3314
+ const cache = minimizedGenieTextureByNodeIDRef.current;
3315
+ cache.delete(nodeID);
3316
+ cache.set(nodeID, texture);
3317
+ },
3318
+ []
3319
+ );
3320
+ const clearMinimizedGenieTexture = useCallback6((nodeID) => {
3321
+ minimizedGenieTextureByNodeIDRef.current.delete(nodeID);
3322
+ }, []);
3323
+ const pruneMinimizedGenieTextures = useCallback6(
3324
+ (retainNodeID) => {
3325
+ const minimizedNodeIDs = new Set(
3326
+ controller.getSnapshot().nodes.filter((node) => node.isMinimized === true).map((node) => node.id)
3327
+ );
3328
+ if (retainNodeID) {
3329
+ minimizedNodeIDs.add(retainNodeID);
3330
+ }
3331
+ for (const nodeID of minimizedGenieTextureByNodeIDRef.current.keys()) {
3332
+ if (!minimizedNodeIDs.has(nodeID)) {
3333
+ minimizedGenieTextureByNodeIDRef.current.delete(nodeID);
3334
+ }
3335
+ }
3336
+ },
3337
+ [controller]
3338
+ );
3203
3339
  const hideNodeForGenie = useCallback6((nodeID) => {
3204
3340
  setGenieHiddenNodeIDs((current) => {
3205
3341
  if (current.has(nodeID)) {
@@ -3223,6 +3359,81 @@ function useWorkbenchGenieAnimation({
3223
3359
  (current) => current?.id === nodeID ? null : current
3224
3360
  );
3225
3361
  }, []);
3362
+ const clearRenderedPreviewCapture = useCallback6((id) => {
3363
+ if (pendingRenderedPreviewCaptureRef.current?.id !== id) {
3364
+ return;
3365
+ }
3366
+ pendingRenderedPreviewCaptureRef.current = null;
3367
+ renderedPreviewCaptureElementRef.current = null;
3368
+ setPendingRenderedPreviewCapture(null);
3369
+ }, []);
3370
+ const requestRenderedGeniePreviewTexture = useCallback6(
3371
+ async ({
3372
+ node,
3373
+ textureRect
3374
+ }) => {
3375
+ if (!isUsableGenieRect(textureRect)) {
3376
+ return null;
3377
+ }
3378
+ const previewViewport = {
3379
+ height: textureRect.height,
3380
+ width: textureRect.width
3381
+ };
3382
+ let preview = null;
3383
+ try {
3384
+ preview = renderNodeGeniePreview?.(node, {
3385
+ previewViewport
3386
+ }) ?? null;
3387
+ } catch {
3388
+ preview = null;
3389
+ }
3390
+ if (isEmptyGeniePreview(preview)) {
3391
+ return null;
3392
+ }
3393
+ const captureID = renderedPreviewCaptureIDRef.current + 1;
3394
+ renderedPreviewCaptureIDRef.current = captureID;
3395
+ const pendingCapture = {
3396
+ id: captureID,
3397
+ nodeID: node.id,
3398
+ preview,
3399
+ rect: textureRect,
3400
+ usedFallbackPreview: false
3401
+ };
3402
+ pendingRenderedPreviewCaptureRef.current = pendingCapture;
3403
+ flushSync(() => {
3404
+ setPendingRenderedPreviewCapture(pendingCapture);
3405
+ });
3406
+ try {
3407
+ await waitForNextAnimationFrame();
3408
+ await waitForNextAnimationFrame();
3409
+ if (pendingRenderedPreviewCaptureRef.current?.id !== captureID) {
3410
+ return null;
3411
+ }
3412
+ const element = renderedPreviewCaptureElementRef.current;
3413
+ if (!element) {
3414
+ return null;
3415
+ }
3416
+ const preparedCapture = prepareElementTextureCapture(element);
3417
+ if (!preparedCapture) {
3418
+ return null;
3419
+ }
3420
+ prepareRenderedGeniePreviewCloneForTexture(
3421
+ preparedCapture.clone,
3422
+ textureRect
3423
+ );
3424
+ const texture = await renderPreparedElementTexture({
3425
+ ...preparedCapture,
3426
+ rect: textureRect
3427
+ });
3428
+ return texture;
3429
+ } catch {
3430
+ return null;
3431
+ } finally {
3432
+ clearRenderedPreviewCapture(captureID);
3433
+ }
3434
+ },
3435
+ [clearRenderedPreviewCapture, renderNodeGeniePreview]
3436
+ );
3226
3437
  const setupCanvas = useCallback6(() => {
3227
3438
  const canvas = canvasRef.current;
3228
3439
  if (!canvas) {
@@ -3428,11 +3639,12 @@ function useWorkbenchGenieAnimation({
3428
3639
  );
3429
3640
  useEffect3(() => () => stopAnimation(false), [stopAnimation]);
3430
3641
  const startOpenOrRestoreAnimation = useCallback6(
3431
- async (nodeID, anchorKey, generation, dockRectFallback) => {
3642
+ async (nodeID, anchorKey, generation, dockRectFallback, minimizedNode) => {
3432
3643
  const effectiveMinimizeAnimation = shouldReduceMotion() ? "off" : minimizeAnimation;
3433
3644
  if (effectiveMinimizeAnimation === "off") {
3434
3645
  animationCleanupRef.current = null;
3435
3646
  showNodeForGenie(nodeID);
3647
+ clearMinimizedGenieTexture(nodeID);
3436
3648
  return;
3437
3649
  }
3438
3650
  await waitForNextAnimationFrame();
@@ -3446,6 +3658,7 @@ function useWorkbenchGenieAnimation({
3446
3658
  if (!dockRect || !nodeElement || !isUsableGenieRect(dockRect)) {
3447
3659
  animationCleanupRef.current = null;
3448
3660
  showNodeForGenie(nodeID);
3661
+ clearMinimizedGenieTexture(nodeID);
3449
3662
  return;
3450
3663
  }
3451
3664
  flushSync(() => {
@@ -3459,30 +3672,48 @@ function useWorkbenchGenieAnimation({
3459
3672
  flushSync(() => {
3460
3673
  showNodeForGenie(nodeID);
3461
3674
  });
3675
+ clearMinimizedGenieTexture(nodeID);
3462
3676
  },
3463
3677
  onComplete: () => {
3464
3678
  flushSync(() => {
3465
3679
  showNodeForGenie(nodeID);
3466
3680
  });
3681
+ clearMinimizedGenieTexture(nodeID);
3467
3682
  }
3468
3683
  });
3469
3684
  return;
3470
3685
  }
3471
- const captureTarget = nodeElement ? resolveWorkbenchCaptureElement(nodeElement) : null;
3472
- if (!dockRect || !nodeElement || !captureTarget || !isUsableGenieRect(dockRect)) {
3686
+ if (!dockRect || !isUsableGenieRect(dockRect)) {
3473
3687
  animationCleanupRef.current = null;
3474
3688
  showNodeForGenie(nodeID);
3689
+ clearMinimizedGenieTexture(nodeID);
3475
3690
  return;
3476
3691
  }
3477
- const texture = await captureElementTexture(captureTarget).catch(
3478
- () => null
3479
- );
3692
+ const cachedTexture = readMinimizedGenieTexture(nodeID);
3693
+ const restoredWindowRect = nodeElement ? viewportRectFromElement(nodeElement) : null;
3694
+ const shouldRestoreFromRenderedPreview = minimizedNode !== null && (shouldCaptureNodePreviewImage?.(minimizedNode) ?? true) === false;
3695
+ const renderedPreviewTexture = cachedTexture || !shouldRestoreFromRenderedPreview || !restoredWindowRect || !isUsableGenieRect(restoredWindowRect) ? null : await requestRenderedGeniePreviewTexture({
3696
+ node: minimizedNode,
3697
+ textureRect: restoredWindowRect
3698
+ }).catch(() => null);
3699
+ if (generation !== animationGenerationRef.current) {
3700
+ return;
3701
+ }
3702
+ const captureTarget = cachedTexture || renderedPreviewTexture || !nodeElement ? null : resolveWorkbenchCaptureElement(nodeElement);
3703
+ if (!cachedTexture && !renderedPreviewTexture && !captureTarget) {
3704
+ animationCleanupRef.current = null;
3705
+ showNodeForGenie(nodeID);
3706
+ clearMinimizedGenieTexture(nodeID);
3707
+ return;
3708
+ }
3709
+ const texture = cachedTexture ?? renderedPreviewTexture ?? (captureTarget ? await captureElementTexture(captureTarget).catch(() => null) : null);
3480
3710
  if (generation !== animationGenerationRef.current) {
3481
3711
  return;
3482
3712
  }
3483
3713
  if (!texture) {
3484
3714
  animationCleanupRef.current = null;
3485
3715
  showNodeForGenie(nodeID);
3716
+ clearMinimizedGenieTexture(nodeID);
3486
3717
  return;
3487
3718
  }
3488
3719
  runGenieAnimation({
@@ -3492,12 +3723,14 @@ function useWorkbenchGenieAnimation({
3492
3723
  flushSync(() => {
3493
3724
  showNodeForGenie(nodeID);
3494
3725
  });
3726
+ clearMinimizedGenieTexture(nodeID);
3495
3727
  clearCanvas();
3496
3728
  },
3497
3729
  onComplete: () => {
3498
3730
  flushSync(() => {
3499
3731
  showNodeForGenie(nodeID);
3500
3732
  });
3733
+ clearMinimizedGenieTexture(nodeID);
3501
3734
  clearCanvas();
3502
3735
  },
3503
3736
  skipStop: true,
@@ -3506,11 +3739,15 @@ function useWorkbenchGenieAnimation({
3506
3739
  },
3507
3740
  [
3508
3741
  clearCanvas,
3742
+ clearMinimizedGenieTexture,
3509
3743
  minimizeAnimation,
3744
+ readMinimizedGenieTexture,
3745
+ requestRenderedGeniePreviewTexture,
3510
3746
  resolveDockAnchorRect,
3511
3747
  resolveNodeElement,
3512
3748
  runGenieAnimation,
3513
3749
  runScaleWindowAnimation,
3750
+ shouldCaptureNodePreviewImage,
3514
3751
  showNodeForGenie
3515
3752
  ]
3516
3753
  );
@@ -3541,24 +3778,39 @@ function useWorkbenchGenieAnimation({
3541
3778
  const dockRectFallback = resolveDockAnchorRect(anchorKey);
3542
3779
  hideNodeForGenie(nodeID);
3543
3780
  animationCleanupRef.current = () => {
3781
+ if (rafRef.current !== null) {
3782
+ window.cancelAnimationFrame(rafRef.current);
3783
+ rafRef.current = null;
3784
+ }
3544
3785
  flushSync(() => {
3545
3786
  showNodeForGenie(nodeID);
3546
3787
  });
3788
+ clearMinimizedGenieTexture(nodeID);
3547
3789
  clearCanvas();
3548
3790
  };
3549
3791
  const generation = animationGenerationRef.current;
3550
- flushSync(() => {
3551
- void launch();
3792
+ rafRef.current = window.requestAnimationFrame(() => {
3793
+ rafRef.current = null;
3794
+ if (generation !== animationGenerationRef.current) {
3795
+ return;
3796
+ }
3797
+ void Promise.resolve(launch()).catch(() => null).then(() => {
3798
+ if (generation !== animationGenerationRef.current) {
3799
+ return;
3800
+ }
3801
+ void startOpenOrRestoreAnimation(
3802
+ nodeID,
3803
+ anchorKey,
3804
+ generation,
3805
+ dockRectFallback,
3806
+ target
3807
+ );
3808
+ });
3552
3809
  });
3553
- void startOpenOrRestoreAnimation(
3554
- nodeID,
3555
- anchorKey,
3556
- generation,
3557
- dockRectFallback
3558
- );
3559
3810
  },
3560
3811
  [
3561
3812
  clearCanvas,
3813
+ clearMinimizedGenieTexture,
3562
3814
  controller,
3563
3815
  hideNodeForGenie,
3564
3816
  minimizeAnimation,
@@ -3571,10 +3823,21 @@ function useWorkbenchGenieAnimation({
3571
3823
  const minimizeNodeToAnchor = useCallback6(
3572
3824
  (nodeID, minimize) => {
3573
3825
  void (async () => {
3826
+ pruneMinimizedGenieTextures();
3574
3827
  const target = controller.getSnapshot().nodes.find((node) => node.id === nodeID);
3575
3828
  if (!target) {
3829
+ logWorkbenchGenieDiagnostic(
3830
+ debugDiagnostics,
3831
+ "workbench.genie.minimize.skipped",
3832
+ {
3833
+ nodeId: nodeID,
3834
+ reason: "target_missing"
3835
+ },
3836
+ "warn"
3837
+ );
3576
3838
  return;
3577
3839
  }
3840
+ clearMinimizedGenieTexture(nodeID);
3578
3841
  const runMinimize = minimize ?? (() => controller.commands.minimizeNode(nodeID));
3579
3842
  const effectiveMinimizeAnimation = shouldReduceMotion() ? "off" : minimizeAnimation;
3580
3843
  const shouldCapturePreview = shouldCaptureNodePreviewImage?.(target) ?? true;
@@ -3635,6 +3898,16 @@ function useWorkbenchGenieAnimation({
3635
3898
  const generation2 = animationGenerationRef.current;
3636
3899
  const nodeElement2 = resolveNodeElement(nodeID);
3637
3900
  if (!nodeElement2) {
3901
+ logWorkbenchGenieDiagnostic(
3902
+ debugDiagnostics,
3903
+ "workbench.genie.minimize.skipped",
3904
+ {
3905
+ ...describeGenieNode(target),
3906
+ mode: "scale",
3907
+ reason: "node_element_missing"
3908
+ },
3909
+ "warn"
3910
+ );
3638
3911
  runMinimize();
3639
3912
  return;
3640
3913
  }
@@ -3685,8 +3958,30 @@ function useWorkbenchGenieAnimation({
3685
3958
  return;
3686
3959
  }
3687
3960
  const anchorKey2 = resolveAnchorKeyForNode(pendingMinimizedNode3);
3688
- const dockRect2 = resolveDockAnchorRect(anchorKey2);
3961
+ const dockRect2 = await resolveDockAnchorRectAfterRender(
3962
+ anchorKey2,
3963
+ () => generation2 === animationGenerationRef.current
3964
+ );
3965
+ if (generation2 !== animationGenerationRef.current) {
3966
+ return;
3967
+ }
3689
3968
  if (!nodeElement2.isConnected || !dockRect2 || !isUsableGenieRect(dockRect2)) {
3969
+ logWorkbenchGenieDiagnostic(
3970
+ debugDiagnostics,
3971
+ "workbench.genie.minimize.skipped",
3972
+ {
3973
+ ...describeGenieNode(target),
3974
+ anchorKey: anchorKey2,
3975
+ dockRect: dockRect2,
3976
+ mode: "scale",
3977
+ nodeElementConnected: nodeElement2.isConnected,
3978
+ registeredDockAnchorKeys: Array.from(
3979
+ dockAnchorElementsRef.current.keys()
3980
+ ).filter((key) => key.startsWith("minimized:")),
3981
+ reason: "dock_rect_unusable"
3982
+ },
3983
+ "warn"
3984
+ );
3690
3985
  cleanupPendingMinimize();
3691
3986
  return;
3692
3987
  }
@@ -3712,18 +4007,87 @@ function useWorkbenchGenieAnimation({
3712
4007
  stopAnimation();
3713
4008
  const generation = animationGenerationRef.current;
3714
4009
  const nodeElement = resolveNodeElement(nodeID);
3715
- const captureTarget = nodeElement ? resolveWorkbenchCaptureElement(nodeElement) : null;
3716
- if (!nodeElement || !captureTarget) {
4010
+ if (!nodeElement) {
4011
+ logWorkbenchGenieDiagnostic(
4012
+ debugDiagnostics,
4013
+ "workbench.genie.minimize.skipped",
4014
+ {
4015
+ ...describeGenieNode(target),
4016
+ mode: "genie",
4017
+ reason: "node_element_missing"
4018
+ },
4019
+ "warn"
4020
+ );
3717
4021
  runMinimize();
3718
4022
  return;
3719
4023
  }
3720
- const preparedTexture = prepareElementTextureCapture(captureTarget);
3721
- if (!preparedTexture) {
4024
+ const windowRect = viewportRectFromElement(nodeElement);
4025
+ if (!isUsableGenieRect(windowRect)) {
4026
+ logWorkbenchGenieDiagnostic(
4027
+ debugDiagnostics,
4028
+ "workbench.genie.minimize.skipped",
4029
+ {
4030
+ ...describeGenieNode(target),
4031
+ mode: "genie",
4032
+ reason: "window_rect_unusable",
4033
+ windowRect
4034
+ },
4035
+ "warn"
4036
+ );
3722
4037
  runMinimize();
3723
4038
  return;
3724
4039
  }
4040
+ const pendingMinimizedNode2 = {
4041
+ ...target,
4042
+ isMinimized: true,
4043
+ minimizedAtUnixMs: Date.now()
4044
+ };
4045
+ const componentPreviewTexture = shouldCapturePreview ? null : await requestRenderedGeniePreviewTexture({
4046
+ node: pendingMinimizedNode2,
4047
+ textureRect: windowRect
4048
+ }).catch(() => null);
4049
+ if (generation !== animationGenerationRef.current) {
4050
+ return;
4051
+ }
4052
+ if (!shouldCapturePreview) {
4053
+ if (!componentPreviewTexture) {
4054
+ logWorkbenchGenieDiagnostic(
4055
+ debugDiagnostics,
4056
+ "workbench.genie.texture.missing",
4057
+ {
4058
+ ...describeGenieNode(target),
4059
+ mode: "genie",
4060
+ reason: "component_preview_texture_missing",
4061
+ shouldCapturePreview,
4062
+ windowRect
4063
+ },
4064
+ "warn"
4065
+ );
4066
+ runMinimize();
4067
+ return;
4068
+ }
4069
+ }
4070
+ const captureTarget = resolveWorkbenchCaptureElement(nodeElement);
4071
+ const preparedTexture = shouldCapturePreview ? prepareElementTextureCapture(captureTarget) : null;
4072
+ if (!preparedTexture) {
4073
+ if (!componentPreviewTexture) {
4074
+ logWorkbenchGenieDiagnostic(
4075
+ debugDiagnostics,
4076
+ "workbench.genie.minimize.skipped",
4077
+ {
4078
+ ...describeGenieNode(target),
4079
+ mode: "genie",
4080
+ reason: "prepared_texture_missing",
4081
+ shouldCapturePreview
4082
+ },
4083
+ "warn"
4084
+ );
4085
+ runMinimize();
4086
+ return;
4087
+ }
4088
+ }
3725
4089
  const wasFocusedForCapture = isFocusedWorkbenchNode(controller, nodeID);
3726
- if (!wasFocusedForCapture) {
4090
+ if (shouldCapturePreview && !wasFocusedForCapture) {
3727
4091
  flushSync(() => {
3728
4092
  controller.commands.focusNode(nodeID);
3729
4093
  });
@@ -3732,17 +4096,35 @@ function useWorkbenchGenieAnimation({
3732
4096
  return;
3733
4097
  }
3734
4098
  }
3735
- const previewImageUrlPromise = Promise.resolve(
3736
- captureNodePreviewImage?.(target) ?? null
3737
- ).catch(() => null);
3738
- const texture = await renderPreparedElementTexture(
3739
- preparedTexture
3740
- ).catch(() => null);
4099
+ const previewImageUrlPromise = shouldCapturePreview ? Promise.resolve(captureNodePreviewImage?.(target) ?? null).catch(
4100
+ () => null
4101
+ ) : Promise.resolve(null);
3741
4102
  const previewImageUrl = await previewImageUrlPromise;
4103
+ const previewImageTexture = shouldCapturePreview && previewImageUrl ? await renderPreviewImageTexture({
4104
+ previewImageUrl,
4105
+ rect: windowRect
4106
+ }).catch(() => null) : null;
4107
+ const texture = componentPreviewTexture ?? previewImageTexture ?? (preparedTexture ? await renderPreparedElementTexture(preparedTexture).catch(
4108
+ () => null
4109
+ ) : null);
3742
4110
  if (generation !== animationGenerationRef.current) {
3743
4111
  return;
3744
4112
  }
3745
4113
  if (!texture) {
4114
+ logWorkbenchGenieDiagnostic(
4115
+ debugDiagnostics,
4116
+ "workbench.genie.minimize.skipped",
4117
+ {
4118
+ ...describeGenieNode(target),
4119
+ hasComponentPreviewTexture: Boolean(componentPreviewTexture),
4120
+ hasPreparedDomTexture: Boolean(preparedTexture),
4121
+ hasPreviewImageTexture: Boolean(previewImageTexture),
4122
+ mode: "genie",
4123
+ reason: "texture_missing",
4124
+ shouldCapturePreview
4125
+ },
4126
+ "warn"
4127
+ );
3746
4128
  if (previewImageUrl) {
3747
4129
  writeCachedWorkbenchNodePreviewImage(nodeID, previewImageUrl);
3748
4130
  persistWorkbenchNodePreviewImage(target, previewImageUrl, {
@@ -3769,6 +4151,8 @@ function useWorkbenchGenieAnimation({
3769
4151
  resolveDockPreviewCacheKey: resolveDockPreviewCacheKey2
3770
4152
  });
3771
4153
  }
4154
+ writeMinimizedGenieTexture(nodeID, texture);
4155
+ pruneMinimizedGenieTextures(nodeID);
3772
4156
  let minimizeCommitted = false;
3773
4157
  const commitMinimize = () => {
3774
4158
  if (minimizeCommitted) {
@@ -3777,11 +4161,6 @@ function useWorkbenchGenieAnimation({
3777
4161
  minimizeCommitted = true;
3778
4162
  runMinimize();
3779
4163
  };
3780
- const pendingMinimizedNode2 = {
3781
- ...target,
3782
- isMinimized: true,
3783
- minimizedAtUnixMs: Date.now()
3784
- };
3785
4164
  const cleanupPendingGenieMinimize = () => {
3786
4165
  releaseMinimizedDockEnterAnimation(nodeID);
3787
4166
  flushSync(() => {
@@ -3795,15 +4174,35 @@ function useWorkbenchGenieAnimation({
3795
4174
  registerMinimizedDockEnterAnimation(nodeID);
3796
4175
  flushSync(() => {
3797
4176
  setPendingMinimizedNode(pendingMinimizedNode2);
3798
- hideNodeForGenie(nodeID);
3799
4177
  });
3800
4178
  await waitForNextAnimationFrame();
3801
4179
  if (generation !== animationGenerationRef.current) {
3802
4180
  return;
3803
4181
  }
3804
4182
  const anchorKey = resolveAnchorKeyForNode(pendingMinimizedNode2);
3805
- const dockRect = resolveDockAnchorRect(anchorKey);
4183
+ const dockRect = await resolveDockAnchorRectAfterRender(
4184
+ anchorKey,
4185
+ () => generation === animationGenerationRef.current
4186
+ );
4187
+ if (generation !== animationGenerationRef.current) {
4188
+ return;
4189
+ }
3806
4190
  if (!dockRect || !isUsableGenieRect(dockRect)) {
4191
+ logWorkbenchGenieDiagnostic(
4192
+ debugDiagnostics,
4193
+ "workbench.genie.minimize.skipped",
4194
+ {
4195
+ ...describeGenieNode(target),
4196
+ anchorKey,
4197
+ dockRect,
4198
+ mode: "genie",
4199
+ registeredDockAnchorKeys: Array.from(
4200
+ dockAnchorElementsRef.current.keys()
4201
+ ).filter((key) => key.startsWith("minimized:")),
4202
+ reason: "dock_rect_unusable"
4203
+ },
4204
+ "warn"
4205
+ );
3807
4206
  animationCleanupRef.current = null;
3808
4207
  cleanupPendingGenieMinimize();
3809
4208
  return;
@@ -3826,18 +4225,25 @@ function useWorkbenchGenieAnimation({
3826
4225
  skipStop: true,
3827
4226
  texture
3828
4227
  });
4228
+ flushSync(() => {
4229
+ hideNodeForGenie(nodeID);
4230
+ });
3829
4231
  })();
3830
4232
  },
3831
4233
  [
3832
4234
  clearCanvas,
4235
+ clearMinimizedGenieTexture,
3833
4236
  controller,
3834
4237
  captureNodePreviewImage,
3835
4238
  clearPendingMinimizedNode,
4239
+ debugDiagnostics,
3836
4240
  dockPreviewCache,
3837
4241
  hideNodeForGenie,
3838
4242
  minimizeAnimation,
4243
+ pruneMinimizedGenieTextures,
3839
4244
  registerMinimizedDockEnterAnimation,
3840
4245
  releaseMinimizedDockEnterAnimation,
4246
+ requestRenderedGeniePreviewTexture,
3841
4247
  resolveAnchorKeyForNode,
3842
4248
  resolveDockPreviewCacheKey2,
3843
4249
  resolveDockAnchorRect,
@@ -3848,7 +4254,8 @@ function useWorkbenchGenieAnimation({
3848
4254
  shouldCaptureNodePreviewImage,
3849
4255
  setupCanvas,
3850
4256
  showNodeForGenie,
3851
- stopAnimation
4257
+ stopAnimation,
4258
+ writeMinimizedGenieTexture
3852
4259
  ]
3853
4260
  );
3854
4261
  useEffect3(
@@ -3858,20 +4265,39 @@ function useWorkbenchGenieAnimation({
3858
4265
  }
3859
4266
  minimizedDockEnterAnimationTimersRef.current.clear();
3860
4267
  minimizedDockEnterAnimationNodeIdsRef.current.clear();
4268
+ minimizedGenieTextureByNodeIDRef.current.clear();
3861
4269
  },
3862
4270
  []
3863
4271
  );
3864
4272
  return {
3865
4273
  genieLayer: typeof document === "undefined" ? null : createPortal(
3866
- /* @__PURE__ */ jsx7(
3867
- "canvas",
3868
- {
3869
- ref: canvasRef,
3870
- className: "workbench-genie-layer",
3871
- "data-workbench-genie-layer-state": isCanvasActive ? "active" : "idle",
3872
- "aria-hidden": true
3873
- }
3874
- ),
4274
+ /* @__PURE__ */ jsxs4(Fragment3, { children: [
4275
+ /* @__PURE__ */ jsx7(
4276
+ "canvas",
4277
+ {
4278
+ ref: canvasRef,
4279
+ className: "workbench-genie-layer",
4280
+ "data-workbench-genie-layer-state": isCanvasActive ? "active" : "idle",
4281
+ "aria-hidden": true
4282
+ }
4283
+ ),
4284
+ pendingRenderedPreviewCapture ? /* @__PURE__ */ jsx7(
4285
+ "div",
4286
+ {
4287
+ ref: renderedPreviewCaptureElementRef,
4288
+ className: "workbench-genie-preview-capture",
4289
+ "data-workbench-genie-preview-capture-id": pendingRenderedPreviewCapture.id,
4290
+ "data-workbench-genie-preview-node-id": pendingRenderedPreviewCapture.nodeID,
4291
+ "data-workbench-genie-preview-used-fallback": pendingRenderedPreviewCapture.usedFallbackPreview ? "true" : "false",
4292
+ style: {
4293
+ height: pendingRenderedPreviewCapture.rect.height,
4294
+ width: pendingRenderedPreviewCapture.rect.width
4295
+ },
4296
+ "aria-hidden": true,
4297
+ children: pendingRenderedPreviewCapture.preview
4298
+ }
4299
+ ) : null
4300
+ ] }),
3875
4301
  document.body
3876
4302
  ),
3877
4303
  isNodeGenieHidden: useCallback6(
@@ -3891,11 +4317,12 @@ function useWorkbenchGenieAnimation({
3891
4317
  }
3892
4318
 
3893
4319
  // src/react/WorkbenchSurface.tsx
3894
- import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
4320
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
3895
4321
  function WorkbenchSurface({
3896
4322
  captureNodePreviewImage,
3897
4323
  className,
3898
4324
  controller,
4325
+ debugDiagnostics,
3899
4326
  dockPreviewCache,
3900
4327
  dockPlacement,
3901
4328
  interactive,
@@ -3907,6 +4334,7 @@ function WorkbenchSurface({
3907
4334
  renderBottomChrome,
3908
4335
  renderDock,
3909
4336
  renderNode,
4337
+ renderNodeGeniePreview,
3910
4338
  renderOverlay,
3911
4339
  renderTopChrome,
3912
4340
  renderWindowActions,
@@ -3926,6 +4354,7 @@ function WorkbenchSurface({
3926
4354
  {
3927
4355
  captureNodePreviewImage,
3928
4356
  className,
4357
+ debugDiagnostics,
3929
4358
  dockPreviewCache,
3930
4359
  dockPlacement,
3931
4360
  interactive,
@@ -3937,6 +4366,7 @@ function WorkbenchSurface({
3937
4366
  renderBottomChrome,
3938
4367
  renderDock,
3939
4368
  renderNode,
4369
+ renderNodeGeniePreview,
3940
4370
  renderOverlay,
3941
4371
  renderTopChrome,
3942
4372
  renderWindowActions,
@@ -3956,6 +4386,7 @@ function WorkbenchSurface({
3956
4386
  function WorkbenchSurfaceInner({
3957
4387
  captureNodePreviewImage,
3958
4388
  className,
4389
+ debugDiagnostics,
3959
4390
  dockPreviewCache,
3960
4391
  dockPlacement,
3961
4392
  interactive = true,
@@ -3967,6 +4398,7 @@ function WorkbenchSurfaceInner({
3967
4398
  renderBottomChrome,
3968
4399
  renderDock,
3969
4400
  renderNode,
4401
+ renderNodeGeniePreview,
3970
4402
  renderOverlay,
3971
4403
  renderTopChrome,
3972
4404
  renderWindowActions,
@@ -3992,8 +4424,10 @@ function WorkbenchSurfaceInner({
3992
4424
  const genie = useWorkbenchGenieAnimation({
3993
4425
  captureNodePreviewImage,
3994
4426
  controller,
4427
+ debugDiagnostics,
3995
4428
  dockPreviewCache,
3996
4429
  minimizeAnimation,
4430
+ renderNodeGeniePreview,
3997
4431
  resolveDockAnchorKey,
3998
4432
  resolveDockPreviewCacheKey: resolveDockPreviewCacheKey2,
3999
4433
  shouldCaptureNodePreviewImage
@@ -4012,7 +4446,7 @@ function WorkbenchSurfaceInner({
4012
4446
  wallpaper.fit ?? "cover"
4013
4447
  )
4014
4448
  } : void 0;
4015
- return /* @__PURE__ */ jsxs4(
4449
+ return /* @__PURE__ */ jsxs5(
4016
4450
  "div",
4017
4451
  {
4018
4452
  ref,
@@ -6996,7 +7430,7 @@ function resolveMinimizedStackTrackTranslateXPx(input) {
6996
7430
  }
6997
7431
 
6998
7432
  // src/host/WorkbenchHostDockPopup.tsx
6999
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
7433
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
7000
7434
  var dockPopupCardWidthPx = 165;
7001
7435
  var dockPopupGridGapPx = 8;
7002
7436
  var dockPopupPanelPaddingInlinePx = 12;
@@ -7549,7 +7983,7 @@ function WorkbenchHostDockPopup({
7549
7983
  showAllWindowsLabel,
7550
7984
  showOpen: showOpen === true
7551
7985
  }
7552
- ) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
7986
+ ) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
7553
7987
  /* @__PURE__ */ jsx9("div", { className: "mb-2.5 flex items-center justify-between", children: /* @__PURE__ */ jsx9("span", { className: "min-w-0 truncate text-sm font-semibold", children: label }) }),
7554
7988
  isMinimizedStack ? /* @__PURE__ */ jsx9(
7555
7989
  "div",
@@ -7608,7 +8042,7 @@ function WorkbenchHostDockPopup({
7608
8042
  }
7609
8043
  )
7610
8044
  }
7611
- ) : /* @__PURE__ */ jsxs5("div", { className: "grid max-h-[min(52vh,420px)] grid-cols-[repeat(var(--desktop-dock-popup-columns,2),165px)] gap-2 overflow-auto overscroll-contain", children: [
8045
+ ) : /* @__PURE__ */ jsxs6("div", { className: "grid max-h-[min(52vh,420px)] grid-cols-[repeat(var(--desktop-dock-popup-columns,2),165px)] gap-2 overflow-auto overscroll-contain", children: [
7612
8046
  items.map((item) => {
7613
8047
  const previewMemoryKey = resolveDockPopupPreviewMemoryKey(
7614
8048
  item.node,
@@ -7635,7 +8069,7 @@ function WorkbenchHostDockPopup({
7635
8069
  item.node.id
7636
8070
  );
7637
8071
  }),
7638
- showCreateNew !== false ? /* @__PURE__ */ jsxs5(
8072
+ showCreateNew !== false ? /* @__PURE__ */ jsxs6(
7639
8073
  "button",
7640
8074
  {
7641
8075
  className: "flex h-[103px] w-[165px] min-w-0 flex-col items-center justify-center gap-2 rounded-[8px] border border-dashed border-[var(--border-1)] bg-transparency-block text-center text-[var(--text-secondary)] transition-colors hover:bg-transparency-hover hover:text-[var(--text-primary)]",
@@ -7691,14 +8125,14 @@ function WorkbenchHostDockContextMenu({
7691
8125
  const hasOpenCommand = !hasOpenWindows;
7692
8126
  const hasDockActionGroup = Boolean(dockRetention) || hasNewWindowCommand || hasOpenCommand;
7693
8127
  const hasWindowActionGroup = hasOpenWindows;
7694
- return /* @__PURE__ */ jsxs5(
8128
+ return /* @__PURE__ */ jsxs6(
7695
8129
  "div",
7696
8130
  {
7697
8131
  className: "flex min-w-0 flex-col gap-1",
7698
8132
  "data-desktop-dock-context-menu": "true",
7699
8133
  role: "menu",
7700
8134
  children: [
7701
- hasOpenWindows ? /* @__PURE__ */ jsx9(Fragment3, { children: /* @__PURE__ */ jsx9("div", { className: "max-h-48 min-w-0 overflow-auto overscroll-contain", children: items.map((item) => /* @__PURE__ */ jsx9(
8135
+ hasOpenWindows ? /* @__PURE__ */ jsx9(Fragment4, { children: /* @__PURE__ */ jsx9("div", { className: "max-h-48 min-w-0 overflow-auto overscroll-contain", children: items.map((item) => /* @__PURE__ */ jsx9(
7702
8136
  WorkbenchHostDockContextMenuItem,
7703
8137
  {
7704
8138
  checked: !item.isMinimized,
@@ -7742,7 +8176,7 @@ function WorkbenchHostDockContextMenu({
7742
8176
  onSelect: onCreateNew
7743
8177
  }
7744
8178
  ) : null,
7745
- hasOpenWindows ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
8179
+ hasOpenWindows ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
7746
8180
  hasDockActionGroup ? /* @__PURE__ */ jsx9(WorkbenchHostDockContextMenuSeparator, {}) : null,
7747
8181
  canShowAllWindows && onShowAllWindows ? /* @__PURE__ */ jsx9(
7748
8182
  WorkbenchHostDockContextMenuItem,
@@ -7792,7 +8226,7 @@ function WorkbenchHostDockContextMenuItem({
7792
8226
  label,
7793
8227
  onSelect
7794
8228
  }) {
7795
- return /* @__PURE__ */ jsxs5(
8229
+ return /* @__PURE__ */ jsxs6(
7796
8230
  "button",
7797
8231
  {
7798
8232
  className: cn(
@@ -7971,7 +8405,7 @@ var WorkbenchHostDockPopupCard = forwardRef(function WorkbenchHostDockPopupCard2
7971
8405
  [handleSelect]
7972
8406
  );
7973
8407
  const hasReadyPreview = previewState.status === "ready";
7974
- return /* @__PURE__ */ jsxs5(
8408
+ return /* @__PURE__ */ jsxs6(
7975
8409
  "div",
7976
8410
  {
7977
8411
  ref,
@@ -7986,7 +8420,7 @@ var WorkbenchHostDockPopupCard = forwardRef(function WorkbenchHostDockPopupCard2
7986
8420
  "data-minimized": item.isMinimized ? "true" : void 0,
7987
8421
  style,
7988
8422
  children: [
7989
- /* @__PURE__ */ jsxs5(
8423
+ /* @__PURE__ */ jsxs6(
7990
8424
  "div",
7991
8425
  {
7992
8426
  "aria-label": title,
@@ -8039,7 +8473,7 @@ function WorkbenchHostDockPopupCardPreview({
8039
8473
  previewState
8040
8474
  }) {
8041
8475
  if (previewState.status !== "ready") {
8042
- return /* @__PURE__ */ jsxs5(
8476
+ return /* @__PURE__ */ jsxs6(
8043
8477
  "span",
8044
8478
  {
8045
8479
  className: "flex min-h-0 min-w-0 flex-1 flex-col justify-center gap-[7px] rounded-md border border-[var(--border-1)] bg-transparency-block px-3 py-[11px]",
@@ -8100,7 +8534,11 @@ function WorkbenchHostDockPopupCardLabel({ title }) {
8100
8534
  }
8101
8535
 
8102
8536
  // src/host/WorkbenchHostDock.tsx
8103
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
8537
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
8538
+ var minimizedDockPreviewViewport = {
8539
+ height: 34.2,
8540
+ width: 46.8
8541
+ };
8104
8542
  function stripDockDescriptionTerminalPunctuation(value) {
8105
8543
  const trimmed = value.trim();
8106
8544
  if (!trimmed || trimmed.endsWith("...") || trimmed.endsWith("\u2026")) {
@@ -8108,6 +8546,13 @@ function stripDockDescriptionTerminalPunctuation(value) {
8108
8546
  }
8109
8547
  return trimmed.replace(/[。..]+$/u, "");
8110
8548
  }
8549
+ function activateDockButtonFromKeyboard(event, disabled = false) {
8550
+ if (disabled || event.key !== "Enter" && event.key !== " ") {
8551
+ return;
8552
+ }
8553
+ event.preventDefault();
8554
+ event.currentTarget.click();
8555
+ }
8111
8556
  function isDockVisualMutationActive(element) {
8112
8557
  if (!element) {
8113
8558
  return false;
@@ -8997,7 +9442,8 @@ function WorkbenchHostDock({
8997
9442
  host,
8998
9443
  isFocused: context.focusedNodeId === node.id,
8999
9444
  isMinimized: node.isMinimized,
9000
- node
9445
+ node,
9446
+ previewViewport: minimizedDockPreviewViewport
9001
9447
  });
9002
9448
  },
9003
9449
  [
@@ -9121,7 +9567,7 @@ function WorkbenchHostDock({
9121
9567
  instanceMode: dockContextMenuInstanceMode,
9122
9568
  matchedNodes: popupEntry.matchedNodes
9123
9569
  }).kind === "launch";
9124
- return /* @__PURE__ */ jsxs6(
9570
+ return /* @__PURE__ */ jsxs7(
9125
9571
  "div",
9126
9572
  {
9127
9573
  className: "flex justify-center pointer-events-none",
@@ -9134,7 +9580,7 @@ function WorkbenchHostDock({
9134
9580
  style: dockFrameSize === null ? void 0 : {
9135
9581
  "--desktop-dock-frame-size": `${dockFrameSize}px`
9136
9582
  },
9137
- children: /* @__PURE__ */ jsxs6(
9583
+ children: /* @__PURE__ */ jsxs7(
9138
9584
  "div",
9139
9585
  {
9140
9586
  ref: dockMeasureRef,
@@ -9369,7 +9815,7 @@ function WorkbenchHostDock({
9369
9815
  entryId: entry.id
9370
9816
  });
9371
9817
  },
9372
- children: /* @__PURE__ */ jsxs6(
9818
+ children: /* @__PURE__ */ jsxs7(
9373
9819
  "span",
9374
9820
  {
9375
9821
  className: "desktop-dock__icon-shell",
@@ -9489,14 +9935,16 @@ function WorkbenchHostDock({
9489
9935
  stackLabel
9490
9936
  );
9491
9937
  const stackButton = /* @__PURE__ */ jsx10(
9492
- "button",
9938
+ "span",
9493
9939
  {
9494
9940
  "aria-expanded": stackPopupActive,
9495
9941
  "aria-haspopup": "dialog",
9496
9942
  "aria-label": stackLabel,
9497
9943
  className: "desktop-dock__btn desktop-dock__minimized-btn",
9498
9944
  "data-interactive": "true",
9499
- type: "button",
9945
+ role: "button",
9946
+ tabIndex: 0,
9947
+ onKeyDown: activateDockButtonFromKeyboard,
9500
9948
  onPointerDown: (event) => {
9501
9949
  if (event.button !== 0) {
9502
9950
  return;
@@ -9520,7 +9968,7 @@ function WorkbenchHostDock({
9520
9968
  }
9521
9969
  );
9522
9970
  },
9523
- children: /* @__PURE__ */ jsxs6(
9971
+ children: /* @__PURE__ */ jsxs7(
9524
9972
  "span",
9525
9973
  {
9526
9974
  className: "desktop-dock__minimized-stack-icon",
@@ -9621,13 +10069,18 @@ function WorkbenchHostDock({
9621
10069
  node.title
9622
10070
  );
9623
10071
  const dockButton = /* @__PURE__ */ jsx10(
9624
- "button",
10072
+ "span",
9625
10073
  {
9626
10074
  "aria-label": i18n.t("launch", { title: node.title }),
10075
+ "aria-disabled": isPendingMinimizedNode ? true : void 0,
9627
10076
  className: "desktop-dock__btn desktop-dock__minimized-btn",
9628
- "data-interactive": "true",
9629
- disabled: isPendingMinimizedNode,
9630
- type: "button",
10077
+ "data-interactive": isPendingMinimizedNode ? "false" : "true",
10078
+ role: "button",
10079
+ tabIndex: isPendingMinimizedNode ? -1 : 0,
10080
+ onKeyDown: (event) => activateDockButtonFromKeyboard(
10081
+ event,
10082
+ isPendingMinimizedNode
10083
+ ),
9631
10084
  onPointerDown: (event) => {
9632
10085
  if (event.button !== 0 || isPendingMinimizedNode) {
9633
10086
  return;
@@ -10228,7 +10681,7 @@ function WorkbenchHostDockMinimizedNodePreview({
10228
10681
  return renderMinimizedDockPreviewPlaceholder(className);
10229
10682
  }
10230
10683
  function renderMinimizedDockPreviewPlaceholder(className) {
10231
- return /* @__PURE__ */ jsxs6(
10684
+ return /* @__PURE__ */ jsxs7(
10232
10685
  "span",
10233
10686
  {
10234
10687
  className: ["desktop-dock__minimized-preview", className].filter(Boolean).join(" "),
@@ -10412,7 +10865,7 @@ function WorkbenchHostDockHoverPanel({
10412
10865
  if (!entry) {
10413
10866
  return null;
10414
10867
  }
10415
- return /* @__PURE__ */ jsxs6(
10868
+ return /* @__PURE__ */ jsxs7(
10416
10869
  "div",
10417
10870
  {
10418
10871
  ref: hoverPanelRef,
@@ -10593,6 +11046,77 @@ function resolveNextDockItemPresence(item, initialized, previousPresence, should
10593
11046
  }
10594
11047
  return previousPresence ?? "entering";
10595
11048
  }
11049
+ function resolveDockPresenceItems(input) {
11050
+ const currentByKey = new Map(input.current.map((item) => [item.key, item]));
11051
+ const currentIndexByKey = new Map(
11052
+ input.current.map((item, index) => [item.key, index])
11053
+ );
11054
+ const nextByKey = new Map(
11055
+ input.nextSourceItems.map((item) => [item.key, item])
11056
+ );
11057
+ const nextKeys = new Set(input.nextSourceItems.map((item) => item.key));
11058
+ const currentVisibleItemCount = input.current.filter(
11059
+ (item) => item.presence !== "exiting"
11060
+ ).length;
11061
+ const shouldRetainExitingItems = input.nextSourceItems.length < currentVisibleItemCount;
11062
+ const emittedKeys = /* @__PURE__ */ new Set();
11063
+ const nextItems = [];
11064
+ let currentIndex = 0;
11065
+ const emitExitingUntil = (nextCurrentIndex) => {
11066
+ while (currentIndex < nextCurrentIndex) {
11067
+ const currentItem = input.current[currentIndex];
11068
+ currentIndex += 1;
11069
+ if (shouldRetainExitingItems && currentItem && !nextKeys.has(currentItem.key) && !emittedKeys.has(currentItem.key)) {
11070
+ emittedKeys.add(currentItem.key);
11071
+ nextItems.push({
11072
+ ...currentItem,
11073
+ presence: "exiting"
11074
+ });
11075
+ }
11076
+ }
11077
+ };
11078
+ for (const item of input.nextSourceItems) {
11079
+ const previousIndex = currentIndexByKey.get(item.key);
11080
+ if (previousIndex !== void 0) {
11081
+ emitExitingUntil(previousIndex);
11082
+ currentIndex = Math.max(currentIndex, previousIndex + 1);
11083
+ }
11084
+ emittedKeys.add(item.key);
11085
+ nextItems.push({
11086
+ item,
11087
+ key: item.key,
11088
+ presence: resolveNextDockItemPresence(
11089
+ item,
11090
+ input.initialized,
11091
+ currentByKey.get(item.key)?.presence,
11092
+ input.shouldAnimateMinimizedDockEnter
11093
+ )
11094
+ });
11095
+ }
11096
+ for (const currentItem of input.current) {
11097
+ if (shouldRetainExitingItems && !nextKeys.has(currentItem.key)) {
11098
+ if (emittedKeys.has(currentItem.key)) {
11099
+ continue;
11100
+ }
11101
+ emittedKeys.add(currentItem.key);
11102
+ nextItems.push({
11103
+ ...currentItem,
11104
+ presence: "exiting"
11105
+ });
11106
+ }
11107
+ }
11108
+ return nextItems.filter(
11109
+ (item) => nextByKey.has(item.key) || item.presence === "exiting"
11110
+ );
11111
+ }
11112
+ function resolveDockPresenceSettleMs(items) {
11113
+ if (items.some(
11114
+ (item) => item.item.kind === "minimized" && (item.presence === "entering" || item.presence === "exiting")
11115
+ )) {
11116
+ return minimizedDockSlotLayoutAnimationMs;
11117
+ }
11118
+ return dockPresenceAnimationMs;
11119
+ }
10596
11120
  function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
10597
11121
  const latestItemsByKey = useRef8(/* @__PURE__ */ new Map());
10598
11122
  const shouldAnimateMinimizedDockEnterRef = useRef8(
@@ -10612,73 +11136,13 @@ function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
10612
11136
  useEffect9(() => {
10613
11137
  let nextSettleMs = dockPresenceAnimationMs;
10614
11138
  setPresentItems((current) => {
10615
- const nextSourceItems = [...latestItemsByKey.current.values()];
10616
- const currentByKey = new Map(current.map((item) => [item.key, item]));
10617
- const currentIndexByKey = new Map(
10618
- current.map((item, index) => [item.key, index])
10619
- );
10620
- const nextByKey = new Map(
10621
- nextSourceItems.map((item) => [item.key, item])
10622
- );
10623
- const nextKeys = new Set(nextSourceItems.map((item) => item.key));
10624
- const currentVisibleItemCount = current.filter(
10625
- (item) => item.presence !== "exiting"
10626
- ).length;
10627
- const shouldRetainExitingItems = nextSourceItems.length < currentVisibleItemCount;
10628
- const emittedKeys = /* @__PURE__ */ new Set();
10629
- const nextItems = [];
10630
- let currentIndex = 0;
10631
- const emitExitingUntil = (nextCurrentIndex) => {
10632
- while (currentIndex < nextCurrentIndex) {
10633
- const currentItem = current[currentIndex];
10634
- currentIndex += 1;
10635
- if (shouldRetainExitingItems && currentItem && !nextKeys.has(currentItem.key) && !emittedKeys.has(currentItem.key)) {
10636
- emittedKeys.add(currentItem.key);
10637
- nextItems.push({
10638
- ...currentItem,
10639
- presence: "exiting"
10640
- });
10641
- }
10642
- }
10643
- };
10644
- for (const item of nextSourceItems) {
10645
- const previousIndex = currentIndexByKey.get(item.key);
10646
- if (previousIndex !== void 0) {
10647
- emitExitingUntil(previousIndex);
10648
- currentIndex = Math.max(currentIndex, previousIndex + 1);
10649
- }
10650
- emittedKeys.add(item.key);
10651
- nextItems.push({
10652
- item,
10653
- key: item.key,
10654
- presence: resolveNextDockItemPresence(
10655
- item,
10656
- initialized.current,
10657
- currentByKey.get(item.key)?.presence,
10658
- shouldAnimateMinimizedDockEnterRef.current
10659
- )
10660
- });
10661
- }
10662
- for (const currentItem of current) {
10663
- if (shouldRetainExitingItems && !nextKeys.has(currentItem.key)) {
10664
- if (emittedKeys.has(currentItem.key)) {
10665
- continue;
10666
- }
10667
- emittedKeys.add(currentItem.key);
10668
- nextItems.push({
10669
- ...currentItem,
10670
- presence: "exiting"
10671
- });
10672
- }
10673
- }
10674
- const filteredItems = nextItems.filter(
10675
- (item) => nextByKey.has(item.key) || item.presence === "exiting"
10676
- );
10677
- if (filteredItems.some(
10678
- (item) => item.item.kind === "minimized" && (item.presence === "entering" || item.presence === "exiting")
10679
- )) {
10680
- nextSettleMs = minimizedDockSlotLayoutAnimationMs;
10681
- }
11139
+ const filteredItems = resolveDockPresenceItems({
11140
+ current,
11141
+ initialized: initialized.current,
11142
+ nextSourceItems: [...latestItemsByKey.current.values()],
11143
+ shouldAnimateMinimizedDockEnter: shouldAnimateMinimizedDockEnterRef.current
11144
+ });
11145
+ nextSettleMs = resolveDockPresenceSettleMs(filteredItems);
10682
11146
  return filteredItems;
10683
11147
  });
10684
11148
  initialized.current = true;
@@ -10691,7 +11155,13 @@ function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
10691
11155
  }, nextSettleMs);
10692
11156
  return () => globalThis.clearTimeout(timeout);
10693
11157
  }, [itemKeys]);
10694
- return presentItems.map((presentItem) => {
11158
+ const renderedPresentItems = resolveDockPresenceItems({
11159
+ current: presentItems,
11160
+ initialized: initialized.current,
11161
+ nextSourceItems: [...latestItemsByKey.current.values()],
11162
+ shouldAnimateMinimizedDockEnter
11163
+ });
11164
+ return renderedPresentItems.map((presentItem) => {
10695
11165
  const latestItem = latestItemsByKey.current.get(presentItem.key);
10696
11166
  return latestItem ? { ...presentItem, item: latestItem } : presentItem;
10697
11167
  });
@@ -11139,7 +11609,7 @@ function createWorkbenchHostNodeHeaderContext({
11139
11609
 
11140
11610
  // src/host/WorkbenchHostWindowActions.tsx
11141
11611
  import { Button as Button5, CloseIcon as CloseIcon2, MinimizeIcon as MinimizeIcon2 } from "@tutti-os/ui-system";
11142
- import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
11612
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
11143
11613
  function WorkbenchHostWindowActions({
11144
11614
  context,
11145
11615
  host,
@@ -11152,7 +11622,7 @@ function WorkbenchHostWindowActions({
11152
11622
  }
11153
11623
  const minimizable = definition.window?.minimizable !== false;
11154
11624
  const closable = definition.window?.closable !== false;
11155
- return /* @__PURE__ */ jsxs7(Fragment4, { children: [
11625
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
11156
11626
  minimizable ? /* @__PURE__ */ jsx11(
11157
11627
  Button5,
11158
11628
  {
@@ -11190,7 +11660,7 @@ function WorkbenchHostWindowActions({
11190
11660
  }
11191
11661
 
11192
11662
  // src/host/useWorkbenchHostSurfaceRenderers.tsx
11193
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
11663
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
11194
11664
  function useWorkbenchHostSurfaceRenderers(input) {
11195
11665
  const renderBottomChrome = useMemo6(() => {
11196
11666
  const renderChrome = input.renderBottomChrome;
@@ -11426,6 +11896,40 @@ function useWorkbenchHostSurfaceRenderers(input) {
11426
11896
  },
11427
11897
  [input.nodeDefinitionByType]
11428
11898
  );
11899
+ const renderNodeGeniePreview = useCallback11(
11900
+ (node, {
11901
+ previewViewport
11902
+ }) => {
11903
+ const minimizedDock = input.nodeDefinitionByType.get(node.data.typeId)?.window?.minimizedDock;
11904
+ if (minimizedDock?.kind !== "component") {
11905
+ return null;
11906
+ }
11907
+ const externalState = readWorkbenchHostExternalState({
11908
+ externalStateSource: input.externalStateSource,
11909
+ node,
11910
+ workspaceId: input.workspaceId
11911
+ });
11912
+ const preview = minimizedDock.providePreview({
11913
+ externalNodeState: externalState.externalNodeState,
11914
+ externalWorkspaceState: externalState.externalWorkspaceState,
11915
+ host: input.hostSession,
11916
+ isFocused: input.hostSession.getSnapshot().nodeStack.at(-1) === node.id,
11917
+ isMinimized: node.isMinimized,
11918
+ node,
11919
+ previewViewport
11920
+ });
11921
+ return preview ? renderMinimizedDockPreviewContent(
11922
+ preview,
11923
+ "workbench-genie-preview-capture__preview"
11924
+ ) : null;
11925
+ },
11926
+ [
11927
+ input.externalStateSource,
11928
+ input.hostSession,
11929
+ input.nodeDefinitionByType,
11930
+ input.workspaceId
11931
+ ]
11932
+ );
11429
11933
  const resolveDockAnchorKey = useCallback11(
11430
11934
  (node) => {
11431
11935
  if (node.isMinimized && isWorkbenchMinimizedDockEligibleNode({
@@ -11482,6 +11986,7 @@ function useWorkbenchHostSurfaceRenderers(input) {
11482
11986
  renderBottomChrome,
11483
11987
  renderDock,
11484
11988
  renderNode,
11989
+ renderNodeGeniePreview,
11485
11990
  renderTopChrome,
11486
11991
  renderWindowActions,
11487
11992
  renderWindowHeader,
@@ -11578,7 +12083,7 @@ var WorkbenchHostNodeRenderErrorBoundary = class extends Component {
11578
12083
  }
11579
12084
  render() {
11580
12085
  if (this.state.hasError) {
11581
- return /* @__PURE__ */ jsxs8(
12086
+ return /* @__PURE__ */ jsxs9(
11582
12087
  "div",
11583
12088
  {
11584
12089
  "data-workbench-node-render-error": "true",
@@ -11767,6 +12272,7 @@ function WorkbenchHost({
11767
12272
  className,
11768
12273
  captureNodePreviewImage: surfaceRenderers.captureNodePreviewImage,
11769
12274
  controller: hostSession.controller,
12275
+ debugDiagnostics,
11770
12276
  dockPreviewCache,
11771
12277
  dockPlacement,
11772
12278
  interactive: !isHydrating,
@@ -11784,6 +12290,7 @@ function WorkbenchHost({
11784
12290
  renderBottomChrome: surfaceRenderers.renderBottomChrome,
11785
12291
  renderDock: surfaceRenderers.renderDock,
11786
12292
  renderNode: surfaceRenderers.renderNode,
12293
+ renderNodeGeniePreview: surfaceRenderers.renderNodeGeniePreview,
11787
12294
  renderOverlay: missionControlRenderedState ? () => /* @__PURE__ */ jsx13(
11788
12295
  WorkbenchMissionControlOverlay,
11789
12296
  {