@tutti-os/workbench-surface 0.0.14 → 0.0.16

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.d.ts CHANGED
@@ -449,6 +449,7 @@ interface WorkbenchHostDockEntryAction {
449
449
  label: string;
450
450
  pendingLabel?: string;
451
451
  }
452
+ type WorkbenchHostDockEntryDiagnostics = Record<string, unknown>;
452
453
  interface WorkbenchHostDockPopupItemInput {
453
454
  externalNodeState?: unknown;
454
455
  externalWorkspaceState?: unknown;
@@ -490,6 +491,7 @@ interface WorkbenchHostDockEntry {
490
491
  iconSize?: "default" | "large";
491
492
  id: string;
492
493
  label: string;
494
+ diagnostics?: WorkbenchHostDockEntryDiagnostics;
493
495
  instanceMode?: WorkbenchHostNodeInstanceStrategy["mode"];
494
496
  launchBehavior?: WorkbenchHostDockEntryLaunchBehavior;
495
497
  launchPayload?: unknown;
@@ -504,7 +506,7 @@ interface WorkbenchHostDockEntry {
504
506
  typeId: string;
505
507
  visibility?: WorkbenchHostDockEntryVisibility;
506
508
  }
507
- type WorkbenchHostDockEntryDynamicState = Partial<Pick<WorkbenchHostDockEntry, "attentionToken" | "badge" | "hoverActions" | "launchBehavior" | "order" | "state" | "visibility">>;
509
+ type WorkbenchHostDockEntryDynamicState = Partial<Pick<WorkbenchHostDockEntry, "attentionToken" | "badge" | "diagnostics" | "hoverActions" | "launchBehavior" | "order" | "state" | "visibility">>;
508
510
  interface WorkbenchHostDockEntryStateSource {
509
511
  getEntryState(entryId: string): WorkbenchHostDockEntryDynamicState | null | undefined;
510
512
  subscribe(listener: () => void): () => void;
package/dist/index.js CHANGED
@@ -1994,6 +1994,12 @@ var defaultWindowChromeI18n = createWorkbenchWindowChromeI18nRuntime(
1994
1994
  dictionaries: [workbenchWindowChromeI18nResources.en]
1995
1995
  })
1996
1996
  );
1997
+ function resolveWorkbenchNodeLaunchSource(data) {
1998
+ if (data && typeof data === "object" && "launchSource" in data && typeof data.launchSource === "string" && data.launchSource.length > 0) {
1999
+ return data.launchSource;
2000
+ }
2001
+ return void 0;
2002
+ }
1997
2003
  function resolveWorkbenchNodeTypeId(data) {
1998
2004
  if (data && typeof data === "object" && "typeId" in data && typeof data.typeId === "string") {
1999
2005
  return data.typeId;
@@ -2103,6 +2109,7 @@ function WorkbenchWindowFrame({
2103
2109
  "data-focused": isFocused ? "true" : "false",
2104
2110
  "data-display-mode": node.displayMode,
2105
2111
  "data-genie-state": genie.isNodeGenieHidden(node.id) ? "hidden" : "visible",
2112
+ "data-launch-source": resolveWorkbenchNodeLaunchSource(node.data),
2106
2113
  "data-minimized-mount": hiddenMounted ? "hidden" : "visible",
2107
2114
  "data-presentation-mode": presentationMode ?? "default",
2108
2115
  "data-slot": "viewport-menu-boundary",
@@ -3889,6 +3896,8 @@ var initializedMetadataKeys = [
3889
3896
  ];
3890
3897
  var COMPACT_LAUNCH_WIDTH_THRESHOLD = 1440;
3891
3898
  var COMPACT_LAUNCH_FRAME_SCALE = 0.85;
3899
+ var closedDockWindowFramesMetadataKey = "workbenchHostClosedDockWindowFrames";
3900
+ var maxClosedDockWindowFrameEntries = 128;
3892
3901
  function createDefaultLaunchResult(definition, input) {
3893
3902
  return {
3894
3903
  defaultFrame: definition.frame,
@@ -4006,6 +4015,81 @@ function resolveWorkbenchLaunchedHostNodeFrame(input) {
4006
4015
  constraints: input.currentState.layoutConstraints
4007
4016
  });
4008
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
+ }
4009
4093
  function resolveLaunchFramePolicy(result) {
4010
4094
  return result.framePolicy;
4011
4095
  }
@@ -4499,6 +4583,12 @@ function sanitizeMetadata(metadata) {
4499
4583
  sanitized[key] = true;
4500
4584
  }
4501
4585
  }
4586
+ if (metadata?.[closedDockWindowFramesMetadataKey] !== void 0) {
4587
+ return writeClosedDockWindowFrameEntries(
4588
+ sanitized,
4589
+ readClosedDockWindowFrameEntries(metadata).values()
4590
+ );
4591
+ }
4502
4592
  return Object.keys(sanitized).length > 0 ? sanitized : void 0;
4503
4593
  }
4504
4594
 
@@ -4518,6 +4608,7 @@ var WorkbenchHostSessionController = class {
4518
4608
  isDisposed = false;
4519
4609
  isSnapshotLoaded = false;
4520
4610
  loadedSnapshot;
4611
+ closedDockWindowFrameEntries = /* @__PURE__ */ new Map();
4521
4612
  loadGeneration = 0;
4522
4613
  loadPromise = null;
4523
4614
  nodeLeases = /* @__PURE__ */ new Map();
@@ -4543,6 +4634,9 @@ var WorkbenchHostSessionController = class {
4543
4634
  this.nodeDefinitionByType.set(definition.typeId, definition);
4544
4635
  }
4545
4636
  this.loadedSnapshot = this.readCachedSnapshot();
4637
+ this.closedDockWindowFrameEntries = readClosedDockWindowFrameEntries(
4638
+ this.loadedSnapshot?.metadata
4639
+ );
4546
4640
  this.projectedNodes = input.projectedNodes ?? [];
4547
4641
  this.controller = createWorkbenchController(
4548
4642
  stateFromSnapshotOrDefinitions(
@@ -4648,6 +4742,7 @@ var WorkbenchHostSessionController = class {
4648
4742
  }
4649
4743
  closeNode(nodeId) {
4650
4744
  this.runWhenReady(this.loadGeneration, () => {
4745
+ this.rememberClosedDockWindowFrame(nodeId);
4651
4746
  this.controller.commands.closeNode(nodeId);
4652
4747
  });
4653
4748
  }
@@ -4770,6 +4865,9 @@ var WorkbenchHostSessionController = class {
4770
4865
  return;
4771
4866
  }
4772
4867
  this.loadedSnapshot = snapshot;
4868
+ this.closedDockWindowFrameEntries = readClosedDockWindowFrameEntries(
4869
+ snapshot?.metadata
4870
+ );
4773
4871
  this.controller.commands.replaceState(
4774
4872
  stateFromSnapshotOrDefinitions(
4775
4873
  snapshot,
@@ -4783,6 +4881,7 @@ var WorkbenchHostSessionController = class {
4783
4881
  return;
4784
4882
  }
4785
4883
  this.loadedSnapshot = null;
4884
+ this.closedDockWindowFrameEntries = /* @__PURE__ */ new Map();
4786
4885
  this.controller.commands.replaceState(
4787
4886
  stateFromSnapshotOrDefinitions(
4788
4887
  null,
@@ -4826,6 +4925,7 @@ var WorkbenchHostSessionController = class {
4826
4925
  if (this.isDisposed || resolvedDecision === "keep-open" || generation !== this.loadGeneration) {
4827
4926
  return;
4828
4927
  }
4928
+ this.rememberClosedDockWindowFrame(node.id);
4829
4929
  this.controller.commands.closeNode(node.id);
4830
4930
  }, noop);
4831
4931
  }
@@ -4957,18 +5057,19 @@ var WorkbenchHostSessionController = class {
4957
5057
  if (!definition) {
4958
5058
  return null;
4959
5059
  }
5060
+ const launchResult = this.applyClosedDockWindowFrame(result);
4960
5061
  const exactExisting = this.findExistingNodeByTarget({
4961
- instanceId: result.instanceId,
4962
- typeId: result.typeId
5062
+ instanceId: launchResult.instanceId,
5063
+ typeId: launchResult.typeId
4963
5064
  });
4964
- const dockEntryExisting = exactExisting ? null : this.findExistingNodeByDockEntry(result);
5065
+ const dockEntryExisting = exactExisting ? null : this.findExistingNodeByDockEntry(launchResult);
4965
5066
  const existing = exactExisting ?? dockEntryExisting;
4966
5067
  if (existing) {
4967
5068
  this.controller.commands.setNodeSizeConstraints(
4968
5069
  existing.id,
4969
- result.sizeConstraints ?? definition.sizeConstraints ?? null
5070
+ launchResult.sizeConstraints ?? definition.sizeConstraints ?? null
4970
5071
  );
4971
- if (exactExisting && result.framePolicy === "cascade-same-type-centered") {
5072
+ if (exactExisting && launchResult.framePolicy === "cascade-same-type-centered") {
4972
5073
  const currentState = this.controller.getSnapshot();
4973
5074
  const resolvedFrame2 = resolveWorkbenchLaunchedHostNodeFrame({
4974
5075
  currentState: {
@@ -4979,26 +5080,26 @@ var WorkbenchHostSessionController = class {
4979
5080
  )
4980
5081
  },
4981
5082
  definition,
4982
- result
5083
+ result: launchResult
4983
5084
  });
4984
5085
  this.controller.commands.resizeNode(existing.id, resolvedFrame2);
4985
5086
  }
4986
5087
  this.restoreAndFocusNode(existing.id);
4987
- if (result.activation) {
4988
- this.activateNodeNow({ nodeId: existing.id }, result.activation);
5088
+ if (launchResult.activation) {
5089
+ this.activateNodeNow({ nodeId: existing.id }, launchResult.activation);
4989
5090
  }
4990
5091
  return existing.id;
4991
5092
  }
4992
5093
  const resolvedFrame = resolveWorkbenchLaunchedHostNodeFrame({
4993
5094
  currentState: this.controller.getSnapshot(),
4994
5095
  definition,
4995
- result
5096
+ result: launchResult
4996
5097
  });
4997
5098
  const nextNode = createWorkbenchLaunchedHostNode({
4998
- activation: this.createActivationEnvelope(result.activation),
5099
+ activation: this.createActivationEnvelope(launchResult.activation),
4999
5100
  definition,
5000
5101
  resolvedFrame,
5001
- result
5102
+ result: launchResult
5002
5103
  });
5003
5104
  this.controller.commands.openNode(nextNode);
5004
5105
  this.controller.commands.focusNode(nextNode.id);
@@ -5154,10 +5255,13 @@ var WorkbenchHostSessionController = class {
5154
5255
  ),
5155
5256
  {
5156
5257
  activeSpaceId: this.loadedSnapshot?.activeSpaceId,
5157
- metadata: {
5158
- ...this.loadedSnapshot?.metadata ?? {},
5159
- [initializedMetadataKey]: true
5160
- },
5258
+ metadata: writeClosedDockWindowFrameEntries(
5259
+ {
5260
+ ...this.loadedSnapshot?.metadata ?? {},
5261
+ [initializedMetadataKey]: true
5262
+ },
5263
+ this.closedDockWindowFrameEntries.values()
5264
+ ),
5161
5265
  spaces: this.loadedSnapshot?.spaces
5162
5266
  }
5163
5267
  )
@@ -5171,6 +5275,42 @@ var WorkbenchHostSessionController = class {
5171
5275
  this.loadedSnapshot = savedSnapshot;
5172
5276
  }, noop);
5173
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
+ }
5174
5314
  loadedSnapshotSaveKey() {
5175
5315
  if (!this.loadedSnapshot) {
5176
5316
  return null;
@@ -5522,11 +5662,7 @@ import {
5522
5662
  ArrowRightIcon,
5523
5663
  Button as Button4,
5524
5664
  ChevronDownIcon,
5525
- ChevronUpIcon,
5526
- Tooltip,
5527
- TooltipContent,
5528
- TooltipProvider,
5529
- TooltipTrigger
5665
+ ChevronUpIcon
5530
5666
  } from "@tutti-os/ui-system";
5531
5667
 
5532
5668
  // src/host/dockEntries.ts
@@ -5599,6 +5735,9 @@ function resolveWorkbenchDockEntryClick(input) {
5599
5735
  }
5600
5736
  return { actionId: input.entry.clickActionId, kind: "action" };
5601
5737
  }
5738
+ if (isWorkbenchDockEntryBlocked(input.entry)) {
5739
+ return { kind: "blocked" };
5740
+ }
5602
5741
  if (input.instanceMode === "multi" && input.matchedNodes.length > 0) {
5603
5742
  return { kind: "open-popup" };
5604
5743
  }
@@ -5608,9 +5747,6 @@ function resolveWorkbenchDockEntryClick(input) {
5608
5747
  if (input.matchedNodes.length > 1) {
5609
5748
  return { kind: "open-popup" };
5610
5749
  }
5611
- if (isWorkbenchDockEntryBlocked(input.entry)) {
5612
- return { kind: "blocked" };
5613
- }
5614
5750
  return { kind: "launch" };
5615
5751
  }
5616
5752
  function isWorkbenchDockEntryBlocked(entry) {
@@ -8024,7 +8160,7 @@ function WorkbenchHostDock({
8024
8160
  style: dockFrameSize === null ? void 0 : {
8025
8161
  "--desktop-dock-frame-size": `${dockFrameSize}px`
8026
8162
  },
8027
- children: /* @__PURE__ */ jsx10(TooltipProvider, { delayDuration: 500, children: /* @__PURE__ */ jsxs6(
8163
+ children: /* @__PURE__ */ jsxs6(
8028
8164
  "div",
8029
8165
  {
8030
8166
  ref: dockMeasureRef,
@@ -8104,13 +8240,13 @@ function WorkbenchHostDock({
8104
8240
  "aria-expanded": currentPopup ? true : void 0,
8105
8241
  "aria-haspopup": clickResolution.kind === "open-popup" ? "dialog" : void 0,
8106
8242
  "aria-label": i18n.t("launch", { title: entry.label }),
8107
- "aria-disabled": clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes,
8243
+ "aria-disabled": clickResolution.kind === "blocked" ? true : void 0,
8108
8244
  className: "desktop-dock__btn",
8109
- "data-interactive": clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes ? "false" : "true",
8245
+ "data-interactive": clickResolution.kind === "blocked" ? "false" : "true",
8110
8246
  title: entry.label,
8111
8247
  type: "button",
8112
8248
  onPointerDown: () => {
8113
- if (clickResolution.kind === "blocked" && !resolvedEntry.hasMatchingNodes) {
8249
+ if (clickResolution.kind === "blocked") {
8114
8250
  return;
8115
8251
  }
8116
8252
  beginDockIconInteraction(anchorKey);
@@ -8119,8 +8255,18 @@ function WorkbenchHostDock({
8119
8255
  logWorkbenchDockDebug("dock.click", debugDiagnostics, {
8120
8256
  anchorKey,
8121
8257
  clickResolution,
8258
+ dockDiagnostics: entry.diagnostics ?? null,
8122
8259
  dockNodeState: resolvedEntry.dockNodeState,
8123
8260
  entryId: entry.id,
8261
+ entryLaunchBehavior: entry.launchBehavior ?? "enabled",
8262
+ entryOrder: entry.order ?? null,
8263
+ entryState: entry.state ?? { kind: "enabled" },
8264
+ entryVisibility: entry.visibility ?? "always",
8265
+ hoverActions: entry.hoverActions?.map((action) => ({
8266
+ disabled: action.disabled === true,
8267
+ id: action.id,
8268
+ pending: Boolean(action.pendingLabel)
8269
+ })) ?? [],
8124
8270
  instanceMode: instanceMode ?? null,
8125
8271
  matchedNodeCount: resolvedEntry.matchedNodes.length,
8126
8272
  matchedNodeIds: resolvedEntry.matchedNodes.map(
@@ -8278,17 +8424,7 @@ function WorkbenchHostDock({
8278
8424
  closeHoverPanelImmediate(entry.id);
8279
8425
  handleDockPointerLeave();
8280
8426
  },
8281
- children: hasHoverPanel ? dockButton2 : /* @__PURE__ */ jsxs6(Tooltip, { children: [
8282
- /* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: dockButton2 }),
8283
- /* @__PURE__ */ jsx10(
8284
- TooltipContent,
8285
- {
8286
- side: dockPlacement === "left" ? "right" : "top",
8287
- sideOffset: DOCK_MAGNIFIED_TOOLTIP_SIDE_OFFSET,
8288
- children: entry.label
8289
- }
8290
- )
8291
- ] })
8427
+ children: dockButton2
8292
8428
  },
8293
8429
  dockItem.key
8294
8430
  );
@@ -8381,17 +8517,7 @@ function WorkbenchHostDock({
8381
8517
  "data-presence": dockItem.presence,
8382
8518
  "data-section-id": "minimized",
8383
8519
  "data-stack-dispatching": stackDispatching ? "true" : void 0,
8384
- children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
8385
- /* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: stackButton }),
8386
- /* @__PURE__ */ jsx10(
8387
- TooltipContent,
8388
- {
8389
- side: dockPlacement === "left" ? "right" : "top",
8390
- sideOffset: DOCK_MAGNIFIED_TOOLTIP_SIDE_OFFSET,
8391
- children: i18n.t("minimizedWindows")
8392
- }
8393
- )
8394
- ] })
8520
+ children: stackButton
8395
8521
  },
8396
8522
  dockItem.key
8397
8523
  );
@@ -8470,17 +8596,7 @@ function WorkbenchHostDock({
8470
8596
  "data-presence": dockItem.presence,
8471
8597
  "data-promoted-from-stack": promotedNodeId === node.id ? "true" : void 0,
8472
8598
  "data-section-id": "minimized",
8473
- children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
8474
- /* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: dockButton }),
8475
- /* @__PURE__ */ jsx10(
8476
- TooltipContent,
8477
- {
8478
- side: dockPlacement === "left" ? "right" : "top",
8479
- sideOffset: DOCK_MAGNIFIED_TOOLTIP_SIDE_OFFSET,
8480
- children: node.title
8481
- }
8482
- )
8483
- ] })
8599
+ children: dockButton
8484
8600
  },
8485
8601
  dockItem.key
8486
8602
  );
@@ -8534,7 +8650,7 @@ function WorkbenchHostDock({
8534
8650
  ) : null
8535
8651
  ]
8536
8652
  }
8537
- ) })
8653
+ )
8538
8654
  }
8539
8655
  ),
8540
8656
  popupEntry && activePopup ? /* @__PURE__ */ jsx10(
@@ -9471,7 +9587,6 @@ var dockHoverPanelCloseDelayMs = 160;
9471
9587
  var dockHoverPanelHitSlopPx = 12;
9472
9588
  var dockHoverPanelBridgeSlopPx = 6;
9473
9589
  var dockHoverPanelPointerRestTolerancePx = 4;
9474
- var DOCK_MAGNIFIED_TOOLTIP_SIDE_OFFSET = 40;
9475
9590
  var dockWallpaperSampleCanvasMaxSizePx = 192;
9476
9591
  var dockWallpaperToneSampleCount = 7;
9477
9592
  var dockWallpaperDarkLuminanceThreshold = 132;