@fluid-app/fluid-cli-widget 0.1.28 → 0.1.29

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,192 +1,2 @@
1
- import { i as createWidgetPackageDevelopmentDescriptor } from "../src-CVadzcYM.mjs";
2
- //#region src/vite/plugin.ts
3
- function fluidWidgetDevPlugin() {
4
- return {
5
- name: "fluid-widget-dev-routes",
6
- apply: "serve",
7
- config: createWidgetDevServerConfig,
8
- configureServer(server) {
9
- server.middlewares.use(async (request, response, next) => {
10
- const pathname = (request.url ?? "").split("?")[0] ?? "";
11
- if (pathname === "/__runtime-entry__" || pathname === "/__runtime-entry__/") {
12
- sendJavaScript(request, response, buildRuntimeEntryBootstrapScript(WIDGET_ENTRY_MODULE_PATH, getRequestOrigin(request)));
13
- return;
14
- }
15
- if (pathname === "/__widget-packages__" || pathname === "/__widget-packages__/") {
16
- await sendWidgetPackages(server, request, response);
17
- return;
18
- }
19
- next();
20
- });
21
- }
22
- };
23
- }
24
- /**
25
- * Widget projects intentionally have no HTML entrypoint, so Vite cannot
26
- * discover the worker's dependency graph during its normal startup scan.
27
- * Without an explicit entry, the first live-preview request discovers and
28
- * optimizes the portal worker after the server reports ready. Vite reloads
29
- * the worker mid-mount, closing its message thread and stranding the preview
30
- * in a permanent error state.
31
- *
32
- * Pre-optimizing the worker entry makes the initial graph stable before a
33
- * preview can connect. Vite follows this entry's imports, so React and the
34
- * Remote DOM worker runtime are optimized in the same startup pass.
35
- */
36
- function createWidgetDevServerConfig() {
37
- return { optimizeDeps: { include: ["@fluid-app/portal-sdk/widgets/worker"] } };
38
- }
39
- /** Source-relative path of a widget project's entry module. The dev
40
- * scaffold always places it here (see the widget template) — this is
41
- * the one thing the bootstrap script below needs from the plugin
42
- * that isn't itself part of the race/preamble fix. */
43
- const WIDGET_ENTRY_MODULE_PATH = "/src/index.ts";
44
- /**
45
- * Builds the `/__runtime-entry__` worker bootstrap. Runtime-verified
46
- * against a real widget dev server + `@fluid-app/widget-runtime` host
47
- * in a Playwright harness (styled Leaderboard widget rendering,
48
- * mutation records flowing, `mounted.ready` resolving) — every piece
49
- * below exists to fix something that reproduced in that harness:
50
- *
51
- * 1. **Message-buffer race (the silent-mount killer).** The host
52
- * (widget-runtime) posts the "mount" message immediately after
53
- * `new Worker(...)`. A production-built worker bundle evaluates
54
- * synchronously and never races that message. In dev, though, the
55
- * worker's module graph loads asynchronously over many HTTP
56
- * requests (this bootstrap script, then the entry module, then
57
- * everything it imports, each a separate fetch) — so the "mount"
58
- * message can be dispatched and consumed by the Worker's default
59
- * (listener-less) handling before the widget entry's own
60
- * `message` listener, wired up deep inside the awaited import
61
- * below, is ever attached. The message is dropped silently, with
62
- * no error anywhere — the old `void import("/src/index.ts")` also
63
- * swallowed the import's own rejections, compounding the silence.
64
- * Traced live: host→worker "mount" posts, zero worker→host replies
65
- * until this buffer-and-replay was added; after it, mutation
66
- * records flow and `mounted.ready` resolves. Fix: buffer every
67
- * message from the instant the worker boots (before anything else
68
- * runs), then replay the buffer, in order, once the entry module
69
- * has finished importing and has had the chance to attach its
70
- * real listener.
71
- * 2. **React-refresh preamble.** `@vitejs/plugin-react` injects a
72
- * preamble into every dev-transformed `.tsx` module referencing
73
- * `window.$RefreshReg$` / `$RefreshSig$`. A Worker global scope has
74
- * no `window` and no preamble, so importing a react-based widget
75
- * entry throws `window is not defined`, and — once `window` is
76
- * aliased to `globalThis` — throws again on the bare
77
- * `$RefreshSig$` alias. Installing the canonical preamble
78
- * (`RefreshRuntime.injectIntoGlobalHook` + no-op `$RefreshReg$` /
79
- * `$RefreshSig$`) before the entry import fixes both. The
80
- * `window = globalThis` alias was checked against widget-runtime's
81
- * worker lockdown / scope detection (which is
82
- * `globalThis.postMessage`-based) — no conflict. `/@react-refresh`
83
- * is `@vitejs/plugin-react`'s stable virtual module id, but a
84
- * widget project isn't guaranteed to use the react plugin, so the
85
- * whole block is try/catch-guarded — a project with no
86
- * `/@react-refresh` module (no react plugin) boots straight
87
- * through to the entry import unaffected.
88
- * 3. **`await import`, not `void import`.** A genuine failure to
89
- * import the entry module (syntax error, missing dependency, …)
90
- * must reject the worker's module evaluation loudly, not vanish
91
- * the way a fire-and-forget `void import` did.
92
- * 4. **Absolute dev-server imports.** Mist and portal previews load the
93
- * worker from another origin, so the host wraps this entry in a blob URL.
94
- * Root-relative specifiers inside a blob module resolve against the host
95
- * page (or fail to resolve at all), not this Vite server. Anchoring both
96
- * imports to the request origin keeps the entire dev module graph on the
97
- * widget server.
98
- *
99
- * Dev-only: this only ever serves from `configureServer` (the
100
- * plugin's `apply: "serve"` gate), never a built/published package.
101
- */
102
- function buildRuntimeEntryBootstrapScript(entryPath, origin) {
103
- const refreshRuntimeUrl = new URL("/@react-refresh", origin).toString();
104
- const entryUrl = new URL(entryPath, origin).toString();
105
- return [
106
- "// Dev-only worker bootstrap — see buildRuntimeEntryBootstrapScript",
107
- "// in packages/cli/widget/src/vite/plugin.ts for why each piece",
108
- "// here exists (message-buffer race, react-refresh preamble,",
109
- "// await-not-void import).",
110
- "const __q = [];",
111
- "const __buf = (e) => { __q.push(e.data); };",
112
- "self.addEventListener(\"message\", __buf);",
113
- "if (typeof window === \"undefined\") { globalThis.window = globalThis; }",
114
- "try {",
115
- ` const { default: RefreshRuntime } = await import(${JSON.stringify(refreshRuntimeUrl)});`,
116
- " RefreshRuntime.injectIntoGlobalHook(window);",
117
- " window.$RefreshReg$ = () => {};",
118
- " window.$RefreshSig$ = () => (type) => type;",
119
- " window.__vite_plugin_react_preamble_installed__ = true;",
120
- "} catch {",
121
- " // No @vitejs/plugin-react in this widget project (no",
122
- " // /@react-refresh virtual module) — nothing to preinstall.",
123
- "}",
124
- `await import(${JSON.stringify(entryUrl)});`,
125
- "self.removeEventListener(\"message\", __buf);",
126
- "for (const data of __q) {",
127
- " self.dispatchEvent(new MessageEvent(\"message\", { data }));",
128
- "}",
129
- ""
130
- ].join("\n");
131
- }
132
- /**
133
- * The shared development descriptor workflow appends Vite's `?direct` query
134
- * to CSS URLs so preview hosts receive real CSS instead of JS style modules.
135
- * Published descriptors use a separate build path and never receive it.
136
- */
137
- function createDevWidgetPackageDescriptors(configModule, origin) {
138
- return [createWidgetPackageDevelopmentDescriptor({
139
- sourceModule: configModule,
140
- owner: "droplet",
141
- origin,
142
- workerEntryPath: "/__runtime-entry__",
143
- defaultCssPath: "/styles.css"
144
- })];
145
- }
146
- async function sendWidgetPackages(server, request, response) {
147
- if (handleOptions(request, response)) return;
148
- try {
149
- sendJson(response, createDevWidgetPackageDescriptors(await server.ssrLoadModule("/fluid.widget.config.ts"), getRequestOrigin(request)));
150
- } catch (error) {
151
- server.config.logger.error(`[fluid] Failed to load widget packages: ${String(error)}`);
152
- sendJson(response, { error: String(error) }, 500);
153
- }
154
- }
155
- function sendJavaScript(request, response, source) {
156
- if (handleOptions(request, response)) return;
157
- setCorsHeaders(response);
158
- response.setHeader("Content-Type", "text/javascript");
159
- response.statusCode = 200;
160
- response.end(source);
161
- }
162
- function sendJson(response, body, statusCode = 200) {
163
- setCorsHeaders(response);
164
- response.setHeader("Content-Type", "application/json");
165
- response.statusCode = statusCode;
166
- response.end(JSON.stringify(body, null, 2));
167
- }
168
- function handleOptions(request, response) {
169
- if (request.method !== "OPTIONS") return false;
170
- setCorsHeaders(response);
171
- response.statusCode = 204;
172
- response.end();
173
- return true;
174
- }
175
- function setCorsHeaders(response) {
176
- response.setHeader("Access-Control-Allow-Origin", "*");
177
- response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
178
- response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
179
- }
180
- function getRequestOrigin(request) {
181
- const host = readHeader(request.headers.host);
182
- if (!host) throw new Error("Widget dev request is missing the Host header.");
183
- return `${readHeader(request.headers["x-forwarded-proto"]) === "https" ? "https" : "http"}://${host}`;
184
- }
185
- function readHeader(value) {
186
- const candidate = Array.isArray(value) ? value[0] : value;
187
- return typeof candidate === "string" && candidate.trim().length > 0 ? candidate.trim() : void 0;
188
- }
189
- //#endregion
1
+ import { a as buildRuntimeEntryBootstrapScript, c as fluidWidgetDevPlugin, o as createDevWidgetPackageDescriptors, s as createWidgetDevServerConfig } from "../src-_wfDoBLp.mjs";
190
2
  export { buildRuntimeEntryBootstrapScript, createDevWidgetPackageDescriptors, createWidgetDevServerConfig, fluidWidgetDevPlugin };
