@ggui-ai/ui-visual-tester 0.23.0 → 0.25.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.
- package/fixture/fixture-runtime.js +109 -15
- package/package.json +4 -4
|
@@ -28577,6 +28577,8 @@
|
|
|
28577
28577
|
buildWireConfig: () => buildWireConfig,
|
|
28578
28578
|
connectionSource: () => connectionSource,
|
|
28579
28579
|
useAction: () => useAction,
|
|
28580
|
+
useActionPending: () => useActionPending,
|
|
28581
|
+
useActionSpent: () => useActionSpent,
|
|
28580
28582
|
useApp: () => useApp,
|
|
28581
28583
|
useAuth: () => useAuth,
|
|
28582
28584
|
useContract: () => useContract,
|
|
@@ -28667,8 +28669,30 @@
|
|
|
28667
28669
|
}, [actionName, dispatch, onDispatchSuppressed]);
|
|
28668
28670
|
}
|
|
28669
28671
|
|
|
28670
|
-
// ../wire/dist/
|
|
28672
|
+
// ../wire/dist/action-spent.js
|
|
28671
28673
|
var import_react35 = __toESM(require_react(), 1);
|
|
28674
|
+
var ActionSpentContext = (0, import_react35.createContext)(null);
|
|
28675
|
+
var noSubscription = () => () => void 0;
|
|
28676
|
+
function useActionSpent(actionName) {
|
|
28677
|
+
const source = (0, import_react35.useContext)(ActionSpentContext);
|
|
28678
|
+
const subscribe = (0, import_react35.useCallback)((listener) => source === null ? noSubscription() : source.subscribe(listener), [source]);
|
|
28679
|
+
const read = (0, import_react35.useCallback)(() => source === null ? false : source.isSpent(actionName), [source, actionName]);
|
|
28680
|
+
return (0, import_react35.useSyncExternalStore)(subscribe, read, read);
|
|
28681
|
+
}
|
|
28682
|
+
|
|
28683
|
+
// ../wire/dist/action-pending.js
|
|
28684
|
+
var import_react36 = __toESM(require_react(), 1);
|
|
28685
|
+
var ActionPendingContext = (0, import_react36.createContext)(null);
|
|
28686
|
+
var noSubscription2 = () => () => void 0;
|
|
28687
|
+
function useActionPending(actionName) {
|
|
28688
|
+
const source = (0, import_react36.useContext)(ActionPendingContext);
|
|
28689
|
+
const subscribe = (0, import_react36.useCallback)((listener) => source === null ? noSubscription2() : source.subscribe(listener), [source]);
|
|
28690
|
+
const read = (0, import_react36.useCallback)(() => source === null ? false : source.isPending(actionName), [source, actionName]);
|
|
28691
|
+
return (0, import_react36.useSyncExternalStore)(subscribe, read, read);
|
|
28692
|
+
}
|
|
28693
|
+
|
|
28694
|
+
// ../wire/dist/useGguiContext.js
|
|
28695
|
+
var import_react37 = __toESM(require_react(), 1);
|
|
28672
28696
|
function useGguiContext(slotName) {
|
|
28673
28697
|
const contextName = slotName.length === 0 ? "Context" : slotName.charAt(0).toUpperCase() + slotName.slice(1) + "Context";
|
|
28674
28698
|
const registry = globalThis.__ggui__?.contexts;
|
|
@@ -28676,19 +28700,19 @@
|
|
|
28676
28700
|
if (!ctx) {
|
|
28677
28701
|
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.`);
|
|
28678
28702
|
}
|
|
28679
|
-
return (0,
|
|
28703
|
+
return (0, import_react37.useContext)(ctx);
|
|
28680
28704
|
}
|
|
28681
28705
|
|
|
28682
28706
|
// ../wire/dist/useStream.js
|
|
28683
|
-
var
|
|
28707
|
+
var import_react38 = __toESM(require_react(), 1);
|
|
28684
28708
|
function useStream(channelName) {
|
|
28685
28709
|
const { subscribe } = useWireContext();
|
|
28686
|
-
const [latest, setLatest] = (0,
|
|
28687
|
-
const [all, setAll] = (0,
|
|
28688
|
-
const [isComplete, setIsComplete] = (0,
|
|
28689
|
-
const allRef = (0,
|
|
28710
|
+
const [latest, setLatest] = (0, import_react38.useState)(null);
|
|
28711
|
+
const [all, setAll] = (0, import_react38.useState)([]);
|
|
28712
|
+
const [isComplete, setIsComplete] = (0, import_react38.useState)(false);
|
|
28713
|
+
const allRef = (0, import_react38.useRef)(all);
|
|
28690
28714
|
allRef.current = all;
|
|
28691
|
-
(0,
|
|
28715
|
+
(0, import_react38.useEffect)(() => {
|
|
28692
28716
|
return subscribe(channelName, (delivery) => {
|
|
28693
28717
|
const typed = delivery.payload;
|
|
28694
28718
|
setLatest(typed);
|
|
@@ -28726,7 +28750,7 @@
|
|
|
28726
28750
|
}
|
|
28727
28751
|
|
|
28728
28752
|
// ../wire/dist/useRender.js
|
|
28729
|
-
var
|
|
28753
|
+
var import_react39 = __toESM(require_react(), 1);
|
|
28730
28754
|
|
|
28731
28755
|
// ../wire/dist/connection-store.js
|
|
28732
28756
|
var ConnectionWriterConflictError = class extends Error {
|
|
@@ -28823,7 +28847,7 @@
|
|
|
28823
28847
|
// ../wire/dist/useRender.js
|
|
28824
28848
|
function useRender() {
|
|
28825
28849
|
const { render } = useWireContext();
|
|
28826
|
-
const isConnected = (0,
|
|
28850
|
+
const isConnected = (0, import_react39.useSyncExternalStore)(connectionSource.subscribe, connectionSource.getSnapshot, connectionSource.getSnapshot);
|
|
28827
28851
|
return { sessionId: render.sessionId, isConnected };
|
|
28828
28852
|
}
|
|
28829
28853
|
|
|
@@ -29399,6 +29423,7 @@
|
|
|
29399
29423
|
}
|
|
29400
29424
|
}
|
|
29401
29425
|
};
|
|
29426
|
+
var DEFAULT_ACTION_PENDING_BOUND_MS = 2e4;
|
|
29402
29427
|
function buildWireConfig(opts) {
|
|
29403
29428
|
let internalSeq = 0;
|
|
29404
29429
|
const nextClientSeq = opts.nextClientSeq ?? (() => {
|
|
@@ -29407,7 +29432,59 @@
|
|
|
29407
29432
|
});
|
|
29408
29433
|
const validateEnvelope = opts.validateEnvelope ?? validateOutboundActionEnvelope;
|
|
29409
29434
|
const spentOneShots = /* @__PURE__ */ new Set();
|
|
29435
|
+
const isSpent = (actionName) => opts.getActiveActionSpec()?.[actionName]?.oneShot === true && (spentOneShots.has(actionName) || opts.getSpentOneShots?.()?.includes(actionName) === true);
|
|
29436
|
+
const spentListeners = /* @__PURE__ */ new Set();
|
|
29437
|
+
const notifySpent = () => {
|
|
29438
|
+
for (const listener of [...spentListeners])
|
|
29439
|
+
listener();
|
|
29440
|
+
};
|
|
29441
|
+
const actionSpent = {
|
|
29442
|
+
isSpent,
|
|
29443
|
+
subscribe: (listener) => {
|
|
29444
|
+
spentListeners.add(listener);
|
|
29445
|
+
return () => {
|
|
29446
|
+
spentListeners.delete(listener);
|
|
29447
|
+
};
|
|
29448
|
+
}
|
|
29449
|
+
};
|
|
29450
|
+
opts.spentInputsChanged?.(notifySpent);
|
|
29451
|
+
const pendingBoundMs = opts.actionPendingBoundMs ?? DEFAULT_ACTION_PENDING_BOUND_MS;
|
|
29452
|
+
const pending = /* @__PURE__ */ new Map();
|
|
29453
|
+
const pendingListeners = /* @__PURE__ */ new Set();
|
|
29454
|
+
const notifyPending = () => {
|
|
29455
|
+
for (const listener of [...pendingListeners])
|
|
29456
|
+
listener();
|
|
29457
|
+
};
|
|
29458
|
+
const markPending = (actionName) => {
|
|
29459
|
+
const previous = pending.get(actionName);
|
|
29460
|
+
if (previous !== void 0)
|
|
29461
|
+
clearTimeout(previous);
|
|
29462
|
+
pending.set(actionName, setTimeout(() => {
|
|
29463
|
+
pending.delete(actionName);
|
|
29464
|
+
notifyPending();
|
|
29465
|
+
}, pendingBoundMs));
|
|
29466
|
+
notifyPending();
|
|
29467
|
+
};
|
|
29468
|
+
const actionPending = {
|
|
29469
|
+
isPending: (actionName) => pending.has(actionName),
|
|
29470
|
+
subscribe: (listener) => {
|
|
29471
|
+
pendingListeners.add(listener);
|
|
29472
|
+
return () => {
|
|
29473
|
+
pendingListeners.delete(listener);
|
|
29474
|
+
};
|
|
29475
|
+
}
|
|
29476
|
+
};
|
|
29477
|
+
opts.pendingInputsChanged?.(() => {
|
|
29478
|
+
if (pending.size === 0)
|
|
29479
|
+
return;
|
|
29480
|
+
for (const timer of pending.values())
|
|
29481
|
+
clearTimeout(timer);
|
|
29482
|
+
pending.clear();
|
|
29483
|
+
notifyPending();
|
|
29484
|
+
});
|
|
29410
29485
|
return {
|
|
29486
|
+
actionSpent,
|
|
29487
|
+
actionPending,
|
|
29411
29488
|
app: opts.app,
|
|
29412
29489
|
render: opts.render,
|
|
29413
29490
|
auth: opts.auth,
|
|
@@ -29417,8 +29494,7 @@
|
|
|
29417
29494
|
if (actionSpec === void 0)
|
|
29418
29495
|
opts.onActionSpecAbsent?.(actionName);
|
|
29419
29496
|
const isOneShot = actionSpec?.[actionName]?.oneShot === true;
|
|
29420
|
-
|
|
29421
|
-
if (isOneShot && spent) {
|
|
29497
|
+
if (isSpent(actionName)) {
|
|
29422
29498
|
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.`);
|
|
29423
29499
|
opts.onDispatchSuppressed?.({
|
|
29424
29500
|
reason: "one-shot-spent",
|
|
@@ -29443,9 +29519,12 @@
|
|
|
29443
29519
|
opts.onViolation(new ClientContractViolationError("outbound-action", result.violations));
|
|
29444
29520
|
return;
|
|
29445
29521
|
}
|
|
29446
|
-
if (isOneShot)
|
|
29522
|
+
if (isOneShot && !spentOneShots.has(actionName)) {
|
|
29447
29523
|
spentOneShots.add(actionName);
|
|
29524
|
+
notifySpent();
|
|
29525
|
+
}
|
|
29448
29526
|
opts.emitEnvelope(envelope);
|
|
29527
|
+
markPending(actionName);
|
|
29449
29528
|
},
|
|
29450
29529
|
subscribe: (channelName, handler) => {
|
|
29451
29530
|
return opts.streamBus.subscribe(channelName, (env) => {
|
|
@@ -32439,12 +32518,18 @@
|
|
|
32439
32518
|
|
|
32440
32519
|
// ../design/dist/themes/ThemeProvider.js
|
|
32441
32520
|
var import_jsx_runtime58 = __toESM(require_jsx_runtime(), 1);
|
|
32442
|
-
var
|
|
32443
|
-
var ThemeContext = (0,
|
|
32521
|
+
var import_react40 = __toESM(require_react(), 1);
|
|
32522
|
+
var ThemeContext = (0, import_react40.createContext)(null);
|
|
32444
32523
|
|
|
32445
32524
|
// ../design/dist/rendering/css-tokens.js
|
|
32446
32525
|
var EXPANDED_FRAME = { outerRadiusVar: "--ggui-shape-radius-xl", insetPx: 16 };
|
|
32447
32526
|
var EXPANDED_FRAME_INNER_RADIUS = `max(0px, calc(var(${EXPANDED_FRAME.outerRadiusVar}) - ${EXPANDED_FRAME.insetPx}px))`;
|
|
32527
|
+
var EXPANDED_FRAME_CHROME = {
|
|
32528
|
+
radius: `var(${EXPANDED_FRAME.outerRadiusVar})`,
|
|
32529
|
+
hairline: "color-mix(in srgb, var(--ggui-color-onGround) 6%, transparent)",
|
|
32530
|
+
shadowVar: "--ggui-shape-shadow-sm",
|
|
32531
|
+
ground: "var(--ggui-color-neutral-50)"
|
|
32532
|
+
};
|
|
32448
32533
|
|
|
32449
32534
|
// ../design/dist/rendering/rewrite-imports.js
|
|
32450
32535
|
var WIRE_EXPORTS = [
|
|
@@ -32459,6 +32544,15 @@
|
|
|
32459
32544
|
"useGguiContext",
|
|
32460
32545
|
"useContract",
|
|
32461
32546
|
"useWireContext",
|
|
32547
|
+
// ggui#1223: a `oneShot` action's spent state, as data. It is served here
|
|
32548
|
+
// one release BEFORE the triad teaches it, because a card that imports a name
|
|
32549
|
+
// its runtime's shim lacks fails at module link. A card generated to use it
|
|
32550
|
+
// must never meet a runtime without it.
|
|
32551
|
+
"useActionSpent",
|
|
32552
|
+
// ggui#1398: whether a dispatched action is waiting for the agent's answer,
|
|
32553
|
+
// as data. Served one release before the triad teaches it, for the same
|
|
32554
|
+
// reason as `useActionSpent` above.
|
|
32555
|
+
"useActionPending",
|
|
32462
32556
|
// Provider component
|
|
32463
32557
|
"GguiWireProvider"
|
|
32464
32558
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/ui-visual-tester",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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/
|
|
57
|
-
"@ggui-ai/wire": "0.
|
|
58
|
-
"@ggui-ai/
|
|
56
|
+
"@ggui-ai/protocol": "0.25.0",
|
|
57
|
+
"@ggui-ai/wire": "0.25.0",
|
|
58
|
+
"@ggui-ai/design": "0.25.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@playwright/test": "^1.60.0",
|