@ggui-ai/ui-visual-tester 0.22.0 → 0.24.0

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.
@@ -20061,11 +20061,20 @@
20061
20061
 
20062
20062
  // ../design/dist/interact/trait.js
20063
20063
  var import_react = __toESM(require_react(), 1);
20064
+ function frameAttributes(frame) {
20065
+ if (frame === void 0)
20066
+ return {};
20067
+ return {
20068
+ ...frame.surface !== void 0 ? { "data-ggui-surface": frame.surface } : {},
20069
+ ...frame.bleed === true ? { "data-ggui-bleed": "" } : {}
20070
+ };
20071
+ }
20064
20072
  function renderWithTrait(trait, traitProps, root, children) {
20073
+ const marks = frameAttributes(root.frame);
20065
20074
  if (trait === void 0) {
20066
- return (0, import_react.createElement)("div", { className: root.className, style: root.style }, children);
20075
+ return (0, import_react.createElement)("div", { className: root.className, style: root.style, ...marks }, children);
20067
20076
  }
20068
- return (0, import_react.createElement)(trait, { ...traitProps, className: root.className, style: root.style }, children);
20077
+ return (0, import_react.createElement)(trait, { ...traitProps, className: root.className, style: root.style, ...marks }, children);
20069
20078
  }
20070
20079
 
20071
20080
  // ../design/dist/primitives/color-slots.js
@@ -20195,9 +20204,14 @@
20195
20204
  surface,
20196
20205
  style,
20197
20206
  className,
20207
+ bleed,
20198
20208
  as: Trait,
20199
20209
  ...traitProps
20200
20210
  } = props;
20211
+ const frame = {
20212
+ ...surface !== "transparent" ? { surface: surface ?? "default" } : {},
20213
+ ...bleed === true ? { bleed: true } : {}
20214
+ };
20201
20215
  const resolvedPadding = resolveSpacing(padding);
20202
20216
  const resolvedSurface = resolveSurfaceCss(surface ?? "default");
20203
20217
  const resolvedOnColor = resolveSurfaceOnColorCss(surface ?? "default");
@@ -20215,10 +20229,11 @@
20215
20229
  if (scope !== void 0) {
20216
20230
  return renderWithTrait(Trait, traitProps, {
20217
20231
  className: className ? `${className} ${scope.className}` : scope.className,
20218
- style: composedStyle
20232
+ style: composedStyle,
20233
+ frame
20219
20234
  }, (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [(0, import_jsx_runtime2.jsx)("style", { children: scope.css }), children] }));
20220
20235
  }
20221
- return renderWithTrait(Trait, traitProps, { className, style: composedStyle }, children);
20236
+ return renderWithTrait(Trait, traitProps, { className, style: composedStyle, frame }, children);
20222
20237
  }
20223
20238
 
20224
20239
  // ../design/dist/primitives/Stack.js
@@ -20315,7 +20330,9 @@
20315
20330
  // ../design/dist/primitives/Box.js
20316
20331
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
20317
20332
  function Box(props) {
20318
- const { children, padding, paddingX, paddingY, margin, surface, assetColor, assetSemantic, radius: radius2, style, className, as: Trait, ...traitProps } = props;
20333
+ const { children, padding, paddingX, paddingY, margin, surface, assetColor, assetSemantic, radius: radius2, style, className, bleed, as: Trait, ...traitProps } = props;
20334
+ const markedSurface = surface !== void 0 && surface !== "transparent" ? surface : void 0;
20335
+ const frame = markedSurface === void 0 && bleed !== true ? void 0 : { ...markedSurface !== void 0 ? { surface: markedSurface } : {}, ...bleed === true ? { bleed: true } : {} };
20319
20336
  let computedPadding;
20320
20337
  if (paddingX !== void 0 || paddingY !== void 0) {
20321
20338
  const px = resolveSpacing(paddingX) || "0";
@@ -20340,10 +20357,11 @@
20340
20357
  if (scope !== void 0) {
20341
20358
  return renderWithTrait(Trait, traitProps, {
20342
20359
  className: className ? `${className} ${scope.className}` : scope.className,
20343
- style: composedStyle
20360
+ style: composedStyle,
20361
+ ...frame !== void 0 ? { frame } : {}
20344
20362
  }, (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [(0, import_jsx_runtime4.jsx)("style", { children: scope.css }), children] }));
20345
20363
  }
20346
- return renderWithTrait(Trait, traitProps, { className, style: composedStyle }, children);
20364
+ return renderWithTrait(Trait, traitProps, { className, style: composedStyle, ...frame !== void 0 ? { frame } : {} }, children);
20347
20365
  }
20348
20366
 
20349
20367
  // ../design/dist/primitives/Divider.js
@@ -28559,6 +28577,7 @@
28559
28577
  buildWireConfig: () => buildWireConfig,
