@fluid-app/fluid-cli-widget 0.1.34 → 0.1.35

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.
@@ -1,340 +1,134 @@
1
1
  # {{projectName}}
2
2
 
3
- Standalone Fluid widget project generated by `@fluid-app/fluid-cli-widget`.
3
+ A standalone Fluid Remote DOM widget package associated with an existing
4
+ Droplet. This project publishes widget code and metadata. It does not create a
5
+ Droplet, portal application, iframe preview, or backend service.
4
6
 
5
- This project builds and publishes a widget package only. It does **not** scaffold droplets, a full portal application, or backend services.
7
+ ## Start development
6
8
 
7
- ## Scripts
9
+ Requirements: Node.js, pnpm, and a Fluid CLI login for commands that read or
10
+ write Droplet state.
8
11
 
9
12
  ```bash
10
- pnpm dev # Run local widget protocol endpoints
11
- pnpm typecheck # Run TypeScript checking
12
- pnpm validate # Validate package metadata with the Fluid widget CLI
13
- pnpm build # Validate and build runtime artifacts
14
- pnpm run widget:link # Link this project to an existing Fluid droplet
15
- pnpm run widget:publish # Publish to the linked droplet
16
- pnpm run widget:publish --dry-run # Build upload payload without uploading
13
+ pnpm install
14
+ pnpm run widget:link
15
+ pnpm dev
17
16
  ```
18
17
 
19
- ## Project structure
18
+ `pnpm dev` starts Vite, normally on http://localhost:5174. It serves the live
19
+ package descriptor at `/__widget-packages__` and the worker bootstrap at
20
+ `/__runtime-entry__`. A portal or builder host loads those endpoints. The
21
+ command does not create a separate preview application or publication build.
20
22
 
