@asteby/metacore-runtime-react 9.1.0 → 10.0.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/dist/addon-layout-context.d.ts +49 -0
  3. package/dist/addon-layout-context.d.ts.map +1 -0
  4. package/dist/addon-layout-context.js +94 -0
  5. package/dist/addon-loader.d.ts +14 -2
  6. package/dist/addon-loader.d.ts.map +1 -1
  7. package/dist/addon-loader.js +7 -1
  8. package/dist/dynamic-form-schema.d.ts +5 -0
  9. package/dist/dynamic-form-schema.d.ts.map +1 -1
  10. package/dist/dynamic-form-schema.js +34 -0
  11. package/dist/dynamic-form.d.ts.map +1 -1
  12. package/dist/dynamic-form.js +18 -2
  13. package/dist/dynamic-relation.d.ts.map +1 -1
  14. package/dist/dynamic-relation.js +59 -22
  15. package/dist/hotswap-reload-policy.d.ts +155 -0
  16. package/dist/hotswap-reload-policy.d.ts.map +1 -0
  17. package/dist/hotswap-reload-policy.js +227 -0
  18. package/dist/index.d.ts +6 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +6 -0
  21. package/dist/manifest-hotswap-subscriber.d.ts +83 -0
  22. package/dist/manifest-hotswap-subscriber.d.ts.map +1 -0
  23. package/dist/manifest-hotswap-subscriber.js +104 -0
  24. package/dist/metadata-cache.d.ts +35 -0
  25. package/dist/metadata-cache.d.ts.map +1 -1
  26. package/dist/metadata-cache.js +55 -0
  27. package/dist/types.d.ts +20 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/use-options-resolver.d.ts +87 -0
  30. package/dist/use-options-resolver.d.ts.map +1 -0
  31. package/dist/use-options-resolver.js +147 -0
  32. package/dist/use-org-config-bridge.d.ts +28 -0
  33. package/dist/use-org-config-bridge.d.ts.map +1 -0
  34. package/dist/use-org-config-bridge.js +50 -0
  35. package/package.json +4 -4
  36. package/src/__tests__/hotswap-reload-policy.test.ts +249 -0
  37. package/src/__tests__/manifest-hotswap-subscriber.test.ts +179 -0
  38. package/src/__tests__/use-options-resolver.test.ts +127 -0
  39. package/src/__tests__/wasm-client-sri.test.ts +82 -0
  40. package/src/addon-layout-context.tsx +137 -0
  41. package/src/addon-loader.tsx +21 -1
  42. package/src/dynamic-form-schema.ts +36 -0
  43. package/src/dynamic-form.tsx +40 -2
  44. package/src/dynamic-relation.tsx +55 -20
  45. package/src/hotswap-reload-policy.ts +360 -0
  46. package/src/index.ts +43 -0
  47. package/src/manifest-hotswap-subscriber.ts +164 -0
  48. package/src/metadata-cache.ts +86 -0
  49. package/src/types.ts +24 -0
  50. package/src/use-options-resolver.ts +232 -0
  51. package/src/use-org-config-bridge.ts +60 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,180 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 10.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9ce8269: feat: hot-swap reload policy (RFC-0001 D4 close)
