@glasshome/widget-sdk 1.8.0 → 1.9.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.
@@ -0,0 +1,33 @@
1
+ import { createContext as e, useContext as t } from "solid-js";
2
+ //#region src/framework/hooks/use-widget-context.ts
3
+ var n = e();
4
+ function r() {
5
+ let e = t(n);
6
+ if (!e) throw Error("useWidgetContext must be used within a Widget component");
7
+ return e;
8
+ }
9
+ //#endregion
10
+ //#region src/framework/theming/tokens.css?raw
11
+ var i = "/* Authoritative SDK CSS contract. Injected once on first Widget mount\n * via framework/theming/tokens.ts.\n *\n * All widget-internal visual scale is driven from CSS custom properties +\n * container queries on the `.glasshome-widget` shell (`container-type: size`).\n * No JS classifier, no size tier enum.\n */\n\n:root {\n --tone-success: oklch(0.70 0.18 145);\n --tone-warning: oklch(0.80 0.17 75);\n --tone-danger: oklch(0.64 0.22 27);\n --tone-info: oklch(0.66 0.20 245);\n --tone-neutral: oklch(0.65 0.02 250);\n --tone-accent: oklch(0.74 0.18 60);\n\n --widget-grad-strength: 1;\n --widget-glow-default: 0.4;\n --widget-border-highlight: oklch(0.98 0 0 / 0.4);\n}\n\n.dark {\n --tone-success: oklch(0.74 0.18 145);\n --tone-warning: oklch(0.82 0.17 75);\n --tone-danger: oklch(0.68 0.22 27);\n --tone-info: oklch(0.70 0.20 245);\n --tone-neutral: oklch(0.69 0.02 250);\n --tone-accent: oklch(0.78 0.18 60);\n\n --widget-grad-strength: 1.1;\n --widget-glow-default: 0.5;\n --widget-border-highlight: oklch(1 0 0 / 0.08);\n}\n\n@property --widget-color {\n syntax: \"<color>\";\n inherits: true;\n initial-value: oklch(0.65 0.02 250);\n}\n\n/* === Widget shell ===\n * Fluid scale tokens — tied to the shell's own container box (cqb=height, cqi=width).\n * Icon/pad/gap track HEIGHT (cqb) not the shortest side: two widgets of equal\n * height match regardless of width (a 2×2 and a 4×2 get the same icon). cqmin\n * starved portrait tiles by pinning the icon to the narrow width. Tune\n * multipliers / clamps to adjust density. */\n.glasshome-widget {\n --widget-color: var(--tone-neutral);\n --widget-glow-strength: var(--widget-glow-default);\n\n --widget-icon-box: clamp(44px, 25cqb, 58px);\n --widget-icon-glyph: clamp(24px, 14cqb, 33px);\n --widget-pad: clamp(8px, 3.5cqb, 22px);\n --widget-gap: clamp(6px, 2cqb, 14px);\n --widget-title-size: clamp(12px, 2cqi, 18px);\n --widget-subtitle: clamp(12px, 2.5cqi, 20px);\n --widget-status-size: clamp(14px, 5cqi, 36px);\n --widget-value-size: clamp(18px, 7cqi, 48px);\n --widget-badge-size: clamp(11px, 1.6cqi, 14px);\n}\n\n/* Shell material. Two channels, both host/variant-driven:\n * - `--widget-backdrop`: the live backdrop filter (the shared glass recipe is\n * blur + saturate, matching ui's CARD_BLUR). Default none — a variant opts\n * in (classic-glass), and a performant-blur host disables it via override\n * sheet when it paints a precomputed frost instead.\n * - `--glass-frost`: one full background layer (image + position / size +\n * repeat) composited at the BOTTOM, under the accent gradient — the same\n * frost slot ui's `.glass` formula ends its background list with. Unset →\n * none. A performant-blur host sets it per shell with the viewport-aligned\n * slice of its pre-blurred bitmap. */\n.glasshome-widget {\n background:\n var(--widget-gradient,\n linear-gradient(135deg,\n color-mix(in oklch, var(--widget-color) calc(22% * var(--widget-grad-strength)), transparent),\n color-mix(in oklch, var(--widget-color-to, var(--widget-color)) calc(11% * var(--widget-grad-strength)), transparent)\n )\n ),\n var(--glass-frost, none);\n backdrop-filter: var(--widget-backdrop, none);\n -webkit-backdrop-filter: var(--widget-backdrop, none);\n box-shadow: inset 0 1px 0 var(--widget-border-highlight);\n}\n\n/* === Slot: content layout === */\n/* Default: column. Flip to row only on wide-and-short widgets (matches the\n * old contentLayout heuristic: aspect > 1 AND height < 150px). */\n.glasshome-widget-content {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n width: 100%;\n height: 100%;\n padding: var(--widget-pad);\n gap: var(--widget-gap);\n overflow: hidden;\n}\n@container widget (min-aspect-ratio: 1) and (max-height: 149px) {\n .glasshome-widget-content {\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n }\n}\n\n/* === Compact 1×1 ===\n * Small in BOTH axes → stack the tile: icon on top, label + status centered\n * under it. Icon stays moderate (not a hero) so the text keeps room. Wide\n * tiles (2×1) fail the width test and keep the row layout above. Placed after\n * the row-flip so it wins. */\n@container widget (max-width: 189px) and (max-height: 189px) {\n .glasshome-widget-content {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: clamp(4px, 2.5cqmin, 8px);\n }\n .glasshome-widget-content > * { align-items: center; text-align: center; }\n .glasshome-widget-icon {\n --widget-icon-box: clamp(36px, 30cqmin, 56px);\n --widget-icon-glyph: clamp(20px, 17cqmin, 32px);\n }\n .glasshome-widget-title { font-size: clamp(11px, 6cqmin, 14px); max-width: 100%; }\n .glasshome-widget-status,\n .glasshome-widget-value { font-size: clamp(13px, 7cqmin, 17px); }\n .glasshome-widget-content [class*=\"ml-auto\"] { margin-left: 0 !important; }\n}\n.glasshome-widget-content [class*=\"ml-auto\"] {\n position: relative;\n z-index: 30;\n}\n\n/* === Slot: icon === */\n.glasshome-widget-icon {\n width: var(--widget-icon-box);\n height: var(--widget-icon-box);\n border-radius: var(--radius-sm, 12px);\n background: var(--widget-icon-color, var(--widget-color));\n box-shadow: 0 0 calc(var(--widget-glow-strength) * 30px)\n color-mix(in oklch, var(--widget-icon-color, var(--widget-color)) 50%, transparent);\n color: oklch(from var(--widget-icon-color, var(--widget-color)) calc(0.52 + sign(0.5 - l) * 0.43) calc(c * 0.2) h);\n}\n.glasshome-widget-icon-glyph {\n font-size: var(--widget-icon-glyph);\n line-height: 1;\n}\n.glasshome-widget-icon-stack {\n position: absolute;\n width: 102%;\n height: 102%;\n border-radius: var(--radius-sm, 12px);\n}\n.glasshome-widget-icon-stack-2 { bottom: -14%; right: -10%; opacity: 0.45; z-index: -2; }\n.glasshome-widget-icon-stack-3 { bottom: -28%; right: -20%; opacity: 0.20; z-index: -3; }\n\n/* === Slot: text === */\n.glasshome-widget-title {\n font-size: var(--widget-title-size);\n line-height: 1.2;\n color: color-mix(in oklch, var(--color-foreground, currentColor) 60%, transparent);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.glasshome-widget-subtitle {\n font-size: var(--widget-subtitle);\n color: color-mix(in oklch, var(--color-foreground, currentColor) 60%, transparent);\n}\n.glasshome-widget-status {\n font-size: var(--widget-status-size);\n font-weight: 700;\n color: var(--color-foreground, currentColor);\n line-height: 1.1;\n}\n.glasshome-widget-value {\n font-size: var(--widget-value-size);\n font-weight: 700;\n color: var(--color-foreground, currentColor);\n line-height: 1.05;\n}\n.glasshome-widget-badge {\n font-size: var(--widget-badge-size);\n font-weight: 500;\n padding: 0.125rem 0.5rem;\n border-radius: 9999px;\n background: color-mix(in oklch, var(--color-foreground, currentColor) 20%, transparent);\n color: var(--color-foreground, currentColor);\n}\n\n.glasshome-widget-loading {\n background: color-mix(in oklch, var(--widget-color) 20%, transparent);\n}\n\n/* === Slider fill ===\n * Value flows in via --widget-fill-value (0-100). Vertical orientation auto-\n * detected via container query. */\n.glasshome-widget-slider-fill {\n --widget-fill-value: 0;\n background: color-mix(in oklch, var(--widget-icon-color, var(--widget-color)) 30%, transparent);\n box-shadow: 0 0 calc(var(--widget-glow-strength) * 40px)\n color-mix(in oklch, var(--widget-icon-color, var(--widget-color)) 40%, transparent);\n clip-path: inset(0 calc((100 - var(--widget-fill-value)) * 1%) 0 0);\n}\n@container widget (max-aspect-ratio: 1) {\n .glasshome-widget-slider-fill {\n clip-path: inset(calc((100 - var(--widget-fill-value)) * 1%) 0 0 0);\n }\n}\n", a = !1, o, s = /* @__PURE__ */ new WeakSet();
12
+ function c() {
13
+ return o || (o = new CSSStyleSheet(), o.replaceSync(i.replace(/:root\b/g, ":host").replace(/^\.dark\b/gm, ":host(.dark)"))), o;
14
+ }
15
+ function l(e) {
16
+ if (typeof ShadowRoot < "u" && e instanceof ShadowRoot) {
17
+ if (s.has(e)) return;
18
+ let t = c();
19
+ e.adoptedStyleSheets.includes(t) || (e.adoptedStyleSheets = [...e.adoptedStyleSheets, t]), s.add(e);
20
+ return;
21
+ }
22
+ if (a) return;
23
+ let t = e ?? (typeof document > "u" ? void 0 : document);
24
+ if (!t) return;
25
+ if (t.head.querySelector?.("style[data-glasshome-tokens]")) {
26
+ a = !0;
27
+ return;
28
+ }
29
+ let n = t.createElement("style");
30
+ n.setAttribute("data-glasshome-tokens", ""), n.textContent = i, t.head.appendChild(n), a = !0;
31
+ }
32
+ //#endregion
33
+ export { n, r, l as t };
package/dist/ui.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Host UI primitives, re-exported so the SDK is the single gated door.
3
+ *
4
+ * The host serves `@glasshome/ui/solid` through its import map with no compat
5
+ * check of its own, so a widget importing it directly is exposed to ui drift
6
+ * across the Hub auto-update boundary. Routed through here, the same
7
+ * primitives sit behind `sdkVersion` — the one contract `checkSdkCompat`
8
+ * enforces (same model as the sync-layer hooks).
9
+ *
10
+ * The set is exactly what shipped widgets use; extending it is an additive
11
+ * SDK minor. Direct `@glasshome/ui` imports keep working but are deprecated
12
+ * (`direct-ui-import` in the registry), removed in 2.0.0.
13
+ *
14
+ * `@glasshome/ui/solid` is a vite external of this package: these re-exports
15
+ * resolve to the host-provided singleton at runtime, never a bundled copy.
16
+ */
17
+ export { Badge, Button, type Color, ColorSlider, ColorWheel, Input, Label, parseColor, ResponsiveDialog, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogHeader, ResponsiveDialogTitle, SchemaForm, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider, Switch, } from "@glasshome/ui/solid";
@@ -11,18 +11,6 @@ export interface GlasshomeWidgetOptions {
11
11
  * author decide which states matter rather than discovering the loss never.
12
12
  */
13
13
  export declare const MAX_EXAMPLES = 20;
14
- /**
15
- * Configuration-drift guard (D-11). Imports the built bundle, derives the JSON
16
- * Schema from its configSchema, and compares against the recorded
17
- * `.schema-hash`. A shape change without a configVersion bump fails the build:
18
- * tsc cannot catch this class of break because the old persisted config still
19
- * parses, only its meaning shifts. Legacy plain-hash records (written by
20
- * pre-1.7 builds) cannot prove a missing bump, so they only warn, then upgrade
21
- * to the JSON record format.
22
- *
23
- * When `manifestPath` is given (single-widget projects), the generated JSON
24
- * Schema is also written into `manifest.json` as `schema`.
25
- */
26
14
  export declare function runSchemaGuard(args: {
27
15
  outFile: string;
28
16
  hashFile: string;
@@ -37,6 +25,14 @@ export declare function runSchemaGuard(args: {
37
25
  * must be bundled, because leaving it external is a runtime 404.
38
26
  */
39
27
  export declare function isWidgetExternal(id: string): boolean;
28
+ /**
29
+ * True when a widget source file imports `@glasshome/ui` directly. The regex is
30
+ * the registry's `sourcePattern` for `direct-ui-import`, so the build warning and
31
+ * the widget-cli lint can never disagree on what counts as a direct import.
32
+ * node_modules is excluded: dependencies and the SDK's own generated entries in
33
+ * BUILD_CACHE_DIR (the theme `@import`) are the SDK's business, not the widget's.
34
+ */
35
+ export declare function isDirectUiImportSource(id: string, code: string): boolean;
40
36
  interface DiscoveredWidget {
41
37
  name: string;
42
38
  entry: string;
@@ -1,17 +1,19 @@
1
- import { formatSchemaError as e, isHostProvidedModule as t, widgetManifestSchema as n } from "@glasshome/widget-contract";
2
- import { createHash as r } from "node:crypto";
3
- import { existsSync as i, mkdirSync as a, readFileSync as o, readdirSync as s, rmSync as c, statSync as l, writeFileSync as u } from "node:fs";
4
- import { createRequire as d } from "node:module";
5
- import { dirname as f, join as p, resolve as m, sep as h } from "node:path";
6
- import { fileURLToPath as g } from "node:url";
7
- import _ from "@tailwindcss/vite";
1
+ import { deprecations as e, formatDeprecation as t } from "../deprecations.js";
2
+ import { formatSchemaError as n, isHostProvidedModule as r, widgetManifestSchema as i } from "@glasshome/widget-contract";
3
+ import { spawnSync as a } from "node:child_process";
4
+ import { createHash as o } from "node:crypto";
5
+ import { existsSync as s, mkdirSync as c, readFileSync as l, readdirSync as u, rmSync as d, statSync as f, writeFileSync as p } from "node:fs";
6
+ import { createRequire as m } from "node:module";
7
+ import { dirname as h, join as g, relative as _, resolve as v, sep as y } from "node:path";
8
+ import { fileURLToPath as b } from "node:url";
9
+ import x from "@tailwindcss/vite";
8
10
  //#region src/vite/index.ts
9
- function v(e) {
11
+ function S(e) {
10
12
  let t = JSON.stringify(e, Object.keys(e).sort());
11
- return r("sha256").update(t).digest("hex").slice(0, 16);
13
+ return o("sha256").update(t).digest("hex").slice(0, 16);
12
14
  }
13
- var y = 20;
14
- async function b(e, t) {
15
+ var C = 20;
16
+ async function w(e, t) {
15
17
  if (e === void 0) return;
16
18
  let n;
17
19
  try {
@@ -31,54 +33,103 @@ async function b(e, t) {
31
33
  let a = i.error.issues.map((e) => ` examples${e.path.length ? `[${e.path.join("][")}]` : ""}: ${e.message}`).join("\n");
32
34
  throw Error(`[widget-sdk] Invalid \`examples\` for "${t}":\n${a}\nEach example needs a \`config\` object and a \`size\` of whole positive tiles, and at most 20 are rendered.`);
33
35
  }
34
- async function x(e) {
35
- if (!i(e.outFile)) return;
36
- let t;
37
- try {
38
- t = (await import(`${e.outFile}?t=${Date.now()}`)).default;
39
- } catch {
40
- return;
41
- }
42
- if (await b(t?.manifest?.examples, t?.manifest?.name ?? e.widgetName), !t?.configSchema) return;
43
- let n;
36
+ function T(e, t) {
37
+ if (!e?.length) return;
38
+ let n = e.map((e) => {
39
+ let t = e.label ? `"${e.label}"` : `#${e.index}`;
40
+ return [` examples[${e.index}] ${t}:`, ...e.problems.map((e) => ` ${e}`)].join("\n");
41
+ }).join("\n");
42
+ throw Error(`[widget-sdk] Example config rejected by "${t}"'s own configSchema:\n${n}\nEach example's \`config\` must be a config the widget would actually accept, or its preview renders empty.`);
43
+ }
44
+ function E(e) {
45
+ let t = h(b(import.meta.url)), n = [g(t, "introspect.js"), g(t, "introspect.ts")].find((e) => s(e));
46
+ if (!n) return {
47
+ ok: !1,
48
+ reason: `introspect not found next to ${t}`
49
+ };
50
+ let r = a(process.execPath, [
51
+ "--conditions",
52
+ "browser",
53
+ n,
54
+ e
55
+ ], {
56
+ encoding: "utf-8",
57
+ timeout: 3e4
58
+ });
59
+ if (r.error) return {
60
+ ok: !1,
61
+ reason: r.error.message
62
+ };
63
+ if (r.status !== 0) return {
64
+ ok: !1,
65
+ reason: (r.stderr || "").trim().split("\n")[0] || `exit ${r.status}`
66
+ };
44
67
  try {
45
- let { z: e } = await import("zod");
46
- n = e.toJSONSchema(t.configSchema, { unrepresentable: "any" });
68
+ return {
69
+ ok: !0,
70
+ value: JSON.parse(r.stdout)
71
+ };
47
72
  } catch {
48
- return;
73
+ return {
74
+ ok: !1,
75
+ reason: "introspect returned unparseable output"
76
+ };
49
77
  }
50
- if (e.manifestPath && i(e.manifestPath)) {
51
- let t = JSON.parse(o(e.manifestPath, "utf-8"));
52
- t.schema = n, u(e.manifestPath, JSON.stringify(t, null, 2));
53
- }
54
- let r = v(n), a = t.manifest?.configVersion ?? null, s = t.manifest?.name ?? e.widgetName;
55
- if (i(e.hashFile)) {
56
- let t = o(e.hashFile, "utf-8").trim(), n = null;
78
+ }
79
+ var D = [
80
+ "schema",
81
+ "defaultConfig",
82
+ "examples",
83
+ "configVersion"
84
+ ];
85
+ function O(e, t, n) {
86
+ let r = l(e, "utf-8"), i = JSON.parse(r), a = {
87
+ ...t.manifest ?? {},
88
+ schema: n
89
+ };
90
+ for (let e of D) a[e] !== void 0 && (i[e] = a[e]);
91
+ let o = `${JSON.stringify(i, null, 2)}\n`;
92
+ o !== r && p(e, o);
93
+ }
94
+ async function k(e) {
95
+ if (!s(e.outFile)) return;
96
+ let t = E(e.outFile);
97
+ if (!t.ok) throw Error(`[widget-sdk] Could not read "${e.widgetName}" to check it: ${t.reason}\n Its examples and config schema could not be validated, so the build cannot continue.
98
+ This usually means the bundle throws while being imported.`);
99
+ let n = t.value, r = n.manifest?.name ?? e.widgetName;
100
+ await w(n.manifest?.examples, r), T(n.exampleConfigIssues, r);
101
+ let i = n.jsonSchema;
102
+ if (!i) return;
103
+ e.manifestPath && s(e.manifestPath) && O(e.manifestPath, n, i);
104
+ let a = S(i), o = n.manifest?.configVersion ?? null, c = n.manifest?.name ?? e.widgetName;
105
+ if (s(e.hashFile)) {
106
+ let t = l(e.hashFile, "utf-8").trim(), n = null;
57
107
  if (t.startsWith("{")) try {
58
108
  n = JSON.parse(t);
59
109
  } catch {
60
110
  n = null;
61
111
  }
62
112
  if (n) {
63
- if (n.hash !== r && n.configVersion === a) throw Error(`[widget-sdk] Config schema shape changed for "${s}" without a configVersion bump. Bump configVersion in defineWidget (currently ${a ?? "unset"}) or revert the schema change, then rebuild. Recorded in ${e.hashFile}`);
64
- } else t !== r && console.warn(`[widget-sdk] Schema shape changed for "${s}" — verify configVersion was bumped`);
113
+ if (n.hash !== a && n.configVersion === o) throw Error(`[widget-sdk] Config schema shape changed for "${c}" without a configVersion bump. Bump configVersion in defineWidget (currently ${o ?? "unset"}) or revert the schema change, then rebuild. Recorded in ${e.hashFile}`);
114
+ } else t !== a && console.warn(`[widget-sdk] Schema shape changed for "${c}" — verify configVersion was bumped`);
65
115
  }
66
- u(e.hashFile, `${JSON.stringify({
67
- hash: r,
68
- configVersion: a
116
+ p(e.hashFile, `${JSON.stringify({
117
+ hash: a,
118
+ configVersion: o
69
119
  })}\n`);
70
120
  }
71
- var S = "virtual:glasshome-widget", C = "\0virtual:glasshome-widget", w = "/@glasshome/preview";
72
- function T(e) {
73
- return h === "\\" ? e.split(h).join("/") : e;
121
+ var A = "virtual:glasshome-widget", j = "\0virtual:glasshome-widget", M = "/@glasshome/preview";
122
+ function N(e) {
123
+ return y === "\\" ? e.split(y).join("/") : e;
74
124
  }
75
- function E() {
76
- return m(f(g(import.meta.url)), "../../preview");
125
+ function P() {
126
+ let e = b(import.meta.url);
127
+ return v(h(e), "../../preview");
77
128
  }
78
- function D(e) {
79
- return t(e);
129
+ function F(e) {
130
+ return r(e);
80
131
  }
81
- function O() {
132
+ function I() {
82
133
  return {
83
134
  name: "glasshome-widget:sync-layer-guard",
84
135
  apply: "build",
@@ -88,15 +139,38 @@ function O() {
88
139
  }
89
140
  };
90
141
  }
91
- var k = "node_modules/.cache/glasshome-widgets";
92
- function A(e) {
142
+ var L = /\.(ts|tsx|js|jsx)$/;
143
+ function R(t, n) {
144
+ let r = N(t).split("?")[0] ?? "";
145
+ if (r.includes("/node_modules/") || !L.test(r)) return !1;
146
+ let i = e.find((e) => e.id === "direct-ui-import");
147
+ return i?.sourcePattern ? new RegExp(i.sourcePattern).test(n) : !1;
148
+ }
149
+ function z() {
150
+ let n = /* @__PURE__ */ new Set();
151
+ return {
152
+ name: "glasshome-widget:ui-import-guard",
153
+ apply: "build",
154
+ enforce: "pre",
155
+ transform(e, t) {
156
+ return R(t, e) && n.add(N(_(process.cwd(), t.split("?")[0] ?? t))), null;
157
+ },
158
+ buildEnd() {
159
+ if (n.size === 0) return;
160
+ let r = e.find((e) => e.id === "direct-ui-import"), i = r ? t(r) : "[@glasshome/widget-sdk] Direct @glasshome/ui imports are deprecated; import the same export from \"@glasshome/widget-sdk\".", a = [...n].map((e) => ` ${e}`).join("\n");
161
+ console.warn(`${i}\n Direct @glasshome/ui import(s) in:\n${a}`);
162
+ }
163
+ };
164
+ }
165
+ var B = "node_modules/.cache/glasshome-widgets";
166
+ function V(e) {
93
167
  try {
94
- return d(p(process.cwd(), "package.json")).resolve(e);
168
+ return m(g(process.cwd(), "package.json")).resolve(e);
95
169
  } catch {
96
170
  return null;
97
171
  }
98
172
  }
99
- var j = [
173
+ var H = [
100
174
  "--radius",
101
175
  "--background",
102
176
  "--foreground",
@@ -106,128 +180,130 @@ var j = [
106
180
  "--muted",
107
181
  "--accent"
108
182
  ];
109
- function M(e, t) {
110
- let n = p(e, `${t}.css`);
111
- if (!i(n)) return;
112
- let r = o(n, "utf-8");
183
+ function U(e, t) {
184
+ let n = g(e, `${t}.css`);
185
+ if (!s(n)) return;
186
+ let r = l(n, "utf-8");
113
187
  for (let e of r.matchAll(/:host[^{]*\{([^}]*)\}/g)) {
114
- let n = e[1] ?? "", r = j.find((e) => n.includes(`${e}:`));
188
+ let n = e[1] ?? "", r = H.find((e) => n.includes(`${e}:`));
115
189
  if (r) throw Error(`[glasshome-widget] "${t}.css" defines theme variable "${r}" on :host. Theme variables must inherit from the document so widgets follow the household theme — never re-scope them into the widget's shadow root.`);
116
190
  }
117
191
  }
118
- function N(e, t, n) {
119
- let r = m(e, k);
120
- a(r, { recursive: !0 });
121
- let o = A("@glasshome/ui/styles");
122
- if (!o) throw Error("[widget-sdk] @glasshome/ui is required to build widgets (it provides the theme every GlassHome widget ships with). Add it to your widget's dependencies.");
123
- let s = m(f(o), "../.."), c = A("@glasshome/widget-sdk/tailwind-sources"), l = c ? f(c) : null, d = [
124
- p(s, "src"),
125
- p(s, "dist"),
126
- ...l ? [p(l, "src"), p(l, "dist")] : [],
127
- f(n)
128
- ].filter((e) => i(e)), h = m(r, `${t}.tailwind.css`);
129
- u(h, [
192
+ function W(e, t, n) {
193
+ let r = v(e, B);
194
+ c(r, { recursive: !0 });
195
+ let i = V("@glasshome/ui/styles");
196
+ if (!i) throw Error("[widget-sdk] @glasshome/ui is required to build widgets (it provides the theme every GlassHome widget ships with). Add it to your widget's dependencies.");
197
+ let a = v(h(i), "../.."), o = V("@glasshome/widget-sdk/tailwind-sources"), l = o ? h(o) : null, u = [
198
+ g(a, "src"),
199
+ g(a, "dist"),
200
+ ...l ? [g(l, "src"), g(l, "dist")] : [],
201
+ h(n)
202
+ ].filter((e) => s(e)), d = v(r, `${t}.tailwind.css`);
203
+ p(d, [
130
204
  "@import \"tailwindcss\" source(none);",
131
205
  "@import \"tw-animate-css\";",
132
206
  "@import \"@glasshome/ui/styles/theme\";",
133
- ...d.map((e) => `@source "${T(e)}";`),
207
+ ...u.map((e) => `@source "${N(e)}";`),
134
208
  "@custom-variant dark {",
135
209
  " &:is(.dark *) { @slot }",
136
210
  " :host(.dark) & { @slot }",
137
211
  "}",
138
212
  ""
139
213
  ].join("\n"));
140
- let g = m(r, `${t}.entry.ts`);
141
- return u(g, [
142
- `import "${T(h)}";`,
143
- `export { default } from "${T(n)}";`,
214
+ let f = v(r, `${t}.entry.ts`);
215
+ return p(f, [
216
+ `import "${N(d)}";`,
217
+ `export { default } from "${N(n)}";`,
144
218
  ""
145
- ].join("\n")), g;
219
+ ].join("\n")), f;
146
220
  }
147
- function P() {
148
- let e = _();
221
+ function G() {
222
+ let e = x();
149
223
  for (let t of e) t.apply ??= "build";
150
224
  return e;
151
225
  }
152
- function F(e) {
226
+ function K(e) {
153
227
  let t = [];
154
- if (!i(e)) return t;
155
- for (let n of s(e)) {
156
- let r = m(e, n);
157
- if (!l(r).isDirectory()) continue;
158
- let a = m(r, "index.tsx"), o = m(r, "manifest.json");
159
- i(a) && i(o) && t.push({
228
+ if (!s(e)) return t;
229
+ for (let n of u(e)) {
230
+ let r = v(e, n);
231
+ if (!f(r).isDirectory()) continue;
232
+ let i = v(r, "index.tsx"), a = v(r, "manifest.json");
233
+ s(i) && s(a) && t.push({
160
234
  name: n,
161
- entry: a
235
+ entry: i
162
236
  });
163
237
  }
164
238
  return t;
165
239
  }
166
- function I(t, r) {
167
- let c = [];
168
- if (i(t)) for (let a of s(t)) {
169
- if (!l(p(t, a)).isDirectory()) continue;
170
- let s = p(t, a, "manifest.json");
171
- if (!i(s)) continue;
172
- let u = n.safeParse(JSON.parse(o(s, "utf-8")));
173
- if (!u.success) throw Error(`[registry] Invalid manifest for "${a}": ${e(u.error)}`);
174
- let d = i(p(r, `${a}.css`));
175
- c.push({
176
- ...u.data,
240
+ function q(e, t) {
241
+ let r = [];
242
+ if (s(e)) for (let a of u(e)) {
243
+ if (!f(g(e, a)).isDirectory()) continue;
244
+ let o = g(e, a, "manifest.json");
245
+ if (!s(o)) continue;
246
+ let c = i.safeParse(JSON.parse(l(o, "utf-8")));
247
+ if (!c.success) throw Error(`[registry] Invalid manifest for "${a}": ${n(c.error)}`);
248
+ let u = s(g(t, `${a}.css`));
249
+ r.push({
250
+ ...c.data,
177
251
  bundleUrl: `./${a}.js`,
178
- ...d ? { cssUrl: `./${a}.css` } : {}
252
+ ...u ? { cssUrl: `./${a}.css` } : {}
179
253
  });
180
254
  }
181
- let d = {
255
+ let a = {
182
256
  version: 1,
183
257
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
184
258
  baseUrl: "./",
185
- widgets: c
259
+ widgets: r
186
260
  };
187
- i(r) || a(r, { recursive: !0 }), u(p(r, "registry.json"), JSON.stringify(d, null, 2)), console.log(`[registry] Generated registry.json with ${c.length} widget(s)`);
261
+ s(t) || c(t, { recursive: !0 }), p(g(t, "registry.json"), JSON.stringify(a, null, 2)), console.log(`[registry] Generated registry.json with ${r.length} widget(s)`);
188
262
  }
189
- async function L(e) {
190
- let { build: t } = await import("vite"), n = process.cwd(), r = m(n, e?.srcDir ?? "src"), o = m(n, e?.outDir ?? "dist"), s = F(r);
191
- if (s.length === 0) {
263
+ async function J(e) {
264
+ let { build: t } = await import("vite"), n = process.cwd(), r = v(n, e?.srcDir ?? "src"), i = v(n, e?.outDir ?? "dist"), a = K(r);
265
+ if (a.length === 0) {
192
266
  console.warn("[glasshome-widgets] No widgets found in", r);
193
267
  return;
194
268
  }
195
269
  if (e?.only) {
196
270
  let t = new Set(e.only);
197
- s = s.filter((e) => t.has(e.name));
271
+ a = a.filter((e) => t.has(e.name));
198
272
  }
199
- e?.only || i(o) && c(o, { recursive: !0 }), i(o) || a(o, { recursive: !0 });
200
- for (let r of s) await t({
273
+ e?.only || s(i) && d(i, { recursive: !0 }), s(i) || c(i, { recursive: !0 });
274
+ for (let r of a) await t({
201
275
  configFile: !1,
202
276
  root: n,
203
277
  plugins: [
204
- ...P(),
278
+ ...G(),
205
279
  ...e?.plugins ?? [],
206
- O()
280
+ I(),
281
+ z()
207
282
  ],
208
283
  ...e?.viteConfig,
209
284
  build: {
210
285
  lib: {
211
- entry: N(n, r.name, r.entry),
286
+ entry: W(n, r.name, r.entry),
212
287
  formats: ["es"],
213
288
  fileName: r.name,
214
289
  cssFileName: r.name
215
290
  },
216
- rollupOptions: { external: D },
217
- outDir: o,
291
+ rollupOptions: { external: F },
292
+ outDir: i,
218
293
  emptyOutDir: !1,
219
294
  copyPublicDir: !1,
220
295
  ...e?.viteConfig?.build
221
296
  },
222
297
  logLevel: "warn"
223
- }), M(o, r.name), await x({
224
- outFile: p(o, `${r.name}.js`),
225
- hashFile: p(f(r.entry), ".schema-hash"),
226
- widgetName: r.name
298
+ }), U(i, r.name), await k({
299
+ outFile: g(i, `${r.name}.js`),
300
+ hashFile: g(h(r.entry), ".schema-hash"),
301
+ widgetName: r.name,
302
+ manifestPath: g(h(r.entry), "manifest.json")
227
303
  });
228
- I(r, o);
304
+ q(r, i);
229
305
  }
230
- function R(e) {
306
+ function Y(e) {
231
307
  let t = e?.entry ?? "src/index.tsx", n = {
232
308
  name: "glasshome-widget:build",
233
309
  apply: "build",
@@ -235,12 +311,12 @@ function R(e) {
235
311
  let e = process.cwd();
236
312
  return { build: {
237
313
  lib: {
238
- entry: N(e, "index", m(e, t)),
314
+ entry: W(e, "index", v(e, t)),
239
315
  formats: ["es"],
240
316
  fileName: "index",
241
317
  cssFileName: "index"
242
318
  },
243
- rollupOptions: { external: D }
319
+ rollupOptions: { external: F }
244
320
  } };
245
321
  }
246
322
  }, r = {
@@ -248,19 +324,19 @@ function R(e) {
248
324
  apply: "serve",
249
325
  enforce: "pre",
250
326
  config() {
251
- let e = E(), t = process.cwd();
327
+ let e = P(), t = process.cwd();
252
328
  return { server: { fs: { allow: [
253
329
  e,
254
330
  t,
255
- m(t, "..", "..")
331
+ v(t, "..", "..")
256
332
  ] } } };
257
333
  },
258
334
  configureServer(e) {
259
- let t = p(E(), "preview.html");
260
- e.middlewares.use(async (n, r, i) => {
261
- if (n.url === "/" || n.url === "/index.html") try {
262
- let i = o(t, "utf-8");
263
- i = await e.transformIndexHtml(n.url, i), r.setHeader("Content-Type", "text/html"), r.statusCode = 200, r.end(i);
335
+ let t = P(), n = g(t, "preview.html");
336
+ e.middlewares.use(async (t, r, i) => {
337
+ if (t.url === "/" || t.url === "/index.html") try {
338
+ let i = l(n, "utf-8");
339
+ i = await e.transformIndexHtml(t.url, i), r.setHeader("Content-Type", "text/html"), r.statusCode = 200, r.end(i);
264
340
  } catch (e) {
265
341
  i(e);
266
342
  }
@@ -268,33 +344,37 @@ function R(e) {
268
344
  });
269
345
  },
270
346
  resolveId(e) {
271
- if (e === w) return T(p(E(), "host.tsx"));
272
- if (e === S) return C;
347
+ if (e === M) {
348
+ let e = P();
349
+ return N(g(e, "host.tsx"));
350
+ }
351
+ if (e === A) return j;
273
352
  },
274
353
  load(e) {
275
- if (e === C) return `export { default } from "${T(m(process.cwd(), t))}";`;
354
+ if (e === j) return `export { default } from "${N(v(process.cwd(), t))}";`;
276
355
  }
277
356
  }, i = {
278
357
  name: "glasshome-widget:schema",
279
358
  apply: "build",
280
359
  async closeBundle() {
281
- M(m(process.cwd(), "dist"), "index"), await x({
282
- outFile: m(process.cwd(), "dist", "index.js"),
283
- hashFile: m(process.cwd(), ".schema-hash"),
360
+ U(v(process.cwd(), "dist"), "index"), await k({
361
+ outFile: v(process.cwd(), "dist", "index.js"),
362
+ hashFile: v(process.cwd(), ".schema-hash"),
284
363
  widgetName: "index",
285
- manifestPath: m(process.cwd(), "manifest.json")
364
+ manifestPath: v(process.cwd(), "manifest.json")
286
365
  });
287
366
  }
288
367
  };
289
368
  return [
290
- ...P(),
369
+ ...G(),
291
370
  n,
292
- O(),
371
+ I(),
372
+ z(),
293
373
  i,
294
374
  r
295
375
  ];
296
376
  }
297
- function z(e) {
377
+ function X(e) {
298
378
  let t = e?.srcDir ?? "src", n = e?.outDir ?? "dist", r = [];
299
379
  return [{
300
380
  name: "glasshome-widgets:build",
@@ -313,7 +393,7 @@ function z(e) {
313
393
  if (e === "\0glasshome-noop") return "export {}";
314
394
  },
315
395
  async closeBundle() {
316
- await L({
396
+ await J({
317
397
  srcDir: t,
318
398
  outDir: n,
319
399
  plugins: r
@@ -322,4 +402,4 @@ function z(e) {
322
402
  }];
323
403
  }
324
404
  //#endregion
325
- export { y as MAX_EXAMPLES, L as buildWidgets, F as discoverWidgets, I as generateRegistry, R as glasshomeWidget, z as glasshomeWidgets, D as isWidgetExternal, x as runSchemaGuard };
405
+ export { C as MAX_EXAMPLES, J as buildWidgets, K as discoverWidgets, q as generateRegistry, Y as glasshomeWidget, X as glasshomeWidgets, R as isDirectUiImportSource, F as isWidgetExternal, k as runSchemaGuard };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Reads a built widget bundle's definition and prints it as JSON.
3
+ *
4
+ * Runs as its own process because a widget bundle cannot be imported by the
5
+ * build: `solid-js/web` resolves to its server build under Node's conditions
6
+ * and throws "Client-only API called on the server side" the moment the module
7
+ * initialises, and forcing browser conditions then needs a DOM for the
8
+ * templates Solid creates at import time. The build spawns this with
9
+ * `--conditions browser` and a DOM in place, so the import succeeds.
10
+ *
11
+ * Until this existed the guard swallowed that import failure and silently did
12
+ * nothing — for every widget, on every build.
13
+ *
14
+ * Usage: bun --conditions browser introspect.js <bundle.js>
15
+ * Prints: { manifest, jsonSchema } on stdout, or exits non-zero with a reason.
16
+ */
17
+ export interface ExampleConfigIssue {
18
+ index: number;
19
+ label?: string;
20
+ problems: string[];
21
+ }