@tutti-os/workbench-surface 0.0.2 → 0.0.4

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
@@ -1712,6 +1712,9 @@ function WorkbenchDockFrame({
1712
1712
  },
1713
1713
  registerDockAnchor: (anchorKey, element) => {
1714
1714
  genie.registerDockAnchor(anchorKey, element);
1715
+ },
1716
+ shouldAnimateMinimizedDockEnter: (nodeID) => {
1717
+ return genie.shouldAnimateMinimizedDockEnter(nodeID);
1715
1718
  }
1716
1719
  },
1717
1720
  minimizedNodes,
@@ -2589,6 +2592,7 @@ import { jsx as jsx7 } from "react/jsx-runtime";
2589
2592
  var genieDurationMs = 400;
2590
2593
  var genieMaxDevicePixelRatio = 2;
2591
2594
  var genieSnapshotScale = 1;
2595
+ var minimizedDockSlotEnterAnimationMs = 720;
2592
2596
  var dockPreviewMaxWidth = 260;
2593
2597
  var dockPreviewMaxHeight = 170;
2594
2598
  var dockPreviewImageCacheMaxEntries = 96;
@@ -2868,6 +2872,10 @@ function useWorkbenchGenieAnimation({
2868
2872
  const rafRef = useRef4(null);
2869
2873
  const animationGenerationRef = useRef4(0);
2870
2874
  const animationCleanupRef = useRef4(null);
2875
+ const minimizedDockEnterAnimationNodeIdsRef = useRef4(/* @__PURE__ */ new Set());
2876
+ const minimizedDockEnterAnimationTimersRef = useRef4(
2877
+ /* @__PURE__ */ new Map()
2878
+ );
2871
2879
  const [isCanvasActive, setIsCanvasActive] = useState4(false);
2872
2880
  const [genieHiddenNodeIDs, setGenieHiddenNodeIDs] = useState4(
2873
2881
  () => /* @__PURE__ */ new Set()
@@ -2895,6 +2903,40 @@ function useWorkbenchGenieAnimation({
2895
2903
  (node) => resolveDockAnchorKey?.(node) ?? node.id,
2896
2904
  [resolveDockAnchorKey]
2897
2905
  );
2906
+ const releaseMinimizedDockEnterAnimation = useCallback6((nodeID) => {
2907
+ const timer = minimizedDockEnterAnimationTimersRef.current.get(nodeID);
2908
+ if (timer) {
2909
+ clearTimeout(timer);
2910
+ minimizedDockEnterAnimationTimersRef.current.delete(nodeID);
2911
+ }
2912
+ minimizedDockEnterAnimationNodeIdsRef.current.delete(nodeID);
2913
+ }, []);
2914
+ const registerMinimizedDockEnterAnimation = useCallback6(
2915
+ (nodeID) => {
2916
+ releaseMinimizedDockEnterAnimation(nodeID);
2917
+ minimizedDockEnterAnimationNodeIdsRef.current.add(nodeID);
2918
+ },
2919
+ [releaseMinimizedDockEnterAnimation]
2920
+ );
2921
+ const scheduleReleaseMinimizedDockEnterAnimation = useCallback6(
2922
+ (nodeID, delayMs = minimizedDockSlotEnterAnimationMs) => {
2923
+ const existing = minimizedDockEnterAnimationTimersRef.current.get(nodeID);
2924
+ if (existing) {
2925
+ clearTimeout(existing);
2926
+ }
2927
+ minimizedDockEnterAnimationTimersRef.current.set(
2928
+ nodeID,
2929
+ setTimeout(() => {
2930
+ minimizedDockEnterAnimationTimersRef.current.delete(nodeID);
2931
+ minimizedDockEnterAnimationNodeIdsRef.current.delete(nodeID);
2932
+ }, delayMs)
2933
+ );
2934
+ },
2935
+ []
2936
+ );
2937
+ const shouldAnimateMinimizedDockEnter = useCallback6((nodeID) => {
2938
+ return minimizedDockEnterAnimationNodeIdsRef.current.has(nodeID);
2939
+ }, []);
2898
2940
  const hideNodeForGenie = useCallback6((nodeID) => {
2899
2941
  setGenieHiddenNodeIDs((current) => {
2900
2942
  if (current.has(nodeID)) {
@@ -3198,6 +3240,7 @@ function useWorkbenchGenieAnimation({
3198
3240
  clearCanvas();
3199
3241
  };
3200
3242
  animationCleanupRef.current = cleanupHiddenNode;
3243
+ registerMinimizedDockEnterAnimation(nodeID);
3201
3244
  flushSync(() => {
3202
3245
  hideNodeForGenie(nodeID);
3203
3246
  runMinimize();
@@ -3206,12 +3249,14 @@ function useWorkbenchGenieAnimation({
3206
3249
  window.requestAnimationFrame(() => resolve());
3207
3250
  });
3208
3251
  if (generation !== animationGenerationRef.current) {
3252
+ releaseMinimizedDockEnterAnimation(nodeID);
3209
3253
  return;
3210
3254
  }
3211
3255
  const minimizedTarget = controller.getSnapshot().nodes.find((node) => node.id === nodeID) ?? target;
3212
3256
  const anchorKey = resolveAnchorKeyForNode(minimizedTarget);
3213
3257
  const dockRect = resolveDockAnchorRect(anchorKey);
3214
3258
  if (!dockRect || !isUsableGenieRect(dockRect)) {
3259
+ releaseMinimizedDockEnterAnimation(nodeID);
3215
3260
  animationCleanupRef.current = null;
3216
3261
  cleanupHiddenNode();
3217
3262
  return;
@@ -3220,12 +3265,14 @@ function useWorkbenchGenieAnimation({
3220
3265
  direction: "minimize",
3221
3266
  dockRect,
3222
3267
  onCancel: () => {
3268
+ releaseMinimizedDockEnterAnimation(nodeID);
3223
3269
  flushSync(() => {
3224
3270
  showNodeForGenie(nodeID);
3225
3271
  });
3226
3272
  clearCanvas();
3227
3273
  },
3228
3274
  onComplete: () => {
3275
+ scheduleReleaseMinimizedDockEnterAnimation(nodeID);
3229
3276
  flushSync(() => {
3230
3277
  showNodeForGenie(nodeID);
3231
3278
  });
@@ -3242,16 +3289,29 @@ function useWorkbenchGenieAnimation({
3242
3289
  captureNodePreviewImage,
3243
3290
  dockPreviewCache,
3244
3291
  hideNodeForGenie,
3292
+ registerMinimizedDockEnterAnimation,
3293
+ releaseMinimizedDockEnterAnimation,
3245
3294
  resolveAnchorKeyForNode,
3246
3295
  resolveDockPreviewCacheKey2,
3247
3296
  resolveDockAnchorRect,
3248
3297
  resolveNodeElement,
3249
3298
  runGenieAnimation,
3299
+ scheduleReleaseMinimizedDockEnterAnimation,
3250
3300
  setupCanvas,
3251
3301
  showNodeForGenie,
3252
3302
  stopAnimation
3253
3303
  ]
3254
3304
  );
3305
+ useEffect3(
3306
+ () => () => {
3307
+ for (const timer of minimizedDockEnterAnimationTimersRef.current.values()) {
3308
+ clearTimeout(timer);
3309
+ }
3310
+ minimizedDockEnterAnimationTimersRef.current.clear();
3311
+ minimizedDockEnterAnimationNodeIdsRef.current.clear();
3312
+ },
3313
+ []
3314
+ );
3255
3315
  return {
3256
3316
  genieLayer: typeof document === "undefined" ? null : createPortal(
3257
3317
  /* @__PURE__ */ jsx7(
@@ -3271,7 +3331,8 @@ function useWorkbenchGenieAnimation({
3271
3331
  ),
3272
3332
  launchNodeFromAnchor,
3273
3333
  minimizeNodeToAnchor,
3274
- registerDockAnchor
3334
+ registerDockAnchor,
3335
+ shouldAnimateMinimizedDockEnter
3275
3336
  };
3276
3337
  }
3277
3338
 
@@ -4566,8 +4627,11 @@ var WorkbenchHostSessionController = class {
4566
4627
  return null;
4567
4628
  }
4568
4629
  const launchSource = resolveWorkbenchHostLaunchSource(input);
4630
+ const currentState = this.controller.getSnapshot();
4569
4631
  const request = {
4570
4632
  ...input,
4633
+ layoutConstraints: currentState.layoutConstraints,
4634
+ surfaceSize: currentState.surfaceSize,
4571
4635
  workspaceId: this.input.workspaceId
4572
4636
  };
4573
4637
  if (launchSource !== null) {
@@ -5318,11 +5382,11 @@ import { useCallback as useCallback11, useMemo as useMemo6 } from "react";
5318
5382
  // src/host/WorkbenchHostDock.tsx
5319
5383
  import {
5320
5384
  useCallback as useCallback10,
5321
- useEffect as useEffect8,
5385
+ useEffect as useEffect9,
5322
5386
  useLayoutEffect as useLayoutEffect4,
5323
5387
  useMemo as useMemo5,
5324
- useRef as useRef7,
5325
- useState as useState7
5388
+ useRef as useRef8,
5389
+ useState as useState8
5326
5390
  } from "react";
5327
5391
  import {
5328
5392
  ArrowLeftIcon,
@@ -5330,10 +5394,7 @@ import {
5330
5394
  Button as Button4,
5331
5395
  ChevronDownIcon,
5332
5396
  ChevronUpIcon,
5333
- Tooltip,
5334
- TooltipContent,
5335
- TooltipProvider,
5336
- TooltipTrigger
5397
+ TooltipProvider
5337
5398
  } from "@tutti-os/ui-system";
5338
5399
 
5339
5400
  // src/host/dockEntries.ts
@@ -5437,6 +5498,8 @@ var MAGNIFICATION_SETTLE_EPSILON = 0.2;
5437
5498
  var MAGNIFICATION_SIZE_EPSILON = 0.2;
5438
5499
  var MAX_MAGNIFICATION_STEP_SECONDS = 1 / 30;
5439
5500
  var MAGNIFICATION_INFLUENCE_PADDING = 8;
5501
+ var DOCK_MAGNIFICATION_ENTRY_RAMP_MS = 90;
5502
+ var DOCK_MAGNIFICATION_CROSS_AXIS_PADDING = 8;
5440
5503
  var dockMagnificationShellBySlot = /* @__PURE__ */ new WeakMap();
5441
5504
  function mapDistanceToTargetSize(distance, baseSize = DOCK_ICON_BASE_SIZE, peakSize = DOCK_ICON_PEAK_SIZE, halfRange = DOCK_MAGNIFICATION_HALF_RANGE) {
5442
5505
  const absoluteDistance = Math.abs(distance);
@@ -5446,6 +5509,55 @@ function mapDistanceToTargetSize(distance, baseSize = DOCK_ICON_BASE_SIZE, peakS
5446
5509
  const influence = 1 - absoluteDistance / halfRange;
5447
5510
  return baseSize + (peakSize - baseSize) * influence;
5448
5511
  }
5512
+ function applyDockMagnificationEntryRamp(targetSize, baseSize, progress) {
5513
+ const clampedProgress = Math.min(1, Math.max(0, progress));
5514
+ return baseSize + (targetSize - baseSize) * clampedProgress;
5515
+ }
5516
+ function resolveDockMagnificationHitBounds(slotRects, dockPlacement, crossAxisPadding = DOCK_MAGNIFICATION_CROSS_AXIS_PADDING) {
5517
+ if (slotRects.length === 0) {
5518
+ return null;
5519
+ }
5520
+ let mainStart = Number.POSITIVE_INFINITY;
5521
+ let mainEnd = Number.NEGATIVE_INFINITY;
5522
+ let crossStart = Number.POSITIVE_INFINITY;
5523
+ let crossEnd = Number.NEGATIVE_INFINITY;
5524
+ for (const rect of slotRects) {
5525
+ if (dockPlacement === "left") {
5526
+ mainStart = Math.min(mainStart, rect.top);
5527
+ mainEnd = Math.max(mainEnd, rect.bottom);
5528
+ crossStart = Math.min(crossStart, rect.left);
5529
+ crossEnd = Math.max(crossEnd, rect.right);
5530
+ } else {
5531
+ mainStart = Math.min(mainStart, rect.left);
5532
+ mainEnd = Math.max(mainEnd, rect.right);
5533
+ crossStart = Math.min(crossStart, rect.top);
5534
+ crossEnd = Math.max(crossEnd, rect.bottom);
5535
+ }
5536
+ }
5537
+ const effectiveCrossAxisEndPadding = dockPlacement === "left" ? crossAxisPadding + (DOCK_ICON_PEAK_SIZE - DOCK_ICON_BASE_SIZE) : crossAxisPadding;
5538
+ return {
5539
+ crossEnd: crossEnd + effectiveCrossAxisEndPadding,
5540
+ crossStart: crossStart - crossAxisPadding,
5541
+ mainEnd,
5542
+ mainStart
5543
+ };
5544
+ }
5545
+ function isDockMagnificationPointInsideHitBounds({
5546
+ clientX,
5547
+ clientY,
5548
+ dockPlacement,
5549
+ hitBounds
5550
+ }) {
5551
+ if (!hitBounds) {
5552
+ return false;
5553
+ }
5554
+ const mainAxis = dockPlacement === "left" ? clientY : clientX;
5555
+ const crossAxis = dockPlacement === "left" ? clientX : clientY;
5556
+ return mainAxis >= hitBounds.mainStart && mainAxis <= hitBounds.mainEnd && crossAxis >= hitBounds.crossStart && crossAxis <= hitBounds.crossEnd;
5557
+ }
5558
+ function resolveDockMagnificationSlotCenter(rect, dockPlacement, baseSize = DOCK_ICON_BASE_SIZE) {
5559
+ return dockPlacement === "left" ? rect.top + baseSize / 2 : rect.left + baseSize / 2;
5560
+ }
5449
5561
  var DOCK_MAGNIFICATION_SPRING_SUBSTEPS = 8;
5450
5562
  function advanceDockMagnificationSpring(current, target, deltaSeconds) {
5451
5563
  const subDeltaSeconds = deltaSeconds / DOCK_MAGNIFICATION_SPRING_SUBSTEPS;
@@ -5470,6 +5582,14 @@ function hasAppliedSizeChanged(previous, next) {
5470
5582
  }
5471
5583
  return Math.abs(previous.size - next.size) > MAGNIFICATION_SIZE_EPSILON;
5472
5584
  }
5585
+ function resolveDockMagnificationSlotLayoutSize({
5586
+ size
5587
+ }) {
5588
+ return { height: size, width: size };
5589
+ }
5590
+ function isDockMagnificationSlotLayoutLocked(slotElement) {
5591
+ return slotElement.dataset.collapsing === "true" || slotElement.dataset.presence === "entering" || slotElement.dataset.presence === "exiting";
5592
+ }
5473
5593
  function resolveDockMagnificationShell(slotElement) {
5474
5594
  const cachedShell = dockMagnificationShellBySlot.get(slotElement);
5475
5595
  if (cachedShell !== void 0 && (cachedShell === null || slotElement.contains(cachedShell))) {
@@ -5506,8 +5626,11 @@ function applyDockSlotMagnification(slotElement, size, baseSize, appliedStylesRe
5506
5626
  return;
5507
5627
  }
5508
5628
  const scale = nextStyle.size / baseSize;
5509
- slotElement.style.width = `${nextStyle.size}px`;
5510
- slotElement.style.height = `${nextStyle.size}px`;
5629
+ const layoutSize = resolveDockMagnificationSlotLayoutSize({
5630
+ size: nextStyle.size
5631
+ });
5632
+ slotElement.style.width = `${layoutSize.width}px`;
5633
+ slotElement.style.height = `${layoutSize.height}px`;
5511
5634
  shell.style.transform = `scale(${scale})`;
5512
5635
  }
5513
5636
  function clearDockSlotMagnification(slotElement, appliedStylesRef) {
@@ -5533,7 +5656,9 @@ function useDockMagnification({
5533
5656
  );
5534
5657
  const animationFrameRef = useRef5(null);
5535
5658
  const lastFrameTimeRef = useRef5(null);
5659
+ const entryRampStartedAtRef = useRef5(null);
5536
5660
  const restCentersRef = useRef5(null);
5661
+ const hitBoundsRef = useRef5(null);
5537
5662
  const slotOrderRef = useRef5([]);
5538
5663
  const magnifyActiveRef = useRef5(false);
5539
5664
  const setMagnifyActive = useCallback8(
@@ -5560,14 +5685,25 @@ function useDockMagnification({
5560
5685
  const captureRestCenters = useCallback8(() => {
5561
5686
  const slots = slotRefs.current;
5562
5687
  const centers = /* @__PURE__ */ new Map();
5688
+ const slotRects = [];
5563
5689
  const order = [];
5564
5690
  for (const [anchorKey, slotElement] of slots) {
5565
5691
  order.push(anchorKey);
5566
5692
  const rect = slotElement.getBoundingClientRect();
5567
- const center = dockPlacement === "left" ? rect.top + rect.height / 2 : rect.left + rect.width / 2;
5693
+ slotRects.push({
5694
+ bottom: rect.bottom,
5695
+ left: rect.left,
5696
+ right: rect.right,
5697
+ top: rect.top
5698
+ });
5699
+ const center = resolveDockMagnificationSlotCenter(rect, dockPlacement);
5568
5700
  centers.set(anchorKey, center);
5569
5701
  }
5570
5702
  slotOrderRef.current = order;
5703
+ hitBoundsRef.current = resolveDockMagnificationHitBounds(
5704
+ slotRects,
5705
+ dockPlacement
5706
+ );
5571
5707
  restCentersRef.current = centers;
5572
5708
  }, [dockPlacement, slotRefs]);
5573
5709
  const runAnimationFrame = useCallback8(
@@ -5578,6 +5714,7 @@ function useDockMagnification({
5578
5714
  const pointerAxis = pointerAxisRef.current;
5579
5715
  if (pointerAxis !== null) {
5580
5716
  captureRestCenters();
5717
+ entryRampStartedAtRef.current ??= frameTime;
5581
5718
  }
5582
5719
  const slots = slotRefs.current;
5583
5720
  const order = slotOrderRef.current;
@@ -5590,6 +5727,7 @@ function useDockMagnification({
5590
5727
  let shouldContinue = false;
5591
5728
  let allSettled = true;
5592
5729
  const influenceRadius = DOCK_MAGNIFICATION_HALF_RANGE + MAGNIFICATION_INFLUENCE_PADDING;
5730
+ const entryRampProgress = pointerAxis === null || entryRampStartedAtRef.current === null ? 1 : (frameTime - entryRampStartedAtRef.current) / DOCK_MAGNIFICATION_ENTRY_RAMP_MS;
5593
5731
  for (let index = 0; index < order.length; index += 1) {
5594
5732
  const anchorKey = order[index];
5595
5733
  if (!anchorKey) {
@@ -5602,7 +5740,11 @@ function useDockMagnification({
5602
5740
  const distance = pointerAxis === null ? DOCK_MAGNIFICATION_HALF_RANGE : pointerAxis - center;
5603
5741
  const absoluteDistance = Math.abs(distance);
5604
5742
  const inRange = absoluteDistance < influenceRadius;
5605
- const targetSize = inRange ? mapDistanceToTargetSize(distance) : DOCK_ICON_BASE_SIZE;
5743
+ const targetSize = inRange ? applyDockMagnificationEntryRamp(
5744
+ mapDistanceToTargetSize(distance),
5745
+ DOCK_ICON_BASE_SIZE,
5746
+ entryRampProgress
5747
+ ) : DOCK_ICON_BASE_SIZE;
5606
5748
  const currentSpring = springsRef.current.get(anchorKey) ?? {
5607
5749
  value: DOCK_ICON_BASE_SIZE,
5608
5750
  velocity: 0
@@ -5635,6 +5777,11 @@ function useDockMagnification({
5635
5777
  if (!slotElement) {
5636
5778
  continue;
5637
5779
  }
5780
+ if (isDockMagnificationSlotLayoutLocked(slotElement)) {
5781
+ springsRef.current.delete(anchorKey);
5782
+ clearDockSlotMagnification(slotElement, appliedStylesRef.current);
5783
+ continue;
5784
+ }
5638
5785
  if (pointerAxis === null && isDockMagnificationSpringSettled(nextSpring, DOCK_ICON_BASE_SIZE)) {
5639
5786
  springsRef.current.delete(anchorKey);
5640
5787
  clearDockSlotMagnification(slotElement, appliedStylesRef.current);
@@ -5653,7 +5800,9 @@ function useDockMagnification({
5653
5800
  shouldContinue = true;
5654
5801
  }
5655
5802
  if (pointerAxis === null && allSettled) {
5803
+ entryRampStartedAtRef.current = null;
5656
5804
  restCentersRef.current = null;
5805
+ hitBoundsRef.current = null;
5657
5806
  slotOrderRef.current = [];
5658
5807
  setMagnifyActive(false);
5659
5808
  }
@@ -5674,9 +5823,23 @@ function useDockMagnification({
5674
5823
  }, [runAnimationFrame]);
5675
5824
  const handlePointerMove = useCallback8(
5676
5825
  (clientX, clientY) => {
5677
- pendingPointerAxisRef.current = dockPlacement === "left" ? clientY : clientX;
5678
5826
  if (restCentersRef.current === null) {
5679
5827
  captureRestCenters();
5828
+ }
5829
+ if (!isDockMagnificationPointInsideHitBounds({
5830
+ clientX,
5831
+ clientY,
5832
+ dockPlacement,
5833
+ hitBounds: hitBoundsRef.current
5834
+ })) {
5835
+ pendingPointerAxisRef.current = null;
5836
+ pointerAxisRef.current = null;
5837
+ entryRampStartedAtRef.current = null;
5838
+ scheduleAnimation();
5839
+ return;
5840
+ }
5841
+ pendingPointerAxisRef.current = dockPlacement === "left" ? clientY : clientX;
5842
+ if (!magnifyActiveRef.current) {
5680
5843
  setMagnifyActive(true);
5681
5844
  }
5682
5845
  scheduleAnimation();
@@ -5693,6 +5856,7 @@ function useDockMagnification({
5693
5856
  springsRef.current.delete(anchorKey);
5694
5857
  restCentersRef.current?.delete(anchorKey);
5695
5858
  appliedStylesRef.current.delete(anchorKey);
5859
+ hitBoundsRef.current = null;
5696
5860
  slotOrderRef.current = slotOrderRef.current.filter(
5697
5861
  (key) => key !== anchorKey
5698
5862
  );
@@ -5707,6 +5871,7 @@ function useDockMagnification({
5707
5871
  stopAnimation();
5708
5872
  pendingPointerAxisRef.current = null;
5709
5873
  pointerAxisRef.current = null;
5874
+ entryRampStartedAtRef.current = null;
5710
5875
  lastFrameTimeRef.current = null;
5711
5876
  }, [stopAnimation]);
5712
5877
  const resetMagnification = useCallback8(() => {
@@ -5717,9 +5882,11 @@ function useDockMagnification({
5717
5882
  springsRef.current.clear();
5718
5883
  appliedStylesRef.current.clear();
5719
5884
  restCentersRef.current = null;
5885
+ hitBoundsRef.current = null;
5720
5886
  slotOrderRef.current = [];
5721
5887
  pointerAxisRef.current = null;
5722
5888
  pendingPointerAxisRef.current = null;
5889
+ entryRampStartedAtRef.current = null;
5723
5890
  setMagnifyActive(false);
5724
5891
  }, [setMagnifyActive, slotRefs, stopAnimation]);
5725
5892
  useEffect6(
@@ -5827,13 +5994,67 @@ function orderWorkbenchMinimizedDockNodes(input) {
5827
5994
  }).map(({ node }) => node);
5828
5995
  }
5829
5996
 
5997
+ // src/host/minimizedDockStackPromotion.ts
5998
+ import { useEffect as useEffect7, useRef as useRef6, useState as useState6 } from "react";
5999
+ var minimizedDockStackPromotionDurationMs = 520;
6000
+ function detectMinimizedDockStackPromotion(previous, next) {
6001
+ const previousVisibleNodeIds = new Set(
6002
+ previous.filter(
6003
+ (slot) => slot.kind === "node"
6004
+ ).map((slot) => slot.node.id)
6005
+ );
6006
+ const previousStackNodeIds = previous.find((slot) => slot.kind === "stack")?.nodes.map((node) => node.id) ?? [];
6007
+ for (const slot of next) {
6008
+ if (slot.kind !== "node") {
6009
+ continue;
6010
+ }
6011
+ if (!previousVisibleNodeIds.has(slot.node.id) && previousStackNodeIds.includes(slot.node.id)) {
6012
+ return slot.node.id;
6013
+ }
6014
+ }
6015
+ return null;
6016
+ }
6017
+ function useMinimizedDockStackPromotion(slots) {
6018
+ const previousSlotsRef = useRef6(slots);
6019
+ const promotionTimerRef = useRef6(null);
6020
+ const [promotedNodeId, setPromotedNodeId] = useState6(null);
6021
+ const [stackDispatching, setStackDispatching] = useState6(false);
6022
+ useEffect7(() => {
6023
+ const promotedNodeId2 = detectMinimizedDockStackPromotion(
6024
+ previousSlotsRef.current,
6025
+ slots
6026
+ );
6027
+ previousSlotsRef.current = slots;
6028
+ if (!promotedNodeId2) {
6029
+ return void 0;
6030
+ }
6031
+ setPromotedNodeId(promotedNodeId2);
6032
+ setStackDispatching(true);
6033
+ if (promotionTimerRef.current !== null) {
6034
+ clearTimeout(promotionTimerRef.current);
6035
+ }
6036
+ promotionTimerRef.current = setTimeout(() => {
6037
+ promotionTimerRef.current = null;
6038
+ setPromotedNodeId(null);
6039
+ setStackDispatching(false);
6040
+ }, minimizedDockStackPromotionDurationMs);
6041
+ return () => {
6042
+ if (promotionTimerRef.current !== null) {
6043
+ clearTimeout(promotionTimerRef.current);
6044
+ promotionTimerRef.current = null;
6045
+ }
6046
+ };
6047
+ }, [slots]);
6048
+ return { promotedNodeId, stackDispatching };
6049
+ }
6050
+
5830
6051
  // src/host/WorkbenchHostDockPopup.tsx
5831
6052
  import {
5832
6053
  forwardRef,
5833
6054
  useCallback as useCallback9,
5834
- useEffect as useEffect7,
5835
- useRef as useRef6,
5836
- useState as useState6
6055
+ useEffect as useEffect8,
6056
+ useRef as useRef7,
6057
+ useState as useState7
5837
6058
  } from "react";
5838
6059
  import { createPortal as createPortal2 } from "react-dom";
5839
6060
  import { Button as Button3, CloseIcon, FileCreateIcon, cn } from "@tutti-os/ui-system";
@@ -5845,7 +6066,7 @@ var minimizedStackCardWidthPx = 118;
5845
6066
  var minimizedStackLeftPlacementGapPx = 4;
5846
6067
  var minimizedStackLeftDockClearancePx = 2;
5847
6068
  var workbenchLeftDockLeftInsetPx = 4;
5848
- var workbenchLeftDockViewportWidthPx = 104;
6069
+ var workbenchLeftDockViewportWidthPx = 124;
5849
6070
  var minimizedStackLeftGutterMinPx = 4;
5850
6071
  var minimizedStackLeftPopupNudgeLeftPx = 60;
5851
6072
  var minimizedStackLeftPopupNudgeDownPx = 32;
@@ -5974,7 +6195,7 @@ var dockPopupGridGapPx = 8;
5974
6195
  var dockPopupPanelPaddingInlinePx = 12;
5975
6196
  var dockPopupPanelBorderInlinePx = 2;
5976
6197
  var dockPopupPlacementGapPx = 14;
5977
- var dockPopupMinimizedStackLaunchDisappearMs = 120;
6198
+ var dockPopupMinimizedStackLaunchDisappearMs = 0;
5978
6199
  var dockPopupMinimizedStackPopupZIndex = 100300;
5979
6200
  var popupCardMagnificationRange = 160;
5980
6201
  var popupCardMaxScale = 1.16;
@@ -6036,11 +6257,11 @@ function WorkbenchHostDockPopup({
6036
6257
  const resolvedVariant = variant ?? "default";
6037
6258
  const isMinimizedStack = resolvedVariant === "minimized-stack";
6038
6259
  const createCardCount = showCreateNew === false ? 0 : 1;
6039
- const cardElementsRef = useRef6(/* @__PURE__ */ new Map());
6040
- const minimizedStackViewportRef = useRef6(null);
6041
- const [pointer, setPointer] = useState6(null);
6042
- const [minimizedStackScrollOffset, setMinimizedStackScrollOffset] = useState6(0);
6043
- const [capturedPreviewByNodeId, setCapturedPreviewByNodeId] = useState6({});
6260
+ const cardElementsRef = useRef7(/* @__PURE__ */ new Map());
6261
+ const minimizedStackViewportRef = useRef7(null);
6262
+ const [pointer, setPointer] = useState7(null);
6263
+ const [minimizedStackScrollOffset, setMinimizedStackScrollOffset] = useState7(0);
6264
+ const [capturedPreviewByNodeId, setCapturedPreviewByNodeId] = useState7({});
6044
6265
  const columnCount = Math.min(Math.max(items.length + createCardCount, 1), 3);
6045
6266
  const popupWidthPx = columnCount * dockPopupCardWidthPx + Math.max(0, columnCount - 1) * dockPopupGridGapPx + dockPopupPanelPaddingInlinePx * 2 + dockPopupPanelBorderInlinePx;
6046
6267
  const popupCenterY = anchorRect.top + anchorRect.height / 2;
@@ -6118,7 +6339,7 @@ function WorkbenchHostDockPopup({
6118
6339
  },
6119
6340
  []
6120
6341
  );
6121
- useEffect7(() => {
6342
+ useEffect8(() => {
6122
6343
  if (!isLeftMinimizedStack) {
6123
6344
  return;
6124
6345
  }
@@ -6130,7 +6351,7 @@ function WorkbenchHostDockPopup({
6130
6351
  document.body.removeAttribute("data-desktop-dock-minimized-stack-open");
6131
6352
  };
6132
6353
  }, [isLeftMinimizedStack]);
6133
- useEffect7(() => {
6354
+ useEffect8(() => {
6134
6355
  const handlePointerDown = (event) => {
6135
6356
  if (!(event.target instanceof Element)) {
6136
6357
  onClose();
@@ -6156,13 +6377,13 @@ function WorkbenchHostDockPopup({
6156
6377
  };
6157
6378
  }, [onClose]);
6158
6379
  const previewCaptureKey = items.map((item) => `${item.node.id}:${item.previewImageUrl ?? ""}`).join("|");
6159
- useEffect7(() => {
6380
+ useEffect8(() => {
6160
6381
  if (!isMinimizedStack) {
6161
6382
  return;
6162
6383
  }
6163
6384
  setMinimizedStackScrollOffset(initialMinimizedStackScrollOffset);
6164
6385
  }, [initialMinimizedStackScrollOffset, isMinimizedStack, items.length]);
6165
- useEffect7(() => {
6386
+ useEffect8(() => {
6166
6387
  if (!isMinimizedStack) {
6167
6388
  return;
6168
6389
  }
@@ -6183,7 +6404,7 @@ function WorkbenchHostDockPopup({
6183
6404
  viewport.addEventListener("wheel", handleWheel, { passive: false });
6184
6405
  return () => viewport.removeEventListener("wheel", handleWheel);
6185
6406
  }, [isMinimizedStack, minimizedStackMaxScrollOffset]);
6186
- useEffect7(() => {
6407
+ useEffect8(() => {
6187
6408
  let cancelled = false;
6188
6409
  const missingItems = items.filter(
6189
6410
  (item) => !item.previewImageUrl && !capturedPreviewByNodeId[item.node.id]
@@ -6404,9 +6625,9 @@ var WorkbenchHostDockPopupCard = forwardRef(function WorkbenchHostDockPopupCard2
6404
6625
  }, ref) {
6405
6626
  const title = item.title?.trim() || item.node.title;
6406
6627
  const isMinimizedStack = variant === "minimized-stack";
6407
- const [isLaunching, setIsLaunching] = useState6(false);
6408
- const launchTimerRef = useRef6(null);
6409
- useEffect7(
6628
+ const [isLaunching, setIsLaunching] = useState7(false);
6629
+ const launchTimerRef = useRef7(null);
6630
+ useEffect8(
6410
6631
  () => () => {
6411
6632
  if (launchTimerRef.current !== null) {
6412
6633
  clearTimeout(launchTimerRef.current);
@@ -6530,7 +6751,12 @@ function stripDockDescriptionTerminalPunctuation(value) {
6530
6751
  return trimmed.replace(/[。..]+$/u, "");
6531
6752
  }
6532
6753
  function isDockVisualMutationActive(element) {
6533
- return element?.hasAttribute("data-dock-pointer-active") === true || element?.hasAttribute("data-dock-hover-panel-open") === true;
6754
+ if (!element) {
6755
+ return false;
6756
+ }
6757
+ return element.hasAttribute("data-dock-pointer-active") || element.hasAttribute("data-dock-hover-panel-open") || element.querySelector(
6758
+ '[data-collapsing="true"], [data-presence="entering"], [data-presence="exiting"], [data-stack-dispatching="true"], [data-promoted-from-stack="true"]'
6759
+ ) !== null;
6534
6760
  }
6535
6761
  function WorkbenchHostDock({
6536
6762
  context,
@@ -6550,45 +6776,49 @@ function WorkbenchHostDock({
6550
6776
  () => new Set(context.minimizedNodes.map((node) => node.id)),
6551
6777
  [context.minimizedNodes]
6552
6778
  );
6553
- const dockMeasureRef = useRef7(null);
6554
- const dockItemsRef = useRef7(null);
6555
- const hoverPanelRef = useRef7(null);
6556
- const hoverPanelCloseTimerRef = useRef7(
6779
+ const dockMeasureRef = useRef8(null);
6780
+ const dockItemsRef = useRef8(null);
6781
+ const hoverPanelRef = useRef8(null);
6782
+ const hoverPanelCloseTimerRef = useRef8(
6557
6783
  null
6558
6784
  );
6559
- const hoverPanelOpenTimerRef = useRef7(
6785
+ const hoverPanelOpenTimerRef = useRef8(
6560
6786
  null
6561
6787
  );
6562
- const hoverPanelScheduledPointRef = useRef7(null);
6563
- const minimizedLaunchTimersRef = useRef7(
6564
- /* @__PURE__ */ new Map()
6565
- );
6566
- const hoverPanelRestTargetRef = useRef7(null);
6567
- const activeHoverPanelRef = useRef7(
6788
+ const hoverPanelScheduledPointRef = useRef8(null);
6789
+ const hoverPanelRestTargetRef = useRef8(null);
6790
+ const activeHoverPanelRef = useRef8(
6568
6791
  null
6569
6792
  );
6570
- const pendingDockStateRefreshRef = useRef7(false);
6571
- const slotRefs = useRef7(/* @__PURE__ */ new Map());
6572
- const previousAttentionTokenByEntryId = useRef7(/* @__PURE__ */ new Map());
6573
- const attentionTimeouts = useRef7(
6793
+ const pendingDockStateRefreshRef = useRef8(false);
6794
+ const slotRefs = useRef8(/* @__PURE__ */ new Map());
6795
+ const previousAttentionTokenByEntryId = useRef8(/* @__PURE__ */ new Map());
6796
+ const attentionTimeouts = useRef8(
6574
6797
  /* @__PURE__ */ new Map()
6575
6798
  );
6576
- const [activeAttentionEntryIds, setActiveAttentionEntryIds] = useState7(/* @__PURE__ */ new Set());
6577
- const [activePopup, setActivePopup] = useState7(null);
6578
- const [activeHoverPanel, setActiveHoverPanel] = useState7(null);
6579
- const [activeMinimizedStackPopup, setActiveMinimizedStackPopup] = useState7(null);
6580
- const [pendingActionKeys, setPendingActionKeys] = useState7(
6799
+ const [activeAttentionEntryIds, setActiveAttentionEntryIds] = useState8(/* @__PURE__ */ new Set());
6800
+ const [activePopup, setActivePopup] = useState8(null);
6801
+ const [activeHoverPanel, setActiveHoverPanel] = useState8(null);
6802
+ const [activeMinimizedStackPopup, setActiveMinimizedStackPopup] = useState8(null);
6803
+ const [pendingActionKeys, setPendingActionKeys] = useState8(
6581
6804
  () => /* @__PURE__ */ new Set()
6582
6805
  );
6583
- const [dockFrameSize, setDockFrameSize] = useState7(null);
6806
+ const [dockFrameSize, setDockFrameSize] = useState8(null);
6584
6807
  const { triggerDockBounce } = useDockBounce(slotRefs);
6585
- const [dockScrollState, setDockScrollState] = useState7(() => ({
6808
+ const [dockScrollState, setDockScrollState] = useState8(() => ({
6586
6809
  canScrollBackward: false,
6587
6810
  canScrollForward: false,
6588
6811
  hasOverflow: false
6589
6812
  }));
6590
- const [dockStateRevision, setDockStateRevision] = useState7(0);
6591
- const [externalStateRevision, setExternalStateRevision] = useState7(0);
6813
+ const [dockStateRevision, setDockStateRevision] = useState8(0);
6814
+ const [externalStateRevision, setExternalStateRevision] = useState8(0);
6815
+ const [
6816
+ collapsingMinimizedLaunchAnchorKeys,
6817
+ setCollapsingMinimizedLaunchAnchorKeys
6818
+ ] = useState8(() => /* @__PURE__ */ new Set());
6819
+ const collapsingMinimizedLaunchTimerRef = useRef8(
6820
+ /* @__PURE__ */ new Map()
6821
+ );
6592
6822
  const clearHoverPanelCloseTimer = useCallback10(() => {
6593
6823
  if (hoverPanelCloseTimerRef.current === null) {
6594
6824
  return;
@@ -6604,17 +6834,47 @@ function WorkbenchHostDock({
6604
6834
  hoverPanelOpenTimerRef.current = null;
6605
6835
  hoverPanelScheduledPointRef.current = null;
6606
6836
  }, []);
6607
- useEffect8(
6837
+ useEffect9(
6608
6838
  () => () => {
6609
6839
  clearHoverPanelCloseTimer();
6610
6840
  clearHoverPanelOpenTimer();
6611
- for (const timer of minimizedLaunchTimersRef.current.values()) {
6841
+ for (const timer of collapsingMinimizedLaunchTimerRef.current.values()) {
6612
6842
  clearTimeout(timer);
6613
6843
  }
6614
- minimizedLaunchTimersRef.current.clear();
6844
+ collapsingMinimizedLaunchTimerRef.current.clear();
6615
6845
  },
6616
6846
  [clearHoverPanelCloseTimer, clearHoverPanelOpenTimer]
6617
6847
  );
6848
+ const clearCollapsingMinimizedLaunch = useCallback10((anchorKey) => {
6849
+ const timer = collapsingMinimizedLaunchTimerRef.current.get(anchorKey);
6850
+ if (timer) {
6851
+ clearTimeout(timer);
6852
+ collapsingMinimizedLaunchTimerRef.current.delete(anchorKey);
6853
+ }
6854
+ setCollapsingMinimizedLaunchAnchorKeys((current) => {
6855
+ if (!current.has(anchorKey)) {
6856
+ return current;
6857
+ }
6858
+ const next = new Set(current);
6859
+ next.delete(anchorKey);
6860
+ return next;
6861
+ });
6862
+ }, []);
6863
+ const scheduleCollapsingMinimizedLaunchClear = useCallback10(
6864
+ (anchorKey) => {
6865
+ const existing = collapsingMinimizedLaunchTimerRef.current.get(anchorKey);
6866
+ if (existing) {
6867
+ clearTimeout(existing);
6868
+ }
6869
+ collapsingMinimizedLaunchTimerRef.current.set(
6870
+ anchorKey,
6871
+ setTimeout(() => {
6872
+ clearCollapsingMinimizedLaunch(anchorKey);
6873
+ }, minimizedDockSlotLayoutAnimationMs)
6874
+ );
6875
+ },
6876
+ [clearCollapsingMinimizedLaunch]
6877
+ );
6618
6878
  useLayoutEffect4(() => {
6619
6879
  const element = dockMeasureRef.current;
6620
6880
  if (!element || typeof window === "undefined") {
@@ -6659,7 +6919,7 @@ function WorkbenchHostDock({
6659
6919
  pendingDockStateRefreshRef.current = false;
6660
6920
  setDockStateRevision((revision) => revision + 1);
6661
6921
  }, []);
6662
- useEffect8(() => {
6922
+ useEffect9(() => {
6663
6923
  if (!dockStateSource) {
6664
6924
  return void 0;
6665
6925
  }
@@ -6693,6 +6953,7 @@ function WorkbenchHostDock({
6693
6953
  }),
6694
6954
  [context.nodes, nodeDefinitions]
6695
6955
  );
6956
+ const { promotedNodeId, stackDispatching } = useMinimizedDockStackPromotion(minimizedDockSlots);
6696
6957
  const dockItems = useMemo5(
6697
6958
  () => createWorkbenchHostDockItems({
6698
6959
  minimizedDockSlots,
@@ -6700,7 +6961,10 @@ function WorkbenchHostDock({
6700
6961
  }),
6701
6962
  [minimizedDockSlots, resolvedEntries]
6702
6963
  );
6703
- const presentDockItems = useDockPresenceItems(dockItems);
6964
+ const presentDockItems = useDockPresenceItems(
6965
+ dockItems,
6966
+ (nodeId) => context.genie.shouldAnimateMinimizedDockEnter(nodeId)
6967
+ );
6704
6968
  const dockWidth = useMemo5(
6705
6969
  () => resolveWorkbenchHostDockItemsWidth(dockItems),
6706
6970
  [dockItems]
@@ -6726,7 +6990,7 @@ function WorkbenchHostDock({
6726
6990
  }
6727
6991
  dockMeasureRef.current?.removeAttribute("data-dock-hover-panel-open");
6728
6992
  }, []);
6729
- useEffect8(() => {
6993
+ useEffect9(() => {
6730
6994
  activeHoverPanelRef.current = activeHoverPanel;
6731
6995
  }, [activeHoverPanel]);
6732
6996
  const closeHoverPanelImmediate = useCallback10(
@@ -6752,6 +7016,23 @@ function WorkbenchHostDock({
6752
7016
  setDockHoverPanelOpen
6753
7017
  ]
6754
7018
  );
7019
+ const scheduleHoverPanelClose = useCallback10(
7020
+ (entryId) => {
7021
+ clearHoverPanelOpenTimer();
7022
+ clearHoverPanelCloseTimer();
7023
+ hoverPanelCloseTimerRef.current = setTimeout(() => {
7024
+ hoverPanelCloseTimerRef.current = null;
7025
+ closeHoverPanelImmediate(entryId);
7026
+ handleDockPointerLeave();
7027
+ }, dockHoverPanelCloseDelayMs);
7028
+ },
7029
+ [
7030
+ clearHoverPanelCloseTimer,
7031
+ clearHoverPanelOpenTimer,
7032
+ closeHoverPanelImmediate,
7033
+ handleDockPointerLeave
7034
+ ]
7035
+ );
6755
7036
  const showHoverPanel = useCallback10(
6756
7037
  (entryId, anchorKey, anchorElement) => {
6757
7038
  const dockElement = dockMeasureRef.current;
@@ -6815,6 +7096,38 @@ function WorkbenchHostDock({
6815
7096
  },
6816
7097
  []
6817
7098
  );
7099
+ const isPointerInsideActiveHoverPanelRegion = useCallback10(
7100
+ (clientX, clientY) => {
7101
+ const activeHoverPanel2 = activeHoverPanelRef.current;
7102
+ if (!activeHoverPanel2) {
7103
+ return false;
7104
+ }
7105
+ const anchorSlot = slotRefs.current.get(activeHoverPanel2.anchorKey);
7106
+ const panel = hoverPanelRef.current;
7107
+ if (!anchorSlot || !panel || !anchorSlot.isConnected || !panel.isConnected) {
7108
+ return false;
7109
+ }
7110
+ const anchorRect = anchorSlot.getBoundingClientRect();
7111
+ const panelRect = panel.getBoundingClientRect();
7112
+ return rectContainsPoint(
7113
+ anchorRect,
7114
+ clientX,
7115
+ clientY,
7116
+ dockHoverPanelHitSlopPx
7117
+ ) || rectContainsPoint(
7118
+ panelRect,
7119
+ clientX,
7120
+ clientY,
7121
+ dockHoverPanelHitSlopPx
7122
+ ) || rectContainsPoint(
7123
+ createHoverPanelBridgeRect(anchorRect, panelRect),
7124
+ clientX,
7125
+ clientY,
7126
+ dockHoverPanelBridgeSlopPx
7127
+ );
7128
+ },
7129
+ []
7130
+ );
6818
7131
  const scheduleHoverPanelAtPointAfterRest = useCallback10(
6819
7132
  (clientX, clientY) => {
6820
7133
  const scheduledPoint = hoverPanelScheduledPointRef.current;
@@ -6857,6 +7170,7 @@ function WorkbenchHostDock({
6857
7170
  hoverPanelRestTargetRef.current = null;
6858
7171
  clearHoverPanelOpenTimer();
6859
7172
  closeHoverPanelImmediate();
7173
+ pauseDockMagnification();
6860
7174
  if (!anchorKey) {
6861
7175
  return false;
6862
7176
  }
@@ -6864,6 +7178,7 @@ function WorkbenchHostDock({
6864
7178
  if (!slotElement) {
6865
7179
  return false;
6866
7180
  }
7181
+ clearSlotMagnification(anchorKey);
6867
7182
  if (slotElement.dataset.collapsing === "true") {
6868
7183
  return true;
6869
7184
  }
@@ -6879,30 +7194,34 @@ function WorkbenchHostDock({
6879
7194
  slotElement.dataset.collapsing = "true";
6880
7195
  return true;
6881
7196
  },
6882
- [clearHoverPanelOpenTimer, closeHoverPanelImmediate]
7197
+ [
7198
+ clearHoverPanelOpenTimer,
7199
+ clearSlotMagnification,
7200
+ closeHoverPanelImmediate,
7201
+ pauseDockMagnification
7202
+ ]
6883
7203
  );
6884
7204
  const runDockMinimizedLaunchAfterCollapse = useCallback10(
6885
7205
  (anchorKey, launch) => {
6886
- const shouldDelayLaunch = beginDockMinimizedInteraction(anchorKey);
6887
- if (!shouldDelayLaunch) {
6888
- launch();
6889
- return;
6890
- }
6891
- if (minimizedLaunchTimersRef.current.has(anchorKey)) {
6892
- return;
6893
- }
6894
- const timer = setTimeout(() => {
6895
- minimizedLaunchTimersRef.current.delete(anchorKey);
6896
- launch();
6897
- }, dockMinimizedSlotCollapseLaunchDelayMs);
6898
- minimizedLaunchTimersRef.current.set(anchorKey, timer);
7206
+ beginDockMinimizedInteraction(anchorKey);
7207
+ setCollapsingMinimizedLaunchAnchorKeys((current) => {
7208
+ const next = new Set(current);
7209
+ next.add(anchorKey);
7210
+ return next;
7211
+ });
7212
+ scheduleCollapsingMinimizedLaunchClear(anchorKey);
7213
+ launch();
6899
7214
  },
6900
- [beginDockMinimizedInteraction]
7215
+ [beginDockMinimizedInteraction, scheduleCollapsingMinimizedLaunchClear]
6901
7216
  );
6902
7217
  const handleDockPointerTravel = useCallback10(
6903
7218
  (clientX, clientY) => {
6904
7219
  if (activeHoverPanelRef.current !== null) {
6905
- closeHoverPanelImmediate();
7220
+ if (isPointerInsideActiveHoverPanelRegion(clientX, clientY)) {
7221
+ clearHoverPanelCloseTimer();
7222
+ return;
7223
+ }
7224
+ scheduleHoverPanelClose(activeHoverPanelRef.current.entryId);
6906
7225
  handleDockPointerLeave();
6907
7226
  return;
6908
7227
  }
@@ -6910,9 +7229,11 @@ function WorkbenchHostDock({
6910
7229
  scheduleHoverPanelAtPointAfterRest(clientX, clientY);
6911
7230
  },
6912
7231
  [
6913
- closeHoverPanelImmediate,
7232
+ clearHoverPanelCloseTimer,
6914
7233
  handleDockPointerMove,
6915
7234
  handleDockPointerLeave,
7235
+ isPointerInsideActiveHoverPanelRegion,
7236
+ scheduleHoverPanelClose,
6916
7237
  scheduleHoverPanelAtPointAfterRest
6917
7238
  ]
6918
7239
  );
@@ -6982,7 +7303,7 @@ function WorkbenchHostDock({
6982
7303
  )
6983
7304
  )
6984
7305
  );
6985
- useEffect8(() => {
7306
+ useEffect9(() => {
6986
7307
  const shouldSubscribe = activePopup !== null || hasMinimizedPreviewCapture;
6987
7308
  if (!shouldSubscribe || !externalStateSource?.subscribe) {
6988
7309
  return void 0;
@@ -6991,7 +7312,7 @@ function WorkbenchHostDock({
6991
7312
  setExternalStateRevision((revision) => revision + 1);
6992
7313
  });
6993
7314
  }, [activePopup, externalStateSource, hasMinimizedPreviewCapture]);
6994
- useEffect8(() => {
7315
+ useEffect9(() => {
6995
7316
  const nextAttentionIds = /* @__PURE__ */ new Set();
6996
7317
  for (const entry of renderedDockEntries) {
6997
7318
  const nextToken = entry.attentionToken ?? null;
@@ -7032,7 +7353,7 @@ function WorkbenchHostDock({
7032
7353
  );
7033
7354
  }
7034
7355
  }, [renderedDockEntries]);
7035
- useEffect8(
7356
+ useEffect9(
7036
7357
  () => () => {
7037
7358
  for (const timeout of attentionTimeouts.current.values()) {
7038
7359
  globalThis.clearTimeout(timeout);
@@ -7206,6 +7527,7 @@ function WorkbenchHostDock({
7206
7527
  "aria-disabled": clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes,
7207
7528
  className: "desktop-dock__btn",
7208
7529
  "data-interactive": clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes ? "false" : "true",
7530
+ title: entry.label,
7209
7531
  type: "button",
7210
7532
  onPointerDown: () => {
7211
7533
  if (clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes) {
@@ -7352,17 +7674,7 @@ function WorkbenchHostDock({
7352
7674
  closeHoverPanelImmediate(entry.id);
7353
7675
  handleDockPointerLeave();
7354
7676
  },
7355
- children: hasHoverPanel ? dockButton2 : /* @__PURE__ */ jsxs6(Tooltip, { children: [
7356
- /* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: dockButton2 }),
7357
- /* @__PURE__ */ jsx10(
7358
- TooltipContent,
7359
- {
7360
- side: dockPlacement === "left" ? "right" : "top",
7361
- sideOffset: 26,
7362
- children: entry.label
7363
- }
7364
- )
7365
- ] })
7677
+ children: dockButton2
7366
7678
  },
7367
7679
  dockItem.key
7368
7680
  );
@@ -7378,6 +7690,7 @@ function WorkbenchHostDock({
7378
7690
  "aria-label": i18n.t("minimizedWindows"),
7379
7691
  className: "desktop-dock__btn desktop-dock__minimized-btn",
7380
7692
  "data-interactive": "true",
7693
+ title: i18n.t("minimizedWindows"),
7381
7694
  type: "button",
7382
7695
  onPointerDown: () => {
7383
7696
  beginDockMinimizedInteraction();
@@ -7453,17 +7766,8 @@ function WorkbenchHostDock({
7453
7766
  "data-popup-active": stackPopupActive,
7454
7767
  "data-presence": dockItem.presence,
7455
7768
  "data-section-id": "minimized",
7456
- children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
7457
- /* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: stackButton }),
7458
- /* @__PURE__ */ jsx10(
7459
- TooltipContent,
7460
- {
7461
- side: dockPlacement === "left" ? "right" : "top",
7462
- sideOffset: 26,
7463
- children: i18n.t("minimizedWindows")
7464
- }
7465
- )
7466
- ] })
7769
+ "data-stack-dispatching": stackDispatching ? "true" : void 0,
7770
+ children: stackButton
7467
7771
  },
7468
7772
  dockItem.key
7469
7773
  );
@@ -7475,6 +7779,7 @@ function WorkbenchHostDock({
7475
7779
  "aria-label": i18n.t("launch", { title: node.title }),
7476
7780
  className: "desktop-dock__btn desktop-dock__minimized-btn",
7477
7781
  "data-interactive": "true",
7782
+ title: node.title,
7478
7783
  type: "button",
7479
7784
  onPointerDown: () => {
7480
7785
  beginDockMinimizedInteraction(slot.anchorKey);
@@ -7510,22 +7815,14 @@ function WorkbenchHostDock({
7510
7815
  {
7511
7816
  ref: registerDockSlot(slot.anchorKey),
7512
7817
  className: "desktop-dock__slot desktop-dock__slot--minimized",
7818
+ "data-collapsing": collapsingMinimizedLaunchAnchorKeys.has(slot.anchorKey) ? "true" : void 0,
7513
7819
  "data-desktop-dock-anchor-key": slot.anchorKey,
7514
7820
  "data-desktop-dock-slot": "true",
7515
7821
  "data-node-state": "minimized",
7516
7822
  "data-presence": dockItem.presence,
7823
+ "data-promoted-from-stack": promotedNodeId === node.id ? "true" : void 0,
7517
7824
  "data-section-id": "minimized",
7518
- children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
7519
- /* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: dockButton }),
7520
- /* @__PURE__ */ jsx10(
7521
- TooltipContent,
7522
- {
7523
- side: dockPlacement === "left" ? "right" : "top",
7524
- sideOffset: 26,
7525
- children: node.title
7526
- }
7527
- )
7528
- ] })
7825
+ children: dockButton
7529
7826
  },
7530
7827
  dockItem.key
7531
7828
  );
@@ -7572,7 +7869,7 @@ function WorkbenchHostDock({
7572
7869
  if (relatedTarget instanceof Node && anchorSlot?.contains(relatedTarget)) {
7573
7870
  return;
7574
7871
  }
7575
- closeHoverPanelImmediate(activeHoverPanel.entryId);
7872
+ scheduleHoverPanelClose(activeHoverPanel.entryId);
7576
7873
  handleDockPointerLeave();
7577
7874
  }
7578
7875
  }
@@ -7698,13 +7995,15 @@ function WorkbenchHostDock({
7698
7995
  onCreateNew: () => void 0,
7699
7996
  onSelectNode: (nodeId) => {
7700
7997
  closePopup();
7701
- context.genie.launchNodeFromAnchor(
7702
- activeMinimizedStackSlot.anchorKey,
7998
+ const anchorKey = resolveWorkbenchMinimizedDockAnchorKeyForNode({
7703
7999
  nodeId,
7704
- () => {
8000
+ slots: minimizedDockSlots
8001
+ }) ?? activeMinimizedStackSlot.anchorKey;
8002
+ runDockMinimizedLaunchAfterCollapse(anchorKey, () => {
8003
+ context.genie.launchNodeFromAnchor(anchorKey, nodeId, () => {
7705
8004
  host.focusNode(nodeId);
7706
- }
7707
- );
8005
+ });
8006
+ });
7708
8007
  },
7709
8008
  showCreateNew: false,
7710
8009
  resolveDockPreviewCacheKey: (node) => resolveDockPreviewCacheKey(workspaceId, node),
@@ -7722,10 +8021,10 @@ function WorkbenchHostDockMinimizedNodePreview({
7722
8021
  node,
7723
8022
  workspaceId
7724
8023
  }) {
7725
- const [previewImageUrl, setPreviewImageUrl] = useState7(
8024
+ const [previewImageUrl, setPreviewImageUrl] = useState8(
7726
8025
  () => readCachedWorkbenchNodePreviewImage(node.id)
7727
8026
  );
7728
- useEffect8(() => {
8027
+ useEffect9(() => {
7729
8028
  let cancelled = false;
7730
8029
  const cachedPreviewImageUrl = readCachedWorkbenchNodePreviewImage(node.id);
7731
8030
  setPreviewImageUrl(cachedPreviewImageUrl);
@@ -7980,19 +8279,52 @@ function createWorkbenchHostDockItems({
7980
8279
  }
7981
8280
  return items;
7982
8281
  }
7983
- function useDockPresenceItems(items) {
7984
- const latestItemsByKey = useRef7(/* @__PURE__ */ new Map());
8282
+ function resolveMinimizedDockItemNodeId(item) {
8283
+ if (item.kind !== "minimized" || item.slot.kind !== "node") {
8284
+ return null;
8285
+ }
8286
+ return item.slot.node.id;
8287
+ }
8288
+ function resolveNextDockItemPresence(item, initialized, previousPresence, shouldAnimateMinimizedDockEnter) {
8289
+ if (!initialized) {
8290
+ return "present";
8291
+ }
8292
+ if (item.key === "separator:minimized") {
8293
+ return "present";
8294
+ }
8295
+ if (item.kind === "minimized") {
8296
+ const nodeId = resolveMinimizedDockItemNodeId(item);
8297
+ if (nodeId && shouldAnimateMinimizedDockEnter(nodeId)) {
8298
+ if (previousPresence === "exiting") {
8299
+ return "entering";
8300
+ }
8301
+ return previousPresence ?? "entering";
8302
+ }
8303
+ return "present";
8304
+ }
8305
+ if (previousPresence === "exiting") {
8306
+ return "entering";
8307
+ }
8308
+ return previousPresence ?? "entering";
8309
+ }
8310
+ function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
8311
+ const latestItemsByKey = useRef8(/* @__PURE__ */ new Map());
8312
+ const shouldAnimateMinimizedDockEnterRef = useRef8(
8313
+ shouldAnimateMinimizedDockEnter
8314
+ );
7985
8315
  latestItemsByKey.current = new Map(items.map((item) => [item.key, item]));
8316
+ shouldAnimateMinimizedDockEnterRef.current = shouldAnimateMinimizedDockEnter;
7986
8317
  const itemKeys = items.map((item) => item.key).join("\0");
7987
- const [presentItems, setPresentItems] = useState7(
8318
+ const [presentItems, setPresentItems] = useState8(
7988
8319
  () => items.map((item) => ({
7989
8320
  item,
7990
8321
  key: item.key,
7991
8322
  presence: "present"
7992
8323
  }))
7993
8324
  );
7994
- const initialized = useRef7(false);
7995
- useEffect8(() => {
8325
+ const initialized = useRef8(false);
8326
+ useEffect9(() => {
8327
+ let nextSettleMs = dockPresenceAnimationMs;
7996
8328
  setPresentItems((current) => {
7997
8329
  const nextSourceItems = [...latestItemsByKey.current.values()];
7998
8330
  const currentByKey = new Map(current.map((item) => [item.key, item]));
@@ -8033,7 +8365,12 @@ function useDockPresenceItems(items) {
8033
8365
  nextItems.push({
8034
8366
  item,
8035
8367
  key: item.key,
8036
- presence: initialized.current ? currentByKey.get(item.key)?.presence === "exiting" ? "entering" : currentByKey.get(item.key)?.presence ?? "entering" : "present"
8368
+ presence: resolveNextDockItemPresence(
8369
+ item,
8370
+ initialized.current,
8371
+ currentByKey.get(item.key)?.presence,
8372
+ shouldAnimateMinimizedDockEnterRef.current
8373
+ )
8037
8374
  });
8038
8375
  }
8039
8376
  for (const currentItem of current) {
@@ -8048,9 +8385,15 @@ function useDockPresenceItems(items) {
8048
8385
  });
8049
8386
  }
8050
8387
  }
8051
- return nextItems.filter(
8388
+ const filteredItems = nextItems.filter(
8052
8389
  (item) => nextByKey.has(item.key) || item.presence === "exiting"
8053
8390
  );
8391
+ if (filteredItems.some(
8392
+ (item) => item.item.kind === "minimized" && (item.presence === "entering" || item.presence === "exiting")
8393
+ )) {
8394
+ nextSettleMs = minimizedDockSlotLayoutAnimationMs;
8395
+ }
8396
+ return filteredItems;
8054
8397
  });
8055
8398
  initialized.current = true;
8056
8399
  const timeout = globalThis.setTimeout(() => {
@@ -8059,7 +8402,7 @@ function useDockPresenceItems(items) {
8059
8402
  (item) => item.presence === "entering" ? { ...item, presence: "present" } : item
8060
8403
  )
8061
8404
  );
8062
- }, dockPresenceAnimationMs);
8405
+ }, nextSettleMs);
8063
8406
  return () => globalThis.clearTimeout(timeout);
8064
8407
  }, [itemKeys]);
8065
8408
  return presentItems.map((presentItem) => {
@@ -8068,9 +8411,8 @@ function useDockPresenceItems(items) {
8068
8411
  });
8069
8412
  }
8070
8413
  var DOCK_BOUNCE_MS = 600;
8071
- var dockMinimizedSlotCollapseLaunchDelayMs = 260;
8072
8414
  function useDockBounce(slotRefs) {
8073
- const timeoutsRef = useRef7(/* @__PURE__ */ new Map());
8415
+ const timeoutsRef = useRef8(/* @__PURE__ */ new Map());
8074
8416
  const clearDockBounce = useCallback10(
8075
8417
  (anchorKey) => {
8076
8418
  const timeout = timeoutsRef.current.get(anchorKey);
@@ -8085,7 +8427,7 @@ function useDockBounce(slotRefs) {
8085
8427
  },
8086
8428
  [slotRefs]
8087
8429
  );
8088
- useEffect8(
8430
+ useEffect9(
8089
8431
  () => () => {
8090
8432
  for (const anchorKey of timeoutsRef.current.keys()) {
8091
8433
  clearDockBounce(anchorKey);
@@ -8128,9 +8470,29 @@ function resolveWorkbenchHostDockItemsWidth(items) {
8128
8470
  return itemWidth + gapWidth + dockItemsHorizontalPaddingPx;
8129
8471
  }
8130
8472
  var dockHoverPanelOpenDelayMs = 450;
8473
+ var dockHoverPanelCloseDelayMs = 160;
8131
8474
  var dockHoverPanelHitSlopPx = 12;
8475
+ var dockHoverPanelBridgeSlopPx = 6;
8132
8476
  var dockHoverPanelPointerRestTolerancePx = 4;
8477
+ function rectContainsPoint(rect, clientX, clientY, slopPx = 0) {
8478
+ return clientX >= rect.left - slopPx && clientX <= rect.right + slopPx && clientY >= rect.top - slopPx && clientY <= rect.bottom + slopPx;
8479
+ }
8480
+ function createHoverPanelBridgeRect(anchor, panel) {
8481
+ if (anchor.right <= panel.left || panel.right <= anchor.left) {
8482
+ const left2 = Math.min(anchor.right, panel.right);
8483
+ const right2 = Math.max(anchor.left, panel.left);
8484
+ const top2 = Math.max(anchor.top, panel.top);
8485
+ const bottom2 = Math.min(anchor.bottom, panel.bottom);
8486
+ return new DOMRect(left2, top2, right2 - left2, Math.max(0, bottom2 - top2));
8487
+ }
8488
+ const left = Math.max(anchor.left, panel.left);
8489
+ const right = Math.min(anchor.right, panel.right);
8490
+ const top = Math.min(anchor.bottom, panel.bottom);
8491
+ const bottom = Math.max(anchor.top, panel.top);
8492
+ return new DOMRect(left, top, Math.max(0, right - left), bottom - top);
8493
+ }
8133
8494
  var dockPresenceAnimationMs = 300;
8495
+ var minimizedDockSlotLayoutAnimationMs = 720;
8134
8496
  var dockItemsGapPx = 10.8;
8135
8497
  var dockItemsHorizontalPaddingPx = 12.6;
8136
8498
  var dockSeparatorOuterWidthPx = 8.1;
@@ -8644,6 +9006,7 @@ export {
8644
9006
  createWorkbenchSnapshotFromState,
8645
9007
  createWorkbenchStateFromSnapshot,
8646
9008
  createWorkbenchWindowChromeI18nRuntime,
9009
+ getWorkbenchLayoutFrame,
8647
9010
  resolveWorkbenchHostPrepareClose,
8648
9011
  useWorkbenchSelector,
8649
9012
  workbenchFocusInputActivationType,