8
+
9
+ Closes the gap between `useManifestHotSwapSubscriber` (already invalidates
10
+ the metadata cache) and the federation container of an already-mounted
11
+ addon, which keeps the old code in memory until something forces a
12
+ re-evaluation.
13
+
14
+ **runtime-react:**
15
+ - New `hotswap-reload-policy` module ships three policies and a single
16
+ hook that wires the chosen policy to the manifest hot-swap stream:
17
+ - `useHotSwapReload(client, { strategy })` returns `{ addonVersionMap }`
18
+ — a reactive map `addonKey → hashShort` that hosts wire into
19
+ `<AddonRoute version={addonVersionMap[addonKey]} ... />`. Default
20
+ strategy is `"rekey"`: React unmounts and remounts the addon subtree
21
+ on every swap, which forces the federation loader to re-fetch
22
+ `remoteEntry.js?v=<hash8>` and re-evaluate the exposed module.
23
+ - `strategy: "page-reload"` is an opt-in `window.location.reload()`
24
+ escape hatch for immersive addons with critical in-progress state
25
+ (POS, kitchen-display). Pair with `onBeforeReload` to surface an
26
+ "unsaved changes" prompt — returning `false` cancels the reload.
27
+ - `strategy: "manual"` only invokes `onSwap` with the message;
28
+ the host decides what to do.
29
+ - `clearFederationContainer(scope)` helper for hosts that hit
30
+ `Container already registered` after a re-key; call it from the
31
+ `onSwap` callback before the addon route re-mounts.
32
+ - `applyHotSwapReload` exported for non-React shells that want to
33
+ drive the policy from a vanilla container.
34
+
35
+ **sdk:**
36
+ - `loadFederatedAddon(spec, addonKey, version?)` accepts an optional
37
+ `version` so the loader cache-busts `remoteEntry.js` via
38
+ `?v=<hash8>` when the manifest hash bumps. Cache key includes the
39
+ version so a fresh hash triggers a fresh load instead of returning
40
+ the memoized old container.
41
+ - New `withVersionParam(url, hash)` helper (idempotent, fragment-safe,
42
+ replaces prior `v=` entries) exported for symmetry — the
43
+ runtime-react module re-uses the same algorithm.
44
+
45
+ **starter-core:**
46
+ - `<AddonRoute>` accepts a new optional `version?: string` prop. When
47
+ it changes, the route's children are wrapped in a `Fragment` with a
48
+ new `key`, forcing the federation loader to re-evaluate.
49
+
50
+ ### Host wire-up (4-5 lines)
51
+
52
+ ```tsx
53
+ const ws = useWebSocket()
54
+ useManifestHotSwapSubscriber(ws) // metadata cache
55
+ const { addonVersionMap } = useHotSwapReload(ws, { strategy: 'rekey' })
56
+ // …in your router:
57
+ <AddonRoute version={addonVersionMap[addonKey]} shell={renderShell}>
58
+ <AddonLoader scope={addonKey} url={remoteEntryUrl} api={api} />
59
+ </AddonRoute>
60
+ ```
61
+
62
+ Re-keying is intentionally destructive: any state inside the addon is
63
+ lost because the code version changed. Hosts that need a confirmation
64
+ gate should pass `onBeforeReload` and prompt the user before the swap
65
+ applies.
66
+
67
+ - 04362f2: feat: immersive layout, federation shared-deps helper polish, wasm client
68
+
69
+ **sdk:**
70
+ - `FrontendSpec` now carries `layout?: "shell" | "immersive"`. Mirrors the
71
+ upcoming kernel-side `manifest.FrontendSpec.Layout` field. `undefined` is
72
+ treated as `"shell"` (legacy behaviour) so the change is purely additive.
73
+ Exposed as the `AddonLayout` type alias for explicit consumers.
74
+ - New `wasm-client` module — frontend twin of `kernel/runtime/wasm`. Ships
75
+ `loadAddonWasm({ url, integrity, imports })` (SRI verification + instantiate
76
+ pipeline) and `callAddonExport(instance, fn, payload)` honouring the same
77
+ `ptr<<32 | len` packed ABI the Go example backends use (`alloc`, `free`,
78
+ `memory`). Lets POS / kitchen-display / signage addons run their compiled
79
+ module locally for sub-50ms latency without a webhook round trip. Typed
80
+ errors (`WasmIntegrityError`, `WasmAbiError`) surface failure cause cleanly.
81
+
82
+ **runtime-react:**
83
+ - New `<AddonLayoutProvider>`, `useAddonLayout()`, `useAddonLayoutControl()`
84
+ and `useDeclareAddonLayout()` API in `addon-layout-context`. The host shell
85
+ reads the active layout and hides Sidebar / Topbar / breadcrumbs when an
86
+ addon declares `layout: "immersive"`. Cleanup restores chrome on unmount,
87
+ so navigating away from an immersive addon brings the shell back.
88
+ - `<AddonLoader>` accepts an optional `layout` prop and propagates it through
89
+ the context, so hosts get the chrome switch wired without per-route plumbing.
90
+
91
+ **starter-config:**
92
+ - `metacoreFederationShared()` now accepts `extra: Record<string, ShareConfig>`
93
+ for the typical "I just want to add a package with explicit config" case
94
+ (`extra: { lodash: { singleton: true } }`). The existing `extras: string[]`
95
+ and `overrides` knobs are retained for backwards compatibility.
96
+ - `METACORE_FEDERATION_SINGLETONS` adds `@asteby/metacore-app-providers` so
97
+ the SDK's transport-agnostic platform provider keeps a single instance
98
+ between host and addons.
99
+
100
+ - ba60c8f: feat: immersive route wrapper + manifest hot-swap subscriber (RFC-0001 D1 + D4)
101
+
102
+ **runtime-react:**
103
+ - `metadata-cache` gains `invalidateAddon(addonKey, matcher?)` and `clearAll()`
104
+ so consumers can flush scoped cache entries when an addon's manifest hash
105
+ changes. The default matcher recognises `addonKey`, `${addonKey}.`,
106
+ `${addonKey}:` and `${addonKey}/` prefixes; hosts that namespace their
107
+ `model` keys differently can pass a custom matcher.
108
+ - New `manifest-hotswap-subscriber` module ships:
109
+ - `ADDON_MANIFEST_CHANGED_TYPE` — the `ws.MessageType` constant the kernel
110
+ emits via `bridge.WSManifestBroadcaster`.
111
+ - `wireHotSwapInvalidation(client, options?)` — imperative helper hosts call
112
+ once at boot. Accepts any object exposing `subscribe(type, handler)`
113
+ (structurally compatible with `useWebSocket().subscribe`), invalidates
114
+ the metadata cache for the bumped addon, and optionally invokes an
115
+ `onSwap` side-effect callback (handy for forcing a `window.location.reload()`
116
+ when the running addon's bundle hash changes, since metadata invalidation
117
+ alone does not swap the federation container already in memory).
118
+ - `useManifestHotSwapSubscriber(client)` — React hook variant for hosts
119
+ that prefer mounting the wire-up next to their WebSocket provider.
120
+
121
+ **starter-core:**
122
+ - New `AddonRoute` component closes the host side of RFC-0001 D1 (immersive
123
+ end-to-end). It reads `useAddonLayout()` from runtime-react and either
124
+ renders the addon inside a caller-provided shell renderer (default
125
+ `"shell"` layout) or strips chrome and pins the addon to the viewport
126
+ (`fixed inset-0 z-50`) when the active layout is `"immersive"`. Supports
127
+ both prop-driven layout (no shell flash for always-immersive routes like
128
+ POS / kitchen-display) and context-driven layout (addon calls
129
+ `useDeclareAddonLayout("immersive")` after mount). Cleanup restores
130
+ `"shell"` so navigating away brings the chrome back.
131
+
132
+ Together these closures unblock zero-polling hot-swap reloads in the
133
+ metadata layer and let immersive addons own the viewport without each app
134
+ re-implementing the shell branch.
135
+
136
+ ### Patch Changes
137
+
138
+ - Updated dependencies [9ce8269]
139
+ - Updated dependencies [04362f2]
140
+ - @asteby/metacore-sdk@2.5.0
141
+
142
+ ## 9.2.0
143
+
144
+ ### Minor Changes
145
+
146
+ - 150a907: feat: useOptionsResolver hook + locale-aware Validation via OrgConfigProvider
147
+
148
+ **runtime-react:**
149
+ - New `useOptionsResolver(args)` hook that consumes the v0.9.0 kernel
150
+ envelope `{ success, data, meta: { type, count } }` from
151
+ `GET /api/options/:model?field=…`. Replaces the ad-hoc `/data/<model>`
152
+ reads `<DynamicRelation>` used to do.
153
+ - `<DynamicForm>` now renders a Ref-driven `<RefSelect>` whenever an
154
+ `ActionFieldDef.ref` is present — apps stop hardcoding option lists for
155
+ belongs_to FKs.
156
+ - `<DynamicRelation>` (kind="many_to_many") prefers the canonical options
157
+ endpoint via `useOptionsResolver`. The legacy `referencesEndpoint` prop
158
+ remains a working escape hatch for apps wired against custom routes.
159
+ - `ColumnDefinition.ref` and `ColumnDefinition.validation` are now part of
160
+ the metadata contract the SDK reads. `ActionFieldDef.ref` joins the
161
+ field-level type so addons can declare ref-aware modal fields.
162
+ - New `setOrgConfigBridge` / `resolveValidatorToken` surface lets apps
163
+ feed a `useOrgConfig`-backed resolver into the SDK's validator
164
+ pipeline. Validators with `custom: '$org.<key>'` are resolved at form
165
+ build time; unresolved tokens degrade to no-op so missing config does
166
+ not crash forms.
167
+ - New `registerValidator(slug, fn)` lets apps install their own
168
+ region-specific validators (e.g. `mx.rfc`, `co.nit`) without leaking
169
+ fiscal vocabulary into the SDK.
170
+
171
+ **app-providers:**
172
+ - New `OrgConfigProvider` + `useOrgConfig()` companion to
173
+ `PlatformConfigProvider`. Apps wire a per-org config fetcher and the
174
+ provider exposes typed `currency`, `locale`, `validators` plus a
175
+ `resolveValidator(refOrKey)` helper for the `$org.<key>` reference
176
+ contract the kernel ≥ v0.9.0 emits.
177
+
3
178
  ## 9.1.0