21
- ```text
22
- .
23
- ├── AGENTS.md # Portable AI authoring guidance
24
- ├── CLAUDE.md # Claude-compatible AI authoring guidance
25
- ├── .agents/skills/ # Agent skill docs for this project
26
- ├── .claude/skills/ # Claude skill docs for this project
27
- ├── fluid.widget.config.ts # Widget CLI ownership + package exports
28
- ├── manifest.ts # defineWidget/defineWidgetPackage metadata
29
- ├── styles.css # Shadow-root runtime styles
30
- ├── src/index.ts # Remote DOM worker entry
31
- └── src/widgets/review-carousel/ReviewCarousel.tsx
32
- ```
33
-
34
- ## Local development endpoints
35
-
36
- `pnpm dev` exposes:
37
-
38
- - `/__widget-packages__`: the local canonical package descriptor consumed by Fluid hosts.
39
- - `/__runtime-entry__`: the Remote DOM worker entry referenced by that descriptor.
40
-
41
- The standalone project does not include a second host or iframe preview app.
42
- Portal and builder hosts render the worker through the shared Remote DOM runtime.
43
-
44
- The standalone source of truth is `manifest.ts`, with droplet ownership in
45
- `fluid.widget.config.ts`. Company portal projects instead use
46
- `src/widgets.config.ts` and `fluid portal deploy`.
47
-
48
- ## Package boundary
49
-
50
- Stay inside the standalone widget package:
51
-
52
- - Put reusable widget components under `src/widgets/`.
53
- - Put package and palette metadata in `manifest.ts`.
54
- - Keep runtime registration in `src/index.ts`.
55
- - Keep runtime styles in `styles.css` or CSS files imported by widget modules.
56
- - Do not add Next.js app folders, Rails code, API servers, portal app shells, or droplet scaffolding.
57
-
58
- Droplet ownership is managed by the Fluid widget CLI through `fluid.widget.config.ts`. Link the project with `pnpm run widget:link` or pass a droplet option to CLI commands when needed.
59
-
60
- ## Authoring widgets
61
-
62
- Widgets are authored with `defineWidget()` and grouped with `defineWidgetPackage()` from `@fluid-app/portal-sdk/widgets/worker`.
63
-
64
- `defineWidget()` describes one widget:
65
-
66
- - `name`: stable URL-safe widget name. Changing it changes the generated widget type.
67
- - `component`: React component rendered inside the Remote DOM worker.
68
- - `displayName`, `description`, `icon`, `category`: builder palette metadata.
69
- - `defaultProps`: JSON-serializable defaults for new widget instances.
70
- - `propertySchema`: builder property panel fields.
71
- - `container`: `block`, `card`, `inline`, or `fullscreen`.
72
- - `uses`: every typed portal function called by this widget. Pass the function values instead of writing capability names.
73
- - `resizable`: false/omitted, true, `horizontal`, `vertical`, `both`, or an object with horizontal/vertical flags and optional minimum sizes.
74
-
75
- `defineWidgetPackage()` describes the package:
76
-
77
- - Keep `packageType` set to `droplet` for this template.
78
- - Keep `widgets` as the array of widgets exported by this package.
79
- - Use a SemVer `version` without build metadata.
80
- - Do not manually set `packageStableId` in this generated droplet template; the CLI injects the linked droplet key during validation, build, and publish.
81
- - Do not author runtime artifact URLs. Dev and build inject the served `workerEntryUrl`.
82
-
83
- ## Manifest structure
84
-
85
- `manifest.ts` should remain the single source of package metadata:
86
-
87
- ```ts
88
- import {
89
- defineWidget,
90
- defineWidgetPackage,
91
- } from "@fluid-app/portal-sdk/widgets/worker";
92
- import { ExampleWidget } from "./src/widgets/example/ExampleWidget";
93
-
94
- export const exampleWidget = defineWidget({
95
- name: "ExampleWidget",
96
- component: ExampleWidget,
97
- displayName: "Example Widget",
98
- description: "A focused reusable Fluid widget.",
99
- icon: "box",
100
- category: "components",
101
- container: "card",
102
- defaultProps: {
103
- title: "Featured content",
104
- },
105
- propertySchema: {
106
- tabsConfig: [{ id: "content", label: "Content" }],
107
- dataSourceTargetProps: ["title"],
108
- fields: [
109
- {
110
- key: "title",
111
- label: "Title",
112
- type: "text",
113
- defaultValue: "Featured content",
114
- tab: "content",
115
- group: "Copy",
116
- },
117
- ],
118
- },
119
- });
120
-
121
- export const widgetPackage = defineWidgetPackage({
122
- scope: "droplet",
123
- version: "0.1.0",
124
- packageType: "droplet",
125
- widgets: [exampleWidget],
126
- });
127
-
128
- export const widgetPackages = [widgetPackage] as const;
129
- export default widgetPackage;
130
- ```
131
-
132
- ## Typed portal functions
133
-
134
- Worker code imports typed portal functions from
135
- `@fluid-app/portal-sdk/widgets/worker`, calls them directly, and lists the same
136
- function values in the widget's `uses` array:
137
-
138
- ```ts
139
- import {
140
- defineWidget,
141
- getUserAccount,
142
- navigateTo,
143
- } from "@fluid-app/portal-sdk/widgets/worker";
144
-
145
- async function openProfile(): Promise<void> {
146
- const account = await getUserAccount();
147
- await navigateTo(account.slug);
148
- }
149
-
150
- export const profileButtonWidget = defineWidget({
151
- name: "ProfileButton",
152
- component: ProfileButton,
153
- uses: [getUserAccount, navigateTo],
154
- });
155
- ```
156
-
157
- Available built-ins are `getUserAccount`, `getStore`, `getPortalApp`,
158
- `getPortalProfile`, `getNavigationState`, `buildPortalHref`, `navigateTo`,
159
- `getFullscreenState`, `requestFullscreen`, and `exitFullscreen`. Getters await
160
- and return resolved data instead of exposing query-status snapshots. A
161
- navigation target may be a slug string, `{ slug }`, or `{ href }`.
162
-
163
- `defineWidget()` derives the existing descriptor declarations from `uses`.
164
- Both the worker and host enforce those declarations. If a call fails with
165
- `PortalFunctionError` code `NOT_DECLARED`, add that function to the widget's
166
- `uses` array. Do not author a raw `capabilities` array.
167
-
168
- `getUserAccount()` returns an allowlisted account view. It may include email,
169
- but excludes phone numbers, addresses, payment details, credentials,
170
- government or tax identifiers, dates of birth, raw metadata, and unknown
171
- future fields. The host's `tenantClient` is not exposed to widgets.
172
-
173
- For company-specific operations, define a shared typed function with
174
- `definePortalFunction<Output, Input>({ capability, version, method })`, call it
175
- directly, and include it in `uses`. The portal owner installs a typed host
176
- handler with `implementPortalFunction(function, handler)` in
177
- `remoteWidgets.functions`. Built-ins cannot be overridden; duplicate custom
178
- implementations are rejected.
23
+ `widget:link` selects an existing Droplet and writes its ID to
24
+ `fluid.widget.config.ts`. If the ID is already present, validation and local
25
+ builds do not require a login.
179
26
 
