@tutti-os/workbench-surface 0.0.15 → 0.0.17

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
@@ -3896,6 +3896,8 @@ var initializedMetadataKeys = [
3896
3896
  ];
3897
3897
  var COMPACT_LAUNCH_WIDTH_THRESHOLD = 1440;
3898
3898
  var COMPACT_LAUNCH_FRAME_SCALE = 0.85;
3899
+ var closedDockWindowFramesMetadataKey = "workbenchHostClosedDockWindowFrames";
3900
+ var maxClosedDockWindowFrameEntries = 128;
3899
3901
  function createDefaultLaunchResult(definition, input) {
3900
3902
  return {
3901
3903
  defaultFrame: definition.frame,
@@ -4013,6 +4015,81 @@ function resolveWorkbenchLaunchedHostNodeFrame(input) {
4013
4015
  constraints: input.currentState.layoutConstraints
4014
4016
  });
4015
4017
  }
4018
+ function closedDockWindowFrameEntryKey(input) {
4019
+ return JSON.stringify([input.typeId, input.dockEntryId]);
4020
+ }
4021
+ function readClosedDockWindowFrameEntries(metadata) {
4022
+ const value = metadata?.[closedDockWindowFramesMetadataKey];
4023
+ if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.entries)) {
4024
+ return /* @__PURE__ */ new Map();
4025
+ }
4026
+ const entries = /* @__PURE__ */ new Map();
4027
+ for (const entry of value.entries.slice(0, maxClosedDockWindowFrameEntries)) {
4028
+ const normalized = normalizeClosedDockWindowFrameEntry(entry);
4029
+ if (!normalized) {
4030
+ continue;
4031
+ }
4032
+ entries.set(closedDockWindowFrameEntryKey(normalized), normalized);
4033
+ }
4034
+ return entries;
4035
+ }
4036
+ function writeClosedDockWindowFrameEntries(metadata, entries) {
4037
+ const normalizedEntries = Array.from(entries).map(normalizeClosedDockWindowFrameEntry).filter((entry) => entry !== null).slice(-maxClosedDockWindowFrameEntries);
4038
+ const nextMetadata = { ...metadata ?? {} };
4039
+ if (normalizedEntries.length === 0) {
4040
+ delete nextMetadata[closedDockWindowFramesMetadataKey];
4041
+ } else {
4042
+ nextMetadata[closedDockWindowFramesMetadataKey] = {
4043
+ version: 1,
4044
+ entries: normalizedEntries
4045
+ };
4046
+ }
4047
+ return Object.keys(nextMetadata).length > 0 ? nextMetadata : void 0;
4048
+ }
4049
+ function normalizeClosedDockWindowFrameEntry(value) {
4050
+ if (!isRecord(value)) {
4051
+ return null;
4052
+ }
4053
+ const typeId = normalizeNonEmptyString(value.typeId);
4054
+ const dockEntryId = normalizeNonEmptyString(value.dockEntryId);
4055
+ const frame = normalizeWorkbenchFrame(value.frame);
4056
+ if (!typeId || !dockEntryId || !frame) {
4057
+ return null;
4058
+ }
4059
+ return {
4060
+ dockEntryId,
4061
+ frame,
4062
+ typeId
4063
+ };
4064
+ }
4065
+ function normalizeWorkbenchFrame(value) {
4066
+ if (!isRecord(value)) {
4067
+ return null;
4068
+ }
4069
+ const x = normalizeFiniteNumber(value.x);
4070
+ const y = normalizeFiniteNumber(value.y);
4071
+ const width = normalizeFiniteNumber(value.width);
4072
+ const height = normalizeFiniteNumber(value.height);
4073
+ if (x === null || y === null || width === null || height === null) {
4074
+ return null;
4075
+ }
4076
+ if (width < 160 || height < 120) {
4077
+ return null;
4078
+ }
4079
+ return { height, width, x, y };
4080
+ }
4081
+ function normalizeFiniteNumber(value) {
4082
+ if (typeof value !== "number" || !Number.isFinite(value)) {
4083
+ return null;
4084
+ }
4085
+ return Math.round(value);
4086
+ }
4087
+ function normalizeNonEmptyString(value) {
4088
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4089
+ }
4090
+ function isRecord(value) {
4091
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4092
+ }
4016
4093
  function resolveLaunchFramePolicy(result) {
4017
4094
  return result.framePolicy;
4018
4095
  }
@@ -4506,6 +4583,12 @@ function sanitizeMetadata(metadata) {
4506
4583
  sanitized[key] = true;
4507
4584
  }
4508
4585
  }
4586
+ if (metadata?.[closedDockWindowFramesMetadataKey] !== void 0) {
4587
+ return writeClosedDockWindowFrameEntries(
4588
+ sanitized,
4589
+ readClosedDockWindowFrameEntries(metadata).values()
4590
+ );
4591
+ }
4509
4592
  return Object.keys(sanitized).length > 0 ? sanitized : void 0;
4510
4593
  }
4511
4594
 