4
179
 
5
180
  ### Minor Changes
@@ -0,0 +1,49 @@
1
+ import type { AddonLayout } from '@asteby/metacore-sdk';
2
+ export type { AddonLayout };
3
+ interface AddonLayoutState {
4
+ /** Active layout. `"shell"` (default) or `"immersive"`. */
5
+ layout: AddonLayout;
6
+ /**
7
+ * Imperative setter for the host or an addon-loader to mutate the active
8
+ * layout. Exposed for advanced use; most callers should use
9
+ * `useDeclareAddonLayout(layout)` from a route component, which scopes the
10
+ * change to the route's mount lifetime.
11
+ */
12
+ setLayout: (layout: AddonLayout) => void;
13
+ }
14
+ export interface AddonLayoutProviderProps {
15
+ /** Initial layout — usually `"shell"`. */
16
+ initial?: AddonLayout;
17
+ children: React.ReactNode;
18
+ }
19
+ /**
20
+ * Wrap the host app once, above the router outlet. The provider keeps the
21
+ * currently-active layout in state; addon-loader and `useDeclareAddonLayout`
22
+ * mutate it from below.
23
+ */
24
+ export declare function AddonLayoutProvider({ initial, children, }: AddonLayoutProviderProps): import("react/jsx-runtime").JSX.Element;
25
+ /**
26
+ * Read the currently-active layout. The host shell calls this and decides
27
+ * whether to render its chrome. Returns `"shell"` when no provider is
28
+ * mounted, so apps that have not adopted immersive addons keep working.
29
+ */
30
+ export declare function useAddonLayout(): AddonLayout;
31
+ /**
32
+ * Imperative API — the value returned mirrors `useAddonLayout()` but also
33
+ * exposes the setter for hosts that need to flip the layout outside of a
34
+ * route lifecycle (e.g. a hotkey forcing kiosk mode). Most addon entries do
35
+ * NOT need this; prefer `useDeclareAddonLayout`.
36
+ */
37
+ export declare function useAddonLayoutControl(): AddonLayoutState;
38
+ /**
39
+ * Declare the layout from the addon side. Mounts the value, restores
40
+ * `"shell"` on unmount. Skip when `layout` is undefined so route components
41
+ * can pass `manifest.frontend?.layout` directly without branching.
42
+ *
43
+ * function PosEntry({ manifest }: { manifest: Manifest }) {
44
+ * useDeclareAddonLayout(manifest.frontend?.layout)
45
+ * return <PosScreen />
46
+ * }
47
+ */
48
+ export declare function useDeclareAddonLayout(layout: AddonLayout | undefined): void;
49
+ //# sourceMappingURL=addon-layout-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addon-layout-context.d.ts","sourceRoot":"","sources":["../src/addon-layout-context.tsx"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEvD,YAAY,EAAE,WAAW,EAAE,CAAA;AAE3B,UAAU,gBAAgB;IACtB,2DAA2D;IAC3D,MAAM,EAAE,WAAW,CAAA;IACnB;;;;;OAKG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;CAC3C;AAWD,MAAM,WAAW,wBAAwB;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,OAAiB,EACjB,QAAQ,GACX,EAAE,wBAAwB,2CAW1B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,gBAAgB,CAExD;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAY3E"}
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // AddonLayoutContext — broadcast the active addon entry's layout selection
3
+ // (`shell` vs `immersive`) up to the host so it can hide/show its chrome
4
+ // (Sidebar, Topbar, breadcrumbs) when an immersive addon is mounted.
5
+ //
6
+ // Why a context rather than a prop on the host shell:
7
+ //
8
+ // 1. The host shell is rendered ABOVE the addon route in the tree, but the
9
+ // decision about what layout the addon wants comes from the addon itself
10
+ // (manifest.frontend.layout) which the AddonLoader knows about at mount
11
+ // time. A bottom-up signal via context inverts the dependency cleanly.
12
+ //
13
+ // 2. Addon entries can swap layouts at runtime (think a kiosk-mode toggle
14
+ // inside a POS). A context value reactively updates the host without
15
+ // asking each route to wire props.
16
+ //
17
+ // 3. When the user navigates AWAY from an immersive addon, the AddonLoader
18
+ // unmounts, its layout context updater fires `setLayout("shell")` from
19
+ // a cleanup effect, and the chrome restores automatically.
20
+ //
21
+ // Host integration (starter-core, ops, …):
22
+ //
23
+ // function AppShell({ children }) {
24
+ // const layout = useAddonLayout()
25
+ // const chrome = layout !== "immersive"
26
+ // return (
27
+ // <div className={chrome ? "grid grid-cols-[280px_1fr]" : "h-dvh w-dvw"}>
28
+ // {chrome && <Sidebar />}
29
+ // <main>{chrome && <Topbar />}{children}</main>
30
+ // </div>
31
+ // )
32
+ // }
33
+ //
34
+ // The context defaults to `"shell"`, so apps that never mount an
35
+ // `<AddonLayoutProvider>` keep the legacy behaviour.
36
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
37
+ const defaultState = {
38
+ layout: 'shell',
39
+ setLayout: () => {
40
+ /* noop — provider missing; consumers degrade to legacy "shell" */
41
+ },
42
+ };
43
+ const AddonLayoutContext = createContext(defaultState);
44
+ /**
45
+ * Wrap the host app once, above the router outlet. The provider keeps the
46
+ * currently-active layout in state; addon-loader and `useDeclareAddonLayout`
47
+ * mutate it from below.
48
+ */
49
+ export function AddonLayoutProvider({ initial = 'shell', children, }) {
50
+ const [layout, setLayout] = useState(initial);
51
+ const value = useMemo(() => ({ layout, setLayout }), [layout]);
52
+ return (_jsx(AddonLayoutContext.Provider, { value: value, children: children }));
53
+ }
54
+ /**
55
+ * Read the currently-active layout. The host shell calls this and decides
56
+ * whether to render its chrome. Returns `"shell"` when no provider is
57
+ * mounted, so apps that have not adopted immersive addons keep working.
58
+ */
59
+ export function useAddonLayout() {
60
+ return useContext(AddonLayoutContext).layout;
61
+ }
62
+ /**
63
+ * Imperative API — the value returned mirrors `useAddonLayout()` but also
64
+ * exposes the setter for hosts that need to flip the layout outside of a
65
+ * route lifecycle (e.g. a hotkey forcing kiosk mode). Most addon entries do
66
+ * NOT need this; prefer `useDeclareAddonLayout`.
67
+ */
68
+ export function useAddonLayoutControl() {
69
+ return useContext(AddonLayoutContext);
70
+ }
71
+ /**
72
+ * Declare the layout from the addon side. Mounts the value, restores
73
+ * `"shell"` on unmount. Skip when `layout` is undefined so route components
74
+ * can pass `manifest.frontend?.layout` directly without branching.
75
+ *
76
+ * function PosEntry({ manifest }: { manifest: Manifest }) {
77
+ * useDeclareAddonLayout(manifest.frontend?.layout)
78
+ * return <PosScreen />
79
+ * }
80
+ */
81
+ export function useDeclareAddonLayout(layout) {
82
+ const { setLayout } = useAddonLayoutControl();
83
+ // useCallback so the effect only re-runs on a real layout change, not on
84
+ // every render of the consumer that happens to forward an inline literal.
85
+ const apply = useCallback(setLayout, [setLayout]);
86
+ useEffect(() => {
87
+ if (!layout || layout === 'shell')
88
+ return;
89
+ apply(layout);
90
+ return () => {
91
+ apply('shell');
92
+ };
93
+ }, [layout, apply]);
94
+ }
@@ -1,4 +1,4 @@
1
- import type { AddonAPI } from '@asteby/metacore-sdk';
1
+ import type { AddonAPI, AddonLayout } from '@asteby/metacore-sdk';
2
2
  declare global {
3
3
  interface Window {
4
4
  [key: string]: any;
@@ -21,7 +21,19 @@ export interface AddonLoaderProps {
21
21
  onReady?: () => void;
22
22
  /** Called if loading fails. */
23
23
  onError?: (err: Error) => void;
24
+ /**
25
+ * Layout the host shell should render the addon under, mirroring
26
+ * `manifest.frontend.layout`. Default (undefined / `"shell"`) keeps the
27
+ * legacy chrome (Sidebar, Topbar, breadcrumbs). `"immersive"` flips the
28
+ * shared {@link useAddonLayout} context so the host shell hides chrome
29
+ * while the addon is mounted and restores it on unmount.
30
+ *
31
+ * Hosts that consume the context (see `useAddonLayout` /
32
+ * `<AddonLayoutProvider>`) do NOT need to branch on this prop themselves
33
+ * — the loader sets the context value via {@link useDeclareAddonLayout}.
34
+ */
35
+ layout?: AddonLayout;
24
36
  children?: React.ReactNode;
25
37
  }
26
- export declare function AddonLoader({ scope, url, module, api, fallback, onReady, onError, children, }: AddonLoaderProps): import("react/jsx-runtime").JSX.Element;
38
+ export declare function AddonLoader({ scope, url, module, api, fallback, onReady, onError, layout, children, }: AddonLoaderProps): import("react/jsx-runtime").JSX.Element;
27
39
  //# sourceMappingURL=addon-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;QAClB,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACrD;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAuCD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,GAAG,EACH,MAAqB,EACrB,GAAG,EACH,QAAe,EACf,OAAO,EACP,OAAO,EACP,QAAQ,GACX,EAAE,gBAAgB,2CAgClB"}
1
+ {"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;QAClB,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACrD;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAuCD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,GAAG,EACH,MAAqB,EACrB,GAAG,EACH,QAAe,EACf,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,GACX,EAAE,gBAAgB,2CAsClB"}
@@ -3,6 +3,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
3
3
  // waits for the `window[scope]` container to initialize, then calls the
4
4
  // addon's `register(api)` export with the AddonAPI injected by the host.
5
5
  import { useEffect, useRef, useState } from 'react';
6
+ import { useDeclareAddonLayout } from './addon-layout-context';
6
7
  const loadedScripts = new Map();
7
8
  function loadScript(url, scope) {
8
9
  const key = `${scope}::${url}`;
@@ -34,10 +35,15 @@ async function loadRemote(scope, module) {
34
35
  const factory = await container.get(module);
35
36
  return factory();
36
37
  }
37
- export function AddonLoader({ scope, url, module = './register', api, fallback = null, onReady, onError, children, }) {
38
+ export function AddonLoader({ scope, url, module = './register', api, fallback = null, onReady, onError, layout, children, }) {
38
39
  const [status, setStatus] = useState('loading');
39
40
  const [error, setError] = useState(null);
40
41
  const didRegister = useRef(false);
42
+ // Propagate the addon's preferred layout to the host shell via context.
43
+ // No-op when `layout` is undefined or `"shell"` (legacy default). Cleanup
44
+ // restores `"shell"` automatically when the loader unmounts, so chrome
45
+ // returns as soon as the user navigates away from an immersive addon.
46
+ useDeclareAddonLayout(layout);
41
47
  useEffect(() => {
42
48
  let cancelled = false;
43
49
  (async () => {
@@ -1,5 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import type { ActionFieldDef } from './types';
3
+ /**
4
+ * Apps register validator implementations by slug. The slug is the value
5
+ * `OrgConfig.validators[<key>]` returns for a $org.<key> reference.
6
+ */
7
+ export declare function registerValidator(slug: string, fn: (s: z.ZodString) => z.ZodString): void;
3
8
  export declare function buildZodSchema(fields: ActionFieldDef[]): z.ZodObject<{
4
9
  [x: string]: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
5
10
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAA;AAM9D,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAuCD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAU3D"}
1
+ {"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAA;AAiB9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AA4CD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAU3D"}
@@ -2,6 +2,36 @@
2
2
  // callers (and unit tests) can use the zod schema without pulling in React or
3
3
  // metacore-ui primitives.
4
4
  import { z } from 'zod';
5
+ import { resolveValidatorToken } from './use-org-config-bridge';
6
+ /**
7
+ * Built-in validators the SDK knows how to apply by symbolic name. Apps
8
+ * that wire `OrgConfigProvider` map `$org.<key>` references to one of
9
+ * these slugs (or to a custom slug they register). Unknown slugs are a
10
+ * no-op so unresolved $org references degrade to "no extra check"
11
+ * rather than a runtime crash — matches the kernel's pass-through
12
+ * semantics for unresolved references.
13
+ */
14
+ const builtinValidators = {
15
+ // The SDK ships ZERO fiscal vocabulary by default. Apps register
16
+ // their own validators (mx.rfc, co.nit, pe.ruc, etc.) via
17
+ // `registerValidator` so kernel/SDK stay region-agnostic.
18
+ };
19
+ /**
20
+ * Apps register validator implementations by slug. The slug is the value
21
+ * `OrgConfig.validators[<key>]` returns for a $org.<key> reference.
22
+ */
23
+ export function registerValidator(slug, fn) {
24
+ builtinValidators[slug] = fn;
25
+ }
26
+ function applyCustomValidator(s, customToken) {
27
+ if (!customToken)
28
+ return s;
29
+ const resolved = resolveValidatorToken(customToken);
30
+ if (!resolved)
31
+ return s;
32
+ const fn = builtinValidators[resolved];
33
+ return fn ? fn(s) : s;
34
+ }
5
35
  // Builds a zod object schema from an ActionFieldDef[]. Required fields stay
6
36
  // non-empty; optional fields accept undefined / "". Validation rules
7
37
  // (regex/min/max) layer on top: for numeric columns they bound the value, for
@@ -46,6 +76,10 @@ function fieldToZod(field) {
46
76
  s = s.email('Email inválido');
47
77
  if (field.type === 'url')
48
78
  s = s.url('URL inválida');
79
+ // Custom validator: a literal slug (`mx.rfc`) OR a `$org.<key>`
80
+ // reference resolved through the OrgConfigProvider. Unknown slugs
81
+ // pass through as no-ops so apps never crash on missing config.
82
+ s = applyCustomValidator(s, v.custom);
49
83
  if (field.required) {
50
84
  return s.min(Math.max(typeof v.min === 'number' ? v.min : 1, 1), `${field.label} es requerido`);
51
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAErE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AAExC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,2CA4DlB"}
1
+ {"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAGrE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AAExC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,2CAgElB"}
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import { useEffect, useMemo, useState } from 'react';
6
6
  import { Input, Textarea, Label, Switch, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@asteby/metacore-ui/primitives';
7
7
  import { buildZodSchema, resolveWidget } from './dynamic-form-schema';
8
+ import { useOptionsResolver } from './use-options-resolver';
8
9
  export { buildZodSchema, resolveWidget };
9
10
  export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel = 'Guardar', cancelLabel = 'Cancelar', disabled = false, }) {
10
11
  const [values, setValues] = useState({});
@@ -42,10 +43,17 @@ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitL
42
43
  setSubmitting(false);
43
44
  }
44
45
  };
45
- return (_jsxs("form", { onSubmit: handleSubmit, className: "grid gap-4", children: [fields.map((field) => (_jsxs("div", { className: "grid gap-2", children: [_jsxs(Label, { htmlFor: field.key, children: [field.label, field.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), renderField(field, values[field.key], (v) => update(field.key, v)), errors[field.key] && (_jsx("span", { className: "text-red-500 text-sm", role: "alert", children: errors[field.key] }))] }, field.key))), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: submitting || disabled, children: cancelLabel })), _jsx(Button, { type: "submit", disabled: submitting || disabled, children: submitLabel })] })] }));
46
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "grid gap-4", children: [fields.map((field) => (_jsxs("div", { className: "grid gap-2", children: [_jsxs(Label, { htmlFor: field.key, children: [field.label, field.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), _jsx(FieldRenderer, { field: field, value: values[field.key], onChange: (v) => update(field.key, v) }), errors[field.key] && (_jsx("span", { className: "text-red-500 text-sm", role: "alert", children: errors[field.key] }))] }, field.key))), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: submitting || disabled, children: cancelLabel })), _jsx(Button, { type: "submit", disabled: submitting || disabled, children: submitLabel })] })] }));
46
47
  }