180
- Custom inputs and outputs must be JSON values: null, booleans, finite numbers,
181
- strings, arrays, and objects composed from those values. Do not use
182
- `undefined`, functions, Dates, Maps, Sets, class instances, `NaN`, or
183
- `Infinity`. The runtime validates the protocol boundary as well as the
184
- TypeScript contract.
27
+ ## Project map
185
28
 
186
- ## Property schema reference
187
-
188
- Property schemas define editable builder controls. They must be JSON-serializable.
189
-
190
- Top-level schema keys:
191
-
192
- - `tabsConfig`: optional array of tab objects with `id` and `label`.
193
- - `fields`: array of controls shown in the builder property panel.
194
- - `dataSourceTargetProps`: prop keys that can be populated from configured data sources.
195
- - `itemConfigSchema`: optional per-item configuration schema for custom data-source selections.
196
- - `validate`: optional custom validator in in-app manifests; avoid it for published standalone metadata because package metadata must be serializable.
197
-
198
- Base field keys:
199
-
200
- | Key | Purpose |
29
+ | Path | Owner and lifecycle |
201
30
  | --- | --- |
202
- | `key` | Widget prop key written by the field. Use a unique non-prop key for visual-only controls. |
203
- | `label` | Builder label. |
204
- | `type` | Supported field type listed below. |
205
- | `description` | Optional helper text. |
206
- | `defaultValue` | Optional JSON-serializable default. |
207
- | `tab` | Optional tab id matching `tabsConfig`. |
208
- | `group` | Optional group label inside a tab. |
209
- | `advanced` | Marks theme overrides or low-frequency settings; advanced fields render in Custom styling. |
210
- | `requiresKeyValue` | Optional conditional display rule. An array means all conditions must match. |
211
-
212
- Supported field types:
213
-
214
- | Type | Use | Extra keys |
215
- | --- | --- | --- |
216
- | `text` | Single-line string | `placeholder`, `maxLength`, `tokenSuggestions` |
217
- | `textarea` | Multi-line string | `placeholder`, `rows`, `maxLength` |
218
- | `number` | Number input | `min`, `max`, `step` |
219
- | `boolean` | Toggle | None |
220
- | `select` | Dropdown | `options` array with `label` and `value` |
221
- | `color` | Basic color value | None |
222
- | `range` | Slider | Required `min`, `max`; optional `step` |
223
- | `dataSource` | Data-source selector/configuration | None |
224
- | `resource` | Single shareable/resource picker | `allowedTypes` |
225
- | `image` | Media picker | `accept` as `image`, `video`, or `any` |
226
- | `alignment` | Alignment picker | `options.verticalEnabled`, `options.horizontalEnabled` |
227
- | `slider` | Numeric slider | `min`, `max`, `step`, `unit` |
228
- | `colorPicker` | Color picker | `swatches` |
229
- | `sectionHeader` | Visual section heading | `subtitle` |
230
- | `separator` | Visual divider | None |
231
- | `buttonGroup` | Segmented choice | `options` with `value`, optional `label`, `ariaLabel`, `icon` |
232
- | `colorSelect` | Semantic theme color picker | `excludeColors` |
233
- | `sectionLayoutSelect` | Visual section layout picker | None |
234
- | `background` | Combined background resource/color control | Resource/color field keys as needed |
235
- | `contentPosition` | 3-by-3 content position picker | None |
236
- | `textSizeSelect` | Theme text-size picker | None |
237
- | `cssUnit` | Number with unit selector | `allowedUnits`, `defaultUnit`, `minByUnit`, `maxByUnit`, `stepByUnit` |
238
- | `fontPicker` | Google font picker | `placeholder` |
239
- | `stringArray` | Editable list of strings | `placeholder` |
240
- | `borderRadius` | Composite four-corner radius editor | `keys.topLeft`, `keys.topRight`, `keys.bottomLeft`, `keys.bottomRight` |
241
- | `screenPicker` | Portal screen picker | `includeSystemItems` |
242
-
243
- ### Data-source-ready props
244
-
245
- For props that may later be connected to data sources:
246
-
247
- - Add the prop key to `dataSourceTargetProps`.
248
- - Keep the component tolerant of missing, empty, or partially populated values.
249
- - Render useful empty states for empty arrays and failed mapping.
250
- - Keep prop values JSON-serializable.
251
- - Avoid fetching inside the component when a prop or data source can provide the data.
252
- - Use `itemConfigSchema` when each selected item needs widget-specific settings.
253
-
254
- ## Component quality bar
255
-
256
- - Use TypeScript interfaces for public props.
257
- - Provide defaults for optional props.
258
- - Treat incoming props as untrusted and validate before reading nested values.
259
- - Keep render logic deterministic and side-effect free.
260
- - Clean up effects and subscriptions.
261
- - Keep bundles small; avoid heavy libraries unless the widget requires them.
262
- - Avoid secrets, tenant credentials, local URLs, and environment-only assumptions.
263
- - Prefer semantic HTML over div-only structures.
264
-
265
- ## Accessibility
266
-
267
- - Use headings, sections, lists, buttons, labels, and form elements semantically.
268
- - Make every interactive element keyboard reachable.
269
- - Provide visible focus states.
270
- - Add `aria-label` to icon-only controls.
271
- - Add alt text to meaningful images and empty alt text to decorative images.
272
- - Preserve logical heading order inside the widget.
273
- - Use ARIA live regions for important dynamic updates.
274
- - Respect reduced-motion preferences for animation-heavy widgets.
275
- - Use semantic foreground/background token pairs for contrast.
276
-
277
- ## Runtime CSS and theme tokens
278
-
279
- Fluid hosts provide semantic CSS variables. Prefer token-based CSS instead of hard-coded palettes.
280
-
281
- Common CSS variables:
282
-
283
- - Surfaces: `--background`, `--card`, `--popover`.
284
- - Text: `--foreground`, `--card-foreground`, `--popover-foreground`.
285
- - Brand/actions: `--primary`, `--primary-foreground`.
286
- - Supporting UI: `--secondary`, `--secondary-foreground`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground`.
287
- - Status/chrome: `--destructive`, `--destructive-foreground`, `--border`, `--input`, `--ring`.
288
- - Charts: `--chart-1`, `--chart-2`, `--chart-3`, `--chart-4`, `--chart-5`.
289
- - Radius: `--radius`, `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`.
290
- - Theme-engine aliases may include `--font-header`, `--font-body`, `--font-size-extra-small`, `--font-size-small`, `--font-size-regular`, `--font-size-large`, `--font-size-extra-large`, and `--font-size-giant`.
291
-
292
- Tailwind equivalents, when Tailwind is available, are semantic utilities such as `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-primary`, `text-primary-foreground`, `border-border`, `ring-ring`, `rounded-lg`, `text-sm`, and `text-xl`. This standalone template uses plain CSS for runtime portability.
293
-
294
- Light/dark behavior:
295
-
296
- - Fluid themes update variables for light and dark mode.
297
- - Explicit dark mode is represented by `data-theme-mode="dark"` on a host element.
298
- - Some hosts also use system dark mode when no explicit mode is selected.
299
- - Prefer semantic variables so both modes work automatically.
300
- - If mode-specific styling is unavoidable, scope it with `[data-theme-mode="dark"]` and keep values token-based.
301
-
302
- Runtime CSS rules:
303
-
304
- - Import runtime CSS from the guarded build import in `src/index.ts` or another worker module included in the build.
305
- - Prefix selectors with the widget name to prevent host leakage.
306
- - Do not rely on `body` styles for published runtime rendering.
307
- - If published styles are missing, check that the CSS import is reachable from the built entry.
308
-
309
- ## Validation, build, and publish preflight
310
-
311
- Before shipping changes, run:
31
+ | `manifest.ts` | Author-owned package scope, key, version, and widget registrations. Edit for every release or registration change. |
32
+ | `fluid.widget.config.ts` | Author-owned Droplet link and package export. Change the Droplet only when intentionally relinking the project. |
33
+ | `src/index.ts` | Worker entry that starts the package and imports package CSS. |
34
+ | `src/widgets/` | Author-owned widget components. The starter widget is an example and can be replaced. |
35
+ | `styles.css` | Author-owned, package-scoped runtime CSS. Keep its guarded import in `src/index.ts`. |
36
+ | `src/vite-env.d.ts` | Vite environment type declarations. |
37
+ | `vite.config.ts`, `tsconfig.json` | Build configuration. Change only for a concrete toolchain requirement. |
38
+ | `package.json` | Project scripts and dependencies. Author-owned after scaffold creation. |
39
+ | lockfile | Dependency resolution. Commit when dependency changes are intentional. |
40
+ | `.gitignore` | Source-control exclusions for local and generated files. |
41
+ | `.oxlintrc.json` | Generated lint configuration. Change only when project lint policy changes. |
42
+ | `README.md` | Human setup, lifecycle, and recovery guidance. |
43
+ | `AGENTS.md`, `CLAUDE.md` | Root coding-agent guidance. `CLAUDE.md` points to the canonical `AGENTS.md`. |
44
+ | `.agents/skills/fluid-widget-authoring/` | Widget task procedures and reference guidance. |
45
+ | `.claude/skills/` | Compatibility view of the same skill. Do not maintain different instructions there. |
46
+ | `.fluid/widget-build/` | Temporary compilation workspace. Generated and safe to recreate. |
47
+ | `.fluid/tmp/` | Temporary CLI state. Generated and safe to recreate. |
48
+ | `.fluid/widget-dist/` | Publication output: `widget.js`, `manifest.json`, `publish-manifest.json`, CSS, maps, and referenced assets. Generated; never edit or commit it. |
49
+ | `node_modules/` | Installed dependencies. Generated. |
50
+
51
+ ## Package ownership
52
+
53
+ `manifest.ts` is the package contract. Use the installed generated API for its
54
+ complete fields, accepted values, and widget-definition shape; this README does
55
+ not repeat them. Keep package identity, widget registration, defaults,
56
+ property-schema metadata, and declared runtime uses together there.
57
+
58
+ `fluid.widget.config.ts` links that package to a Droplet. It does not define
59
+ package identity, version, or widgets.
60
+
61
+ All metadata and props must satisfy the installed JSON-value types. Do not
62
+ maintain a separate value whitelist in project documentation.
63
+
64
+ ## Theme compliance
65
+
66
+ Widgets must work with the active portal theme and its supported color modes.
67
+ Use the host semantic CSS variables for colors, typography, spacing, radii,
68
+ borders, focus rings, and charts. Use a semantic property only when authors
69
+ need to choose among theme roles.
70
+
71
+ Use `type: "colorSelect"` for a color property. It stores a semantic token.
72
+ The legacy `type: "color"` field is deprecated because it stores an arbitrary
73
+ color that can conflict with the theme. Avoid `colorPicker`, literal colors,
74
+ custom font controls, and custom radius or spacing controls unless the product
75
+ requirement cannot be expressed by the theme engine. Document any necessary
76
+ exception in the field description and provide a theme-derived default.
77
+
78
+ ## Validate and build
312
79
 
313
80
  ```bash
