@colixsystems/widget-sdk 0.117.0 → 0.118.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/README.md +22 -1
- package/dist/contract.cjs +15 -3
- package/dist/contract.js +15 -3
- package/dist/devserver.js +92 -40
- package/dist/flatten-entry.js +817 -0
- package/dist/host.d.ts +8 -4
- package/dist/linter.js +3 -146
- package/dist/source-mask.js +162 -0
- package/dist/theme-components.cjs +70 -10
- package/dist/theme-components.js +70 -10
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -70,7 +70,28 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
70
70
|
|
|
71
71
|
## Status
|
|
72
72
|
|
|
73
|
-
`v0.
|
|
73
|
+
`v0.118.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
74
|
+
|
|
75
|
+
### What's new in 0.118.0 (contract 1.90.0)
|
|
76
|
+
|
|
77
|
+
**A `styleSchema` field's `default` now actually applies.** Declaring `default` on a style field wrote it into the manifest and nothing ever read it back, so a widget's own styling baseline — and any design saved from the Widget Builder preview — was silently dropped on the next render. The host now resolves it onto `props.style`.
|
|
78
|
+
|
|
79
|
+
It is the **weakest** layer, deliberately: it applies only when nothing above it sets that field.
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
styleSchema `default` -> palette / components.<scope> -> widgetStyles[manifestId] -> per-instance props.style
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
So a workspace theme still outranks a widget's own baseline, and a field you leave undefaulted keeps following the theme exactly as before. Nothing changes for a widget that declares no style defaults.
|
|
86
|
+
|
|
87
|
+
The host still does **not** apply style to elements — your widget owns placement and keeps reading `props.style.<field>` (or `useWidgetStyle()`) and applying each value where it chooses:
|
|
88
|
+
|
|
89
|
+
```jsx
|
|
90
|
+
const style = useWidgetStyle();
|
|
91
|
+
<View style={[styles.card, style.cardBackground && { backgroundColor: style.cardBackground }]}>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Keep `themeDefault` for a fallback that IS a theme token: it stays display-only (a greyed placeholder in the Style panel) so the field tracks the workspace theme. Use `default` for a literal constant your code genuinely falls back to — that value now reaches `props.style`, so it must match what your code applies.
|
|
74
95
|
|
|
75
96
|
### What's new in 0.117.0 (contract 1.89.0)
|
|
76
97
|
|
package/dist/contract.cjs
CHANGED
|
@@ -1967,8 +1967,11 @@ const MANIFEST_SCHEMA = {
|
|
|
1967
1967
|
"exposes; the Studio Properties Panel renders a \"Style\" section from " +
|
|
1968
1968
|
"it. The author's resolved values are delivered to the widget under " +
|
|
1969
1969
|
"props.style (one object keyed by style-field name); the widget reads " +
|
|
1970
|
-
"props.style.<field> and applies each wherever it chooses.
|
|
1971
|
-
"
|
|
1970
|
+
"props.style.<field> and applies each wherever it chooses. A field's " +
|
|
1971
|
+
"own `default` is resolved onto props.style as the WEAKEST layer " +
|
|
1972
|
+
"(sc-6750): it applies only when no theme layer and no per-instance " +
|
|
1973
|
+
"value sets that field. The host never applies style to elements " +
|
|
1974
|
+
"— the widget owns placement.",
|
|
1972
1975
|
default: {},
|
|
1973
1976
|
},
|
|
1974
1977
|
rendersOwnChrome: {
|
|
@@ -3515,7 +3518,16 @@ const CONTRACT = deepFreeze({
|
|
|
3515
3518
|
// web build reads deviceorientation angles rather than real acceleration —
|
|
3516
3519
|
// the web half is a widget.web.jsx over window.DeviceMotionEvent. Pinned in
|
|
3517
3520
|
// the compiler's export dependencies, like every other native-module member.
|
|
3518
|
-
|
|
3521
|
+
// 1.90.0: additive (sc-6750) — a `styleSchema` field's declared `default`
|
|
3522
|
+
// now RESOLVES onto `props.style`, as the weakest style layer. It was
|
|
3523
|
+
// written into the manifest and then read by nothing, so an author's saved
|
|
3524
|
+
// colour was silently dropped on the next render. Precedence is unchanged
|
|
3525
|
+
// above it: styleSchema `default` -> palette/`components.<scope>` ->
|
|
3526
|
+
// `widgetStyles[manifestId]` -> per-instance `props.style`, so a workspace
|
|
3527
|
+
// theme still outranks a widget's own baseline and a field the author never
|
|
3528
|
+
// defaulted follows the theme exactly as before. The host still never
|
|
3529
|
+
// applies style to elements — the widget owns placement.
|
|
3530
|
+
version: "1.90.0",
|
|
3519
3531
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3520
3532
|
hooks: HOOKS,
|
|
3521
3533
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1967,8 +1967,11 @@ const MANIFEST_SCHEMA = {
|
|
|
1967
1967
|
"exposes; the Studio Properties Panel renders a \"Style\" section from " +
|
|
1968
1968
|
"it. The author's resolved values are delivered to the widget under " +
|
|
1969
1969
|
"props.style (one object keyed by style-field name); the widget reads " +
|
|
1970
|
-
"props.style.<field> and applies each wherever it chooses.
|
|
1971
|
-
"
|
|
1970
|
+
"props.style.<field> and applies each wherever it chooses. A field's " +
|
|
1971
|
+
"own `default` is resolved onto props.style as the WEAKEST layer " +
|
|
1972
|
+
"(sc-6750): it applies only when no theme layer and no per-instance " +
|
|
1973
|
+
"value sets that field. The host never applies style to elements " +
|
|
1974
|
+
"— the widget owns placement.",
|
|
1972
1975
|
default: {},
|
|
1973
1976
|
},
|
|
1974
1977
|
rendersOwnChrome: {
|
|
@@ -3515,7 +3518,16 @@ const CONTRACT = deepFreeze({
|
|
|
3515
3518
|
// web build reads deviceorientation angles rather than real acceleration —
|
|
3516
3519
|
// the web half is a widget.web.jsx over window.DeviceMotionEvent. Pinned in
|
|
3517
3520
|
// the compiler's export dependencies, like every other native-module member.
|
|
3518
|
-
|
|
3521
|
+
// 1.90.0: additive (sc-6750) — a `styleSchema` field's declared `default`
|
|
3522
|
+
// now RESOLVES onto `props.style`, as the weakest style layer. It was
|
|
3523
|
+
// written into the manifest and then read by nothing, so an author's saved
|
|
3524
|
+
// colour was silently dropped on the next render. Precedence is unchanged
|
|
3525
|
+
// above it: styleSchema `default` -> palette/`components.<scope>` ->
|
|
3526
|
+
// `widgetStyles[manifestId]` -> per-instance `props.style`, so a workspace
|
|
3527
|
+
// theme still outranks a widget's own baseline and a field the author never
|
|
3528
|
+
// defaulted follows the theme exactly as before. The host still never
|
|
3529
|
+
// applies style to elements — the widget owns placement.
|
|
3530
|
+
version: "1.90.0",
|
|
3519
3531
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3520
3532
|
hooks: HOOKS,
|
|
3521
3533
|
primitives: PRIMITIVES,
|
package/dist/devserver.js
CHANGED
|
@@ -67,32 +67,38 @@ import {
|
|
|
67
67
|
shimSpecifierFromSlug,
|
|
68
68
|
} from "./dev-shims.js";
|
|
69
69
|
import { bundleWebEntry } from "./webbundle.js";
|
|
70
|
+
import {
|
|
71
|
+
findImportStatements,
|
|
72
|
+
findRelativeImportStatements,
|
|
73
|
+
} from "./flatten-entry.js";
|
|
70
74
|
|
|
71
75
|
// Bare-import / relative-import detection. The loader rewrites bare specifiers
|
|
72
76
|
// in the ENTRY to client-side blob shims, so they resolve fine. Relative
|
|
73
77
|
// imports in a SINGLE-FILE entry have nowhere to resolve — single-file mode
|
|
74
78
|
// refuses them with guidance. In directory mode, relative imports in the entry
|
|
75
79
|
// AND in siblings are rewritten to absolute dev-server URLs.
|
|
76
|
-
const RELATIVE_IMPORT_RE =
|
|
77
|
-
/(?:^|\n)\s*(?:import|export)[^;\n]*?from\s*['"](\.\.?\/[^'"]+)['"]/g;
|
|
78
|
-
|
|
79
|
-
// Static-import / static-export-from anchored at a statement boundary
|
|
80
|
-
// (start-of-line, `;`, or newline before `import`/`export`) so the rewriter
|
|
81
|
-
// doesn't touch occurrences inside string literals or comment bodies. The
|
|
82
|
-
// dynamic-import form (`import("...")`) is matched separately by
|
|
83
|
-
// `DYNAMIC_IMPORT_RE` below — it's safe to anchor in the same way because
|
|
84
|
-
// it's also valid only as an expression.
|
|
85
80
|
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
81
|
+
// STATIC imports are found by the packer's scanner (`findImportStatements`),
|
|
82
|
+
// never by a local regex. sc-6086 removed RELATIVE_IMPORT_RE and sc-6670 the
|
|
83
|
+
// rewriter's STATIC_IMPORT_RE for the same defect: a `[^'";\n]` clause body
|
|
84
|
+
// cannot span newlines, so a multi-line `import {\n a,\n} from "react"` — the
|
|
85
|
+
// norm in this codebase — read as no import at all and was served verbatim.
|
|
86
|
+
// The scanner walks the clause character by character, so line breaks are
|
|
87
|
+
// nothing special, and it returns the specifier's quote offsets so the
|
|
88
|
+
// rewriter splices exactly the specifier and can never bleed into an adjacent
|
|
89
|
+
// statement. One scanner for the guard, the packer, and the rewriter (§3).
|
|
90
|
+
|
|
91
|
+
// Dynamic import (`import("…")`) is an EXPRESSION, valid at any brace depth,
|
|
92
|
+
// so the statement scanner does not see it — it keeps its own regex. That
|
|
93
|
+
// regex separates the specifier from `import` with `\s*`, which does match
|
|
94
|
+
// newlines, so it has no multi-line gap (sc-6670 verified).
|
|
95
|
+
//
|
|
96
|
+
// Capture groups:
|
|
97
|
+
// 1 = `import` + whitespace + `(` (and the leading boundary char, preserved
|
|
98
|
+
// verbatim during replace so the call isn't glued onto what precedes it)
|
|
90
99
|
// 2 = opening quote
|
|
91
100
|
// 3 = specifier
|
|
92
|
-
const
|
|
93
|
-
/((?:^|[\n;])\s*(?:import|export)\s+(?:[^'";\n]*?\s+from\s*)?)(['"])([^'"]+)\2/gm;
|
|
94
|
-
const DYNAMIC_IMPORT_RE =
|
|
95
|
-
/((?:^|[\s;\(,!?:=])import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
101
|
+
const DYNAMIC_IMPORT_RE = /((?:^|[\s;\(,!?:=])import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
96
102
|
|
|
97
103
|
/**
|
|
98
104
|
* Lazily resolve `sucrase`'s `transform`. Kept out of the module's static
|
|
@@ -137,15 +143,21 @@ export function transpile(transform, source) {
|
|
|
137
143
|
* single-file mode to reject split-impl bundles with a clear message instead
|
|
138
144
|
* of serving a module the browser can't resolve.
|
|
139
145
|
*
|
|
146
|
+
* sc-6086: this delegates to the packer's statement scanner rather than
|
|
147
|
+
* `RELATIVE_IMPORT_RE`, whose `[^;\n]` body cannot span newlines — so a
|
|
148
|
+
* MULTI-LINE `import {\n a,\n b,\n} from "./lib/x.js"` was invisible to it.
|
|
149
|
+
* That blind spot is why `worktime-employer` passed every local check and
|
|
150
|
+
* still shipped an unresolvable import to the marketplace. One scanner, so
|
|
151
|
+
* the dev guard and the packer agree on what a relative import is
|
|
152
|
+
* (CLAUDE.md §3).
|
|
153
|
+
*
|
|
140
154
|
* @param {string} source
|
|
141
155
|
* @returns {string[]} unique relative specifiers
|
|
142
156
|
*/
|
|
143
157
|
export function findRelativeImports(source) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
while ((m = RELATIVE_IMPORT_RE.exec(source))) out.add(m[1]);
|
|
148
|
-
return Array.from(out);
|
|
158
|
+
return Array.from(
|
|
159
|
+
new Set(findRelativeImportStatements(source).map((s) => s.specifier)),
|
|
160
|
+
);
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
/**
|
|
@@ -201,15 +213,16 @@ export function resolveRelativeImport(fromRel, specifier) {
|
|
|
201
213
|
export function rewriteImportsForDirectoryMode(source, ctx) {
|
|
202
214
|
const { fromRel, baseUrl, shimmable } = ctx;
|
|
203
215
|
const unresolved = new Set();
|
|
204
|
-
|
|
216
|
+
// The URL a specifier should become, or null to leave it exactly as written.
|
|
217
|
+
const resolveSpecifier = (spec) => {
|
|
205
218
|
// Relative — resolve under the widget dir.
|
|
206
219
|
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
207
220
|
const rel = resolveRelativeImport(fromRel, spec);
|
|
208
221
|
if (!rel) {
|
|
209
222
|
unresolved.add(spec);
|
|
210
|
-
return
|
|
223
|
+
return null;
|
|
211
224
|
}
|
|
212
|
-
return `${
|
|
225
|
+
return `${baseUrl}/file/${rel}`;
|
|
213
226
|
}
|
|
214
227
|
// Absolute or already-resolved — leave alone.
|
|
215
228
|
if (
|
|
@@ -219,21 +232,36 @@ export function rewriteImportsForDirectoryMode(source, ctx) {
|
|
|
219
232
|
spec.startsWith("blob:") ||
|
|
220
233
|
spec.startsWith("data:")
|
|
221
234
|
) {
|
|
222
|
-
return
|
|
235
|
+
return null;
|
|
223
236
|
}
|
|
224
237
|
// Bare — rewrite if shimmable, else surface as unresolved.
|
|
225
238
|
if (shimmable.includes(spec)) {
|
|
226
|
-
return `${
|
|
239
|
+
return `${baseUrl}/shim/${shimSlugFor(spec)}`;
|
|
227
240
|
}
|
|
228
241
|
unresolved.add(spec);
|
|
229
|
-
return
|
|
242
|
+
return null;
|
|
230
243
|
};
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
|
|
245
|
+
// Pass 1 — static imports, spliced by the scanner's quote offsets. Splicing
|
|
246
|
+
// only what sits BETWEEN the quotes is what makes span bleed impossible:
|
|
247
|
+
// the clause, the trailing `;`, and any adjacent statement are never part of
|
|
248
|
+
// the replaced range. Walk back-to-front so earlier offsets stay valid.
|
|
249
|
+
const statements = findImportStatements(source);
|
|
250
|
+
let out = source;
|
|
251
|
+
for (let i = statements.length - 1; i >= 0; i -= 1) {
|
|
252
|
+
const { specifier, quoteStart, quoteEnd } = statements[i];
|
|
253
|
+
const url = resolveSpecifier(specifier);
|
|
254
|
+
if (url === null) continue;
|
|
255
|
+
out = out.slice(0, quoteStart + 1) + url + out.slice(quoteEnd);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Pass 2 — dynamic `import("…")`, an expression the statement scanner does
|
|
259
|
+
// not report. Its own anchor keeps it off substrings inside string literals
|
|
260
|
+
// like `const s = "import('react')"`.
|
|
261
|
+
out = out.replace(DYNAMIC_IMPORT_RE, (match, prefix, quote, spec) => {
|
|
262
|
+
const url = resolveSpecifier(spec);
|
|
263
|
+
return url === null ? match : `${prefix}${quote}${url}${quote}`;
|
|
264
|
+
});
|
|
237
265
|
return { code: out, unresolved: Array.from(unresolved) };
|
|
238
266
|
}
|
|
239
267
|
|
|
@@ -315,7 +343,10 @@ export function loadWidgetJson(widgetDir) {
|
|
|
315
343
|
const candidates = [
|
|
316
344
|
resolve(widgetDir, p),
|
|
317
345
|
p.startsWith(`first-party-widgets/${widgetSlug}/`)
|
|
318
|
-
? resolve(
|
|
346
|
+
? resolve(
|
|
347
|
+
widgetDir,
|
|
348
|
+
p.slice(`first-party-widgets/${widgetSlug}/`.length),
|
|
349
|
+
)
|
|
319
350
|
: null,
|
|
320
351
|
resolve(widgetDir, "..", "..", p),
|
|
321
352
|
].filter(Boolean);
|
|
@@ -326,7 +357,10 @@ export function loadWidgetJson(widgetDir) {
|
|
|
326
357
|
`${configPath}: ${label} must point at a file under ${widgetDir} (got "${p}")`,
|
|
327
358
|
);
|
|
328
359
|
}
|
|
329
|
-
const manifestAbs = _resolveUnderWidget(
|
|
360
|
+
const manifestAbs = _resolveUnderWidget(
|
|
361
|
+
"manifestSource",
|
|
362
|
+
config.manifestSource,
|
|
363
|
+
);
|
|
330
364
|
|
|
331
365
|
let entryAbs;
|
|
332
366
|
if (config.componentSources && typeof config.componentSources === "object") {
|
|
@@ -353,7 +387,12 @@ export function loadWidgetJson(widgetDir) {
|
|
|
353
387
|
);
|
|
354
388
|
}
|
|
355
389
|
const entryRel = relative(widgetDir, entryAbs).split(sep).join("/");
|
|
356
|
-
return {
|
|
390
|
+
return {
|
|
391
|
+
widgetDir,
|
|
392
|
+
manifestPath: manifestAbs,
|
|
393
|
+
entryPath: entryAbs,
|
|
394
|
+
entryRel,
|
|
395
|
+
};
|
|
357
396
|
}
|
|
358
397
|
|
|
359
398
|
function _isUnder(parentAbs, childAbs) {
|
|
@@ -444,7 +483,13 @@ export function createDevServer({
|
|
|
444
483
|
// entry's own ancestor node_modules by default, so a widget that vendored
|
|
445
484
|
// its deps locally still resolves.
|
|
446
485
|
const bundleNodePaths = [];
|
|
447
|
-
const _frontendNm = resolve(
|
|
486
|
+
const _frontendNm = resolve(
|
|
487
|
+
watchRoot,
|
|
488
|
+
"..",
|
|
489
|
+
"..",
|
|
490
|
+
"frontend",
|
|
491
|
+
"node_modules",
|
|
492
|
+
);
|
|
448
493
|
if (existsSync(_frontendNm)) bundleNodePaths.push(_frontendNm);
|
|
449
494
|
|
|
450
495
|
const sseClients = new Set();
|
|
@@ -700,7 +745,13 @@ export function createDevServer({
|
|
|
700
745
|
if (url === "/__dev/events") return serveEvents(req, res);
|
|
701
746
|
if (url === "/__dev/health") {
|
|
702
747
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
703
|
-
res.end(
|
|
748
|
+
res.end(
|
|
749
|
+
JSON.stringify({
|
|
750
|
+
ok: true,
|
|
751
|
+
manifestId,
|
|
752
|
+
mode: directoryMode ? "directory" : "single-file",
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
704
755
|
return;
|
|
705
756
|
}
|
|
706
757
|
if (directoryMode && url.startsWith("/file/")) {
|
|
@@ -736,7 +787,8 @@ export function createDevServer({
|
|
|
736
787
|
if (ok) onLint("lint: clean");
|
|
737
788
|
else {
|
|
738
789
|
onLint(`lint: ${findings.length} finding(s)`);
|
|
739
|
-
for (const f of findings)
|
|
790
|
+
for (const f of findings)
|
|
791
|
+
onLint(` [${f.rule}] line ${f.line}: ${f.label}`);
|
|
740
792
|
}
|
|
741
793
|
}
|
|
742
794
|
}
|