47
- function renderField(field, value, onChange) {
48
+ function FieldRenderer({ field, value, onChange }) {
48
49
  const widget = resolveWidget(field);
50
+ // Ref-driven select: hook into useOptionsResolver so the canonical
51
+ // /api/options/<ref>?field=id endpoint feeds the dropdown. This is
52
+ // the path the kernel auto-derives for FK columns; legacy callers
53
+ // shipping inline `options` keep working in the branch below.
54
+ if (widget === 'select' && field.ref) {
55
+ return _jsx(RefSelect, { field: field, value: value, onChange: onChange });
56
+ }
49
57
  switch (widget) {
50
58
  case 'textarea':
51
59
  return _jsx(Textarea, { id: field.key, value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
@@ -68,3 +76,11 @@ function renderField(field, value, onChange) {
68
76
  return _jsx(Input, { id: field.key, type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
69
77
  }
70
78
  }
79
+ function RefSelect({ field, value, onChange }) {
80
+ const { options, loading } = useOptionsResolver({
81
+ modelKey: '', // unused — `ref` drives the URL
82
+ fieldKey: 'id',
83
+ ref: field.ref,
84
+ });
85
+ return (_jsxs(Select, { value: value || '', onValueChange: onChange, disabled: loading, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? 'Cargando…' : (field.placeholder || 'Seleccionar...') }) }), _jsx(SelectContent, { children: options.map((opt) => (_jsx(SelectItem, { value: String(opt.id), children: opt.label }, String(opt.id)))) })] }));
86
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-relation.d.ts","sourceRoot":"","sources":["../src/dynamic-relation.tsx"],"names":[],"mappings":"AAyCA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,wBAAwB,EACxB,eAAe,EACf,cAAc,GACjB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD,UAAU,WAAW;IACjB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,6BAA8B,SAAQ,WAAW;IAC9D,IAAI,EAAE,aAAa,CAAA;IACnB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,WAAW;IAC/D,IAAI,EAAE,cAAc,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC1B,6BAA6B,GAC7B,8BAA8B,CAAA;AAEpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAK1D"}
1
+ {"version":3,"file":"dynamic-relation.d.ts","sourceRoot":"","sources":["../src/dynamic-relation.tsx"],"names":[],"mappings":"AA0CA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,wBAAwB,EACxB,eAAe,EACf,cAAc,GACjB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD,UAAU,WAAW;IACjB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,6BAA8B,SAAQ,WAAW;IAC9D,IAAI,EAAE,aAAa,CAAA;IACnB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,WAAW;IAC/D,IAAI,EAAE,cAAc,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC1B,6BAA6B,GAC7B,8BAA8B,CAAA;AAEpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAK1D"}