@ggui-ai/dev-stack 0.17.0 → 0.19.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.
@@ -20526,6 +20526,7 @@ function useAction(actionName) {
20526
20526
  if (onDispatchSuppressed) {
20527
20527
  try {
20528
20528
  onDispatchSuppressed({
20529
+ reason: "duplicate-dispatch",
20529
20530
  actionName,
20530
20531
  payloadSignature: decision.signature,
20531
20532
  payload: data,
@@ -20798,11 +20799,26 @@ function buildWireConfig(opts) {
20798
20799
  return internalSeq;
20799
20800
  });
20800
20801
  const validateEnvelope = opts.validateEnvelope ?? validateOutboundActionEnvelope;
20802
+ const spentOneShots = /* @__PURE__ */ new Set();
20801
20803
  return {
20802
20804
  app: opts.app,
20803
20805
  render: opts.render,
20804
20806
  auth: opts.auth,
20807
+ ...opts.onDispatchSuppressed !== void 0 ? { onDispatchSuppressed: opts.onDispatchSuppressed } : {},
20805
20808
  dispatch: (actionName, data) => {
20809
+ const actionSpec = opts.getActiveActionSpec();
20810
+ const isOneShot = actionSpec?.[actionName]?.oneShot === true;
20811
+ if (isOneShot && spentOneShots.has(actionName)) {
20812
+ 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.`);
20813
+ opts.onDispatchSuppressed?.({
20814
+ reason: "one-shot-spent",
20815
+ actionName,
20816
+ payloadSignature: payloadSignature(actionName, data),
20817
+ payload: data,
20818
+ suppressedAt: Date.now()
20819
+ });
20820
+ return;
20821
+ }
20806
20822
  const envelope = buildActionEnvelope({
20807
20823
  sessionId: opts.render.sessionId,
20808
20824
  type: "data:submit",
@@ -20812,11 +20828,13 @@ function buildWireConfig(opts) {
20812
20828
  },
20813
20829
  clientSeq: nextClientSeq()
20814
20830
  });
20815
- const result = validateEnvelope(opts.getActiveActionSpec(), envelope);
20831
+ const result = validateEnvelope(actionSpec, envelope);
20816
20832
  if (!result.valid) {
20817
20833
  opts.onViolation(new ClientContractViolationError("outbound-action", result.violations));
20818
20834
  return;
20819
20835
  }
20836
+ if (isOneShot)
20837
+ spentOneShots.add(actionName);
20820
20838
  opts.emitEnvelope(envelope);
20821
20839
  },
20822
20840
  subscribe: (channelName, handler) => {
@@ -21645,7 +21663,7 @@ function Button({ children, variant = "primary", size = "md", fullWidth, loading
21645
21663
  alignItems: "center",
21646
21664
  justifyContent: "center",
21647
21665
  gap: "var(--ggui-spacing-2, 8px)",
21648
- borderRadius: "var(--ggui-shape-radius-md, 8px)",
21666
+ borderRadius: "var(--ggui-shape-radius-control, var(--ggui-shape-radius-md, 8px))",
21649
21667
  fontWeight: "var(--ggui-font-weight-medium, 500)",
21650
21668
  cursor: isDisabled ? "not-allowed" : "pointer",
21651
21669
  opacity: isDisabled ? 0.5 : 1,
@@ -21687,7 +21705,7 @@ function Input({ label, placeholder, value, onChange, type = "text", error, help
21687
21705
  }, children: [label, required && (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", style: { color: "var(--ggui-color-error-500, #ef4444)", marginLeft: "2px" }, children: "*" })] }), (0, import_jsx_runtime11.jsx)("input", { id: inputId, type, placeholder, value, onChange: (e) => onChange?.(e.target.value), disabled, required, "aria-invalid": hasError || void 0, "aria-describedby": hasMessage ? messageId : void 0, style: {
21688
21706
  ...sizeStyles2[size],
21689
21707
  width: "100%",
21690
- borderRadius: "var(--ggui-shape-radius-md, 8px)",
21708
+ borderRadius: "var(--ggui-shape-radius-control, var(--ggui-shape-radius-md, 8px))",
21691
21709
  border: `1px solid ${hasError ? "var(--ggui-color-error-500, #ef4444)" : "var(--ggui-color-outline, #d4d4d8)"}`,
21692
21710
  backgroundColor: disabled ? "var(--ggui-color-sunken, #fafafa)" : "var(--ggui-color-sunken, #ffffff)",
21693
21711
  color: "var(--ggui-color-onContainer, #18181b)",
@@ -21717,7 +21735,7 @@ function TextArea({ label, placeholder, value, onChange, rows = 4, error, helper
21717
21735
  }, children: [label, required && (0, import_jsx_runtime12.jsx)("span", { "aria-hidden": "true", style: { color: "var(--ggui-color-error-500, #ef4444)", marginLeft: "2px" }, children: "*" })] }), (0, import_jsx_runtime12.jsx)("textarea", { id: textareaId, placeholder, value, onChange: (e) => onChange?.(e.target.value), rows, disabled, required, maxLength, "aria-invalid": hasError || void 0, "aria-describedby": hasMessage ? messageId : void 0, style: {
21718
21736
  padding: "10px 12px",
21719
21737
  width: "100%",
21720
- borderRadius: "var(--ggui-shape-radius-md, 8px)",
21738
+ borderRadius: "var(--ggui-shape-radius-control, var(--ggui-shape-radius-md, 8px))",
21721
21739
  border: `1px solid ${hasError ? "var(--ggui-color-error-500, #ef4444)" : "var(--ggui-color-outline, #d4d4d8)"}`,
21722
21740
  backgroundColor: disabled ? "var(--ggui-color-sunken, #fafafa)" : "var(--ggui-color-sunken, #ffffff)",
21723
21741
  color: "var(--ggui-color-onContainer, #18181b)",
@@ -21753,7 +21771,7 @@ function Select({ label, value, onChange, options, placeholder, error, helperTex
21753
21771
  }, children: [label, required && (0, import_jsx_runtime13.jsx)("span", { "aria-hidden": "true", style: { color: "var(--ggui-color-error-500, #ef4444)", marginLeft: "2px" }, children: "*" })] }), (0, import_jsx_runtime13.jsxs)("select", { id: selectId, value, onChange: (e) => onChange?.(e.target.value), disabled, required, "aria-invalid": hasError || void 0, "aria-describedby": hasMessage ? messageId : void 0, style: {
21754
21772
  ...sizeStyles3[size],
21755
21773
  width: "100%",
21756
- borderRadius: "var(--ggui-shape-radius-md, 8px)",
21774
+ borderRadius: "var(--ggui-shape-radius-control, var(--ggui-shape-radius-md, 8px))",
21757
21775
  border: `1px solid ${hasError ? "var(--ggui-color-error-500, #ef4444)" : "var(--ggui-color-outline, #d4d4d8)"}`,
21758
21776
  backgroundColor: disabled ? "var(--ggui-color-sunken, #fafafa)" : "var(--ggui-color-sunken, #ffffff)",
21759
21777
  color: value ? "var(--ggui-color-onContainer, #18181b)" : "var(--ggui-color-onSunken, #52525b)",
@@ -29431,6 +29449,7 @@ var consumed_tokens_manifest_default = {
29431
29449
  "--ggui-font-weight-normal",
29432
29450
  "--ggui-font-weight-semibold",
29433
29451
  "--ggui-letter-spacing-heading",
29452
+ "--ggui-shape-radius-control",
29434
29453
  "--ggui-shape-radius-full",
29435
29454
  "--ggui-shape-radius-lg",
29436
29455
  "--ggui-shape-radius-md",
@@ -29579,6 +29598,8 @@ var NEUTRAL_T = [0.04, 0.08, 0.16, 0.28, 0.44, 0.58, 0.7, 0.8, 0.9, 0.96];
29579
29598
  var FAMILIES = ["primary", "tertiary", "success", "warning", "error", "info"];
29580
29599
  var NAMED_SPACING = { xs: "4px", sm: "8px", md: "16px", lg: "24px", xl: "32px", "2xl": "48px" };
29581
29600
  var SIZE_EXP = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4, "4xl": 5 };
29601
+ var ROLE_STOP = { display: "4xl", h1: "3xl", h2: "2xl", body: "base", label: "xs" };
29602
+ var LAYER1_SPACING_UNIT_PX = 4;
29582
29603
  function tokenValue(t) {
29583
29604
  if (t === void 0 || t === null || typeof t !== "object" || !("$value" in t))
29584
29605
  return void 0;
@@ -29589,6 +29610,24 @@ function tokenValue(t) {
29589
29610
  return void 0;
29590
29611
  return /^#[0-9a-fA-F]{3,6}$/.test(v) ? `#${parseHex(v).map((n) => n.toString(16).padStart(2, "0")).join("")}` : v;
29591
29612
  }
29613
+ function describeFound(t) {
29614
+ if (t === null)
29615
+ return "null";
29616
+ if (typeof t !== "object")
29617
+ return `${typeof t} ${JSON.stringify(t)}`;
29618
+ return "$value" in t ? "object with a non-string, non-number $value" : "object without $value";
29619
+ }
29620
+ var defaultDiagnostic = (d) => {
29621
+ console.warn(`[ggui-design] theme member unreadable at ${d.path} \u2014 found ${d.found}; expected a DTCG token ({ $value }). The member was ignored and the fallback used.`);
29622
+ };
29623
+ function readToken(t, path, report) {
29624
+ if (t === void 0)
29625
+ return void 0;
29626
+ const v = tokenValue(t);
29627
+ if (v === void 0)
29628
+ report({ kind: "member-unreadable", path, found: describeFound(t) });
29629
+ return v;
29630
+ }
29592
29631
  function stated(group, key) {
29593
29632
  return tokenValue(group?.[key]);
29594
29633
  }
@@ -29649,7 +29688,15 @@ function parseSize(value) {
29649
29688
  const m = /^([0-9.]+)\s*(rem|px|em)$/.exec(value.trim());
29650
29689
  return m ? { n: Number(m[1]), unit: m[2] } : void 0;
29651
29690
  }
29652
- function deriveThemeVariables(doc, mode) {
29691
+ function deriveThemeVariables(doc, mode, options = {}) {
29692
+ const reported = /* @__PURE__ */ new Set();
29693
+ const sink = options.onDiagnostic ?? defaultDiagnostic;
29694
+ const report = (d) => {
29695
+ if (reported.has(d.path))
29696
+ return;
29697
+ reported.add(d.path);
29698
+ sink(d);
29699
+ };
29653
29700
  const c = Object.fromEntries(Object.entries(doc.color));
29654
29701
  const single = (key) => tokenValue(c[key]);
29655
29702
  const group = (key) => c[key];
@@ -29729,19 +29776,60 @@ function deriveThemeVariables(doc, mode) {
29729
29776
  for (const lh of Object.keys(lineHeight))
29730
29777
  V[`--ggui-font-lineHeight-${lh}`] = String(lineHeight[lh]);
29731
29778
  const ramp = doc.font.ramp;
29732
- const base = ramp ? parseSize(tokenValue(ramp.base) ?? "") : void 0;
29733
- const ratio = ramp ? Number(tokenValue(ramp.ratio)) : NaN;
29779
+ const base = ramp ? parseSize(readToken(ramp.base, "font.ramp.base", report) ?? "") : void 0;
29780
+ const ratio = ramp ? Number(readToken(ramp.ratio, "font.ramp.ratio", report)) : NaN;
29734
29781
  for (const [stop, exp] of Object.entries(SIZE_EXP)) {
29735
29782
  V[`--ggui-font-size-${stop}`] = base && Number.isFinite(ratio) && ratio > 0 ? `${Math.round(base.n * ratio ** exp * 1e3) / 1e3}${base.unit}` : fontSize[stop];
29736
29783
  }
29784
+ const typeScale = doc.typeScale;
29785
+ if (typeScale !== void 0) {
29786
+ const bodySize = parseSize(readToken(typeScale.body?.size, "typeScale.body.size", report) ?? "");
29787
+ if (bodySize !== void 0) {
29788
+ const r = Number.isFinite(ratio) && ratio > 0 ? ratio : 1.25;
29789
+ for (const [stop, exp] of Object.entries(SIZE_EXP)) {
29790
+ V[`--ggui-font-size-${stop}`] = `${Math.round(bodySize.n * r ** exp * 1e3) / 1e3}${bodySize.unit}`;
29791
+ }
29792
+ }
29793
+ for (const [role, stop] of Object.entries(ROLE_STOP)) {
29794
+ const size = readToken(typeScale[role]?.size, `typeScale.${role}.size`, report);
29795
+ if (size !== void 0)
29796
+ V[`--ggui-font-size-${stop}`] = size;
29797
+ }
29798
+ const headingWeight = readToken(typeScale.h1?.weight, "typeScale.h1.weight", report);
29799
+ if (headingWeight !== void 0)
29800
+ V["--ggui-font-weight-heading"] = headingWeight;
29801
+ const bodyWeight = readToken(typeScale.body?.weight, "typeScale.body.weight", report);
29802
+ if (bodyWeight !== void 0)
29803
+ V["--ggui-font-weight-normal"] = bodyWeight;
29804
+ const headingTracking = readToken(typeScale.h1?.tracking, "typeScale.h1.tracking", report);
29805
+ if (headingTracking !== void 0)
29806
+ V["--ggui-letter-spacing-heading"] = headingTracking;
29807
+ const bodyTracking = readToken(typeScale.body?.tracking, "typeScale.body.tracking", report);
29808
+ if (bodyTracking !== void 0)
29809
+ V["--ggui-letter-spacing-body"] = bodyTracking;
29810
+ const bodyLeading = readToken(typeScale.body?.leading, "typeScale.body.leading", report);
29811
+ if (bodyLeading !== void 0)
29812
+ V["--ggui-font-lineHeight-normal"] = bodyLeading;
29813
+ }
29737
29814
  const sp = doc.spacing;
29815
+ const rhythmBase = doc.rhythm ? parseSize(readToken(doc.rhythm.base, "rhythm.base", report) ?? "") : void 0;
29816
+ const onRhythm = (layer1) => {
29817
+ if (rhythmBase === void 0)
29818
+ return void 0;
29819
+ const step = parseSize(layer1);
29820
+ if (step === void 0)
29821
+ return void 0;
29822
+ const scaled = step.n / LAYER1_SPACING_UNIT_PX * rhythmBase.n;
29823
+ return `${Math.round(scaled * 1e3) / 1e3}${rhythmBase.unit}`;
29824
+ };
29738
29825
  for (const [key, value] of Object.entries(spacing))
29739
- V[`--ggui-spacing-${key}`] = stated(sp, key) ?? String(value);
29826
+ V[`--ggui-spacing-${key}`] = onRhythm(String(value)) ?? stated(sp, key) ?? String(value);
29740
29827
  for (const [key, value] of Object.entries(NAMED_SPACING))
29741
- V[`--ggui-spacing-${key}`] = stated(sp, key) ?? value;
29828
+ V[`--ggui-spacing-${key}`] = onRhythm(value) ?? stated(sp, key) ?? value;
29742
29829
  const rad = doc.shape?.radius;
29743
29830
  for (const [key, value] of Object.entries(radius))
29744
29831
  V[`--ggui-shape-radius-${key}`] = stated(rad, key) ?? String(value);
29832
+ V["--ggui-shape-radius-control"] = stated(rad, "control") ?? V["--ggui-shape-radius-md"];
29745
29833
  const sh = doc.shape?.shadow;
29746
29834
  for (const [key, value] of Object.entries(shadow))
29747
29835
  V[`--ggui-shape-shadow-${key}`] = stated(sh, key) ?? String(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/dev-stack",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Shared local dev engine for the ggui protocol: local UI registry discovery, compile-on-demand, filesystem watcher with SSE, and a read-only HTTP surface for dev tooling. Framework-neutral — agent runtime supervision plugs in via an adapter seam, so no agent SDK is hardcoded.",
5
5
  "keywords": [
6
6
  "ggui",
@@ -28,9 +28,9 @@
28
28
  "dependencies": {
29
29
  "chokidar": "^4.0.3",
30
30
  "esbuild": "^0.28.2",
31
- "@ggui-ai/agent-runtime": "0.17.0",
32
- "@ggui-ai/project-config": "0.17.0",
33
- "@ggui-ai/ui-registry": "0.17.0"
31
+ "@ggui-ai/agent-runtime": "0.19.0",
32
+ "@ggui-ai/project-config": "0.19.0",
33
+ "@ggui-ai/ui-registry": "0.19.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^24.0.0",
@@ -40,9 +40,9 @@
40
40
  "react-dom": "^19.0.0",
41
41
  "typescript": "^5.0.0",
42
42
  "vitest": "^3.2.6",
43
- "@ggui-ai/design": "0.17.0",
44
- "@ggui-ai/mcp-apps-react": "0.17.0",
45
- "@ggui-ai/wire": "0.17.0"
43
+ "@ggui-ai/design": "0.19.0",
44
+ "@ggui-ai/mcp-apps-react": "0.19.0",
45
+ "@ggui-ai/wire": "0.19.0"
46
46
  },
47
47
  "repository": {
48
48
  "type": "git",