28560
28578
  connectionSource: () => connectionSource,
28561
28579
  useAction: () => useAction,
28580
+ useActionSpent: () => useActionSpent,
28562
28581
  useApp: () => useApp,
28563
28582
  useAuth: () => useAuth,
28564
28583
  useContract: () => useContract,
@@ -28649,8 +28668,19 @@
28649
28668
  }, [actionName, dispatch, onDispatchSuppressed]);
28650
28669
  }
28651
28670
 
28652
- // ../wire/dist/useGguiContext.js
28671
+ // ../wire/dist/action-spent.js
28653
28672
  var import_react35 = __toESM(require_react(), 1);
28673
+ var ActionSpentContext = (0, import_react35.createContext)(null);
28674
+ var noSubscription = () => () => void 0;
28675
+ function useActionSpent(actionName) {
28676
+ const source = (0, import_react35.useContext)(ActionSpentContext);
28677
+ const subscribe = (0, import_react35.useCallback)((listener) => source === null ? noSubscription() : source.subscribe(listener), [source]);
28678
+ const read = (0, import_react35.useCallback)(() => source === null ? false : source.isSpent(actionName), [source, actionName]);
28679
+ return (0, import_react35.useSyncExternalStore)(subscribe, read, read);
28680
+ }
28681
+
28682
+ // ../wire/dist/useGguiContext.js
28683
+ var import_react36 = __toESM(require_react(), 1);
28654
28684
  function useGguiContext(slotName) {
28655
28685
  const contextName = slotName.length === 0 ? "Context" : slotName.charAt(0).toUpperCase() + slotName.slice(1) + "Context";
28656
28686
  const registry = globalThis.__ggui__?.contexts;
@@ -28658,19 +28688,19 @@
28658
28688
  if (!ctx) {
28659
28689
  throw new Error(`[ggui] useGguiContext('${slotName}'): no Context registered. The contract must declare 'contextSpec.${slotName}' AND the runtime must have synthesized the per-slot React Context at boot. If you see this in production code generated by the LLM, the boilerplate's destructure line drifted from the contract.`);
28660
28690
  }
28661
- return (0, import_react35.useContext)(ctx);
28691
+ return (0, import_react36.useContext)(ctx);
28662
28692
  }
28663
28693
 
28664
28694
  // ../wire/dist/useStream.js