314
81
  pnpm typecheck
315
82
  pnpm validate
316
83
  pnpm build
317
- pnpm run widget:publish --dry-run
318
84
  ```
319
85
 
320
- Publish only after the dry run succeeds:
86
+ Validation checks package metadata and source configuration. Build validates,
87
+ typechecks, and writes `.fluid/widget-dist/`. Inspect `manifest.json` and the
88
+ widget in a portal host; a successful build cannot prove visual, accessibility,
89
+ or theme compatibility.
90
+
91
+ If a build fails, fix the first reported source or metadata error. Remove only
92
+ generated `.fluid/widget-build/`, `.fluid/tmp/`, or `.fluid/widget-dist/` when
93
+ a clean retry is needed. Do not delete author source or configuration.
94
+
95
+ ## Publish and inspect
321
96
 
322
97
  ```bash
98
+ pnpm run widget:publish --dry-run
323
99
  pnpm run widget:publish
100
+ pnpm exec fluid widget status
101
+ pnpm exec fluid widget logs
324
102
  ```
325
103
 
326
- Build output is written under `.fluid/widget-dist/`. The runtime entry starts the package in a worker with `startWidgetPackage()`, while hosts discover its canonical descriptor through the widget catalog.
327
-
328
- ## Common failures
329
-
330
- | Failure | Fix |
331
- | --- | --- |
332
- | Missing droplet UUID | Run `pnpm run widget:link` or pass a droplet option to validate/build/publish. |
333
- | No source package found | Ensure `fluid.widget.config.ts` exports `widgetPackage` or `widgetPackages` from `manifest.ts`. |
334
- | Invalid package type | Keep `packageType: "droplet"`. |
335
- | Invalid package key or widget name | Use URL-safe letters, numbers, dots for package keys, and underscore/hyphen/tilde where allowed. |
336
- | Invalid version | Use SemVer without build metadata. |
337
- | Non-serializable metadata | Remove functions, undefined values, Dates, NaN, Infinity, Maps, Sets, and class instances from `propertySchema` and `defaultProps`. |
338
- | Relative CSS URL rejected | Import CSS into the bundle or remove manual `cssUrls` entries. |
339
- | Styles are absent after publish | Confirm `styles.css` is reachable from the guarded build import in `src/index.ts`. |
340
- | Published widget fails | Guard browser-only APIs, remove local-only URLs, and avoid environment variables required at runtime. |
104
+ The dry run builds and validates the upload payload without creating a remote
105
+ version. Actual publication requires authentication and creates an immutable
106
+ Droplet widget version. Increase `version` in `manifest.ts` before publishing a
107
+ new release.
108
+
109
+ `fluid widget push` first attempts source synchronization and then publication.
110
+ Repository divergence stops before publication. If source synchronization is
111
+ unavailable, the command can continue with publication only. Publication can
112
+ also fail after source synchronization succeeds. Read the phase result before
113
+ retrying; do not assume either phase rolled back the other.
114
+
115
+ ## Network-enabled widgets
116
+
117
+ Declare `networkAccess` in the widget `uses` array only for public HTTP APIs.
118
+ A portal author must approve each exact package ID, package version, and
119
+ capability version. Updating any value invalidates the previous grant.
120
+
121
+ Granted fetch uses the worker's native origin. Fluid does not inject cookies,
122
+ credentials, tokens, or headers. The installed runtime API owns the exact
123
+ destination and ambient-API contract. DNS results and redirect destinations
124
+ are not revalidated, so package review remains required. Any portal data given
125
+ to the widget can be sent to an external service. Worker isolation does not
126
+ protect the reputation of `*.fluid.app` from widget behavior.
127
+
128
+ ## Detailed guidance
129
+
130
+ - `.agents/skills/fluid-widget-authoring/SKILL.md`
131
+ - Installed API contract:
132
+ `node_modules/@fluid-app/portal-sdk/authoring/widget-api/api.md`
133
+ - Installed command contract:
134
+ `node_modules/@fluid-app/fluid-cli-widget/authoring/commands.md`
@@ -45,6 +45,7 @@ export const reviewCarouselWidget = defineWidget({
45
45
  key: "eyebrow",
46
46
  label: "Eyebrow",
47
47
  type: "text",
48
+ description: "Short text shown above the review title.",
48
49
  defaultValue: "Customer stories",
49
50
  tab: "content",
50
51
  group: "Copy",
@@ -53,10 +54,25 @@ export const reviewCarouselWidget = defineWidget({
53
54
  key: "title",
54
55
  label: "Title",
55
56
  type: "text",
57
+ description: "Heading displayed above the review carousel.",
56
58
  defaultValue: "Reviews that travel with your portal",
57
59
  tab: "content",
58
60
  group: "Copy",
59
61
  },
62
+ {
63
+ key: "dataSource",
64
+ label: "Data Source",
65
+ type: "dataSource",
66
+ description: "Configure the review records supplied to the carousel.",
67
+ targetProps: [
68
+ {
69
+ key: "reviews",
70
+ description: "Review records rendered by the carousel.",
71
+ },
72
+ ],
73
+ tab: "content",
74
+ group: "Data",
75
+ },
60
76
  ],
61
77
  },
62
78
  });
@@ -8,18 +8,13 @@
8
8
  * root, and the same widget then looks correct on every portal and in
9
9
  * both light and dark — without a second, hand-written dark palette.
10
10
  *
11
- * • Every var() carries a fallback. Tokens are absent in some hosts (a bare
12
- * dev harness, a preview surface that has not adopted the portal theme),
13
- * and a widget must still be legible there rather than collapsing to
14
- * unstyled text. The fallback is a floor, never the intended look.
15
- *
16
11
  * • Selectors stay prefixed with the widget name so nothing leaks into the
17
12
  * host, and nothing here depends on global body styles.
18
13
  *
19
- * Do not reintroduce hard-coded brand colors. A widget that hard-codes
20
- * #ffffff / #111827 ignores the portal's theme entirely and will look wrong
21
- * on any portal that is not white-on-near-black. See AGENTS.md → "Theme and
22
- * styling guidance". */
14
+ * Do not add fallback brand colors. A missing host theme contract is an
15
+ * integration error; silently replacing it with widget-owned colors hides the
16
+ * error and breaks theme compliance. See
17
+ * .agents/skills/fluid-widget-authoring/references/runtime-and-styling.md. */
23
18
 
24
19
  .review-carousel {
25
20
  font-family: var(
@@ -32,16 +27,15 @@
32
27
  "Segoe UI",
33
28
  sans-serif
34
29
  );
35
- border: 1px solid var(--border, #e5e7eb);
36
- border-radius: var(--radius-xl, 24px);
30
+ border: 1px solid var(--border);
31
+ border-radius: var(--radius-xl);
37
32
  padding: 32px;
38
- background: var(--card, #ffffff);
39
- box-shadow: 0 24px 80px rgb(15 23 42 / 10%);
33
+ background: var(--card);
40
34
  }
41
35
 
42
36
  .review-carousel__eyebrow {
43
37
  margin: 0 0 8px;
44
- color: var(--primary, #4f46e5);
38
+ color: var(--primary);
45
39
  font-size: 0.75rem;
46
40
  font-weight: 700;
47
41
  letter-spacing: 0.08em;
@@ -50,8 +44,8 @@
50
44
 
51
45
  .review-carousel__title {
52
46
  margin: 0;
53
- color: var(--card-foreground, #111827);
54
- font-family: var(--font-header, inherit);
47
+ color: var(--card-foreground);
48
+ font-family: var(--font-header);
55
49
  font-size: clamp(2rem, 4vw, 3.5rem);
56
50
  line-height: 0.95;
57
51
  letter-spacing: -0.05em;
@@ -59,7 +53,7 @@
59
53
 
60
54
  .review-carousel__quote {
61
55
  margin: 32px 0 0;
62
- color: var(--card-foreground, #1f2937);
56
+ color: var(--card-foreground);
63
57
  font-size: clamp(1.25rem, 2vw, 1.75rem);
64
58
  line-height: 1.35;
65
59
  }
@@ -74,13 +68,13 @@
74
68
 
75
69
  .review-carousel__author {
76
70
  margin: 0;
77
- color: var(--card-foreground, #111827);
71
+ color: var(--card-foreground);
78
72
  font-weight: 700;
79
73
  }
80
74
 
81
75
  .review-carousel__role {
82
76
  margin: 4px 0 0;
83
- color: var(--muted-foreground, #6b7280);
77
+ color: var(--muted-foreground);
84
78
  }
85
79
 
86
80
  .review-carousel__controls {
@@ -91,21 +85,20 @@
91
85
  .review-carousel__button {
92
86
  width: 40px;
93
87
  height: 40px;
94
- border: 1px solid var(--border, #d1d5db);
95
- /* Intentionally a pill, not themed — shape is this control's identity. */
96
- border-radius: 999px;
97
- color: var(--card-foreground, #111827);
98
- background: var(--card, #ffffff);
88
+ border: 1px solid var(--border);
89
+ border-radius: var(--radius-xl);
90
+ color: var(--card-foreground);
91
+ background: var(--card);
99
92
  font: inherit;
100
93
  cursor: pointer;
101
94
  }
102
95
 
103
96
  .review-carousel__button:hover {
104
- border-color: var(--primary, #4f46e5);
105
- color: var(--primary, #4f46e5);
97
+ border-color: var(--primary);
98
+ color: var(--primary);
106
99
  }
107
100
 
108
101
  .review-carousel__button:focus-visible {
109
- outline: 2px solid var(--ring, #4f46e5);
102
+ outline: 2px solid var(--ring);
110
103
  outline-offset: 2px;
111
104
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-JKSTx5pZ.d.mts","names":[],"sources":["../../../portal/core/src/remote-dom-widget-package.ts","../../widget-tooling/src/internal/widget-package-validation.ts","../../widget-tooling/src/dev-routes.ts"],"mappings":";;;cAAa,kCAAA;AAAA,cACA,yBAAA;AAAA,KAED,aAAA;AAAA,KAEA,SAAA,GAAY,aAAA,YAAyB,SAAA,KAAc,UAAA;AAAA,UAE9C,UAAA;EAAA,UACS,GAAA,WAAA,SAAA;AAAA;AAAA,UAGT,oCAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;AAAA;AAAA,UAGM,yBAAA;EAAA,SACN,IAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,cAAA,EAAgB,UAAA;EAAA,SAChB,YAAA,EAAc,UAAA;EAAA,SACd,SAAA;EAAA,SACA,SAAA;EAAA,SACA,aAAA;EAAA,SACA,YAAA,WAAuB,oCAAA;AAAA;;;;;;UAQjB,gCAAA;EAAA,SACN,eAAA,SAAwB,kCAAA;EAAA,SACxB,OAAA,SAAgB,yBAAA;EAAA,SAChB,SAAA;EAAA,SACA,MAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA,WAAkB,yBAAA;AAAA;;;KCpBjB,sBAAA;AAAA,UAKK,wBAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;AAAA;AAAA,UAGM,oBAAA;EAAA,SACN,IAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,cAAA,GAAiB,UAAA;EAAA,SACjB,YAAA,GAAe,UAAA;EAAA,SACf,SAAA;EAAA,SACA,aAAA;EAAA,SACA,SAAA;EAAA,SACA,YAAA,YAAwB,wBAAA;AAAA;AAAA,UAGlB,2BAAA;EAAA,SACN,eAAA;EAAA,SACA,KAAA;EAAA,SACA,eAAA;EAAA,SACA,SAAA;EAAA,SACA,WAAA;EAAA,SACA,OAAA;EAAA,SACA,OAAA,WAAkB,oBAAA;EAAA,SAClB,OAAA;AAAA;AAAA,UAGM,wBAAA,SAAiC,yBAAA;EAAA,SACvC,IAAA;AAAA;AAAA,UA4BM,2BAAA;EAAA,SACN,aAAA,EAAe,2BAAA;EAAA,SACf,UAAA;EAAA,SACA,SAAA;EAAA,SACA,KAAA,EAAO,sBAAA;EAAA,SACP,OAAA;EAAA,SACA,OAAA,WAAkB,wBAAA;AAAA;AAAA,KAGjB,gCAAA;AAAA,UAaK,4BAAA;EAAA,SACN,IAAA,EAAM,gCAAA;EAAA,SACN,OAAA;EAAA,SACA,IAAA;AAAA;;;ADjHX;;;;;AACA;;;;AADA,UEgBiB,2BAAA;EFbjB;EAAA,SEeW,eAAA;;WAEA,gBAAA;EFjBC;EAAA,SEmBD,KAAA,GAAQ,sBAAA;EFjBP;;;;;;;EAAA,SEyBD,eAAA;AAAA;AAAA,iBAGK,oBAAA,CACd,OAAA,GAAS,2BAAA,GACR,MAAA;;;AF5BH;;;;;AAIA;;;;;iBEmFgB,2BAAA,CAAA,GAA+B,UAAA;;;;;;;;;;;;;;;;;;;;;;;;AF3D/C;;;;;;;;;;;;;;;;;;;;;;;;;ACXA;;;;;AAKA;;;;;iBC+JgB,gCAAA,CACd,SAAA,UACA,MAAA;;;;;;iBA4Cc,iCAAA,CACd,YAAA,EAAc,QAAA,CAAS,MAAA,oBACvB,MAAA,UACA,KAAA,GAAO,sBAAA,YACG,gCAAA"}