@colixsystems/widget-sdk 0.187.0 → 0.189.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 CHANGED
@@ -75,7 +75,43 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
75
75
 
76
76
  ## Status
77
77
 
78
- `v0.187.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**.
78
+ `v0.189.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**.
79
+
80
+ ### What's new in 0.189.0 (contract 1.150.0)
81
+
82
+ **The import check now catches two shapes that passed lint and then failed to load in the web Player.** This narrows `import-not-vetted`:
83
+
84
+ - **Imports wrapped over several lines.** The check read one line at a time, so a Prettier-wrapped import of a package that is not on the vetted list was never seen. It now reads the whole file and reports the finding on the `from` line. `export { … } from "pkg"` and `export * from "pkg"` are checked too, and so are forms written without spaces (`import{a}from"pkg"`) or inside an expression (`x = require("pkg")`).
85
+ - **Subpaths of packages the web Player provides.** The web Player supplies some packages itself and loads them by their exact name only. `CONTRACT.hostExternalSpecifiers` lists them; today they are `react` (and its JSX runtimes), `react-dom`, `react-native`, `@colixsystems/widget-sdk`, `lucide-react-native`, `react-native-svg`, `react-native-qrcode-svg`, `date-fns`, `decimal.js`, `d3-scale`, `d3-shape`, `d3-array` and `simple-statistics`. A subpath of one of them, such as `date-fns/format` or `react-native/Libraries/…`, cannot load on web. It is now `import-not-vetted` in any file that ships to web. Import from the package itself: `import { format } from "date-fns"`.
86
+
87
+ A subpath of a package your bundle includes is still allowed, for example `leaflet/dist/leaflet.css`, a Leaflet marker image or `react-native-vector-icons/MaterialIcons`. A file that ships only to native (`widget.native.jsx`, or a manifest whose `supportedPlatforms` is `["native"]`) keeps every vetted subpath, because the native build resolves them.
88
+
89
+ `CONTRACT.subpathImportRoots` lists the vetted packages whose subpaths load on web, and the new `subpathImportRootsFor(manifest)` export of `@colixsystems/widget-sdk/linter` returns the roots that load for a file with that manifest. `appstudio-widget lint --manifest` now checks `widget.web.jsx` and `widget.native.jsx` against the platform each one ships to, as the submission check already did.
90
+
91
+ ### What's new in 0.188.0 (contract 1.149.0)
92
+
93
+ **New linter rules for Leaflet maps: `leaflet-css-missing` and `leaflet-default-marker-icon`.** A widget's web entry is bundled with its own copy of Leaflet, and nothing else loads Leaflet's stylesheet. A map that skipped `import "leaflet/dist/leaflet.css"` drew a small patch of tiles inside an otherwise empty box, with its panes out of place and its markers invisible. Even with the stylesheet, Leaflet looks up its default marker image relative to the page, which 404s inside a widget, so a `<Marker>` with no `icon` showed no pin.
94
+
95
+ - **`leaflet-css-missing` (severity `error`).** A file that imports `leaflet` or `react-leaflet` code (an image-only import does not count) must also import `"leaflet/dist/leaflet.css"`. The bundler turns that import into an injected `<style>`. Import it in every file that uses Leaflet; the bundler includes it once.
96
+ - **`leaflet-default-marker-icon` (severity `warning`).** A react-leaflet `<Marker>` with no `icon` prop. Import the marker PNGs (the bundler inlines them as data URLs), build the icon once at module scope and pass it in:
97
+
98
+ ```jsx
99
+ import L from "leaflet";
100
+ import "leaflet/dist/leaflet.css";
101
+ import markerIcon from "leaflet/dist/images/marker-icon.png";
102
+ import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
103
+ import markerShadow from "leaflet/dist/images/marker-shadow.png";
104
+
105
+ const pin = L.icon({
106
+ iconUrl: markerIcon, iconRetinaUrl: markerIcon2x, shadowUrl: markerShadow,
107
+ iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41],
108
+ });
109
+ // <Marker position={[lat, lng]} icon={pin} />
110
+ ```
111
+
112
+ It is a warning because a widget that configures `L.Icon.Default` itself is fine, and is not flagged. `<CircleMarker>` needs no icon.
113
+
114
+ The `leaflet` and `react-leaflet` entries in `CONTRACT.vettedImports` now say the same. Comments are not scanned, so documenting the pattern is safe.
79
115
 