28665
- var import_react36 = __toESM(require_react(), 1);
28695
+ var import_react37 = __toESM(require_react(), 1);
28666
28696
  function useStream(channelName) {
28667
28697
  const { subscribe } = useWireContext();
28668
- const [latest, setLatest] = (0, import_react36.useState)(null);
28669
- const [all, setAll] = (0, import_react36.useState)([]);
28670
- const [isComplete, setIsComplete] = (0, import_react36.useState)(false);
28671
- const allRef = (0, import_react36.useRef)(all);
28698
+ const [latest, setLatest] = (0, import_react37.useState)(null);
28699
+ const [all, setAll] = (0, import_react37.useState)([]);
28700
+ const [isComplete, setIsComplete] = (0, import_react37.useState)(false);
28701
+ const allRef = (0, import_react37.useRef)(all);
28672
28702
  allRef.current = all;
28673
- (0, import_react36.useEffect)(() => {
28703
+ (0, import_react37.useEffect)(() => {
28674
28704
  return subscribe(channelName, (delivery) => {
28675
28705
  const typed = delivery.payload;
28676
28706
  setLatest(typed);
@@ -28708,7 +28738,7 @@
28708
28738
  }
28709
28739
 
28710
28740
  // ../wire/dist/useRender.js
28711
- var import_react37 = __toESM(require_react(), 1);
28741
+ var import_react38 = __toESM(require_react(), 1);
28712
28742
 
28713
28743
  // ../wire/dist/connection-store.js
28714
28744
  var ConnectionWriterConflictError = class extends Error {
@@ -28805,7 +28835,7 @@
28805
28835
  // ../wire/dist/useRender.js
28806
28836
  function useRender() {
28807
28837
  const { render } = useWireContext();
28808
- const isConnected = (0, import_react37.useSyncExternalStore)(connectionSource.subscribe, connectionSource.getSnapshot, connectionSource.getSnapshot);
28838
+ const isConnected = (0, import_react38.useSyncExternalStore)(connectionSource.subscribe, connectionSource.getSnapshot, connectionSource.getSnapshot);
28809
28839
  return { sessionId: render.sessionId, isConnected };
28810
28840
  }
28811
28841
 
@@ -29389,7 +29419,24 @@
29389
29419
  });
29390
29420
  const validateEnvelope = opts.validateEnvelope ?? validateOutboundActionEnvelope;
29391
29421
  const spentOneShots = /* @__PURE__ */ new Set();
29422
+ const isSpent = (actionName) => opts.getActiveActionSpec()?.[actionName]?.oneShot === true && (spentOneShots.has(actionName) || opts.getSpentOneShots?.()?.includes(actionName) === true);
29423
+ const spentListeners = /* @__PURE__ */ new Set();
29424
+ const notifySpent = () => {
29425
+ for (const listener of [...spentListeners])
29426
+ listener();
29427
+ };
29428
+ const actionSpent = {
29429
+ isSpent,
29430
+ subscribe: (listener) => {
29431
+ spentListeners.add(listener);
29432
+ return () => {
29433
+ spentListeners.delete(listener);
29434
+ };
29435
+ }
29436
+ };
29437
+ opts.spentInputsChanged?.(notifySpent);
29392
29438
  return {
29439
+ actionSpent,
29393
29440
  app: opts.app,
29394
29441
  render: opts.render,
29395
29442
  auth: opts.auth,
@@ -29399,7 +29446,7 @@
29399
29446
  if (actionSpec === void 0)
29400
29447
  opts.onActionSpecAbsent?.(actionName);
29401
29448
  const isOneShot = actionSpec?.[actionName]?.oneShot === true;
29402
- if (isOneShot && spentOneShots.has(actionName)) {
29449
+ if (isSpent(actionName)) {
29403
29450
  console.warn(`[ggui] dispatch('${actionName}') suppressed: this action is declared oneShot and already fired for this render. A second gesture is NOT sent to the agent \u2014 the submitted control is spent for the render's lifetime. If the action is meant to repeat, remove \`oneShot\` from its contract entry.`);
29404
29451
  opts.onDispatchSuppressed?.({
29405
29452
  reason: "one-shot-spent",
@@ -29424,8 +29471,10 @@
29424
29471
  opts.onViolation(new ClientContractViolationError("outbound-action", result.violations));
29425
29472
  return;
29426
29473
  }
29427
- if (isOneShot)
29474
+ if (isOneShot && !spentOneShots.has(actionName)) {
29428
29475
  spentOneShots.add(actionName);
29476
+ notifySpent();
29477
+ }
29429
29478
  opts.emitEnvelope(envelope);
29430
29479
  },
29431
29480
  subscribe: (channelName, handler) => {
@@ -32420,8 +32469,12 @@
32420
32469
 
32421
32470
  // ../design/dist/themes/ThemeProvider.js
32422
32471
  var import_jsx_runtime58 = __toESM(require_jsx_runtime(), 1);
32423
- var import_react38 = __toESM(require_react(), 1);
32424
- var ThemeContext = (0, import_react38.createContext)(null);
32472
+ var import_react39 = __toESM(require_react(), 1);
32473
+ var ThemeContext = (0, import_react39.createContext)(null);
32474
+
32475
+ // ../design/dist/rendering/css-tokens.js
32476
+ var EXPANDED_FRAME = { outerRadiusVar: "--ggui-shape-radius-xl", insetPx: 16 };
32477
+ var EXPANDED_FRAME_INNER_RADIUS = `max(0px, calc(var(${EXPANDED_FRAME.outerRadiusVar}) - ${EXPANDED_FRAME.insetPx}px))`;
32425
32478
 
32426
32479
  // ../design/dist/rendering/rewrite-imports.js
32427
32480
  var WIRE_EXPORTS = [
@@ -32436,6 +32489,11 @@
32436
32489
  "useGguiContext",
32437
32490
  "useContract",
32438
32491
  "useWireContext",
32492
+ // ggui#1223: a `oneShot` action's spent state, as data. It is served here
32493
+ // one release BEFORE the triad teaches it, because a card that imports a name
32494
+ // its runtime's shim lacks fails at module link. A card generated to use it
32495
+ // must never meet a runtime without it.
32496
+ "useActionSpent",
32439
32497
  // Provider component
32440
32498
  "GguiWireProvider"
32441
32499
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/ui-visual-tester",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Behavioral validator for ggui-generated component code. Renders a component in real Chromium via Playwright, clicks each action declared in its contract, and verifies the action actually does something — mutating bound state or dispatching to the agent. Catches buttons wired to no-op handlers that structural validators miss. Playwright is an optional peer dependency, injected by the caller.",
5
5
  "keywords": [
6
6
  "ggui",
@@ -53,9 +53,9 @@
53
53
  "dependencies": {
54
54
  "react": "^19.0.0",
55
55
  "react-dom": "^19.0.0",
56
- "@ggui-ai/design": "0.22.0",
57
- "@ggui-ai/protocol": "0.22.0",
58
- "@ggui-ai/wire": "0.22.0"
56
+ "@ggui-ai/design": "0.24.0",
57
+ "@ggui-ai/protocol": "0.24.0",
58
+ "@ggui-ai/wire": "0.24.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@playwright/test": "^1.60.0",