@tutti-os/workbench-surface 0.0.12 → 0.0.14

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
@@ -5356,7 +5356,7 @@ function useWorkbenchHostRuntime({
5356
5356
  snapshotRepository,
5357
5357
  workspaceId
5358
5358
  }) {
5359
- const [, bumpExternalStateRevision] = useState5(0);
5359
+ const [externalStateRevision, bumpExternalStateRevision] = useState5(0);
5360
5360
  const [, bumpHydrationRevision] = useState5(0);
5361
5361
  const hostSession = useMemo4(() => {
5362
5362
  logWorkbenchHostDebug("create-session", debugDiagnostics, {
@@ -5460,6 +5460,7 @@ function useWorkbenchHostRuntime({
5460
5460
  }, [externalStateSource, workspaceId]);
5461
5461
  return {
5462
5462
  chromeContext,
5463
+ externalStateRevision,
5463
5464
  hostI18n,
5464
5465
  isHydrating,
5465
5466
  hostSession,
@@ -7305,6 +7306,7 @@ function WorkbenchHostDock({
7305
7306
  );
7306
7307
  const pendingDockStateRefreshRef = useRef8(false);
7307
7308
  const slotRefs = useRef8(/* @__PURE__ */ new Map());
7309
+ const wallpaperToneElementRefs = useRef8(/* @__PURE__ */ new Map());
7308
7310
  const dockSlotRefCallbacksRef = useRef8(
7309
7311
  /* @__PURE__ */ new Map()
7310
7312
  );
@@ -7485,6 +7487,15 @@ function WorkbenchHostDock({
7485
7487
  dockItems,
7486
7488
  (nodeId) => context.genie.shouldAnimateMinimizedDockEnter(nodeId)
7487
7489
  );
7490
+ const presentDockItemKeys = useMemo5(
7491
+ () => presentDockItems.map((item) => item.key).join("\n"),
7492
+ [presentDockItems]
7493
+ );
7494
+ const wallpaperTones = useDockWallpaperTones({
7495
+ dockItemsRef,
7496
+ elementRefs: wallpaperToneElementRefs,
7497
+ itemKeys: presentDockItemKeys
7498
+ });
7488
7499
  const dockWidth = useMemo5(
7489
7500
  () => resolveWorkbenchHostDockItemsWidth(dockItems),
7490
7501
  [dockItems]
@@ -7514,6 +7525,16 @@ function WorkbenchHostDock({
7514
7525
  registerDockAnchorRef.current = (anchorKey, element) => {
7515
7526
  context.genie.registerDockAnchor(anchorKey, element);
7516
7527
  };
7528
+ const registerWallpaperToneElement = useCallback10(
7529
+ (key) => (element) => {
7530
+ if (element) {
7531
+ wallpaperToneElementRefs.current.set(key, element);
7532
+ return;
7533
+ }
7534
+ wallpaperToneElementRefs.current.delete(key);
7535
+ },
7536
+ []
7537
+ );
7517
7538
  const setDockHoverPanelOpen = useCallback10((open) => {
7518
7539
  if (open) {
7519
7540
  dockMeasureRef.current?.setAttribute(
@@ -7971,8 +7992,10 @@ function WorkbenchHostDock({
7971
7992
  const callback = (element) => {
7972
7993
  if (element) {
7973
7994
  slotRefs.current.set(anchorKey, element);
7995
+ wallpaperToneElementRefs.current.set(anchorKey, element);
7974
7996
  } else {
7975
7997
  slotRefs.current.delete(anchorKey);
7998
+ wallpaperToneElementRefs.current.delete(anchorKey);
7976
7999
  if (!dockMeasureRef.current?.hasAttribute("data-dock-pointer-active")) {
7977
8000
  clearSlotMagnificationRef.current(anchorKey);
7978
8001
  }
@@ -8052,9 +8075,11 @@ function WorkbenchHostDock({
8052
8075
  return /* @__PURE__ */ jsx10(
8053
8076
  "span",
8054
8077
  {
8078
+ ref: registerWallpaperToneElement(dockItem.key),
8055
8079
  className: "desktop-dock__separator",
8056
8080
  "aria-hidden": "true",
8057
- "data-presence": dockItem.presence
8081
+ "data-presence": dockItem.presence,
8082
+ "data-wallpaper-tone": wallpaperTones.get(dockItem.key)
8058
8083
  },
8059
8084
  dockItem.key
8060
8085
  );
@@ -8211,6 +8236,7 @@ function WorkbenchHostDock({
8211
8236
  "data-popup-active": currentPopup ? "true" : void 0,
8212
8237
  "data-presence": dockItem.presence,
8213
8238
  "data-section-id": entry.sectionId,
8239
+ "data-wallpaper-tone": wallpaperTones.get(anchorKey),
8214
8240
  onBlur: (event) => {
8215
8241
  if (hasHoverPanel && !event.currentTarget.contains(event.relatedTarget)) {
8216
8242
  closeHoverPanelImmediate(entry.id);
@@ -9130,6 +9156,257 @@ function useDockPresenceItems(items, shouldAnimateMinimizedDockEnter) {
9130
9156
  return latestItem ? { ...presentItem, item: latestItem } : presentItem;
9131
9157
  });
9132
9158
  }
9159
+ function useDockWallpaperTones({
9160
+ dockItemsRef,
9161
+ elementRefs,
9162
+ itemKeys
9163
+ }) {
9164
+ const [tones, setTones] = useState8(
9165
+ () => /* @__PURE__ */ new Map()
9166
+ );
9167
+ useLayoutEffect5(() => {
9168
+ if (typeof window === "undefined") {
9169
+ return void 0;
9170
+ }
9171
+ let canceled = false;
9172
+ let frameId = null;
9173
+ const updateTones = () => {
9174
+ frameId = null;
9175
+ void resolveDockWallpaperTones({
9176
+ dockItemsElement: dockItemsRef.current,
9177
+ elements: elementRefs.current
9178
+ }).then((nextTones) => {
9179
+ if (canceled) {
9180
+ return;
9181
+ }
9182
+ setTones(
9183
+ (current) => dockWallpaperToneMapsEqual(current, nextTones) ? current : nextTones
9184
+ );
9185
+ });
9186
+ };
9187
+ const scheduleUpdate = () => {
9188
+ if (frameId !== null) {
9189
+ return;
9190
+ }
9191
+ frameId = window.requestAnimationFrame(updateTones);
9192
+ };
9193
+ scheduleUpdate();
9194
+ window.addEventListener("resize", scheduleUpdate);
9195
+ const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(scheduleUpdate);
9196
+ if (dockItemsRef.current) {
9197
+ resizeObserver?.observe(dockItemsRef.current);
9198
+ }
9199
+ const wallpaperElement = dockItemsRef.current?.closest(".workbench-surface")?.querySelector(".workbench-surface__wallpaper");
9200
+ if (wallpaperElement instanceof HTMLElement) {
9201
+ resizeObserver?.observe(wallpaperElement);
9202
+ }
9203
+ return () => {
9204
+ canceled = true;
9205
+ if (frameId !== null) {
9206
+ window.cancelAnimationFrame(frameId);
9207
+ }
9208
+ resizeObserver?.disconnect();
9209
+ window.removeEventListener("resize", scheduleUpdate);
9210
+ };
9211
+ }, [dockItemsRef, elementRefs, itemKeys]);
9212
+ return tones;
9213
+ }
9214
+ async function resolveDockWallpaperTones({
9215
+ dockItemsElement,
9216
+ elements
9217
+ }) {
9218
+ const wallpaperElement = dockItemsElement?.closest(".workbench-surface")?.querySelector(".workbench-surface__wallpaper");
9219
+ if (!(wallpaperElement instanceof HTMLElement)) {
9220
+ return /* @__PURE__ */ new Map();
9221
+ }
9222
+ const wallpaperStyle = window.getComputedStyle(wallpaperElement);
9223
+ const wallpaperUrl = parseDockWallpaperCssUrl(wallpaperStyle.backgroundImage);
9224
+ if (!wallpaperUrl) {
9225
+ return /* @__PURE__ */ new Map();
9226
+ }
9227
+ const wallpaperImage = await loadDockWallpaperImage(wallpaperUrl);
9228
+ if (!wallpaperImage) {
9229
+ return /* @__PURE__ */ new Map();
9230
+ }
9231
+ const sampleCanvas = createDockWallpaperSampleCanvas(wallpaperImage);
9232
+ if (!sampleCanvas) {
9233
+ return /* @__PURE__ */ new Map();
9234
+ }
9235
+ const wallpaperRect = wallpaperElement.getBoundingClientRect();
9236
+ const renderedImageRect = resolveDockWallpaperRenderedImageRect({
9237
+ containerHeight: wallpaperRect.height,
9238
+ containerWidth: wallpaperRect.width,
9239
+ imageHeight: wallpaperImage.naturalHeight,
9240
+ imageWidth: wallpaperImage.naturalWidth,
9241
+ positionX: wallpaperStyle.backgroundPositionX,
9242
+ positionY: wallpaperStyle.backgroundPositionY,
9243
+ size: wallpaperStyle.backgroundSize
9244
+ });
9245
+ const nextTones = /* @__PURE__ */ new Map();
9246
+ for (const [key, element] of elements) {
9247
+ const luminance = sampleDockWallpaperLuminanceAtElement({
9248
+ elementRect: element.getBoundingClientRect(),
9249
+ renderedImageRect,
9250
+ sampleCanvas,
9251
+ wallpaperRect
9252
+ });
9253
+ if (luminance === null) {
9254
+ continue;
9255
+ }
9256
+ nextTones.set(
9257
+ key,
9258
+ luminance < dockWallpaperDarkLuminanceThreshold ? "dark" : "light"
9259
+ );
9260
+ }
9261
+ return nextTones;
9262
+ }
9263
+ function parseDockWallpaperCssUrl(backgroundImage) {
9264
+ const match = /^url\((['"]?)(.*)\1\)$/u.exec(backgroundImage.trim());
9265
+ return match?.[2] ? match[2] : null;
9266
+ }
9267
+ var dockWallpaperImageCache = /* @__PURE__ */ new Map();
9268
+ function loadDockWallpaperImage(url) {
9269
+ const cached = dockWallpaperImageCache.get(url);
9270
+ if (cached) {
9271
+ return cached;
9272
+ }
9273
+ const promise = new Promise((resolve) => {
9274
+ const image = new Image();
9275
+ image.decoding = "async";
9276
+ image.onload = () => resolve(image);
9277
+ image.onerror = () => resolve(null);
9278
+ image.src = url;
9279
+ });
9280
+ dockWallpaperImageCache.set(url, promise);
9281
+ return promise;
9282
+ }
9283
+ function createDockWallpaperSampleCanvas(image) {
9284
+ if (image.naturalWidth <= 0 || image.naturalHeight <= 0) {
9285
+ return null;
9286
+ }
9287
+ const canvas = document.createElement("canvas");
9288
+ const scale = dockWallpaperSampleCanvasMaxSizePx / Math.max(image.naturalWidth, image.naturalHeight);
9289
+ canvas.width = Math.max(1, Math.round(image.naturalWidth * scale));
9290
+ canvas.height = Math.max(1, Math.round(image.naturalHeight * scale));
9291
+ const context = canvas.getContext("2d", { willReadFrequently: true });
9292
+ if (!context) {
9293
+ return null;
9294
+ }
9295
+ try {
9296
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
9297
+ context.getImageData(0, 0, 1, 1);
9298
+ } catch {
9299
+ return null;
9300
+ }
9301
+ return canvas;
9302
+ }
9303
+ function resolveDockWallpaperRenderedImageRect({
9304
+ containerHeight,
9305
+ containerWidth,
9306
+ imageHeight,
9307
+ imageWidth,
9308
+ positionX,
9309
+ positionY,
9310
+ size
9311
+ }) {
9312
+ const imageAspect = imageWidth / imageHeight;
9313
+ const containerAspect = containerWidth / containerHeight;
9314
+ let width = containerWidth;
9315
+ let height = containerHeight;
9316
+ if (size === "cover") {
9317
+ if (containerAspect > imageAspect) {
9318
+ height = containerWidth / imageAspect;
9319
+ } else {
9320
+ width = containerHeight * imageAspect;
9321
+ }
9322
+ } else if (size === "contain") {
9323
+ if (containerAspect > imageAspect) {
9324
+ width = containerHeight * imageAspect;
9325
+ } else {
9326
+ height = containerWidth / imageAspect;
9327
+ }
9328
+ } else if (size !== "100% 100%") {
9329
+ width = imageWidth;
9330
+ height = imageHeight;
9331
+ }
9332
+ return {
9333
+ height,
9334
+ left: resolveDockWallpaperPositionOffset(positionX, containerWidth - width),
9335
+ top: resolveDockWallpaperPositionOffset(
9336
+ positionY,
9337
+ containerHeight - height
9338
+ ),
9339
+ width
9340
+ };
9341
+ }
9342
+ function resolveDockWallpaperPositionOffset(value, availableSpace) {
9343
+ const trimmed = value.trim();
9344
+ if (trimmed.endsWith("%")) {
9345
+ const percentage = Number.parseFloat(trimmed);
9346
+ return Number.isFinite(percentage) ? availableSpace * percentage / 100 : availableSpace / 2;
9347
+ }
9348
+ if (trimmed.endsWith("px")) {
9349
+ const px = Number.parseFloat(trimmed);
9350
+ return Number.isFinite(px) ? px : availableSpace / 2;
9351
+ }
9352
+ if (trimmed === "left" || trimmed === "top") {
9353
+ return 0;
9354
+ }
9355
+ if (trimmed === "right" || trimmed === "bottom") {
9356
+ return availableSpace;
9357
+ }
9358
+ return availableSpace / 2;
9359
+ }
9360
+ function sampleDockWallpaperLuminanceAtElement({
9361
+ elementRect,
9362
+ renderedImageRect,
9363
+ sampleCanvas,
9364
+ wallpaperRect
9365
+ }) {
9366
+ const context = sampleCanvas.getContext("2d", { willReadFrequently: true });
9367
+ if (!context) {
9368
+ return null;
9369
+ }
9370
+ const isVerticalElement = elementRect.height >= elementRect.width;
9371
+ let luminanceSum = 0;
9372
+ let samples = 0;
9373
+ for (let index = 0; index < dockWallpaperToneSampleCount; index += 1) {
9374
+ const ratio = index / (dockWallpaperToneSampleCount - 1);
9375
+ const clientX = isVerticalElement ? elementRect.left + elementRect.width / 2 : elementRect.left + elementRect.width * ratio;
9376
+ const clientY = isVerticalElement ? elementRect.top + elementRect.height * ratio : elementRect.top + elementRect.height / 2;
9377
+ const imageX = (clientX - wallpaperRect.left - renderedImageRect.left) / renderedImageRect.width;
9378
+ const imageY = (clientY - wallpaperRect.top - renderedImageRect.top) / renderedImageRect.height;
9379
+ if (imageX < 0 || imageX > 1 || imageY < 0 || imageY > 1) {
9380
+ continue;
9381
+ }
9382
+ const canvasX = Math.min(
9383
+ sampleCanvas.width - 1,
9384
+ Math.max(0, Math.round(imageX * (sampleCanvas.width - 1)))
9385
+ );
9386
+ const canvasY = Math.min(
9387
+ sampleCanvas.height - 1,
9388
+ Math.max(0, Math.round(imageY * (sampleCanvas.height - 1)))
9389
+ );
9390
+ const pixel = context.getImageData(canvasX, canvasY, 1, 1).data;
9391
+ const red = pixel[0] ?? 0;
9392
+ const green = pixel[1] ?? 0;
9393
+ const blue = pixel[2] ?? 0;
9394
+ luminanceSum += 0.2126 * red + 0.7152 * green + 0.0722 * blue;
9395
+ samples += 1;
9396
+ }
9397
+ return samples > 0 ? luminanceSum / samples : null;
9398
+ }
9399
+ function dockWallpaperToneMapsEqual(left, right) {
9400
+ if (left.size !== right.size) {
9401
+ return false;
9402
+ }
9403
+ for (const [key, value] of left) {
9404
+ if (right.get(key) !== value) {
9405
+ return false;
9406
+ }
9407
+ }
9408
+ return true;
9409
+ }
9133
9410
  var DOCK_BOUNCE_MS = 600;
9134
9411
  function useDockBounce(slotRefs) {
9135
9412
  const timeoutsRef = useRef8(/* @__PURE__ */ new Map());
@@ -9195,6 +9472,9 @@ var dockHoverPanelHitSlopPx = 12;
9195
9472
  var dockHoverPanelBridgeSlopPx = 6;
9196
9473
  var dockHoverPanelPointerRestTolerancePx = 4;
9197
9474
  var DOCK_MAGNIFIED_TOOLTIP_SIDE_OFFSET = 40;
9475
+ var dockWallpaperSampleCanvasMaxSizePx = 192;
9476
+ var dockWallpaperToneSampleCount = 7;
9477
+ var dockWallpaperDarkLuminanceThreshold = 132;
9198
9478
  function rectContainsPoint(rect, clientX, clientY, slopPx = 0) {
9199
9479
  return clientX >= rect.left - slopPx && clientX <= rect.right + slopPx && clientY >= rect.top - slopPx && clientY <= rect.bottom + slopPx;
9200
9480
  }
@@ -9370,7 +9650,7 @@ function WorkbenchHostWindowActions({
9370
9650
  }
9371
9651
 
9372
9652
  // src/host/useWorkbenchHostSurfaceRenderers.tsx
9373
- import { jsx as jsx12 } from "react/jsx-runtime";
9653
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
9374
9654
  function useWorkbenchHostSurfaceRenderers(input) {
9375
9655
  const renderBottomChrome = useMemo6(() => {
9376
9656
  const renderChrome = input.renderBottomChrome;
@@ -9530,7 +9810,7 @@ function useWorkbenchHostSurfaceRenderers(input) {
9530
9810
  {
9531
9811
  debugDiagnostics: input.debugDiagnostics,
9532
9812
  node: context.node,
9533
- resetKey: `${context.node.id}:${context.node.data.typeId}`,
9813
+ resetKey: `${context.node.id}:${context.node.data.typeId}:${input.externalStateRevision}`,
9534
9814
  workspaceId: input.workspaceId,
9535
9815
  children: /* @__PURE__ */ jsx12(
9536
9816
  WorkbenchHostNodeBodyRenderer,
@@ -9545,6 +9825,7 @@ function useWorkbenchHostSurfaceRenderers(input) {
9545
9825
  [
9546
9826
  input.debugDiagnostics,
9547
9827
  input.externalStateSource,
9828
+ input.externalStateRevision,
9548
9829
  input.hostSession,
9549
9830
  input.nodeDefinitionByType,
9550
9831
  input.workspaceId
@@ -9582,6 +9863,7 @@ function useWorkbenchHostSurfaceRenderers(input) {
9582
9863
  },
9583
9864
  [
9584
9865
  input.externalStateSource,
9866
+ input.externalStateRevision,
9585
9867
  input.hostSession,
9586
9868
  input.nodeDefinitionByType,
9587
9869
  input.workspaceId
@@ -9739,11 +10021,57 @@ var WorkbenchHostNodeRenderErrorBoundary = class extends Component {
9739
10021
  }
9740
10022
  render() {
9741
10023
  if (this.state.hasError) {
9742
- return /* @__PURE__ */ jsx12(
10024
+ return /* @__PURE__ */ jsxs8(
9743
10025
  "div",
9744
10026
  {
9745
10027
  "data-workbench-node-render-error": "true",
9746
- style: { height: "100%", width: "100%" }
10028
+ style: {
10029
+ alignItems: "center",
10030
+ boxSizing: "border-box",
10031
+ color: "var(--text-secondary, #6b7280)",
10032
+ display: "flex",
10033
+ flexDirection: "column",
10034
+ gap: 8,
10035
+ height: "100%",
10036
+ justifyContent: "center",
10037
+ padding: 24,
10038
+ textAlign: "center",
10039
+ width: "100%"
10040
+ },
10041
+ children: [
10042
+ /* @__PURE__ */ jsx12(
10043
+ "div",
10044
+ {
10045
+ "data-workbench-node-render-error-message": "true",
10046
+ style: {
10047
+ color: "var(--text-primary, #111827)",
10048
+ fontSize: 14,
10049
+ fontWeight: 600
10050
+ },
10051
+ children: "This workspace view failed to render."
10052
+ }
10053
+ ),
10054
+ /* @__PURE__ */ jsx12("div", { style: { fontSize: 12 }, children: "Try selecting another conversation or reopen the window." }),
10055
+ /* @__PURE__ */ jsx12(
10056
+ "button",
10057
+ {
10058
+ type: "button",
10059
+ onClick: () => this.setState({ hasError: false }),
10060
+ style: {
10061
+ border: "1px solid var(--line-2, #d1d5db)",
10062
+ borderRadius: 6,
10063
+ background: "var(--background-fronted, #ffffff)",
10064
+ color: "var(--text-primary, #111827)",
10065
+ cursor: "pointer",
10066
+ fontSize: 12,
10067
+ fontWeight: 600,
10068
+ marginTop: 4,
10069
+ padding: "6px 10px"
10070
+ },
10071
+ children: "Try again"
10072
+ }
10073
+ )
10074
+ ]
9747
10075
  }
9748
10076
  );
9749
10077
  }
@@ -9823,6 +10151,7 @@ function WorkbenchHost({
9823
10151
  const missionControlEnabled = missionControlMode !== null || onMissionControlAdapterReady !== void 0;
9824
10152
  const {
9825
10153
  chromeContext,
10154
+ externalStateRevision,
9826
10155
  hostI18n,
9827
10156
  hostSession,
9828
10157
  isHydrating,
@@ -9853,6 +10182,7 @@ function WorkbenchHost({
9853
10182
  dockStateSource,
9854
10183
  dockEntries: hostDockEntries,
9855
10184
  externalStateSource: hostRuntimeConfig.externalStateSource,
10185
+ externalStateRevision,
9856
10186
  hostI18n,
9857
10187
  hostSession,
9858
10188
  nodeDefinitionByType,