80
116
  ### What's new in 0.187.0 (contract 1.148.0)
81
117
 
package/dist/cli.js CHANGED
@@ -5,10 +5,15 @@
5
5
  // — REQ-WSDK-DEVKIT local dev server
6
6
 
7
7
  import { readFileSync } from "node:fs";
8
- import { resolve } from "node:path";
8
+ import { basename, resolve } from "node:path";
9
9
  import { pathToFileURL } from "node:url";
10
10
  import { argv, exit, stderr, stdout } from "node:process";
11
- import { lintSource, lintStyleWiring, lintEditability } from "./linter.js";
11
+ import {
12
+ lintSource,
13
+ lintStyleWiring,
14
+ lintEditability,
15
+ narrowManifestForFile,
16
+ } from "./linter.js";
12
17
  import { startDevServer } from "./devserver.js";
13
18
 
14
19
  function usage() {
@@ -99,9 +104,15 @@ async function runLint(rest) {
99
104
  }
100
105
  }
101
106
  const label = positionals.join(", ");
107
+ // Narrow per split-impl file, as the submission gate does.
102
108
  const perFile = Object.entries(files).map(([filePath, source]) => [
103
109
  filePath,
104
- lintSource(source, manifest ? { manifest } : undefined),
110
+ lintSource(
111
+ source,
112
+ manifest
113
+ ? { manifest: narrowManifestForFile(manifest, basename(filePath)) }
114
+ : undefined,
115
+ ),
105
116
  ]);
106
117
  const wiring = manifest
107
118
  ? lintStyleWiring(manifest, files)
package/dist/contract.cjs CHANGED
@@ -3296,13 +3296,16 @@ const VETTED_IMPORTS = [
3296
3296
  platforms: ["web"],
3297
3297
  category: "geo",
3298
3298
  description:
3299
- "Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps.",
3299
+ "Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps. " +
3300
+ 'Every file that imports it must also `import "leaflet/dist/leaflet.css"` (the linter enforces it), and a marker needs an ' +
3301
+ "explicit `L.icon` built from the imported `leaflet/dist/images/*.png`: Leaflet's default icon cannot resolve its image inside a bundle.",
3300
3302
  },
3301
3303
  {
3302
3304
  specifier: "react-leaflet",
3303
3305
  platforms: ["web"],
3304
3306
  category: "geo",
3305
- description: "React bindings for leaflet. Web-only.",
3307
+ description:
3308
+ 'React bindings for leaflet. Web-only. Import "leaflet/dist/leaflet.css" alongside it and pass `icon` to every <Marker>.',
3306
3309
  },
