@artstorefronts/arthelper-nav 0.1.0-alpha.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 +266 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +134 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie.d.ts +13 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +28 -0
- package/dist/cookie.js.map +1 -0
- package/dist/fallback.d.ts +13 -0
- package/dist/fallback.d.ts.map +1 -0
- package/dist/fallback.js +17 -0
- package/dist/fallback.js.map +1 -0
- package/dist/icons.d.ts +14 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +29 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.css +509 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/nav-bar.d.ts +13 -0
- package/dist/nav-bar.d.ts.map +1 -0
- package/dist/nav-bar.js +91 -0
- package/dist/nav-bar.js.map +1 -0
- package/dist/nav-trigger.d.ts +16 -0
- package/dist/nav-trigger.d.ts.map +1 -0
- package/dist/nav-trigger.js +21 -0
- package/dist/nav-trigger.js.map +1 -0
- package/dist/provider.d.ts +19 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +46 -0
- package/dist/provider.js.map +1 -0
- package/dist/resolve.d.ts +32 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +51 -0
- package/dist/resolve.js.map +1 -0
- package/dist/theme.d.ts +13 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +21 -0
- package/dist/theme.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# @artstorefronts/arthelper-nav
|
|
2
|
+
|
|
3
|
+
The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher for the
|
|
4
|
+
satellite apps in the ArtHelper suite.
|
|
5
|
+
|
|
6
|
+
This document is for an engineer wiring the bar into a host app that lives **outside**
|
|
7
|
+
this monorepo. If you're working inside this repo, see `docs/llms/suite-nav.md` for how
|
|
8
|
+
the pieces fit together end to end.
|
|
9
|
+
|
|
10
|
+
## Architecture in one line
|
|
11
|
+
|
|
12
|
+
Configure in data, render in code. The bar's content — theme, logo, and the app catalog
|
|
13
|
+
— lives in a single database row, served as JSON over a public HTTP endpoint. Every
|
|
14
|
+
mounted host fetches that JSON on load. An edit made in the admin editor is live for
|
|
15
|
+
every app in the suite on their next page load — no deploy, no version bump, no
|
|
16
|
+
coordinated release across repos.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @artstorefronts/arthelper-nav
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Peer dependencies (from `package.json` — install these yourself, they are not bundled):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"lucide-react": ">=0.446.0",
|
|
29
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
30
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> **Not yet on npm.** Publishing is automatic: `.github/workflows/release-nav.yml` runs on
|
|
35
|
+
> any push to `develop` that touches `packages/nav`, and publishes when the version in
|
|
36
|
+
> `packages/nav/package.json` is not already on the registry. There is no tag and no
|
|
37
|
+
> manual release step — **bumping that version in your PR is what ships the package**, and
|
|
38
|
+
> a PR that changes the published surface without bumping it fails CI.
|
|
39
|
+
>
|
|
40
|
+
> The `@artstorefronts` scope already exists (it publishes `@artstorefronts/ui` and
|
|
41
|
+
> `@artstorefronts/cli`). What is still missing is a **trusted publisher** configured on
|
|
42
|
+
> npm for this package, pointing at this repository and the workflow filename
|
|
43
|
+
> `release-nav.yml`. Authentication is OIDC, so there is no token to create or rotate.
|
|
44
|
+
> Until that is configured, `npm install @artstorefronts/arthelper-nav` has nothing to
|
|
45
|
+
> resolve.
|
|
46
|
+
>
|
|
47
|
+
> To develop against it before the first publish, use a workspace link or a path
|
|
48
|
+
> dependency from the satellite repo:
|
|
49
|
+
>
|
|
50
|
+
> ```bash
|
|
51
|
+
> cd packages/nav && yarn link
|
|
52
|
+
> cd ../../../<satellite> && yarn link @artstorefronts/arthelper-nav
|
|
53
|
+
> ```
|
|
54
|
+
>
|
|
55
|
+
> `react` and `react-dom` are peer dependencies, so make sure the satellite's bundler
|
|
56
|
+
> resolves a single copy of React — a linked package pulling in its own is the usual
|
|
57
|
+
> cause of the duplicate-React hooks error.
|
|
58
|
+
|
|
59
|
+
## Minimal mounting example
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Import the stylesheet by its deep path — it is not re-exported from the package root.
|
|
63
|
+
import '@artstorefronts/arthelper-nav/dist/index.css';
|
|
64
|
+
import { NavProvider, NavBar, NavTrigger } from '@artstorefronts/arthelper-nav';
|
|
65
|
+
|
|
66
|
+
function AppRoot() {
|
|
67
|
+
return (
|
|
68
|
+
<NavProvider activeAppId="my-app" configUrl="https://api.arthelper.com/rest/nav-config">
|
|
69
|
+
{/* NavBar first, so it occupies real layout space at the top of the shell. */}
|
|
70
|
+
<NavBar />
|
|
71
|
+
<AppHeader />
|
|
72
|
+
<AppContent />
|
|
73
|
+
</NavProvider>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function AppHeader() {
|
|
78
|
+
return (
|
|
79
|
+
<header>
|
|
80
|
+
<Logo />
|
|
81
|
+
{/* NavTrigger is the RESTORE control. Omit it and a viewer who collapses the
|
|
82
|
+
bar has no way to bring it back. */}
|
|
83
|
+
<NavTrigger />
|
|
84
|
+
</header>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`NavProvider` renders no DOM of its own — `NavBar` and whatever else you nest inside it
|
|
90
|
+
stay direct children of your own layout, so the 56px the bar reserves comes purely from
|
|
91
|
+
`NavBar` itself, not from a wrapper.
|
|
92
|
+
|
|
93
|
+
## Components and props
|
|
94
|
+
|
|
95
|
+
### `NavProvider`
|
|
96
|
+
|
|
97
|
+
The context host. Fetches config, resolves the app list, and owns collapsed state. Must
|
|
98
|
+
wrap `NavBar`, `NavTrigger`, and any component calling `useNav()`.
|
|
99
|
+
|
|
100
|
+
| Prop | Type | Required | Description |
|
|
101
|
+
| ------------------ | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
102
|
+
| `configUrl` | `string` | yes | Full URL of the public nav-config endpoint. See [Deriving `configUrl`](#deriving-configurl) below. |
|
|
103
|
+
| `activeAppId` | `string` | yes | The catalog id of the app the host itself is. Never inferred from the URL or the payload — the host always declares it. |
|
|
104
|
+
| `onAppClick` | `(args: NavAppClickArgs) => void` | no | Fired immediately before navigation. The package's only analytics seam — see [`onAppClick`](#onappclick-the-analytics-seam). |
|
|
105
|
+
| `fallbackConfig` | `NavConfigPayload` | no | Overrides the package's compiled-in fallback, e.g. for a satellite with a different first-party destination set. |
|
|
106
|
+
| `initialCollapsed` | `boolean` | no | The collapsed cookie value a server-rendered host already read, so the first byte paints the correct state. See [SSR and the collapsed cookie](#ssr-and-the-collapsed-cookie). |
|
|
107
|
+
| `children` | `ReactNode` | no | — |
|
|
108
|
+
|
|
109
|
+
### `NavBar`
|
|
110
|
+
|
|
111
|
+
Renders the bar itself: waffle toggle, wordmark/logo, the desktop tile row (or, below the
|
|
112
|
+
768px breakpoint, a labelled dropdown trigger and panel with the same entries as rows).
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Default | Description |
|
|
115
|
+
| ----------- | --------- | ------- | -------------------------------------------------------------------------------------- |
|
|
116
|
+
| `sticky` | `boolean` | `false` | Opt in only for a host whose document actually scrolls. See [`sticky`](#sticky) below. |
|
|
117
|
+
| `className` | `string` | — | Appended to the bar's root class list. |
|
|
118
|
+
|
|
119
|
+
### `NavTrigger`
|
|
120
|
+
|
|
121
|
+
The docked waffle-plus-active-app chip a host places in its own header, next to its own
|
|
122
|
+
logo. This is the **restore** control: while the bar is collapsed (`NavBar` reserves
|
|
123
|
+
nothing and is out of the accessibility tree), `NavTrigger` is the only way back. It is
|
|
124
|
+
`display: none` while the bar is expanded. A host that mounts `NavBar` without also
|
|
125
|
+
mounting `NavTrigger` ships a bar a viewer can collapse and never recover.
|
|
126
|
+
|
|
127
|
+
| Prop | Type | Default | Description |
|
|
128
|
+
| ----------- | -------- | ------- | ------------------------------------------ |
|
|
129
|
+
| `className` | `string` | — | Appended to the trigger's root class list. |
|
|
130
|
+
|
|
131
|
+
### Exported types
|
|
132
|
+
|
|
133
|
+
`NavContextValue`, `NavBarProps`, `NavTriggerProps`, `NavProviderProps`, `NavAlignment`,
|
|
134
|
+
`NavApp`, `NavAppClickArgs`, `NavConfigPayload`, `NavLogo`, `NavTheme`, `NavThemeMode`,
|
|
135
|
+
`ResolvedNavApp` — all exported from the package root (`src/index.ts`), also useful if
|
|
136
|
+
you're typing `fallbackConfig` or `onAppClick` yourself.
|
|
137
|
+
|
|
138
|
+
`useNav()` is also exported, for a component nested under `NavProvider` that needs
|
|
139
|
+
`collapsed`, `toggle`, `activeApp`, or `apps` directly — the two host mounts in this
|
|
140
|
+
monorepo use it, for example, to publish the bar's live height as a CSS variable.
|
|
141
|
+
|
|
142
|
+
## `activeAppId`
|
|
143
|
+
|
|
144
|
+
The host is always the one source of truth for which app it is. `NavProvider` never
|
|
145
|
+
infers this from the current URL and never reads it from the config payload — you pass
|
|
146
|
+
it explicitly, and it must match one of the `id`s in the nav-config app catalog (ask the
|
|
147
|
+
ArtHelper team for your app's id, or check the payload at `GET /rest/nav-config`).
|
|
148
|
+
|
|
149
|
+
That entry is always rendered and indicated (its tile carries the active styling) even if
|
|
150
|
+
the payload has it disabled or omits a destination for it. If your own app has no `href`
|
|
151
|
+
in the catalog yet, its tile renders as an inert indicator instead of a link — it never
|
|
152
|
+
disappears from the bar.
|
|
153
|
+
|
|
154
|
+
## Deriving `configUrl`
|
|
155
|
+
|
|
156
|
+
No new environment variable is needed. Derive it from whatever ArtHelper API base URL
|
|
157
|
+
your app already has, by swapping the `graphql` path segment for `rest` and appending
|
|
158
|
+
`/nav-config`:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const NAV_CONFIG_URL = `${ARTHELPER_API_BASE.replace('graphql', 'rest')}/nav-config`;
|
|
162
|
+
// e.g. https://api.arthelper.com/graphql -> https://api.arthelper.com/rest/nav-config
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
This is the same derivation both in-monorepo hosts (`client/`, `town-square/`) use.
|
|
166
|
+
|
|
167
|
+
## `onAppClick`: the analytics seam
|
|
168
|
+
|
|
169
|
+
The package imports no analytics library of any kind. `onAppClick` is the one hook
|
|
170
|
+
through which a host wires its own analytics — it fires immediately before navigation,
|
|
171
|
+
from **both** the desktop tile row and the mobile dropdown, so one callback covers both
|
|
172
|
+
surfaces:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
<NavProvider
|
|
176
|
+
activeAppId="my-app"
|
|
177
|
+
configUrl={NAV_CONFIG_URL}
|
|
178
|
+
onAppClick={({ app, activeAppId, collapsed }) => {
|
|
179
|
+
myAnalytics.track('suite_nav_app_clicked', { targetAppId: app.id, activeAppId, collapsed });
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Navigation itself happens via a real `<a href>` the browser follows natively —
|
|
185
|
+
`onAppClick` never intercepts or prevents it.
|
|
186
|
+
|
|
187
|
+
## SSR and the collapsed cookie
|
|
188
|
+
|
|
189
|
+
The collapsed flag lives in a **cookie** (`arthelper_suite_nav_collapsed`), not
|
|
190
|
+
`localStorage`. That's deliberate: a server-rendered host can read the cookie during SSR
|
|
191
|
+
and pass its value in as `initialCollapsed`, so the very first byte the browser paints
|
|
192
|
+
already has the bar in the right state — no expanded-then-collapsed jump after
|
|
193
|
+
hydration.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
// Next.js App Router example
|
|
197
|
+
import { cookies } from 'next/headers';
|
|
198
|
+
import { COLLAPSED_COOKIE } from '@artstorefronts/arthelper-nav';
|
|
199
|
+
|
|
200
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
201
|
+
const cookieStore = await cookies();
|
|
202
|
+
const initialCollapsed = cookieStore.get(COLLAPSED_COOKIE)?.value === 'true';
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<NavProvider activeAppId="my-app" configUrl={NAV_CONFIG_URL} initialCollapsed={initialCollapsed}>
|
|
206
|
+
{children}
|
|
207
|
+
</NavProvider>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The cookie is **per-origin**. `app.example.com` and `www.example.com` are different
|
|
213
|
+
registrable domains and share no cookie scope, so collapsing the bar on one origin does
|
|
214
|
+
not sync to another — each origin's viewer sets their own collapsed state independently.
|
|
215
|
+
|
|
216
|
+
## Theming
|
|
217
|
+
|
|
218
|
+
`theme.mode` (`'dark' | 'light'`), `theme.accent_color`, and `theme.alignment`
|
|
219
|
+
(`'left' | 'centered'`) all come from the config payload, set by an admin in the nav
|
|
220
|
+
editor. Changing any of them is a config edit, not a package release or a host
|
|
221
|
+
deployment — every mounted app picks it up on its next fetch.
|
|
222
|
+
|
|
223
|
+
## The layout contract
|
|
224
|
+
|
|
225
|
+
`NavBar` reserves exactly 56px from first paint, using the package's compiled-in
|
|
226
|
+
fallback config while the real payload is in flight — so config arrival never shifts the
|
|
227
|
+
page. Collapsing the bar (`toggle()`, or the waffle button, or `NavTrigger`) removes it
|
|
228
|
+
from layout **entirely**: height goes to 0 and the 56px is handed back to the page, not
|
|
229
|
+
just visually hidden.
|
|
230
|
+
|
|
231
|
+
## `sticky`
|
|
232
|
+
|
|
233
|
+
Pass `sticky` only when your host's own document actually scrolls (e.g. a normal page
|
|
234
|
+
layout) — in that case the bar should stick to the top of the viewport as the page
|
|
235
|
+
scrolls beneath it. Leave it `false` (the default) for a host that is a fixed-height flex
|
|
236
|
+
column whose document itself never scrolls (only an inner pane does) — sticky
|
|
237
|
+
positioning there is dead weight.
|
|
238
|
+
|
|
239
|
+
If your host is `sticky` and also has its own sticky header below the bar, that header's
|
|
240
|
+
top offset has to account for the bar's height — **and that offset has to react to
|
|
241
|
+
collapse**, or a viewer who collapses the bar is left with a 56px void at the top of the
|
|
242
|
+
page where your header no longer starts. Read `collapsed` from `useNav()` and publish the
|
|
243
|
+
live height (0 collapsed, 56px expanded) as a CSS variable your header's `top` reads,
|
|
244
|
+
rather than hardcoding 56px once.
|
|
245
|
+
|
|
246
|
+
## Degradation
|
|
247
|
+
|
|
248
|
+
The bar is designed to never render blank and never block first paint:
|
|
249
|
+
|
|
250
|
+
- **Endpoint outage or timeout** — the fetch has a hard 2.5s timeout; on any failure
|
|
251
|
+
(network error, timeout, non-2xx, unparseable body) the bar falls back to its
|
|
252
|
+
compiled-in config and keeps whatever it was already showing. No visible error, no
|
|
253
|
+
console noise by default.
|
|
254
|
+
- **Unknown fields** — anything in the payload the renderer doesn't recognize is dropped
|
|
255
|
+
during normalization, never carried through and never thrown on.
|
|
256
|
+
- **A higher `schema_version` than this renderer understands** — ignored the same way;
|
|
257
|
+
the renderer keeps working on the fields it does know.
|
|
258
|
+
|
|
259
|
+
## The `schema_version` contract
|
|
260
|
+
|
|
261
|
+
Changes to the wire payload are **additive within a version**: fields are only ever
|
|
262
|
+
added, never removed or repurposed, for a given `schema_version`. A renderer must ignore
|
|
263
|
+
fields it doesn't recognize rather than throw. That's what lets the suite's
|
|
264
|
+
asynchronously-deployed satellite fleet run mixed renderer versions against one shared
|
|
265
|
+
payload — an older `@artstorefronts/arthelper-nav` keeps working after a newer field ships on the
|
|
266
|
+
config row. The version number itself is bumped only for a genuinely breaking change.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { NavConfigPayload } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Coerces an arbitrary JSON body into the payload shape. Never throws: a missing,
|
|
4
|
+
* malformed or newer-versioned field falls back to the embedded default so the
|
|
5
|
+
* bar degrades instead of blanking.
|
|
6
|
+
*
|
|
7
|
+
* `null` is reserved for a body that could not be used, because that is the signal
|
|
8
|
+
* the caller reads as "keep showing what you have". An empty `apps` array is NOT such
|
|
9
|
+
* a body: it is what the endpoint serves when an operator has disabled every
|
|
10
|
+
* destination, and treating it as a failure discarded the operator's `theme` and
|
|
11
|
+
* `logo` edits along with it and reverted every host to the compiled-in fallback —
|
|
12
|
+
* the exact inverse of the instruction.
|
|
13
|
+
*/
|
|
14
|
+
declare function normalize(raw: unknown): NavConfigPayload | null;
|
|
15
|
+
declare function cached(url: string): NavConfigPayload | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Fetches the config with a hard timeout. Resolves to null on any failure —
|
|
18
|
+
* network, timeout, non-2xx, or an unparseable body — and the caller keeps
|
|
19
|
+
* whatever it is already showing.
|
|
20
|
+
*/
|
|
21
|
+
declare function fetchConfig(url: string, timeoutMs?: number): Promise<NavConfigPayload | null>;
|
|
22
|
+
export { cached, fetchConfig, normalize };
|
|
23
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAsEvF;;;;;;;;;;;GAWG;AACH,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAuBxD;AAED,iBAAS,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEzD;AAED;;;;GAIG;AACH,iBAAe,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAmB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA0BxG;AAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { FALLBACK_CONFIG } from './fallback.js';
|
|
2
|
+
/** Short enough that a slow endpoint never delays the bar past first paint. */
|
|
3
|
+
const TIMEOUT_MS = 2500;
|
|
4
|
+
/** Last good payload per URL, so a remount paints the fresh config, not the fallback. */
|
|
5
|
+
const memo = new Map();
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function text(value, fallback) {
|
|
10
|
+
return typeof value === 'string' ? value : fallback;
|
|
11
|
+
}
|
|
12
|
+
function mode(value) {
|
|
13
|
+
return value === 'light' ? 'light' : 'dark';
|
|
14
|
+
}
|
|
15
|
+
function alignment(value) {
|
|
16
|
+
return value === 'centered' ? 'centered' : 'left';
|
|
17
|
+
}
|
|
18
|
+
function accent(value) {
|
|
19
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
20
|
+
}
|
|
21
|
+
// Unknown fields are dropped rather than carried, so a newer payload can never
|
|
22
|
+
// reach the renderer as a shape it does not understand.
|
|
23
|
+
function app(raw) {
|
|
24
|
+
if (!isRecord(raw)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (typeof raw.id !== 'string' || raw.id.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
id: raw.id,
|
|
32
|
+
label: text(raw.label, raw.id),
|
|
33
|
+
icon: text(raw.icon, ''),
|
|
34
|
+
href: text(raw.href, ''),
|
|
35
|
+
enabled: raw.enabled === true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parses the catalog, keeping an EMPTY-BUT-VALID list distinguishable from an
|
|
40
|
+
* unusable body. `[]` is a legitimate instruction — an operator who disabled every
|
|
41
|
+
* destination — and must be honoured; `null` means the body could not be read at all
|
|
42
|
+
* and the caller should keep whatever it is already showing.
|
|
43
|
+
*
|
|
44
|
+
* A non-empty array from which not one entry parsed counts as unusable, not as empty:
|
|
45
|
+
* the operator sent apps, we understood none of them, and rendering an empty bar off
|
|
46
|
+
* that is guessing.
|
|
47
|
+
*/
|
|
48
|
+
function apps(raw) {
|
|
49
|
+
if (!Array.isArray(raw)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const parsed = raw.reduce((memoized, entry) => {
|
|
53
|
+
const item = app(entry);
|
|
54
|
+
return item ? [...memoized, item] : memoized;
|
|
55
|
+
}, []);
|
|
56
|
+
if (raw.length > 0 && parsed.length === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Coerces an arbitrary JSON body into the payload shape. Never throws: a missing,
|
|
63
|
+
* malformed or newer-versioned field falls back to the embedded default so the
|
|
64
|
+
* bar degrades instead of blanking.
|
|
65
|
+
*
|
|
66
|
+
* `null` is reserved for a body that could not be used, because that is the signal
|
|
67
|
+
* the caller reads as "keep showing what you have". An empty `apps` array is NOT such
|
|
68
|
+
* a body: it is what the endpoint serves when an operator has disabled every
|
|
69
|
+
* destination, and treating it as a failure discarded the operator's `theme` and
|
|
70
|
+
* `logo` edits along with it and reverted every host to the compiled-in fallback —
|
|
71
|
+
* the exact inverse of the instruction.
|
|
72
|
+
*/
|
|
73
|
+
function normalize(raw) {
|
|
74
|
+
if (!isRecord(raw)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const theme = isRecord(raw.theme) ? raw.theme : {};
|
|
78
|
+
const logo = isRecord(raw.logo) ? raw.logo : {};
|
|
79
|
+
const parsed = apps(raw.apps);
|
|
80
|
+
if (!parsed) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
schema_version: typeof raw.schema_version === 'number' ? raw.schema_version : FALLBACK_CONFIG.schema_version,
|
|
85
|
+
theme: {
|
|
86
|
+
mode: mode(theme.mode),
|
|
87
|
+
accent_color: accent(theme.accent_color),
|
|
88
|
+
alignment: alignment(theme.alignment),
|
|
89
|
+
},
|
|
90
|
+
logo: {
|
|
91
|
+
label: text(logo.label, FALLBACK_CONFIG.logo.label),
|
|
92
|
+
href: text(logo.href, FALLBACK_CONFIG.logo.href),
|
|
93
|
+
},
|
|
94
|
+
apps: parsed,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function cached(url) {
|
|
98
|
+
return memo.get(url);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fetches the config with a hard timeout. Resolves to null on any failure —
|
|
102
|
+
* network, timeout, non-2xx, or an unparseable body — and the caller keeps
|
|
103
|
+
* whatever it is already showing.
|
|
104
|
+
*/
|
|
105
|
+
async function fetchConfig(url, timeoutMs = TIMEOUT_MS) {
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
108
|
+
try {
|
|
109
|
+
// NEVER set credentials to 'include' or 'same-origin'. The endpoint is served
|
|
110
|
+
// through a bare cors() and returns the LITERAL `Access-Control-Allow-Origin: *`,
|
|
111
|
+
// not a reflected origin. `*` and credentials are mutually exclusive in the
|
|
112
|
+
// browser, so credentialed requests would have every cross-origin response
|
|
113
|
+
// rejected — stranding the bar on the embedded fallback forever, silently, with
|
|
114
|
+
// no console error and no visible failure. 'omit' is the only safe value.
|
|
115
|
+
const response = await fetch(url, { signal: controller.signal, credentials: 'omit' });
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const next = normalize(await response.json());
|
|
120
|
+
if (!next) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
memo.set(url, next);
|
|
124
|
+
return next;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export { cached, fetchConfig, normalize };
|
|
134
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NavConfigPayload, ResolvedNavApp } from './types.js';
|
|
2
|
+
interface NavContextValue {
|
|
3
|
+
/** The config currently being rendered — the embedded fallback until the fetch lands. */
|
|
4
|
+
config: NavConfigPayload;
|
|
5
|
+
/** The catalog id of the app the host itself is. Always supplied by the host. */
|
|
6
|
+
activeAppId: string;
|
|
7
|
+
/** Enabled-and-has-destination entries, plus the host's own app. */
|
|
8
|
+
apps: ResolvedNavApp[];
|
|
9
|
+
/** The host's own entry, always present unless the catalog has never heard of it. */
|
|
10
|
+
activeApp: ResolvedNavApp | undefined;
|
|
11
|
+
collapsed: boolean;
|
|
12
|
+
setCollapsed: (collapsed: boolean) => void;
|
|
13
|
+
toggle: () => void;
|
|
14
|
+
/** Fires the host's onAppClick seam. Navigation itself is the anchor's own. */
|
|
15
|
+
activate: (app: ResolvedNavApp) => void;
|
|
16
|
+
}
|
|
17
|
+
declare const NavContext: import("react").Context<NavContextValue | null>;
|
|
18
|
+
declare function useNav(): NavContextValue;
|
|
19
|
+
export { NavContext, useNav };
|
|
20
|
+
export type { NavContextValue };
|
|
21
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnE,UAAU,eAAe;IACvB,yFAAyF;IACzF,MAAM,EAAE,gBAAgB,CAAC;IACzB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,qFAAqF;IACrF,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,+EAA+E;IAC/E,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;CACzC;AAED,QAAA,MAAM,UAAU,iDAA8C,CAAC;AAE/D,iBAAS,MAAM,IAAI,eAAe,CAMjC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
const NavContext = createContext(null);
|
|
4
|
+
function useNav() {
|
|
5
|
+
const value = useContext(NavContext);
|
|
6
|
+
if (!value) {
|
|
7
|
+
throw new Error('useNav must be used inside <NavProvider>');
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export { NavContext, useNav };
|
|
12
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAmBlD,MAAM,UAAU,GAAG,aAAa,CAAyB,IAAI,CAAC,CAAC;AAE/D,SAAS,MAAM;IACb,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/cookie.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The collapsed flag lives in a cookie, not localStorage, so a server-rendered
|
|
3
|
+
* host can read it and paint the first byte already collapsed — no expanded ->
|
|
4
|
+
* collapsed 56px jump after hydration.
|
|
5
|
+
*
|
|
6
|
+
* Per-origin by design: app.arthelper.ai and www.arthelper.com are different
|
|
7
|
+
* registrable domains and share no cookie scope. There is no cross-origin sync.
|
|
8
|
+
*/
|
|
9
|
+
declare const COLLAPSED_COOKIE = "arthelper_suite_nav_collapsed";
|
|
10
|
+
declare function readCollapsed(): boolean | null;
|
|
11
|
+
declare function writeCollapsed(collapsed: boolean): void;
|
|
12
|
+
export { COLLAPSED_COOKIE, readCollapsed, writeCollapsed };
|
|
13
|
+
//# sourceMappingURL=cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,QAAA,MAAM,gBAAgB,kCAAkC,CAAC;AAIzD,iBAAS,aAAa,IAAI,OAAO,GAAG,IAAI,CASvC;AAED,iBAAS,cAAc,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAKhD;AAED,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/cookie.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The collapsed flag lives in a cookie, not localStorage, so a server-rendered
|
|
3
|
+
* host can read it and paint the first byte already collapsed — no expanded ->
|
|
4
|
+
* collapsed 56px jump after hydration.
|
|
5
|
+
*
|
|
6
|
+
* Per-origin by design: app.arthelper.ai and www.arthelper.com are different
|
|
7
|
+
* registrable domains and share no cookie scope. There is no cross-origin sync.
|
|
8
|
+
*/
|
|
9
|
+
const COLLAPSED_COOKIE = 'arthelper_suite_nav_collapsed';
|
|
10
|
+
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
11
|
+
function readCollapsed() {
|
|
12
|
+
if (typeof document === 'undefined') {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const match = document.cookie.split('; ').find((entry) => entry.startsWith(`${COLLAPSED_COOKIE}=`));
|
|
16
|
+
if (!match) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return match.slice(COLLAPSED_COOKIE.length + 1) === 'true';
|
|
20
|
+
}
|
|
21
|
+
function writeCollapsed(collapsed) {
|
|
22
|
+
if (typeof document === 'undefined') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
document.cookie = `${COLLAPSED_COOKIE}=${collapsed}; path=/; max-age=${ONE_YEAR_SECONDS}; SameSite=Lax`;
|
|
26
|
+
}
|
|
27
|
+
export { COLLAPSED_COOKIE, readCollapsed, writeCollapsed };
|
|
28
|
+
//# sourceMappingURL=cookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.js","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAEzD,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;AAE5C,SAAS,aAAa;IACpB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,gBAAgB,GAAG,CAAC,CAAC,CAAC;IACpG,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC;AAC7D,CAAC;AAED,SAAS,cAAc,CAAC,SAAkB;IACxC,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO;IACT,CAAC;IACD,QAAQ,CAAC,MAAM,GAAG,GAAG,gBAAgB,IAAI,SAAS,qBAAqB,gBAAgB,gBAAgB,CAAC;AAC1G,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NavConfigPayload } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The static config compiled into the package. Used whenever the fetch fails,
|
|
4
|
+
* times out, or has not resolved yet, so the bar reserves its final 56px from
|
|
5
|
+
* first paint and never renders blank.
|
|
6
|
+
*
|
|
7
|
+
* It carries ONLY entries that have a destination. Changing it requires a
|
|
8
|
+
* package release, which is exactly why it holds the stable first-party
|
|
9
|
+
* destination and never the satellites — those arrive over the wire.
|
|
10
|
+
*/
|
|
11
|
+
declare const FALLBACK_CONFIG: NavConfigPayload;
|
|
12
|
+
export { FALLBACK_CONFIG };
|
|
13
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;GAQG;AACH,QAAA,MAAM,eAAe,EAAE,gBAKtB,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/fallback.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static config compiled into the package. Used whenever the fetch fails,
|
|
3
|
+
* times out, or has not resolved yet, so the bar reserves its final 56px from
|
|
4
|
+
* first paint and never renders blank.
|
|
5
|
+
*
|
|
6
|
+
* It carries ONLY entries that have a destination. Changing it requires a
|
|
7
|
+
* package release, which is exactly why it holds the stable first-party
|
|
8
|
+
* destination and never the satellites — those arrive over the wire.
|
|
9
|
+
*/
|
|
10
|
+
const FALLBACK_CONFIG = {
|
|
11
|
+
schema_version: 1,
|
|
12
|
+
theme: { mode: 'dark', accent_color: '#2aa34f', alignment: 'left' },
|
|
13
|
+
logo: { label: 'ArtHelper', href: 'https://www.arthelper.com' },
|
|
14
|
+
apps: [{ id: 'home', label: 'Home', icon: 'house', href: 'https://www.arthelper.com', enabled: true }],
|
|
15
|
+
};
|
|
16
|
+
export { FALLBACK_CONFIG };
|
|
17
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAqB;IACxC,cAAc,EAAE,CAAC;IACjB,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE;IACnE,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,2BAA2B,EAAE;IAC/D,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;CACvG,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface NavIconProps {
|
|
2
|
+
name: string;
|
|
3
|
+
size?: number;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
/** An unrecognised icon name degrades to a neutral circle rather than an empty chip. */
|
|
7
|
+
declare function NavIcon({ name, size, className }: NavIconProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
/** The waffle: hide control in the bar, restore affordance in NavTrigger. */
|
|
9
|
+
declare function WaffleIcon({ size, className }: {
|
|
10
|
+
size?: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export { NavIcon, WaffleIcon };
|
|
14
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/icons.tsx"],"names":[],"mappings":"AA+BA,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wFAAwF;AACxF,iBAAS,OAAO,CAAC,EAAE,IAAI,EAAE,IAAS,EAAE,SAA0B,EAAE,EAAE,YAAY,2CAG7E;AAED,6EAA6E;AAC7E,iBAAS,UAAU,CAAC,EAAE,IAAS,EAAE,SAA0B,EAAE,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,2CAEnG;AAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/icons.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { BookOpen, Circle, Frame, House, LayoutGrid, Megaphone, Sparkles, Star, Sunrise, } from 'lucide-react';
|
|
4
|
+
/**
|
|
5
|
+
* The catalog's icon names, keyed exactly as the seed spells them. lucide emits
|
|
6
|
+
* inline stroke SVGs with viewBox="0 0 24 24", stroke-width 2 and round caps,
|
|
7
|
+
* which is the icon contract verbatim — nothing here draws its own paths.
|
|
8
|
+
*/
|
|
9
|
+
const ICONS = {
|
|
10
|
+
house: House,
|
|
11
|
+
sunrise: Sunrise,
|
|
12
|
+
star: Star,
|
|
13
|
+
sparkles: Sparkles,
|
|
14
|
+
frame: Frame,
|
|
15
|
+
'book-open': BookOpen,
|
|
16
|
+
megaphone: Megaphone,
|
|
17
|
+
'layout-grid': LayoutGrid,
|
|
18
|
+
};
|
|
19
|
+
/** An unrecognised icon name degrades to a neutral circle rather than an empty chip. */
|
|
20
|
+
function NavIcon({ name, size = 16, className = 'ahsuite-icon' }) {
|
|
21
|
+
const Icon = ICONS[name] ?? Circle;
|
|
22
|
+
return _jsx(Icon, { "aria-hidden": "true", className: className, width: size, height: size, strokeWidth: 2 });
|
|
23
|
+
}
|
|
24
|
+
/** The waffle: hide control in the bar, restore affordance in NavTrigger. */
|
|
25
|
+
function WaffleIcon({ size = 24, className = 'ahsuite-icon' }) {
|
|
26
|
+
return _jsx(LayoutGrid, { "aria-hidden": "true", className: className, width: size, height: size, strokeWidth: 2 });
|
|
27
|
+
}
|
|
28
|
+
export { NavIcon, WaffleIcon };
|
|
29
|
+
//# sourceMappingURL=icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../src/icons.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EACL,QAAQ,EACR,MAAM,EACN,KAAK,EACL,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,OAAO,GAER,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,KAAK,GAA+B;IACxC,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,QAAQ;IACrB,SAAS,EAAE,SAAS;IACpB,aAAa,EAAE,UAAU;CAC1B,CAAC;AAQF,wFAAwF;AACxF,SAAS,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,GAAG,cAAc,EAAgB;IAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;IACnC,OAAO,KAAC,IAAI,mBAAa,MAAM,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,GAAI,CAAC;AACtG,CAAC;AAED,6EAA6E;AAC7E,SAAS,UAAU,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,GAAG,cAAc,EAAyC;IAClG,OAAO,KAAC,UAAU,mBAAa,MAAM,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,GAAI,CAAC;AAC5G,CAAC;AAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC"}
|