191
-
192
- //# sourceMappingURL=plugin.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/fluid-cli-widget",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Fluid CLI plugin for standalone widget package workflows",
5
5
  "files": [
6
6
  "dist",
@@ -43,8 +43,8 @@
43
43
  "typescript": "^5",
44
44
  "vitest": "^4.0.18",
45
45
  "@fluid-app/fluid-cli-widget-tooling": "0.1.0",
46
- "@fluid-app/fluidos-api-client": "0.1.0",
47
- "@fluid-app/typescript-config": "0.0.0"
46
+ "@fluid-app/typescript-config": "0.0.0",
47
+ "@fluid-app/fluidos-api-client": "0.1.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24.0.0"
@@ -210,6 +210,21 @@ Light/dark behavior:
210
210
 
211
211
  Runtime CSS rules:
212
212
 
213
+ **A stylesheet is the only way to style a widget. Inline styles do not work.**
214
+
215
+ The host sanitizes every Remote DOM mutation before it reaches the shadow root. The
216
+ `style` attribute is rejected and `<style>` elements are a banned tag, so:
217
+
218
+ - `style={{ color: "red" }}` on any element is **silently dropped**. The element renders
219
+ with no styling and no error — this is the single most common reason a widget "looks
220
+ broken" or "came out unstyled".
221
+ - An inline `<style>` block never renders.
222
+ - Only CSS reachable from the widget build graph is published as a `cssUrls` artifact and
223
+ adopted into the shadow root.
224
+
225
+ So: put every rule in a stylesheet, give elements `className`s, and target those classes.
226
+ Never reach for an inline style — it will not survive.
227
+
213
228
  - Put runtime selectors in `styles.css` or imported CSS modules.
214
229
  - Keep selectors prefixed with the widget name to avoid leaking styles into the host.
215
230
  - Do not rely on global body styles for runtime appearance.
@@ -1,22 +1,47 @@
1
+ /* Runtime styles for the example widget.
2
+ *
3
+ * This file is the worked example every new widget is copied from, so it is
4
+ * deliberately written the way widget CSS SHOULD be written:
5
+ *
6
+ * • Colors, fonts, and radii come from the HOST's semantic CSS variables.
7
+ * The portal theme sets them, they inherit through the widget's shadow
8
+ * root, and the same widget then looks correct on every portal and in
9
+ * both light and dark — without a second, hand-written dark palette.
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
+ * • Selectors stay prefixed with the widget name so nothing leaks into the
17
+ * host, and nothing here depends on global body styles.
18
+ *
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". */
23
+
1
24
  .review-carousel {
2
- font-family:
25
+ font-family: var(
26
+ --font-body,
3
27
  Inter,
4
28
  ui-sans-serif,
5
29
  system-ui,
6
30
  -apple-system,
7
31
  BlinkMacSystemFont,
8
32
  "Segoe UI",
9
- sans-serif;
10
- border: 1px solid #e5e7eb;
11
- border-radius: 24px;
33
+ sans-serif
34
+ );
35
+ border: 1px solid var(--border, #e5e7eb);
36
+ border-radius: var(--radius-xl, 24px);
12
37
  padding: 32px;
13
- background: #ffffff;
38
+ background: var(--card, #ffffff);
14
39
  box-shadow: 0 24px 80px rgb(15 23 42 / 10%);
15
40
  }
16
41
 
17
42
  .review-carousel__eyebrow {
18
43
  margin: 0 0 8px;
19
- color: #4f46e5;
44
+ color: var(--primary, #4f46e5);
20
45
  font-size: 0.75rem;
21
46
  font-weight: 700;
22
47
  letter-spacing: 0.08em;
@@ -25,7 +50,8 @@
25
50
 
26
51
  .review-carousel__title {
27
52
  margin: 0;
28
- color: #111827;
53
+ color: var(--card-foreground, #111827);
54
+ font-family: var(--font-header, inherit);
29
55
  font-size: clamp(2rem, 4vw, 3.5rem);
30
56
  line-height: 0.95;
31
57
  letter-spacing: -0.05em;
@@ -33,7 +59,7 @@
33
59
 
34
60
  .review-carousel__quote {
35
61
  margin: 32px 0 0;
36
- color: #1f2937;
62
+ color: var(--card-foreground, #1f2937);
37
63
  font-size: clamp(1.25rem, 2vw, 1.75rem);
38
64
  line-height: 1.35;
39
65
  }
@@ -48,13 +74,13 @@
48
74
 
49
75
  .review-carousel__author {
50
76
  margin: 0;
51
- color: #111827;
77
+ color: var(--card-foreground, #111827);
52
78
  font-weight: 700;
53
79
  }
54
80
 
55
81
  .review-carousel__role {
56
82
  margin: 4px 0 0;
57
- color: #6b7280;
83
+ color: var(--muted-foreground, #6b7280);
58
84
  }
59
85
 
60
86
  .review-carousel__controls {
@@ -65,15 +91,21 @@
65
91
  .review-carousel__button {
66
92
  width: 40px;
67
93
  height: 40px;
68
- border: 1px solid #d1d5db;
94
+ border: 1px solid var(--border, #d1d5db);
95
+ /* Intentionally a pill, not themed — shape is this control's identity. */
69
96
  border-radius: 999px;
70
- color: #111827;
71
- background: #ffffff;
97
+ color: var(--card-foreground, #111827);
98
+ background: var(--card, #ffffff);
72
99
  font: inherit;
73
100
  cursor: pointer;
74
101
  }
75
102
 
76
103
  .review-carousel__button:hover {
77
- border-color: #4f46e5;
78
- color: #4f46e5;
104
+ border-color: var(--primary, #4f46e5);
105
+ color: var(--primary, #4f46e5);
106
+ }
107
+
108
+ .review-carousel__button:focus-visible {
109
+ outline: 2px solid var(--ring, #4f46e5);
110
+ outline-offset: 2px;
79
111
  }
@@ -1,91 +0,0 @@
1
- //#region ../../portal/core/src/remote-dom-widget-package.d.ts
2
- declare const REMOTE_DOM_WIDGET_MANIFEST_VERSION: 1;
3
- declare const REMOTE_DOM_WIDGET_RUNTIME: "remote-dom";
4
- type JsonPrimitive = string | number | boolean | null;
5
- type JsonValue = JsonPrimitive | readonly JsonValue[] | JsonObject;
6
- interface JsonObject {
7
- readonly [key: string]: JsonValue;
8
- }
9
- interface RemoteDomWidgetCapabilityDeclaration {
10
- readonly name: string;
11
- readonly version: string;
12
- }
13
- interface RemoteDomWidgetDefinition {
14
- readonly type: string;
15
- readonly displayName: string;
16
- readonly description: string;
17
- readonly icon: string;
18
- readonly category: string;
19
- readonly propertySchema: JsonObject;
20
- readonly defaultProps: JsonObject;
21
- readonly container: "inline" | "block" | "card" | "fullscreen";
22
- readonly resizable: boolean | "horizontal" | "vertical" | "both";
23
- readonly minSdkVersion: string;
24
- readonly capabilities: readonly RemoteDomWidgetCapabilityDeclaration[];
25
- }
26
- /**
27
- * Canonical package contract shared by the CLI, catalog APIs, and portal hosts.
28
- * Catalog producers resolve every artifact to its public URL before returning
29
- * this descriptor; consumers do not translate legacy manifest shapes.
30
- */
31
- interface RemoteDomWidgetPackageDescriptor {
32
- readonly manifestVersion: typeof REMOTE_DOM_WIDGET_MANIFEST_VERSION;
33
- readonly runtime: typeof REMOTE_DOM_WIDGET_RUNTIME;
34
- readonly packageId: string;
35
- readonly source: "company" | "droplet";
36
- readonly version: string;
37
- readonly workerEntryUrl: string;
38
- readonly cssUrls: readonly string[];
39
- readonly assetUrls: readonly string[];
40
- readonly widgets: readonly RemoteDomWidgetDefinition[];
41
- }
42
- //#endregion
43
- //#region ../widget-tooling/src/internal/widget-package-validation.d.ts
44
- type WidgetPackageOwnerKind = "company" | "droplet";
45
- interface WidgetCapabilityMetadata {
46
- readonly name: string;
47
- readonly version?: string;
48
- }
49
- interface SourceWidgetMetadata {
50
- readonly name: string;
51
- readonly displayName?: string;
52
- readonly description?: string;
53
- readonly icon?: string;
54
- readonly category?: string;
55
- readonly propertySchema?: JsonObject;
56
- readonly defaultProps?: JsonObject;
57
- readonly container?: unknown;
58
- readonly minSdkVersion?: string;
59
- readonly resizable?: unknown;
60
- readonly capabilities?: readonly WidgetCapabilityMetadata[];
61
- }
62
- interface SourceWidgetPackageMetadata {
63
- readonly manifestVersion?: number;
64
- readonly scope?: string;
65
- readonly packageStableId?: string;
66
- readonly packageId: string;
67
- readonly packageType?: string;
68
- readonly version: string;
69
- readonly widgets: readonly SourceWidgetMetadata[];
70
- readonly cssUrls?: readonly string[];
71
- }
72
- interface WidgetDescriptorMetadata extends RemoteDomWidgetDefinition {
73
- readonly name: string;
74
- }
75
- interface ValidatedWidgetPackageBuild {
76
- readonly sourcePackage: SourceWidgetPackageMetadata;
77
- readonly packageKey: string;
78
- readonly packageId: string;
79
- readonly owner: WidgetPackageOwnerKind;
80
- readonly version: string;
81
- readonly widgets: readonly WidgetDescriptorMetadata[];
82
- }
83
- type WidgetPackageValidationErrorCode = "NO_SOURCE_PACKAGE" | "MULTIPLE_SOURCE_PACKAGES" | "INVALID_SOURCE_PACKAGE" | "INVALID_WIDGET_METADATA" | "UNSUPPORTED_OWNER" | "INVALID_PACKAGE_KEY" | "INVALID_VERSION" | "NO_WIDGETS" | "INVALID_WIDGET_NAME" | "INVALID_WIDGET_TYPE" | "DUPLICATE_WIDGET";
84
- interface WidgetPackageValidationError {
85
- readonly code: WidgetPackageValidationErrorCode;
86
- readonly message: string;
87
- readonly path?: string;
88
- }
89
- //#endregion
90
- export { WidgetPackageValidationError as n, RemoteDomWidgetPackageDescriptor as r, ValidatedWidgetPackageBuild as t };
91
- //# sourceMappingURL=index-BYRGkNyt.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-BYRGkNyt.d.mts","names":[],"sources":["../../../portal/core/src/remote-dom-widget-package.ts","../../widget-tooling/src/internal/widget-package-validation.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"}