@@ -4525,6 +4608,7 @@ var WorkbenchHostSessionController = class {
4525
4608
  isDisposed = false;
4526
4609
  isSnapshotLoaded = false;
4527
4610
  loadedSnapshot;
4611
+ closedDockWindowFrameEntries = /* @__PURE__ */ new Map();
4528
4612
  loadGeneration = 0;
4529
4613
  loadPromise = null;
4530
4614
  nodeLeases = /* @__PURE__ */ new Map();
@@ -4550,6 +4634,9 @@ var WorkbenchHostSessionController = class {
4550
4634
  this.nodeDefinitionByType.set(definition.typeId, definition);
4551
4635
  }
4552
4636
  this.loadedSnapshot = this.readCachedSnapshot();
4637
+ this.closedDockWindowFrameEntries = readClosedDockWindowFrameEntries(
4638
+ this.loadedSnapshot?.metadata
4639
+ );
4553
4640
  this.projectedNodes = input.projectedNodes ?? [];
4554
4641
  this.controller = createWorkbenchController(
4555
4642
  stateFromSnapshotOrDefinitions(
@@ -4655,6 +4742,7 @@ var WorkbenchHostSessionController = class {
4655
4742
  }
4656
4743
  closeNode(nodeId) {
4657
4744
  this.runWhenReady(this.loadGeneration, () => {
4745
+ this.rememberClosedDockWindowFrame(nodeId);
4658
4746
  this.controller.commands.closeNode(nodeId);
4659
4747
  });
4660
4748
  }
@@ -4777,6 +4865,9 @@ var WorkbenchHostSessionController = class {
4777
4865
  return;
4778
4866
  }
4779
4867
  this.loadedSnapshot = snapshot;
4868
+ this.closedDockWindowFrameEntries = readClosedDockWindowFrameEntries(
4869
+ snapshot?.metadata
4870
+ );
4780
4871
  this.controller.commands.replaceState(
4781
4872
  stateFromSnapshotOrDefinitions(
4782
4873
  snapshot,
@@ -4790,6 +4881,7 @@ var WorkbenchHostSessionController = class {
4790
4881
  return;
4791
4882
  }
4792
4883
  this.loadedSnapshot = null;
4884
+ this.closedDockWindowFrameEntries = /* @__PURE__ */ new Map();
4793
4885
  this.controller.commands.replaceState(
4794
4886
  stateFromSnapshotOrDefinitions(
4795
4887
  null,
@@ -4833,6 +4925,7 @@ var WorkbenchHostSessionController = class {
4833
4925
  if (this.isDisposed || resolvedDecision === "keep-open" || generation !== this.loadGeneration) {
4834
4926
  return;
4835
4927
  }
4928
+ this.rememberClosedDockWindowFrame(node.id);
4836
4929
  this.controller.commands.closeNode(node.id);
4837
4930
  }, noop);
4838
4931
  }
@@ -4964,18 +5057,19 @@ var WorkbenchHostSessionController = class {
4964
5057
  if (!definition) {
4965
5058
  return null;
4966
5059
  }
5060
+ const launchResult = this.applyClosedDockWindowFrame(result);
4967
5061
  const exactExisting = this.findExistingNodeByTarget({
4968
- instanceId: result.instanceId,
4969
- typeId: result.typeId
5062
+ instanceId: launchResult.instanceId,
5063
+ typeId: launchResult.typeId
4970
5064
  });
4971
- const dockEntryExisting = exactExisting ? null : this.findExistingNodeByDockEntry(result);
5065
+ const dockEntryExisting = exactExisting ? null : this.findExistingNodeByDockEntry(launchResult);
4972
5066
  const existing = exactExisting ?? dockEntryExisting;
4973
5067
  if (existing) {
4974
5068
  this.controller.commands.setNodeSizeConstraints(
4975
5069
  existing.id,
4976
- result.sizeConstraints ?? definition.sizeConstraints ?? null
5070
+ launchResult.sizeConstraints ?? definition.sizeConstraints ?? null
4977
5071
  );
4978
- if (exactExisting && result.framePolicy === "cascade-same-type-centered") {
5072
+ if (exactExisting && launchResult.framePolicy === "cascade-same-type-centered") {
4979
5073
  const currentState = this.controller.getSnapshot();
4980
5074
  const resolvedFrame2 = resolveWorkbenchLaunchedHostNodeFrame({
4981
5075
  currentState: {
@@ -4986,26 +5080,26 @@ var WorkbenchHostSessionController = class {
4986
5080
  )
4987
5081
  },
4988
5082
  definition,
4989
- result
5083
+ result: launchResult
4990
5084
  });
4991
5085
  this.controller.commands.resizeNode(existing.id, resolvedFrame2);
4992
5086
  }
4993
5087
  this.restoreAndFocusNode(existing.id);
4994
- if (result.activation) {
4995
- this.activateNodeNow({ nodeId: existing.id }, result.activation);
5088
+ if (launchResult.activation) {
5089
+ this.activateNodeNow({ nodeId: existing.id }, launchResult.activation);
4996
5090
  }
4997
5091
  return existing.id;
4998
5092
  }
4999
5093
  const resolvedFrame = resolveWorkbenchLaunchedHostNodeFrame({
5000
5094
  currentState: this.controller.getSnapshot(),
5001
5095
  definition,
5002
- result
5096
+ result: launchResult
5003
5097
  });
5004
5098
  const nextNode = createWorkbenchLaunchedHostNode({
5005
- activation: this.createActivationEnvelope(result.activation),
5099
+ activation: this.createActivationEnvelope(launchResult.activation),
5006
5100
  definition,
5007
5101
  resolvedFrame,
5008
- result
5102
+ result: launchResult
5009
5103
  });
5010
5104
  this.controller.commands.openNode(nextNode);
5011
5105
  this.controller.commands.focusNode(nextNode.id);
@@ -5161,10 +5255,13 @@ var WorkbenchHostSessionController = class {
5161
5255
  ),
5162
5256
  {
5163
5257
  activeSpaceId: this.loadedSnapshot?.activeSpaceId,
5164
- metadata: {
5165
- ...this.loadedSnapshot?.metadata ?? {},
5166
- [initializedMetadataKey]: true
5167
- },
5258
+ metadata: writeClosedDockWindowFrameEntries(
5259
+ {
5260
+ ...this.loadedSnapshot?.metadata ?? {},
5261
+ [initializedMetadataKey]: true
5262
+ },
5263
+ this.closedDockWindowFrameEntries.values()
5264
+ ),
5168
5265
  spaces: this.loadedSnapshot?.spaces
5169
5266
  }
5170
5267
  )
@@ -5178,6 +5275,42 @@ var WorkbenchHostSessionController = class {
5178
5275
  this.loadedSnapshot = savedSnapshot;
5179
5276
  }, noop);
5180
5277
  }
5278
+ rememberClosedDockWindowFrame(nodeId) {
5279
+ const node = this.controller.getSnapshot().nodes.find((entry2) => entry2.id === nodeId);
5280
+ const dockEntryId = node?.data.dockEntryId?.trim();
5281
+ if (!node || !dockEntryId) {
5282
+ return;
5283
+ }
5284
+ const entry = {
5285
+ dockEntryId,
5286
+ frame: node.displayMode === "fullscreen" && node.restoreFrame ? node.restoreFrame : node.frame,
5287
+ typeId: node.data.typeId
5288
+ };
5289
+ this.closedDockWindowFrameEntries.set(
5290
+ closedDockWindowFrameEntryKey(entry),
5291
+ entry
5292
+ );
5293
+ }
5294
+ applyClosedDockWindowFrame(result) {
5295
+ const dockEntryId = result.dockEntryId?.trim();
5296
+ if (!dockEntryId) {
5297
+ return result;
5298
+ }
5299
+ const entry = this.closedDockWindowFrameEntries.get(
5300
+ closedDockWindowFrameEntryKey({
5301
+ dockEntryId,
5302
+ typeId: result.typeId
5303
+ })
5304
+ );
5305
+ if (!entry) {
5306
+ return result;
5307
+ }
5308
+ return {
5309
+ ...result,
5310
+ defaultFrame: entry.frame,
5311
+ framePolicy: "absolute"
5312
+ };
5313
+ }
5181
5314
  loadedSnapshotSaveKey() {
5182
5315
  if (!this.loadedSnapshot) {
5183
5316
  return null;
@@ -5602,6 +5735,9 @@ function resolveWorkbenchDockEntryClick(input) {
5602
5735
  }
5603
5736
  return { actionId: input.entry.clickActionId, kind: "action" };
5604
5737
  }
5738
+ if (isWorkbenchDockEntryBlocked(input.entry)) {
5739
+ return { kind: "blocked" };
5740
+ }
5605
5741
  if (input.instanceMode === "multi" && input.matchedNodes.length > 0) {
5606
5742
  return { kind: "open-popup" };
5607
5743
  }
@@ -5611,9 +5747,6 @@ function resolveWorkbenchDockEntryClick(input) {
5611
5747
  if (input.matchedNodes.length > 1) {
5612
5748
  return { kind: "open-popup" };
5613
5749
  }
5614
- if (isWorkbenchDockEntryBlocked(input.entry)) {
5615
- return { kind: "blocked" };
5616
- }
5617
5750
  return { kind: "launch" };
5618
5751
  }
5619
5752
  function isWorkbenchDockEntryBlocked(entry) {
@@ -7302,11 +7435,16 @@ function WorkbenchHostDock({
7302
7435
  const hoverPanelOpenTimerRef = useRef8(
7303
7436
  null
7304
7437
  );
7438
+ const labelTooltipOpenTimerRef = useRef8(
7439
+ null
7440
+ );
7305
7441
  const hoverPanelScheduledPointRef = useRef8(null);
7442
+ const labelTooltipScheduledPointRef = useRef8(null);
7306
7443
  const hoverPanelRestTargetRef = useRef8(null);
7307
7444
  const activeHoverPanelRef = useRef8(
7308
7445
  null
7309
7446
  );
7447
+ const activeLabelTooltipRef = useRef8(null);
7310
7448
  const pendingDockStateRefreshRef = useRef8(false);
7311
7449
  const slotRefs = useRef8(/* @__PURE__ */ new Map());
7312
7450
  const wallpaperToneElementRefs = useRef8(/* @__PURE__ */ new Map());
@@ -7320,6 +7458,7 @@ function WorkbenchHostDock({
7320
7458
  const [activeAttentionEntryIds, setActiveAttentionEntryIds] = useState8(/* @__PURE__ */ new Set());
7321
7459
  const [activePopup, setActivePopup] = useState8(null);
7322
7460
  const [activeHoverPanel, setActiveHoverPanel] = useState8(null);
7461
+ const [activeLabelTooltip, setActiveLabelTooltip] = useState8(null);
7323
7462
  const [activeMinimizedStackPopup, setActiveMinimizedStackPopup] = useState8(null);
7324
7463
  const [pendingActionKeys, setPendingActionKeys] = useState8(
7325
7464
  () => /* @__PURE__ */ new Set()
@@ -7355,16 +7494,29 @@ function WorkbenchHostDock({
7355
7494
  hoverPanelOpenTimerRef.current = null;
7356
7495
  hoverPanelScheduledPointRef.current = null;
7357
7496
  }, []);
7497
+ const clearLabelTooltipOpenTimer = useCallback10(() => {
7498
+ if (labelTooltipOpenTimerRef.current === null) {
7499
+ return;
7500
+ }
7501
+ clearTimeout(labelTooltipOpenTimerRef.current);
7502
+ labelTooltipOpenTimerRef.current = null;
7503
+ labelTooltipScheduledPointRef.current = null;
7504
+ }, []);
7358
7505
  useEffect9(
7359
7506
  () => () => {
7360
7507
  clearHoverPanelCloseTimer();
7361
7508
  clearHoverPanelOpenTimer();
7509
+ clearLabelTooltipOpenTimer();
7362
7510
  for (const timer of collapsingMinimizedLaunchTimerRef.current.values()) {
7363
7511
  clearTimeout(timer);
7364
7512
  }
7365
7513
  collapsingMinimizedLaunchTimerRef.current.clear();
7366
7514
  },
7367
- [clearHoverPanelCloseTimer, clearHoverPanelOpenTimer]
7515
+ [
7516
+ clearHoverPanelCloseTimer,
7517
+ clearHoverPanelOpenTimer,
7518
+ clearLabelTooltipOpenTimer
7519
+ ]
7368
7520
  );
7369
7521
  const clearCollapsingMinimizedLaunch = useCallback10((anchorKey) => {
7370
7522
  const timer = collapsingMinimizedLaunchTimerRef.current.get(anchorKey);
@@ -7551,6 +7703,23 @@ function WorkbenchHostDock({
7551
7703
  useEffect9(() => {
7552
7704
  activeHoverPanelRef.current = activeHoverPanel;
7553
7705
  }, [activeHoverPanel]);
7706
+ useEffect9(() => {
7707
+ activeLabelTooltipRef.current = activeLabelTooltip;
7708
+ }, [activeLabelTooltip]);
7709
+ const closeLabelTooltipImmediate = useCallback10(
7710
+ (targetKey) => {
7711
+ clearLabelTooltipOpenTimer();
7712
+ if (targetKey !== void 0 && activeLabelTooltipRef.current?.key !== targetKey) {
7713
+ return;
7714
+ }
7715
+ if (activeLabelTooltipRef.current === null) {
7716
+ return;
7717
+ }
7718
+ activeLabelTooltipRef.current = null;
7719
+ setActiveLabelTooltip(null);
7720
+ },
7721
+ [clearLabelTooltipOpenTimer]
7722
+ );
7554
7723
  const closeHoverPanelImmediate = useCallback10(
7555
7724
  (entryId) => {
7556
7725
  clearHoverPanelOpenTimer();
@@ -7638,6 +7807,50 @@ function WorkbenchHostDock({
7638
7807
  },
7639
7808
  [clearHoverPanelOpenTimer, showHoverPanel]
7640
7809
  );
7810
+ const showLabelTooltip = useCallback10(
7811
+ (target, anchorKey, anchorElement) => {
7812
+ const dockElement = dockMeasureRef.current;
7813
+ if (!dockElement) {
7814
+ return;
7815
+ }
7816
+ const dockRect = dockElement.getBoundingClientRect();
7817
+ const anchorRect = resolveDockLabelTooltipAnchorRect({
7818
+ dockPlacement,
7819
+ slotElement: anchorElement
7820
+ });
7821
+ clearLabelTooltipOpenTimer();
7822
+ const nextTooltip = {
7823
+ anchorKey,
7824
+ anchorRect: {
7825
+ height: anchorRect.height,
7826
+ left: anchorRect.left - dockRect.left,
7827
+ top: anchorRect.top - dockRect.top,
7828
+ width: anchorRect.width
7829
+ },
7830
+ key: target.key,
7831
+ label: target.label
7832
+ };
7833
+ activeLabelTooltipRef.current = nextTooltip;
7834
+ setActiveLabelTooltip(nextTooltip);
7835
+ },
7836
+ [clearLabelTooltipOpenTimer, dockPlacement]
7837
+ );
7838
+ const scheduleLabelTooltipAfterRest = useCallback10(
7839
+ (target, anchorKey) => {
7840
+ clearLabelTooltipOpenTimer();
7841
+ labelTooltipScheduledPointRef.current = null;
7842
+ labelTooltipOpenTimerRef.current = setTimeout(() => {
7843
+ labelTooltipOpenTimerRef.current = null;
7844
+ labelTooltipScheduledPointRef.current = null;
7845
+ const slotElement = slotRefs.current.get(anchorKey);
7846
+ if (!slotElement) {
7847
+ return;
7848
+ }
7849
+ showLabelTooltip(target, anchorKey, slotElement);
7850
+ }, dockHoverPanelOpenDelayMs);
7851
+ },
7852
+ [clearLabelTooltipOpenTimer, showLabelTooltip]
7853
+ );
7641
7854
  const resolveHoverPanelTargetAtPoint = useCallback10(
7642
7855
  (clientX, clientY) => {
7643
7856
  for (const [anchorKey, slotElement] of slotRefs.current) {
@@ -7654,6 +7867,33 @@ function WorkbenchHostDock({
7654
7867
  },
7655
7868
  []
7656
7869
  );
7870
+ const resolveLabelTooltipTargetAtPoint = useCallback10(
7871
+ (clientX, clientY) => {
7872
+ for (const [anchorKey, slotElement] of slotRefs.current) {
7873
+ if (slotElement.dataset.dockHoverPanelEntryId) {
7874
+ continue;
7875
+ }
7876
+ const key = slotElement.dataset.dockLabelTooltipKey;
7877
+ const label = slotElement.dataset.dockLabelTooltipLabel?.trim() ?? "";
7878
+ if (!key || !label) {
7879
+ continue;
7880
+ }
7881
+ const rect = slotElement.getBoundingClientRect();
7882
+ if (clientX >= rect.left - dockHoverPanelHitSlopPx && clientX <= rect.right + dockHoverPanelHitSlopPx && clientY >= rect.top - dockHoverPanelHitSlopPx && clientY <= rect.bottom + dockHoverPanelHitSlopPx) {
7883
+ return {
7884
+ anchorKey,
7885
+ slotElement,
7886
+ target: {
7887
+ key,
7888
+ label
7889
+ }
7890
+ };
7891
+ }
7892
+ }
7893
+ return null;
7894
+ },
7895
+ []
7896
+ );
7657
7897
  const isPointerInsideActiveHoverPanelRegion = useCallback10(
7658
7898
  (clientX, clientY) => {
7659
7899
  const activeHoverPanel2 = activeHoverPanelRef.current;
@@ -7714,19 +7954,57 @@ function WorkbenchHostDock({
7714
7954
  },
7715
7955
  [clearHoverPanelOpenTimer, resolveHoverPanelTargetAtPoint, showHoverPanel]
7716
7956
  );
7957
+ const scheduleLabelTooltipAtPointAfterRest = useCallback10(
7958
+ (clientX, clientY) => {
7959
+ if (activeLabelTooltipRef.current !== null) {
7960
+ return;
7961
+ }
7962
+ const scheduledPoint = labelTooltipScheduledPointRef.current;
7963
+ if (labelTooltipOpenTimerRef.current !== null && scheduledPoint && Math.abs(clientX - scheduledPoint.clientX) <= dockHoverPanelPointerRestTolerancePx && Math.abs(clientY - scheduledPoint.clientY) <= dockHoverPanelPointerRestTolerancePx) {
7964
+ return;
7965
+ }
7966
+ clearLabelTooltipOpenTimer();
7967
+ labelTooltipScheduledPointRef.current = { clientX, clientY };
7968
+ labelTooltipOpenTimerRef.current = setTimeout(() => {
7969
+ labelTooltipOpenTimerRef.current = null;
7970
+ labelTooltipScheduledPointRef.current = null;
7971
+ if (activeHoverPanelRef.current !== null) {
7972
+ return;
7973
+ }
7974
+ const target = resolveLabelTooltipTargetAtPoint(clientX, clientY);
7975
+ if (!target) {
7976
+ return;
7977
+ }
7978
+ showLabelTooltip(target.target, target.anchorKey, target.slotElement);
7979
+ }, dockHoverPanelOpenDelayMs);
7980
+ },
7981
+ [
7982
+ clearLabelTooltipOpenTimer,
7983
+ resolveLabelTooltipTargetAtPoint,
7984
+ showLabelTooltip
7985
+ ]
7986
+ );
7717
7987
  const beginDockIconInteraction = useCallback10(
7718
7988
  (anchorKey) => {
7719
7989
  hoverPanelRestTargetRef.current = null;
7720
7990
  clearHoverPanelOpenTimer();
7991
+ closeLabelTooltipImmediate();
7721
7992
  closeHoverPanelImmediate();
7722
7993
  triggerDockBounce(anchorKey);
7723
7994
  },
7724
- [clearHoverPanelOpenTimer, closeHoverPanelImmediate, triggerDockBounce]
7995
+ [
7996
+ clearHoverPanelOpenTimer,
7997
+ closeHoverPanelImmediate,
7998
+ closeLabelTooltipImmediate,
7999
+ triggerDockBounce
8000
+ ]
7725
8001
  );
7726
8002
  const beginDockMinimizedInteraction = useCallback10(
7727
8003
  (anchorKey) => {
7728
8004
  hoverPanelRestTargetRef.current = null;
7729
8005
  clearHoverPanelOpenTimer();
8006
+ clearLabelTooltipOpenTimer();
8007
+ closeLabelTooltipImmediate();
7730
8008
  closeHoverPanelImmediate();
7731
8009
  pauseDockMagnification();
7732
8010
  if (!anchorKey) {
@@ -7754,8 +8032,10 @@ function WorkbenchHostDock({
7754
8032
  },
7755
8033
  [
7756
8034
  clearHoverPanelOpenTimer,
8035
+ clearLabelTooltipOpenTimer,
7757
8036
  clearSlotMagnification,
7758
8037
  closeHoverPanelImmediate,
8038
+ closeLabelTooltipImmediate,
7759
8039
  pauseDockMagnification
7760
8040
  ]
7761
8041
  );
@@ -7783,6 +8063,7 @@ function WorkbenchHostDock({
7783
8063
  const handleDockPointerTravel = useCallback10(
7784
8064
  (clientX, clientY) => {
7785
8065
  if (activeHoverPanelRef.current !== null) {
8066
+ closeLabelTooltipImmediate();
7786
8067
  if (isPointerInsideActiveHoverPanelRegion(clientX, clientY)) {
7787
8068
  clearHoverPanelCloseTimer();
7788
8069
  return;
@@ -7791,16 +8072,27 @@ function WorkbenchHostDock({
7791
8072
  handleDockPointerLeave();
7792
8073
  return;
7793
8074
  }
8075
+ const activeLabelTooltip2 = activeLabelTooltipRef.current;
8076
+ if (activeLabelTooltip2) {
8077
+ const target = resolveLabelTooltipTargetAtPoint(clientX, clientY);
8078
+ if (target?.target.key !== activeLabelTooltip2.key) {
8079
+ closeLabelTooltipImmediate(activeLabelTooltip2.key);
8080
+ }
8081
+ }
7794
8082
  handleDockPointerMove(clientX, clientY);
7795
8083
  scheduleHoverPanelAtPointAfterRest(clientX, clientY);
8084
+ scheduleLabelTooltipAtPointAfterRest(clientX, clientY);
7796
8085
  },
7797
8086
  [
7798
8087
  clearHoverPanelCloseTimer,
8088
+ closeLabelTooltipImmediate,
7799
8089
  handleDockPointerMove,
7800
8090
  handleDockPointerLeave,
7801
8091
  isPointerInsideActiveHoverPanelRegion,
8092
+ resolveLabelTooltipTargetAtPoint,
7802
8093
  scheduleHoverPanelClose,
7803
- scheduleHoverPanelAtPointAfterRest
8094
+ scheduleHoverPanelAtPointAfterRest,
8095
+ scheduleLabelTooltipAtPointAfterRest
7804
8096
  ]
7805
8097
  );
7806
8098
  const updateDockScrollState = useCallback10(() => {
@@ -7964,10 +8256,12 @@ function WorkbenchHostDock({
7964
8256
  const closePopup = () => {
7965
8257
  clearHoverPanelCloseTimer();
7966
8258
  clearHoverPanelOpenTimer();
8259
+ clearLabelTooltipOpenTimer();
7967
8260
  hoverPanelRestTargetRef.current = null;
7968
8261
  setDockHoverPanelOpen(false);
7969
8262
  setActivePopup(null);
7970
8263
  setActiveHoverPanel(null);
8264
+ closeLabelTooltipImmediate();
7971
8265
  setActiveMinimizedStackPopup(null);
7972
8266
  };
7973
8267
  const scrollDockItems = (direction) => {
@@ -7977,6 +8271,7 @@ function WorkbenchHostDock({
7977
8271
  }
7978
8272
  resetDockMagnification();
7979
8273
  closeHoverPanelImmediate();
8274
+ closeLabelTooltipImmediate();
7980
8275
  hoverPanelRestTargetRef.current = null;
7981
8276
  const isVertical = dockPlacement === "left";
7982
8277
  const viewportSize = isVertical ? element.clientHeight : element.clientWidth;
@@ -8041,6 +8336,8 @@ function WorkbenchHostDock({
8041
8336
  onPointerLeave: () => {
8042
8337
  hoverPanelRestTargetRef.current = null;
8043
8338
  clearHoverPanelOpenTimer();
8339
+ clearLabelTooltipOpenTimer();
8340
+ closeLabelTooltipImmediate();
8044
8341
  closeHoverPanelImmediate();
8045
8342
  handleDockPointerLeave();
8046
8343
  flushPendingDockStateRefresh();
@@ -8101,19 +8398,19 @@ function WorkbenchHostDock({
8101
8398
  matchedNodes: resolvedEntry.matchedNodes
8102
8399
  });
8103
8400
  const hasHoverPanel = dockEntryHasHoverPanel(entry);
8401
+ const labelTooltipTarget2 = hasHoverPanel ? null : dockLabelTooltipTarget(`entry:${entry.id}`, entry.label);
8104
8402
  const dockButton2 = /* @__PURE__ */ jsx10(
8105
8403
  "button",
8106
8404
  {
8107
8405
  "aria-expanded": currentPopup ? true : void 0,
8108
8406
  "aria-haspopup": clickResolution.kind === "open-popup" ? "dialog" : void 0,
8109
8407
  "aria-label": i18n.t("launch", { title: entry.label }),
8110
- "aria-disabled": clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes,
8408
+ "aria-disabled": clickResolution.kind === "blocked" ? true : void 0,
8111
8409
  className: "desktop-dock__btn",
8112
- "data-interactive": clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes ? "false" : "true",
8113
- title: entry.label,
8410
+ "data-interactive": clickResolution.kind === "blocked" ? "false" : "true",
8114
8411
  type: "button",
8115
8412
  onPointerDown: () => {
8116
- if (clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes) {
8413
+ if (clickResolution.kind === "blocked") {
8117
8414
  return;
8118
8415
  }
8119
8416
  beginDockIconInteraction(anchorKey);
@@ -8122,8 +8419,18 @@ function WorkbenchHostDock({
8122
8419
  logWorkbenchDockDebug("dock.click", debugDiagnostics, {
8123
8420
  anchorKey,
8124
8421
  clickResolution,
8422
+ dockDiagnostics: entry.diagnostics ?? null,
8125
8423
  dockNodeState: resolvedEntry.dockNodeState,
8126
8424
  entryId: entry.id,
8425
+ entryLaunchBehavior: entry.launchBehavior ?? "enabled",
8426
+ entryOrder: entry.order ?? null,
8427
+ entryState: entry.state ?? { kind: "enabled" },
8428
+ entryVisibility: entry.visibility ?? "always",
8429
+ hoverActions: entry.hoverActions?.map((action) => ({
8430
+ disabled: action.disabled === true,
8431
+ id: action.id,
8432
+ pending: Boolean(action.pendingLabel)
8433
+ })) ?? [],
8127
8434
  instanceMode: instanceMode ?? null,
8128
8435
  matchedNodeCount: resolvedEntry.matchedNodes.length,
8129
8436
  matchedNodeIds: resolvedEntry.matchedNodes.map(
@@ -8234,6 +8541,8 @@ function WorkbenchHostDock({
8234
8541
  "data-desktop-dock-slot": "true",
8235
8542
  "data-entry-state": entry.state?.kind ?? "enabled",
8236
8543
  "data-dock-hover-panel-entry-id": hasHoverPanel ? entry.id : void 0,
8544
+ "data-dock-label-tooltip-key": labelTooltipTarget2?.key,
8545
+ "data-dock-label-tooltip-label": labelTooltipTarget2?.label,
8237
8546
  "data-icon-size": entry.iconSize ?? "default",
8238
8547
  "data-node-state": resolvedEntry.dockNodeState,
8239
8548
  "data-popup-active": currentPopup ? "true" : void 0,
@@ -8244,6 +8553,9 @@ function WorkbenchHostDock({
8244
8553
  if (hasHoverPanel && !event.currentTarget.contains(event.relatedTarget)) {
8245
8554
  closeHoverPanelImmediate(entry.id);
8246
8555
  }
8556
+ if (labelTooltipTarget2 && !event.currentTarget.contains(event.relatedTarget)) {
8557
+ closeLabelTooltipImmediate(labelTooltipTarget2.key);
8558
+ }
8247
8559
  },
8248
8560
  onFocus: (event) => {
8249
8561
  if (hasHoverPanel) {
@@ -8252,15 +8564,41 @@ function WorkbenchHostDock({
8252
8564
  anchorKey,
8253
8565
  event.currentTarget
8254
8566
  );
8567
+ return;
8568
+ }
8569
+ if (labelTooltipTarget2) {
8570
+ showLabelTooltip(
8571
+ labelTooltipTarget2,
8572
+ anchorKey,
8573
+ event.currentTarget
8574
+ );
8255
8575
  }
8256
8576
  },
8257
8577
  onPointerEnter: () => {
8258
8578
  if (hasHoverPanel) {
8259
8579
  scheduleHoverPanelAfterRest(entry.id, anchorKey);
8580
+ return;
8581
+ }
8582
+ if (labelTooltipTarget2) {
8583
+ scheduleLabelTooltipAfterRest(
8584
+ labelTooltipTarget2,
8585
+ anchorKey
8586
+ );
8260
8587
  }
8261
8588
  },
8262
8589
  onPointerLeave: (event) => {
8263
8590
  if (!hasHoverPanel) {
8591
+ if (labelTooltipTarget2) {
8592
+ clearLabelTooltipOpenTimer();
8593
+ closeLabelTooltipImmediate(labelTooltipTarget2.key);
8594
+ const relatedTarget2 = event.relatedTarget;
8595
+ if (relatedTarget2 instanceof Node && dockMeasureRef.current?.contains(relatedTarget2)) {
8596
+ scheduleLabelTooltipAtPointAfterRest(
8597
+ event.clientX,
8598
+ event.clientY
8599
+ );
8600
+ }
8601
+ }
8264
8602
  return;
8265
8603
  }
8266
8604
  const relatedTarget = event.relatedTarget;
@@ -8289,15 +8627,19 @@ function WorkbenchHostDock({
8289
8627
  const slot = dockItem.item.slot;
8290
8628
  if (slot.kind === "stack") {
8291
8629
  const stackPopupActive = activeMinimizedStackPopup !== null ? true : void 0;
8630
+ const stackLabel = i18n.t("minimizedWindows");
8631
+ const labelTooltipTarget2 = dockLabelTooltipTarget(
8632
+ `minimized-stack:${slot.anchorKey}`,
8633
+ stackLabel
8634
+ );
8292
8635
  const stackButton = /* @__PURE__ */ jsx10(
8293
8636
  "button",
8294
8637
  {
8295
8638
  "aria-expanded": stackPopupActive,
8296
8639
  "aria-haspopup": "dialog",
8297
- "aria-label": i18n.t("minimizedWindows"),
8640
+ "aria-label": stackLabel,
8298
8641
  className: "desktop-dock__btn desktop-dock__minimized-btn",
8299
8642
  "data-interactive": "true",
8300
- title: i18n.t("minimizedWindows"),
8301
8643
  type: "button",
8302
8644
  onPointerDown: () => {
8303
8645
  beginDockMinimizedInteraction();
@@ -8369,24 +8711,58 @@ function WorkbenchHostDock({
8369
8711
  className: "desktop-dock__slot desktop-dock__slot--minimized",
8370
8712
  "data-desktop-dock-anchor-key": slot.anchorKey,
8371
8713
  "data-desktop-dock-slot": "true",
8714
+ "data-dock-label-tooltip-key": labelTooltipTarget2.key,
8715
+ "data-dock-label-tooltip-label": labelTooltipTarget2.label,
8372
8716
  "data-node-state": "minimized",
8373
8717
  "data-popup-active": stackPopupActive,
8374
8718
  "data-presence": dockItem.presence,
8375
8719
  "data-section-id": "minimized",
8376
8720
  "data-stack-dispatching": stackDispatching ? "true" : void 0,
8721
+ onBlur: (event) => {
8722
+ if (!event.currentTarget.contains(event.relatedTarget)) {
8723
+ closeLabelTooltipImmediate(labelTooltipTarget2.key);
8724
+ }
8725
+ },
8726
+ onFocus: (event) => {
8727
+ showLabelTooltip(
8728
+ labelTooltipTarget2,
8729
+ slot.anchorKey,
8730
+ event.currentTarget
8731
+ );
8732
+ },
8733
+ onPointerEnter: () => {
8734
+ scheduleLabelTooltipAfterRest(
8735
+ labelTooltipTarget2,
8736
+ slot.anchorKey
8737
+ );
8738
+ },
8739
+ onPointerLeave: (event) => {
8740
+ clearLabelTooltipOpenTimer();
8741
+ closeLabelTooltipImmediate(labelTooltipTarget2.key);
8742
+ const relatedTarget = event.relatedTarget;
8743
+ if (relatedTarget instanceof Node && dockMeasureRef.current?.contains(relatedTarget)) {
8744
+ scheduleLabelTooltipAtPointAfterRest(
8745
+ event.clientX,
8746
+ event.clientY
8747
+ );
8748
+ }
8749
+ },
8377
8750
  children: stackButton
8378
8751
  },
8379
8752
  dockItem.key
8380
8753
  );
8381
8754
  }
8382
8755
  const node = slot.node;
8756
+ const labelTooltipTarget = dockLabelTooltipTarget(
8757
+ `minimized-node:${node.id}`,
8758
+ node.title
8759
+ );
8383
8760
  const dockButton = /* @__PURE__ */ jsx10(
8384
8761
  "button",
8385
8762
  {
8386
8763
  "aria-label": i18n.t("launch", { title: node.title }),
8387
8764
  className: "desktop-dock__btn desktop-dock__minimized-btn",
8388
8765
  "data-interactive": "true",
8389
- title: node.title,
8390
8766
  type: "button",
8391
8767
  onPointerDown: () => {
8392
8768
  const restoreIntent = resolveWorkbenchMinimizedDockRestoreIntent({
@@ -8449,10 +8825,41 @@ function WorkbenchHostDock({
8449
8825
  "data-collapsing": collapsingMinimizedLaunchAnchorKeys.has(slot.anchorKey) ? "true" : void 0,
8450
8826
  "data-desktop-dock-anchor-key": slot.anchorKey,
8451
8827
  "data-desktop-dock-slot": "true",
8828
+ "data-dock-label-tooltip-key": labelTooltipTarget.key,
8829
+ "data-dock-label-tooltip-label": labelTooltipTarget.label,
8452
8830
  "data-node-state": "minimized",
8453
8831
  "data-presence": dockItem.presence,
8454
8832
  "data-promoted-from-stack": promotedNodeId === node.id ? "true" : void 0,
8455
8833
  "data-section-id": "minimized",
8834
+ onBlur: (event) => {
8835
+ if (!event.currentTarget.contains(event.relatedTarget)) {
8836
+ closeLabelTooltipImmediate(labelTooltipTarget.key);
8837
+ }
8838
+ },
8839
+ onFocus: (event) => {
8840
+ showLabelTooltip(
8841
+ labelTooltipTarget,
8842
+ slot.anchorKey,
8843
+ event.currentTarget
8844
+ );
8845
+ },
8846
+ onPointerEnter: () => {
8847
+ scheduleLabelTooltipAfterRest(
8848
+ labelTooltipTarget,
8849
+ slot.anchorKey
8850
+ );
8851
+ },
8852
+ onPointerLeave: (event) => {
8853
+ clearLabelTooltipOpenTimer();
8854
+ closeLabelTooltipImmediate(labelTooltipTarget.key);
8855
+ const relatedTarget = event.relatedTarget;
8856
+ if (relatedTarget instanceof Node && dockMeasureRef.current?.contains(relatedTarget)) {
8857
+ scheduleLabelTooltipAtPointAfterRest(
8858
+ event.clientX,
8859
+ event.clientY
8860
+ );
8861
+ }
8862
+ },
8456
8863
  children: dockButton
8457
8864
  },
8458
8865
  dockItem.key
@@ -8504,6 +8911,13 @@ function WorkbenchHostDock({
8504
8911
  handleDockPointerLeave();
8505
8912
  }
8506
8913
  }
8914
+ ) : null,
8915
+ activeLabelTooltip ? /* @__PURE__ */ jsx10(
8916
+ WorkbenchHostDockLabelTooltip,
8917
+ {
8918
+ placement: dockPlacement,
8919
+ state: activeLabelTooltip
8920
+ }
8507
8921
  ) : null
8508
8922
  ]
8509
8923
  }
@@ -8814,6 +9228,29 @@ function WorkbenchHostDockMinimizedNodePreview({
8814
9228
  function dockEntryHasHoverPanel(entry) {
8815
9229
  return Boolean(entry.state?.reason?.trim()) || (entry.hoverActions?.length ?? 0) > 0;
8816
9230
  }
9231
+ function dockLabelTooltipTarget(key, label) {
9232
+ return { key, label: label.trim() };
9233
+ }
9234
+ function resolveDockLabelTooltipAnchorRect({
9235
+ dockPlacement,
9236
+ slotElement
9237
+ }) {
9238
+ const slotRect = slotElement.getBoundingClientRect();
9239
+ if (dockPlacement === "left") {
9240
+ return {
9241
+ height: DOCK_ICON_BASE_SIZE,
9242
+ left: slotRect.left,
9243
+ top: slotRect.top + (slotRect.height - DOCK_ICON_BASE_SIZE) / 2,
9244
+ width: DOCK_ICON_BASE_SIZE
9245
+ };
9246
+ }
9247
+ return {
9248
+ height: DOCK_ICON_BASE_SIZE,
9249
+ left: slotRect.left + (slotRect.width - DOCK_ICON_BASE_SIZE) / 2,
9250
+ top: slotRect.bottom - DOCK_ICON_BASE_SIZE,
9251
+ width: DOCK_ICON_BASE_SIZE
9252
+ };
9253
+ }
8817
9254
  function renderDockBadge(entry, matchedNodeCount) {
8818
9255
  const badge = entry.badge ?? (matchedNodeCount > 1 ? {
8819
9256
  kind: "count",
@@ -8830,6 +9267,26 @@ function renderDockBadge(entry, matchedNodeCount) {
8830
9267
  }
8831
9268
  return /* @__PURE__ */ jsx10("span", { className: "desktop-dock__status-badge", "data-status": badge.status });
8832
9269
  }
9270
+ function WorkbenchHostDockLabelTooltip({
9271
+ placement,
9272
+ state
9273
+ }) {
9274
+ return /* @__PURE__ */ jsx10(
9275
+ "div",
9276
+ {
9277
+ className: "desktop-dock__label-tooltip",
9278
+ "data-dock-placement": placement,
9279
+ role: "tooltip",
9280
+ style: {
9281
+ "--desktop-dock-label-tooltip-anchor-height": `${state.anchorRect.height}px`,
9282
+ "--desktop-dock-label-tooltip-anchor-left": `${state.anchorRect.left}px`,
9283
+ "--desktop-dock-label-tooltip-anchor-top": `${state.anchorRect.top}px`,
9284
+ "--desktop-dock-label-tooltip-anchor-width": `${state.anchorRect.width}px`
9285
+ },
9286
+ children: state.label
9287
+ }
9288
+ );
9289
+ }
8833
9290
  function WorkbenchHostDockHoverPanel({
8834
9291
  entry,
8835
9292
  host,