3307
3310
  {
3308
3311
  specifier: "expo-audio",
@@ -3502,6 +3505,49 @@ const ALLOWED_BARE_IMPORTS = [
3502
3505
  ...CORE_INFRA_BARE_IMPORTS,
3503
3506
  ];
3504
3507
 
3508
+ // sc-10502: the bare specifiers the web host RESOLVES for a loaded widget
3509
+ // instead of letting its bundle inline a copy. The web-entry bundler marks
3510
+ // exactly these `external` and the Studio loader shims each by EXACT name, so
3511
+ // a subpath of one (`date-fns/format`) cannot load in the web Player.
3512
+ // `dev-shims.hostExternalSpecifiers()` returns this list; the loader's
3513
+ // module-load guard (frontend widgetLoader.js) throws if its shims drift.
3514
+ //
3515
+ // Why each entry is host-resolved, not bundled:
3516
+ // * react + its JSX runtimes: one React instance (hooks, context).
3517
+ // * react-dom, react-dom/client: react-leaflet portals through react-dom;
3518
+ // a second renderer beside the host's React crashes (sc-1064).
3519
+ // * @colixsystems/widget-sdk: the host-built SDK carries datastore, theme
3520
+ // and navigation.
3521
+ // * react-native, lucide-react-native, react-native-svg,
3522
+ // react-native-qrcode-svg: share the host's react-native-web instance
3523
+ // (sc-1265, sc-6105).
3524
+ // * date-fns and the maths set: an agent widget is transpiled, not
3525
+ // bundled, so the host must resolve its bare imports (sc-7191).
3526
+ const HOST_EXTERNAL_SPECIFIERS = [
3527
+ "react",
3528
+ "react/jsx-runtime",
3529
+ "react/jsx-dev-runtime",
3530
+ "react-dom",
3531
+ "react-dom/client",
3532
+ "@colixsystems/widget-sdk",
3533
+ "lucide-react-native",
3534
+ "react-native-svg",
3535
+ "date-fns",
3536
+ "react-native-qrcode-svg",
3537
+ "react-native",
3538
+ "decimal.js",
3539
+ "d3-scale",
3540
+ "d3-shape",
3541
+ "d3-array",
3542
+ "simple-statistics",
3543
+ ];
3544
+
3545
+ // Vetted roots whose subpaths load on web (`leaflet/dist/leaflet.css`): the
3546
+ // bundle inlines them, so no exact-name host shim stands in the way.
3547
+ const SUBPATH_IMPORT_ROOTS = ALLOWED_BARE_IMPORTS.filter(
3548
+ (spec) => !HOST_EXTERNAL_SPECIFIERS.includes(spec),
3549
+ );
3550
+
3505
3551
  // REQ-WSDK-PLATFORM §3.5: host-API URL patterns the linter scans for as a
3506
3552
  // soft warning. None of these block the lint by themselves — they prompt
3507
3553
  // the marketplace reviewer to spot-check, and they help authors learn the
@@ -4864,7 +4910,20 @@ const CONTRACT = deepFreeze({
4864
4910
  // New `useUserPhoto()` changes it through the new OPTIONAL context
4865
4911
  // slice `account` ({ setPhoto, removePhoto }); a host without it reads
4866
4912
  // `canEdit: false`.
4867
- version: "1.148.0",
4913
+ // 1.149.0: narrowing (sc-10466) — new linter rule `leaflet-css-missing`
4914
+ // (error): a file importing leaflet / react-leaflet must import
4915
+ // "leaflet/dist/leaflet.css". `leaflet-default-marker-icon` (warning)
4916
+ // flags a react-leaflet <Marker> with no `icon`.
4917
+ // 1.150.0: narrowing (sc-10502) — the linter scans imports across the whole
4918
+ // file, so a wrapped `import { … } from` (or an `export … from`) of an
4919
+ // unvetted package is `import-not-vetted` on its `from` line. A file
4920
+ // that ships to web may import a SUBPATH only of a bundled vetted
4921
+ // package (`subpathImportRoots`, e.g. "leaflet/dist/leaflet.css"); a
4922
+ // host-provided package's subpath ("date-fns/format") is flagged. A
4923
+ // native-only file keeps every vetted subpath (Metro resolves them).
4924
+ // New `hostExternalSpecifiers` / `subpathImportRoots` lists and the
4925
+ // linter export `subpathImportRootsFor(manifest)`.
4926
+ version: "1.150.0",
4868
4927
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4869
4928
  hooks: HOOKS,
4870
4929
  primitives: PRIMITIVES,
@@ -4891,6 +4950,8 @@ const CONTRACT = deepFreeze({
4891
4950
  bannedApis: BANNED_APIS,
4892
4951
  vettedImports: VETTED_IMPORTS,
4893
4952
  allowedBareImports: ALLOWED_BARE_IMPORTS,
4953
+ hostExternalSpecifiers: HOST_EXTERNAL_SPECIFIERS,
4954
+ subpathImportRoots: SUBPATH_IMPORT_ROOTS,
4894
4955
  hostApiUrlPatterns: HOST_API_URL_PATTERNS,
4895
4956
  payloadValueTypes: PAYLOAD_VALUE_TYPES,
4896
4957
  chargeCurrency: CHARGE_CURRENCY,
package/dist/contract.js CHANGED
@@ -3292,13 +3292,16 @@ const VETTED_IMPORTS = [
3292
3292
  platforms: ["web"],
3293
3293
  category: "geo",
3294
3294
  description:
3295
- "Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps.",
3295
+ "Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps. " +
3296
+ 'Every file that imports it must also `import "leaflet/dist/leaflet.css"` (the linter enforces it), and a marker needs an ' +
3297
+ "explicit `L.icon` built from the imported `leaflet/dist/images/*.png`: Leaflet's default icon cannot resolve its image inside a bundle.",
3296
3298
  },
3297
3299
  {
3298
3300
  specifier: "react-leaflet",
3299
3301
  platforms: ["web"],
3300
3302
  category: "geo",
3301
- description: "React bindings for leaflet. Web-only.",
3303
+ description:
3304
+ 'React bindings for leaflet. Web-only. Import "leaflet/dist/leaflet.css" alongside it and pass `icon` to every <Marker>.',
3302
3305
  },
3303
3306
  {
3304
3307
  specifier: "expo-audio",
@@ -3498,6 +3501,49 @@ const ALLOWED_BARE_IMPORTS = [
3498
3501
  ...CORE_INFRA_BARE_IMPORTS,
3499
3502
  ];
3500
3503
 
3504
+ // sc-10502: the bare specifiers the web host RESOLVES for a loaded widget
3505
+ // instead of letting its bundle inline a copy. The web-entry bundler marks
3506
+ // exactly these `external` and the Studio loader shims each by EXACT name, so
3507
+ // a subpath of one (`date-fns/format`) cannot load in the web Player.
3508
+ // `dev-shims.hostExternalSpecifiers()` returns this list; the loader's
3509
+ // module-load guard (frontend widgetLoader.js) throws if its shims drift.
3510
+ //
3511
+ // Why each entry is host-resolved, not bundled:
3512
+ // * react + its JSX runtimes: one React instance (hooks, context).
3513
+ // * react-dom, react-dom/client: react-leaflet portals through react-dom;
3514
+ // a second renderer beside the host's React crashes (sc-1064).
3515
+ // * @colixsystems/widget-sdk: the host-built SDK carries datastore, theme
3516
+ // and navigation.
3517
+ // * react-native, lucide-react-native, react-native-svg,
3518
+ // react-native-qrcode-svg: share the host's react-native-web instance
3519
+ // (sc-1265, sc-6105).
3520
+ // * date-fns and the maths set: an agent widget is transpiled, not
3521
+ // bundled, so the host must resolve its bare imports (sc-7191).
3522
+ const HOST_EXTERNAL_SPECIFIERS = [
3523
+ "react",
3524
+ "react/jsx-runtime",
3525
+ "react/jsx-dev-runtime",
3526
+ "react-dom",
3527
+ "react-dom/client",
3528
+ "@colixsystems/widget-sdk",
3529
+ "lucide-react-native",
3530
+ "react-native-svg",
3531
+ "date-fns",
3532
+ "react-native-qrcode-svg",
3533
+ "react-native",
3534
+ "decimal.js",
3535
+ "d3-scale",
3536
+ "d3-shape",
3537
+ "d3-array",
3538
+ "simple-statistics",
3539
+ ];
3540
+
3541
+ // Vetted roots whose subpaths load on web (`leaflet/dist/leaflet.css`): the
3542
+ // bundle inlines them, so no exact-name host shim stands in the way.
3543
+ const SUBPATH_IMPORT_ROOTS = ALLOWED_BARE_IMPORTS.filter(
3544
+ (spec) => !HOST_EXTERNAL_SPECIFIERS.includes(spec),
3545
+ );
3546
+
3501
3547
  // REQ-WSDK-PLATFORM §3.5: host-API URL patterns the linter scans for as a
3502
3548
  // soft warning. None of these block the lint by themselves — they prompt
3503
3549
  // the marketplace reviewer to spot-check, and they help authors learn the
@@ -4860,7 +4906,20 @@ const CONTRACT = deepFreeze({
4860
4906
  // New `useUserPhoto()` changes it through the new OPTIONAL context
4861
4907
  // slice `account` ({ setPhoto, removePhoto }); a host without it reads
4862
4908
  // `canEdit: false`.
4863
- version: "1.148.0",
4909
+ // 1.149.0: narrowing (sc-10466) — new linter rule `leaflet-css-missing`
4910
+ // (error): a file importing leaflet / react-leaflet must import
4911
+ // "leaflet/dist/leaflet.css". `leaflet-default-marker-icon` (warning)
4912
+ // flags a react-leaflet <Marker> with no `icon`.
4913
+ // 1.150.0: narrowing (sc-10502) — the linter scans imports across the whole
4914
+ // file, so a wrapped `import { … } from` (or an `export … from`) of an
4915
+ // unvetted package is `import-not-vetted` on its `from` line. A file
4916
+ // that ships to web may import a SUBPATH only of a bundled vetted
4917
+ // package (`subpathImportRoots`, e.g. "leaflet/dist/leaflet.css"); a
4918
+ // host-provided package's subpath ("date-fns/format") is flagged. A
4919
+ // native-only file keeps every vetted subpath (Metro resolves them).
4920
+ // New `hostExternalSpecifiers` / `subpathImportRoots` lists and the
4921
+ // linter export `subpathImportRootsFor(manifest)`.
4922
+ version: "1.150.0",
4864
4923
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4865
4924
  hooks: HOOKS,
4866
4925
  primitives: PRIMITIVES,
@@ -4887,6 +4946,8 @@ const CONTRACT = deepFreeze({
4887
4946
  bannedApis: BANNED_APIS,
4888
4947
  vettedImports: VETTED_IMPORTS,
4889
4948
  allowedBareImports: ALLOWED_BARE_IMPORTS,
4949
+ hostExternalSpecifiers: HOST_EXTERNAL_SPECIFIERS,
4950
+ subpathImportRoots: SUBPATH_IMPORT_ROOTS,
4890
4951
  hostApiUrlPatterns: HOST_API_URL_PATTERNS,
4891
4952
  payloadValueTypes: PAYLOAD_VALUE_TYPES,
4892
4953
  chargeCurrency: CHARGE_CURRENCY,
package/dist/dev-shims.js CHANGED
@@ -19,6 +19,8 @@
19
19
  // runtime-enumerates the host's namespace). Imports of these packages from a
20
20
  // sibling .jsx file return `422` from the dev server with a clear message.
21
21
 
22
+ import { CONTRACT } from "./contract.js";
23
+
22
24
  // React named exports the Studio re-exposes through the shim. Mirrors the
23
25
  // browser's `react` named-export surface. Adding a new hook in a React minor
24
26
  // release means appending it here.
@@ -61,81 +63,16 @@ export const REACT_NAMED_EXPORTS = [
61
63
  // React's automatic JSX runtime: jsx / jsxs / Fragment, plus the dev variant.
62
64
  export const REACT_JSX_RUNTIME_EXPORTS = ["jsx", "jsxs", "Fragment", "jsxDEV"];
63
65
 
64
- // REQ-WSDK-PLATFORM bundle (sc-1064): the canonical list of bare specifiers the
65
- // runtime web host RESOLVES for a loaded widget — i.e. the ones the Studio's
66
- // `widgetLoader.buildShims()` re-exports from `globalThis.__appstudioWidgetHost__`
67
- // rather than letting a bundle inline a second copy. The web-entry bundler
68
- // (`webbundle.bundleWebEntry`, used by both the publish packer and the dev
69
- // server) marks EXACTLY these `external`, so everything else the entry imports
70
- // (`react-leaflet`, `leaflet`, CSS, PNG assets, …) gets inlined.
71
- //
72
- // This is the SINGLE source of truth (CLAUDE.md §3). It MUST stay in lockstep
73
- // with the frontend's `widgetLoader._hostVettedModules()` + the core React/SDK
74
- // shims it builds. Two backstops enforce the equality: (1) this list's
75
- // contents are pinned by the SDK-side importable test
76
- // (`packages/widget-sdk/src/__tests__/host-externals.test.js`), and (2) the
77
- // loader's own module-load guard in `frontend/src/services/widgetLoader.js`
78
- // throws at import time if `hostShimmedSpecifiers()` drifts from
79
- // `hostExternalSpecifiers()` — the loader's shimmed set can't be asserted from
80
- // a Node test (it imports `react-dom` etc.), so that equality is enforced by
81
- // the load-time guard rather than a separate frontend test.
82
- //
83
- // Why each entry is host-resolved (NOT bundled):
84
- // * `react`, `react/jsx-runtime`, `react/jsx-dev-runtime` — the widget MUST
85
- // share the host's ONE React instance (hooks + context); a bundled second
86
- // React breaks both.
87
- // * `react-dom`, `react-dom/client` — `react-leaflet`'s Popup/Pane render
88
- // through `createPortal` from `react-dom`. A bundled second react-dom
89
- // paired with the host's external React is a mismatched renderer and
90
- // crashes. The Studio is a React-DOM app, so it shims its own react-dom.
91
- // On React 19 the two specifiers expose DIFFERENT surfaces — createPortal /
92
- // flushSync / version live on `react-dom`, createRoot / hydrateRoot on
93
- // `react-dom/client` — so the loader stashes + shims each from its own real
94
- // host namespace (sc-1064).
95
- // * `@colixsystems/widget-sdk` — the widget must route hooks through the SAME
96
- // SDK instance the host built (datastore/theme/navigation live there).
97
- // * `lucide-react-native`, `react-native-svg`, `date-fns`,
98
- // `react-native-qrcode-svg` — vetted packages the host already bundles +
99
- // shims (`_hostVettedModules`); sharing the host copy keeps icons/SVG on
100
- // the host's react-native-web instance. qrcode-svg draws through
101
- // react-native-svg, so externalising it keeps the QR on that same
102
- // instance instead of a bundle inlining a second one (sc-6105).
103
- const HOST_EXTERNAL_SPECIFIERS = [
104
- "react",
105
- "react/jsx-runtime",
106
- "react/jsx-dev-runtime",
107
- "react-dom",
108
- "react-dom/client",
109
- "@colixsystems/widget-sdk",
110
- "lucide-react-native",
111
- "react-native-svg",
112
- "date-fns",
113
- "react-native-qrcode-svg",
114
- // sc-1265: a bundled widget (and any vetted RN package it inlines, e.g.
115
- // react-native-gesture-handler) imports `react-native`; the host resolves it
116
- // to its OWN single react-native-web instance through a blob shim, so the
117
- // bundle shares one RN-web (StyleSheet/context) instead of inlining a second
118
- // copy. On native, Metro resolves the real react-native in the export.
119
- "react-native",
120
- // sc-7191: the vetted pure-JS maths set. Externalised for the same reason as
121
- // date-fns — an AI-agent widget is transpiled, never bundled, so the host
122
- // must resolve the bare import at runtime.
123
- "decimal.js",
124
- "d3-scale",
125
- "d3-shape",
126
- "d3-array",
127
- "simple-statistics",
128
- ];
129
-
130
66
  /**
131
67
  * The canonical "host-resolved at runtime" bare-specifier set — the externals
132
- * the web-entry bundler must NOT inline. Returns a fresh array each call so a
133
- * caller can't mutate the source of truth.
68
+ * the web-entry bundler must NOT inline. Sourced from
69
+ * `CONTRACT.hostExternalSpecifiers`, which documents each entry. Returns a
70
+ * fresh array each call so a caller can't mutate the source of truth.
134
71
  *
135
72
  * @returns {string[]}
136
73
  */
137
74
  export function hostExternalSpecifiers() {
138
- return [...HOST_EXTERNAL_SPECIFIERS];
75
+ return [...CONTRACT.hostExternalSpecifiers];
139
76
  }
140
77
 
141
78
  function reactShimSrc() {
package/dist/index.d.ts CHANGED
@@ -3380,6 +3380,10 @@ export interface AiWidgetContract {
3380
3380
  readonly bundleExportContract: ReadonlyArray<ContractBundleShape>;
3381
3381
  readonly bannedApis: ReadonlyArray<ContractBannedApi>;
3382
3382
  readonly allowedBareImports: ReadonlyArray<string>;
3383
+ /** Bare specifiers the web host provides by exact name; a widget bundle never inlines them. */
3384
+ readonly hostExternalSpecifiers: ReadonlyArray<string>;
3385
+ /** Bundled vetted package roots: the only roots whose subpaths load in the web Player. */
3386
+ readonly subpathImportRoots: ReadonlyArray<string>;
3383
3387
  /** sc-4686 — the render-time fallback currency when the host supplies none. */
3384
3388
  readonly chargeCurrency: string;
3385
3389
  /** sc-4686 — how each currency is written; read by `formatMoney`, never Intl. */