@artstorefronts/arthelper-nav 0.1.0-alpha.0 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,13 +7,172 @@ This document is for an engineer wiring the bar into a host app that lives **out
7
7
  this monorepo. If you're working inside this repo, see `docs/llms/suite-nav.md` for how
8
8
  the pieces fit together end to end.
9
9
 
10
+ ## Agent implementation prompt
11
+
12
+ Copy the block below and hand it to a coding agent working in the host app. It is written
13
+ to be self-contained apart from this README, which it tells the agent to read — the agent
14
+ will have it locally once the package is installed.
15
+
16
+ ````markdown
17
+ # Task: mount the ArtHelper suite nav bar in this app
18
+
19
+ This app is part of the ArtHelper suite. Add the shared suite bar so the suite reads as one
20
+ product. The bar is a published package — you are wiring it in, not building it.
21
+
22
+ ## First
23
+
24
+ ```bash
25
+ npm install @artstorefronts/arthelper-nav
26
+ ```
27
+
28
+ Then read `node_modules/@artstorefronts/arthelper-nav/README.md` in full before writing any
29
+ code. It is the authoritative API reference and explains the layout contract, the collapsed
30
+ cookie, persistence and degradation behaviour. Peer dependencies are `react`, `react-dom`
31
+ and `lucide-react` — install them yourself if this app doesn't already have them.
32
+
33
+ ## Find this app's catalog id
34
+
35
+ `activeAppId` must be the id the shared catalog uses for this app — not this repo's name.
36
+ List them:
37
+
38
+ ```bash
39
+ curl -s https://api.arthelper.ai/rest/nav-config | jq -r '.apps[].id'
40
+ ```
41
+
42
+ Pick the one that is this app and use it verbatim. A wrong value means the bar cannot
43
+ indicate where the user is and the collapse control has no chip to dock. If none of the ids
44
+ is obviously this app, stop and ask — an id is added by an operator, not by you.
45
+
46
+ ## Wire it in
47
+
48
+ 1. **Import the stylesheet** by deep path in your global CSS entry:
49
+ `@import '@artstorefronts/arthelper-nav/dist/index.css';`
50
+ Under Tailwind v4 every `@import` must precede other rules. The CSS is scoped with an
51
+ `ahsuite-` prefix and uses no Tailwind utilities, so it needs no `@source` entry and
52
+ cannot be purged.
53
+
54
+ 2. **Wrap the app root in `NavProvider`**, passing `configUrl` and `activeAppId`.
55
+
56
+ 3. **Render `NavBar` as the first child of the app shell, in normal document flow.** It
57
+ occupies real layout space; the page should shift down exactly once, at first paint.
58
+ Never wrap it in a fixed/absolute container and never portal it.
59
+
60
+ 4. **Put `NavTrigger` in this app's own header, beside its logo.** This is not optional:
61
+ when the bar is collapsed, `NavTrigger` is the only way to restore it. Ship without it
62
+ and a user who collapses the bar can never get it back. It hides itself while the bar is
63
+ expanded, so you just place it.
64
+
65
+ 5. **If this app server-renders**, read the collapsed cookie on the server and pass it as
66
+ `initialCollapsed`. Import the cookie's name from the package rather than retyping it.
67
+ Without this, a user who collapsed the bar gets an expanded bar in the first byte and a
68
+ 56px jump after hydration.
69
+
70
+ 6. **Optionally pass `onAppClick`** to emit your own analytics. The package imports no
71
+ analytics library, and one callback covers both the desktop tiles and the mobile dropdown.
72
+
73
+ ## `configUrl` — the mistake to avoid
74
+
75
+ It is the ArtHelper API's **REST** endpoint, ending `/rest/nav-config`:
76
+
77
+ - production: `https://api.arthelper.ai/rest/nav-config`
78
+ - local: your ArtHelper stack's API port, same path
79
+
80
+ Read it from this app's environment config with a sensible default; do not hardcode one
81
+ value. **It is not the GraphQL endpoint.** Pointing it at `/graphql` returns 404 and the bar
82
+ falls back _silently_ to its compiled-in catalog — no console error, no visible failure,
83
+ just a bar that never reflects the admin screen. If tiles never update, curl the URL before
84
+ debugging anything else.
85
+
86
+ The endpoint is public, unauthenticated and CORS-open. **Do not send credentials** — the
87
+ response carries a literal `Access-Control-Allow-Origin: *`, which browsers reject for
88
+ credentialed requests.
89
+
90
+ ## Sticky, and the audit that goes with it
91
+
92
+ Decide whether the bar is sticky:
93
+
94
+ - **This app's document scrolls** (a normal page): use `<NavBar sticky />`, and offset your
95
+ own sticky header's `top` by the bar's height. **That offset must react to collapse** —
96
+ read `collapsed` from `useNav()` and use `collapsed ? 0 : <bar height>`. A constant offset
97
+ leaves a visible void at scroll-top once the bar collapses.
98
+ - **The shell is a fixed-height flex column** that never scrolls the document: do not pass
99
+ `sticky` and change no offsets.
100
+
101
+ **Then audit the whole app for surfaces that assume the header's bottom edge, or that they
102
+ can occupy the full viewport height.** Grep for `top-14`, `top: 3.5rem`, `h-screen`,
103
+ `h-svh`, `h-dvh`, `100vh`, `100svh`, `100dvh`, and `calc(100vh - N)` patterns. Each one is
104
+ now short by the bar's height while the bar is expanded, and will sit _behind_ your header
105
+ or run off the bottom of the screen.
106
+
107
+ This is the single largest source of breakage when adopting the bar. The two apps inside the
108
+ ArtHelper monorepo needed **nine** such fixes between them, including a `position: fixed`
109
+ sidebar rail, two pages subtracting a hardcoded pixel count, and an overlay panel rendered
110
+ through a portal that could not inherit a CSS variable at all. Derive them all from one
111
+ shared value rather than sprinkling `collapsed ?` checks through components, and remember
112
+ that a portalled surface may need the value passed to it another way.
113
+
114
+ Judge each hit: a surface inside a scroll container that never touches the viewport edge is
115
+ unaffected, and a modal meant to cover the chrome should keep covering it. Say which you
116
+ changed and which you deliberately did not.
117
+
118
+ ## What is not configurable
119
+
120
+ The bar's **look** — colour, alignment, accent, the wordmark — is compiled into the package,
121
+ not served in the payload. Do not look for theme props or try to restyle it from the host;
122
+ changing it is a package release. Only the app catalog is configurable.
123
+
124
+ ## Expected behaviour, so you don't chase non-bugs
125
+
126
+ - **Only some apps appear.** An app renders only once an operator has both enabled it and
127
+ given it a destination in ArtHelper's admin screen.
128
+ - **This app's own chip always renders**, even if its catalog entry is disabled or has no
129
+ destination. With no destination it is a non-navigating indicator chip. That is correct —
130
+ it tells the user where they are.
131
+ - **The bar survives an endpoint outage** by falling back to a catalog compiled into the
132
+ package, so it renders at full height rather than blank and never blocks first paint.
133
+ - **The catalog is persisted** after the first successful fetch, so repeat loads paint the
134
+ last known catalog rather than the compiled-in one.
135
+
136
+ ## Verify in a real browser, not by reasoning
137
+
138
+ 1. The bar renders above your header; measure its height with `getBoundingClientRect()` and
139
+ confirm it matches the documented height exactly.
140
+ 2. Collapse it: height 0, your header rises to the very top with no gap, chip appears beside
141
+ your logo. Restore it: back to full height.
142
+ 3. Reload while collapsed — it stays collapsed.
143
+ 4. Click another app's tile — the address bar becomes that destination.
144
+ 5. If sticky: scroll a few hundred pixels in both bar states and confirm your header is
145
+ neither covered nor floating above a void.
146
+ 6. Below the `md` breakpoint the tiles become a labelled dropdown; every app is reachable and
147
+ it dismisses without leaving the page.
148
+ 7. Block the config endpoint in devtools — the bar still renders at full height with this
149
+ app's own chip, not blank.
150
+ 8. If this app server-renders, check the console for hydration warnings. There must be none.
151
+
152
+ ## Do not
153
+
154
+ - Do not fork, vendor or patch the package. Report upstream problems instead.
155
+ - Do not hardcode any app's destination — destinations are config.
156
+ - Do not infer `activeAppId` from the URL; the host declares it.
157
+ - Do not send credentials with the config request.
158
+ - Do not render a second bar if this app is ever framed inside another ArtHelper surface.
159
+ - Avoid `yarn link` unless necessary; if you use it, make sure the bundler resolves a single
160
+ copy of React or you will hit the duplicate-React hooks error. `npm pack` plus installing
161
+ the tarball avoids that and resolves more like the real thing.
162
+ ````
163
+
10
164
  ## Architecture in one line
11
165
 
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.
166
+ Configure the catalog in data, ship everything else in code. The **app catalog** lives in
167
+ a single database row, served as JSON over a public HTTP endpoint. Every mounted host
168
+ fetches that JSON on load, so a catalog edit made in the admin editor is live for every
169
+ app in the suite on their next page load — no deploy, no version bump, no coordinated
170
+ release across repos.
171
+
172
+ The payload is `{ schema_version, apps }` and nothing else. The bar's look and its
173
+ wordmark are compiled in, on purpose — see [Theming and the wordmark](#theming-and-the-wordmark).
174
+ The last good catalog is persisted per browser so a return visit paints it rather than the
175
+ compiled-in fallback — see [Persistence](#persistence).
17
176
 
18
177
  ## Install
19
178
 
@@ -31,21 +190,13 @@ Peer dependencies (from `package.json` — install these yourself, they are not
31
190
  }
32
191
  ```
33
192
 
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.
193
+ > **Getting a change released.** There is no tag and no manual release step: bump the
194
+ > version in `packages/nav/package.json` in the PR that changes the package, and merging
195
+ > to `develop` publishes it. CI publishes only when that version is absent from the
196
+ > registry, and a PR that changes the published surface while leaving the version alone
197
+ > fails its own check — so a change cannot quietly land without reaching you.
46
198
  >
47
- > To develop against it before the first publish, use a workspace link or a path
48
- > dependency from the satellite repo:
199
+ > **Testing an unreleased change.** Link the workspace, or use a path dependency:
49
200
  >
50
201
  > ```bash
51
202
  > cd packages/nav && yarn link
@@ -54,7 +205,8 @@ Peer dependencies (from `package.json` — install these yourself, they are not
54
205
  >
55
206
  > `react` and `react-dom` are peer dependencies, so make sure the satellite's bundler
56
207
  > resolves a single copy of React — a linked package pulling in its own is the usual
57
- > cause of the duplicate-React hooks error.
208
+ > cause of the duplicate-React hooks error. `npm pack` plus installing the tarball avoids
209
+ > that failure mode entirely and resolves more like the real thing.
58
210
 
59
211
  ## Minimal mounting example
60
212
 
@@ -108,7 +260,7 @@ wrap `NavBar`, `NavTrigger`, and any component calling `useNav()`.
108
260
 
109
261
  ### `NavBar`
110
262
 
111
- Renders the bar itself: waffle toggle, wordmark/logo, the desktop tile row (or, below the
263
+ Renders the bar itself: waffle toggle, wordmark, the desktop tile row (or, below the
112
264
  768px breakpoint, a labelled dropdown trigger and panel with the same entries as rows).
113
265
 
114
266
  | Prop | Type | Default | Description |
@@ -130,9 +282,8 @@ mounting `NavTrigger` ships a bar a viewer can collapse and never recover.
130
282
 
131
283
  ### Exported types
132
284
 
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
285
+ `NavContextValue`, `NavBarProps`, `NavTriggerProps`, `NavProviderProps`, `NavApp`,
286
+ `NavAppClickArgs`, `NavConfigPayload`, `ResolvedNavApp` — all exported from the package root (`src/index.ts`), also useful if
136
287
  you're typing `fallbackConfig` or `onAppClick` yourself.
137
288
 
138
289
  `useNav()` is also exported, for a component nested under `NavProvider` that needs
@@ -213,12 +364,59 @@ The cookie is **per-origin**. `app.example.com` and `www.example.com` are differ
213
364
  registrable domains and share no cookie scope, so collapsing the bar on one origin does
214
365
  not sync to another — each origin's viewer sets their own collapsed state independently.
215
366
 
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.
367
+ ## Theming and the wordmark
368
+
369
+ There is no theming, deliberately. The bar ships one look — dark "Forest", tiles centred
370
+ on the bar — baked into `dist/index.css`, and the wordmark (`arthelper`, linking to
371
+ `https://www.arthelper.com`) is baked into the component. No part of either can be set by
372
+ a host or by the payload.
373
+
374
+ Both used to be configurable: the payload carried a `theme` block (`mode`,
375
+ `accent_color`, `alignment`) and a `logo` block (`label`, `href`) that an admin edited in
376
+ the nav editor. That was a mistake, and the reason generalizes to every configurable
377
+ field. The bar paints the package's compiled-in fallback before the fetch resolves and
378
+ swaps in the served config afterwards, so **any** configured value that disagrees with the
379
+ fallback's guess produces a visible change on every page load — a dark bar turning light,
380
+ tiles jumping from left to centre, a wordmark rewriting itself. Colour, layout and the
381
+ wordmark are release decisions, reviewed once and then stable. The app catalog is the one
382
+ genuine operational switch, and it is all the payload carries now.
383
+
384
+ The wordmark's href is the site root, not the resolved `home` catalog entry. Deriving it
385
+ was considered and rejected: it would put a static piece of chrome back on config, and
386
+ `home` points at `/community` while the wordmark has always gone to the root.
387
+
388
+ Changing the look or the wordmark now means a package release. A host that wants the bar
389
+ on differently-coloured chrome should change its own surrounding chrome, not the bar.
390
+
391
+ A payload that still carries `theme` or `logo` has them dropped during normalization like
392
+ any other unrecognized field, and an older renderer that still expects them degrades to
393
+ its own compiled-in defaults rather than throwing.
394
+
395
+ ## Persistence
396
+
397
+ The catalog is the one field that can still differ from the compiled-in fallback, and
398
+ because the tile row is centred on the bar, that difference is expensive: every tile the
399
+ fetch adds or removes shifts the whole row by half a tile, so a two-tile fallback resolving
400
+ to a seven-tile config moves the Home tile 230px.
401
+
402
+ So a successful fetch persists the normalized payload to `localStorage` under
403
+ `ahsuite-nav-config-v1`, and on mount the bar prefers that over the compiled-in fallback.
404
+ The fallback becomes the first-ever-visit case.
405
+
406
+ Things worth knowing if you are wiring this into a host:
407
+
408
+ - **The persisted blob is re-validated on read**, through the same `normalize` a network
409
+ body goes through. A blob written months ago by an older renderer is untrusted input.
410
+ - **Entries expire after 30 days**, so a long-dormant browser revalidates rather than
411
+ painting something ancient.
412
+ - **Every storage access is wrapped**, including the `localStorage` property read itself —
413
+ Safari's private mode and Chrome with site data blocked throw on _access_, not just on
414
+ write. An unreachable store simply means the fallback paints.
415
+ - **It cannot help a server-rendered host's first frame.** The server has no access to the
416
+ viewer's `localStorage`, so its HTML is always the compiled-in fallback. The read runs in
417
+ a client-only layout effect — before the browser paints, after hydration has committed —
418
+ which removes the hydration-time swap but not the SSR one. If that first frame matters to
419
+ you, pass the catalog you want server-rendered as `fallbackConfig`.
222
420
 
223
421
  ## The layout contract
224
422
 
@@ -255,6 +453,8 @@ The bar is designed to never render blank and never block first paint:
255
453
  during normalization, never carried through and never thrown on.
256
454
  - **A higher `schema_version` than this renderer understands** — ignored the same way;
257
455
  the renderer keeps working on the fields it does know.
456
+ - **A corrupt, stale or unreachable `localStorage`** — treated as "nothing persisted": the
457
+ compiled-in fallback paints and the fetch revalidates. Never throws.
258
458
 
259
459
  ## The `schema_version` contract
260
460
 
@@ -264,3 +464,10 @@ fields it doesn't recognize rather than throw. That's what lets the suite's
264
464
  asynchronously-deployed satellite fleet run mixed renderer versions against one shared
265
465
  payload — an older `@artstorefronts/arthelper-nav` keeps working after a newer field ships on the
266
466
  config row. The version number itself is bumped only for a genuinely breaking change.
467
+
468
+ **The one exception so far:** `theme` was _removed_ from the payload without bumping
469
+ `schema_version` off 1. Removal is breaking under this contract, and it was allowed only
470
+ because no stable version had ever been released against the old shape — the two
471
+ `0.1.0-alpha.*` publishes both tolerate the field's absence and degrade rather than
472
+ throw, which was verified against the actual tarballs. After the first stable publish the
473
+ same removal would require a version bump and a transition period.
package/dist/config.d.ts CHANGED
@@ -7,17 +7,34 @@ import type { NavConfigPayload } from './types.js';
7
7
  * `null` is reserved for a body that could not be used, because that is the signal
8
8
  * the caller reads as "keep showing what you have". An empty `apps` array is NOT such
9
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.
10
+ * destination, and treating it as a failure reverted every host to the compiled-in
11
+ * fallback and painted a hardcoded Home tile — the exact inverse of the instruction.
12
+ *
13
+ * A payload that still carries the retired `theme` or `logo` blocks drops them here like
14
+ * any other unrecognised field, which is what lets a stage serving the old shape run
15
+ * against this renderer while the endpoint change lands separately.
16
+ *
17
+ * This is also the gate every PERSISTED payload passes through (see `restored`). A blob
18
+ * out of localStorage is untrusted input in exactly the way a network body is — it may
19
+ * have been written months ago by a renderer with a different shape — so it gets the same
20
+ * coercion rather than a JSON.parse and a cast.
13
21
  */
14
22
  declare function normalize(raw: unknown): NavConfigPayload | null;
15
23
  declare function cached(url: string): NavConfigPayload | undefined;
24
+ /**
25
+ * The persisted last-good payload, re-validated. Null when there is nothing stored, the
26
+ * store is unreachable, the blob is corrupt, or the entry is past its staleness cap —
27
+ * every one of which means "paint the compiled-in fallback and wait for the fetch".
28
+ *
29
+ * The dependency runs config -> storage and never back, which is why `writeStored` is
30
+ * called from `fetchConfig` here rather than storage.ts reaching for `normalize` itself.
31
+ */
32
+ declare function restored(): NavConfigPayload | null;
16
33
  /**
17
34
  * Fetches the config with a hard timeout. Resolves to null on any failure —
18
35
  * network, timeout, non-2xx, or an unparseable body — and the caller keeps
19
36
  * whatever it is already showing.
20
37
  */
21
38
  declare function fetchConfig(url: string, timeoutMs?: number): Promise<NavConfigPayload | null>;
22
- export { cached, fetchConfig, normalize };
39
+ export { cached, fetchConfig, normalize, restored };
23
40
  //# sourceMappingURL=config.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA0D3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAYxD;AAED,iBAAS,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEzD;AAED;;;;;;;GAOG;AACH,iBAAS,QAAQ,IAAI,gBAAgB,GAAG,IAAI,CAE3C;AAED;;;;GAIG;AACH,iBAAe,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAmB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA2BxG;AAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC"}
package/dist/config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FALLBACK_CONFIG } from './fallback.js';
2
+ import { readStored, writeStored } from './storage.js';
2
3
  /** Short enough that a slow endpoint never delays the bar past first paint. */
3
4
  const TIMEOUT_MS = 2500;
4
5
  /** Last good payload per URL, so a remount paints the fresh config, not the fallback. */
@@ -9,15 +10,6 @@ function isRecord(value) {
9
10
  function text(value, fallback) {
10
11
  return typeof value === 'string' ? value : fallback;
11
12
  }
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
13
  // Unknown fields are dropped rather than carried, so a newer payload can never
22
14
  // reach the renderer as a shape it does not understand.
23
15
  function app(raw) {
@@ -66,37 +58,45 @@ function apps(raw) {
66
58
  * `null` is reserved for a body that could not be used, because that is the signal
67
59
  * the caller reads as "keep showing what you have". An empty `apps` array is NOT such
68
60
  * 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.
61
+ * destination, and treating it as a failure reverted every host to the compiled-in
62
+ * fallback and painted a hardcoded Home tile — the exact inverse of the instruction.
63
+ *
64
+ * A payload that still carries the retired `theme` or `logo` blocks drops them here like
65
+ * any other unrecognised field, which is what lets a stage serving the old shape run
66
+ * against this renderer while the endpoint change lands separately.
67
+ *
68
+ * This is also the gate every PERSISTED payload passes through (see `restored`). A blob
69
+ * out of localStorage is untrusted input in exactly the way a network body is — it may
70
+ * have been written months ago by a renderer with a different shape — so it gets the same
71
+ * coercion rather than a JSON.parse and a cast.
72
72
  */
73
73
  function normalize(raw) {
74
74
  if (!isRecord(raw)) {
75
75
  return null;
76
76
  }
77
- const theme = isRecord(raw.theme) ? raw.theme : {};
78
- const logo = isRecord(raw.logo) ? raw.logo : {};
79
77
  const parsed = apps(raw.apps);
80
78
  if (!parsed) {
81
79
  return null;
82
80
  }
83
81
  return {
84
82
  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
83
  apps: parsed,
95
84
  };
96
85
  }
97
86
  function cached(url) {
98
87
  return memo.get(url);
99
88
  }
89
+ /**
90
+ * The persisted last-good payload, re-validated. Null when there is nothing stored, the
91
+ * store is unreachable, the blob is corrupt, or the entry is past its staleness cap —
92
+ * every one of which means "paint the compiled-in fallback and wait for the fetch".
93
+ *
94
+ * The dependency runs config -> storage and never back, which is why `writeStored` is
95
+ * called from `fetchConfig` here rather than storage.ts reaching for `normalize` itself.
96
+ */
97
+ function restored() {
98
+ return normalize(readStored());
99
+ }
100
100
  /**
101
101
  * Fetches the config with a hard timeout. Resolves to null on any failure —
102
102
  * network, timeout, non-2xx, or an unparseable body — and the caller keeps
@@ -121,6 +121,7 @@ async function fetchConfig(url, timeoutMs = TIMEOUT_MS) {
121
121
  return null;
122
122
  }
123
123
  memo.set(url, next);
124
+ writeStored(next);
124
125
  return next;
125
126
  }
126
127
  catch {
@@ -130,5 +131,5 @@ async function fetchConfig(url, timeoutMs = TIMEOUT_MS) {
130
131
  clearTimeout(timer);
131
132
  }
132
133
  }
133
- export { cached, fetchConfig, normalize };
134
+ export { cached, fetchConfig, normalize, restored };
134
135
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,32 @@
1
1
  import type { NavConfigPayload } from './types.js';
2
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.
3
+ * The static config compiled into the package. Used whenever the fetch fails, times out,
4
+ * or has not resolved yet, so the bar reserves its final 56px from first paint and never
5
+ * renders blank.
6
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.
7
+ * It carries the WHOLE catalog, not just the enabled entries, and that is load-bearing
8
+ * rather than tidiness. A host's own `activeAppId` entry is always rendered and indicated,
9
+ * resolved from this list when the payload omits it — so a satellite whose id is missing
10
+ * here loses its chip entirely during an endpoint outage, and with it any indication of
11
+ * where the user is and anything for NavTrigger to dock. Every app that can mount the bar
12
+ * needs an entry, whether or not it is currently switched on.
13
+ *
14
+ * `enabled` here still governs what renders: the same enabled-and-has-destination filter
15
+ * runs against this list as against the fetched payload, so a disabled entry contributes
16
+ * its label, icon, order and destination without appearing as a tile.
17
+ *
18
+ * It carries no look and no wordmark. Colour, layout and the wordmark are compiled in
19
+ * (index.css and nav-bar.tsx), so there is nothing for a fetched payload to disagree with
20
+ * and nothing to flash between first paint and the fetch landing. The catalog is the one
21
+ * field that can still differ — which is why the last good payload is persisted; see
22
+ * storage.ts.
23
+ *
24
+ * These are the production domains, which is why they can live in compiled code at all.
25
+ * Changing this list requires a package release, so it is deliberately the stable
26
+ * first-party set — anything situational (a stage host, a locally-run satellite) belongs
27
+ * in the config row, where an admin can edit it without a release. `enabled` states drift
28
+ * from the row over time; that is expected and harmless, since the row wins whenever the
29
+ * endpoint is reachable.
10
30
  */
11
31
  declare const FALLBACK_CONFIG: NavConfigPayload;
12
32
  export { FALLBACK_CONFIG };
@@ -1 +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"}
1
+ {"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,eAAe,EAAE,gBAuBtB,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
package/dist/fallback.js CHANGED
@@ -1,17 +1,55 @@
1
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.
2
+ * The static config compiled into the package. Used whenever the fetch fails, times out,
3
+ * or has not resolved yet, so the bar reserves its final 56px from first paint and never
4
+ * renders blank.
5
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.
6
+ * It carries the WHOLE catalog, not just the enabled entries, and that is load-bearing
7
+ * rather than tidiness. A host's own `activeAppId` entry is always rendered and indicated,
8
+ * resolved from this list when the payload omits it — so a satellite whose id is missing
9
+ * here loses its chip entirely during an endpoint outage, and with it any indication of
10
+ * where the user is and anything for NavTrigger to dock. Every app that can mount the bar
11
+ * needs an entry, whether or not it is currently switched on.
12
+ *
13
+ * `enabled` here still governs what renders: the same enabled-and-has-destination filter
14
+ * runs against this list as against the fetched payload, so a disabled entry contributes
15
+ * its label, icon, order and destination without appearing as a tile.
16
+ *
17
+ * It carries no look and no wordmark. Colour, layout and the wordmark are compiled in
18
+ * (index.css and nav-bar.tsx), so there is nothing for a fetched payload to disagree with
19
+ * and nothing to flash between first paint and the fetch landing. The catalog is the one
20
+ * field that can still differ — which is why the last good payload is persisted; see
21
+ * storage.ts.
22
+ *
23
+ * These are the production domains, which is why they can live in compiled code at all.
24
+ * Changing this list requires a package release, so it is deliberately the stable
25
+ * first-party set — anything situational (a stage host, a locally-run satellite) belongs
26
+ * in the config row, where an admin can edit it without a release. `enabled` states drift
27
+ * from the row over time; that is expected and harmless, since the row wins whenever the
28
+ * endpoint is reachable.
9
29
  */
10
30
  const FALLBACK_CONFIG = {
11
31
  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 }],
32
+ apps: [
33
+ { id: 'home', label: 'Home', icon: 'house', href: 'https://www.arthelper.com/community', enabled: true },
34
+ { id: 'the-daily', label: 'The Daily', icon: 'sunrise', href: 'https://thedaily.arthelper.com', enabled: false },
35
+ { id: 'art-review', label: 'Art Review', icon: 'star', href: 'https://artreview.arthelper.com', enabled: true },
36
+ {
37
+ id: 'creative-sparks',
38
+ label: 'Creative Sparks',
39
+ icon: 'sparkles',
40
+ href: 'https://sparks.arthelper.com',
41
+ enabled: false,
42
+ },
43
+ { id: 'mockups', label: 'Mockups', icon: 'frame', href: 'https://mockups.arthelper.com', enabled: false },
44
+ { id: 'art-books', label: 'Art Books', icon: 'book-open', href: 'https://artbooks.arthelper.com', enabled: false },
45
+ {
46
+ id: 'social-posting',
47
+ label: 'Social Posting',
48
+ icon: 'megaphone',
49
+ href: 'https://copilot.arthelper.com',
50
+ enabled: false,
51
+ },
52
+ ],
15
53
  };
16
54
  export { FALLBACK_CONFIG };
17
55
  //# sourceMappingURL=fallback.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"fallback.js","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,eAAe,GAAqB;IACxC,cAAc,EAAE,CAAC;IACjB,IAAI,EAAE;QACJ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,qCAAqC,EAAE,OAAO,EAAE,IAAI,EAAE;QACxG,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,gCAAgC,EAAE,OAAO,EAAE,KAAK,EAAE;QAChH,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iCAAiC,EAAE,OAAO,EAAE,IAAI,EAAE;QAC/G;YACE,EAAE,EAAE,iBAAiB;YACrB,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,8BAA8B;YACpC,OAAO,EAAE,KAAK;SACf;QACD,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,+BAA+B,EAAE,OAAO,EAAE,KAAK,EAAE;QACzG,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gCAAgC,EAAE,OAAO,EAAE,KAAK,EAAE;QAClH;YACE,EAAE,EAAE,gBAAgB;YACpB,KAAK,EAAE,gBAAgB;YACvB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,+BAA+B;YACrC,OAAO,EAAE,KAAK;SACf;KACF;CACF,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
package/dist/index.css CHANGED
@@ -15,9 +15,20 @@
15
15
  */
16
16
 
17
17
  /* ---------------------------------------------------------------------------
18
- * Tokens. `.ahsuite-panel` inherits from `.ahsuite-bar`; `.ahsuite-docked` lives
19
- * in the host's own header and so carries its own copy.
20
- * Dark "Forest" is the default.
18
+ * Tokens — ONE shipped look, dark "Forest". `.ahsuite-panel` inherits from
19
+ * `.ahsuite-bar`; `.ahsuite-docked` lives in the host's own header and so carries
20
+ * its own copy.
21
+ *
22
+ * These values were briefly served from the config endpoint, as a mode /
23
+ * accent_color / alignment block on the payload, and are baked into the release on
24
+ * purpose now. The bar paints a compiled-in fallback before the fetch resolves, so
25
+ * every served value that disagreed with the fallback's guess flashed on EVERY page
26
+ * load: a dark bar turning light, tiles jumping from left to centre. Colour and
27
+ * layout are release decisions, not operational switches. Only the app catalog is
28
+ * still configurable.
29
+ *
30
+ * There is no light palette and no `data-theme` hook any more. A second look would
31
+ * come back as a second release, not a second runtime branch.
21
32
  * ------------------------------------------------------------------------- */
22
33
  .ahsuite-bar,
23
34
  .ahsuite-docked {
@@ -32,8 +43,6 @@
32
43
  --ahsuite-chip-icon: #c4d2c8;
33
44
  --ahsuite-chip-hover-bg: #ffffff2e;
34
45
  --ahsuite-chip-hover-icon: #9fd9b4;
35
- --ahsuite-chip-active-bg: transparent;
36
- --ahsuite-chip-active-image: linear-gradient(180deg, #3dbe66, #2aa34f);
37
46
  --ahsuite-chip-active-icon: #fff;
38
47
  --ahsuite-label: #c4d2c8;
39
48
  --ahsuite-label-active: #fff;
@@ -41,30 +50,7 @@
41
50
  --ahsuite-surface-border: #ffffff1f;
42
51
  --ahsuite-control-border: #ffffff2e;
43
52
  --ahsuite-row-hover-bg: #ffffff1f;
44
- --ahsuite-accent-text: #9fd9b4;
45
- --ahsuite-tiles-justify: flex-start;
46
- }
47
-
48
- .ahsuite-bar[data-theme='light'],
49
- .ahsuite-docked[data-theme='light'] {
50
- --ahsuite-bar-bg: #fff;
51
- --ahsuite-bar-border: #e6e1d5;
52
- --ahsuite-waffle-icon: #4a5951;
53
- --ahsuite-waffle-hover-bg: #f1ede2;
54
- --ahsuite-waffle-hover-icon: #2e9e58;
55
- --ahsuite-wordmark: #12211a;
56
- --ahsuite-divider: #e6e1d5;
57
- --ahsuite-chip-bg: #f1ede2;
58
- --ahsuite-chip-icon: #4a5951;
59
- --ahsuite-chip-hover-bg: #e4f4e8;
60
- --ahsuite-chip-hover-icon: #2e9e58;
61
- --ahsuite-label: #4a5951;
62
- --ahsuite-label-active: #12211a;
63
- --ahsuite-surface-bg: #fff;
64
- --ahsuite-surface-border: #e6e1d5;
65
- --ahsuite-control-border: #e6e1d5;
66
- --ahsuite-row-hover-bg: #f1ede2;
67
- --ahsuite-accent-text: #2e9e58;
53
+ --ahsuite-eyebrow: #9fd9b4;
68
54
  }
69
55
 
70
56
  /* ---------------------------------------------------------------------------
@@ -143,6 +129,40 @@
143
129
  visibility 0s linear 180ms;
144
130
  }
145
131
 
132
+ /* ---------------------------------------------------------------------------
133
+ * True centring, in three flex children: brand | tiles | balance.
134
+ *
135
+ * The tile row used to be a direct child that grew into the row's leftover space and
136
+ * centred inside it. Because that leftover space starts AFTER the waffle, wordmark and
137
+ * divider, the tiles landed a constant 60.8px right of the bar's real centre — exactly
138
+ * half the width of that left block, at every viewport.
139
+ *
140
+ * So the left block is one flex child and an empty `.ahsuite-balance` mirrors it on the
141
+ * right. Both take `flex: 1 1 0%`, so they split the free space equally whatever the
142
+ * brand actually measures, and the content-sized tile row between them lands on the
143
+ * bar's centre line. The bar's own 10px gap appears once on each side of the row, so it
144
+ * cancels too — the maths is symmetric, not tuned.
145
+ *
146
+ * Neither balancing child may shrink the row's content out of the bar: their 0% basis
147
+ * gives them a scaled shrink factor of 0, so every pixel of negative free space comes
148
+ * off `.ahsuite-tiles` (which clips, see below) and the brand keeps its min-content
149
+ * width. Squeeze harder and the equal shares fall under the brand's min-content, the
150
+ * brand is clamped there, and the balance takes the remainder: the row slides toward
151
+ * the left rather than sitting under the wordmark. Clipped or left-ish, never overlapped.
152
+ * ------------------------------------------------------------------------- */
153
+ .ahsuite-brand {
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 10px;
157
+ flex: 1 1 0%;
158
+ }
159
+
160
+ .ahsuite-balance {
161
+ display: block;
162
+ flex: 1 1 0%;
163
+ min-width: 0;
164
+ }
165
+
146
166
  .ahsuite-waffle {
147
167
  display: inline-flex;
148
168
  align-items: center;
@@ -193,15 +213,27 @@
193
213
  }
194
214
 
195
215
  /* ---------------------------------------------------------------------------
196
- * Desktop tile row. `alignment` drives justify-content and nothing else.
216
+ * Desktop tile row. Sized to its own content and centred by the two balancing
217
+ * siblings above, never by a justify-content it distributes itself.
197
218
  * ------------------------------------------------------------------------- */
198
219
  .ahsuite-tiles {
199
220
  display: flex;
200
221
  gap: 4px;
201
222
  align-items: center;
202
- justify-content: var(--ahsuite-tiles-justify);
223
+ /* Content-sized: `flex-grow: 0` is what makes the row the thing being centred rather
224
+ * than a slot that centres its own contents. Growing it would work out to the same
225
+ * centre line while the tiles fit, and clip symmetrically — half a tile at each end —
226
+ * the moment they do not. `flex-shrink: 1` with `min-width: 0` lets the row give up
227
+ * width under pressure so `overflow: hidden` clips the trailing tiles instead of
228
+ * pushing the row over the wordmark. */
229
+ flex: 0 1 auto;
230
+ justify-content: flex-start;
231
+ min-width: 0;
203
232
  overflow: hidden;
204
- max-width: 1200px;
233
+ /* 100%, not a fixed cap. This is the property the collapse animates (to 0), so it has
234
+ * to stay — but a fixed cap (it was 1200px) would stop the row reaching its content
235
+ * width on a wide viewport and drag it off centre. */
236
+ max-width: 100%;
205
237
  opacity: 1;
206
238
  transform: translateY(0);
207
239
  transition:
@@ -281,13 +313,14 @@ span.ahsuite-row {
281
313
  white-space: nowrap;
282
314
  }
283
315
 
284
- /* accent_color arrives as a single flat fill: --ahsuite-chip-active-bg is the
285
- * colour and --ahsuite-chip-active-image is switched to none. */
316
+ /* The active chip's two-stop gradient is fixed. It used to be overridable by a single
317
+ * flat accent_color from the payload, which meant one config value could not describe
318
+ * the shipped look at all — it could only replace it, and did so a frame late. */
286
319
  .ahsuite-chip--active,
287
320
  .ahsuite-tile--active .ahsuite-chip--active,
288
321
  .ahsuite-tile--active:hover .ahsuite-chip--active {
289
- background-color: var(--ahsuite-chip-active-bg);
290
- background-image: var(--ahsuite-chip-active-image);
322
+ background-color: transparent;
323
+ background-image: linear-gradient(180deg, #3dbe66, #2aa34f);
291
324
  color: var(--ahsuite-chip-active-icon);
292
325
  }
293
326
 
@@ -402,7 +435,7 @@ span.ahsuite-row {
402
435
  font-size: 11px;
403
436
  font-weight: 600;
404
437
  letter-spacing: 0.06em;
405
- color: var(--ahsuite-accent-text);
438
+ color: var(--ahsuite-eyebrow);
406
439
  }
407
440
 
408
441
  .ahsuite-close {
@@ -459,8 +492,8 @@ span.ahsuite-row {
459
492
  }
460
493
 
461
494
  .ahsuite-row:hover .ahsuite-chip--active {
462
- background-color: var(--ahsuite-chip-active-bg);
463
- background-image: var(--ahsuite-chip-active-image);
495
+ background-color: transparent;
496
+ background-image: linear-gradient(180deg, #3dbe66, #2aa34f);
464
497
  color: var(--ahsuite-chip-active-icon);
465
498
  }
466
499
 
@@ -485,6 +518,17 @@ span.ahsuite-row {
485
518
  display: inline-flex;
486
519
  }
487
520
 
521
+ /* Nothing to centre once the tile row is gone, so the balancing pair stands down and
522
+ * the trigger sits beside the wordmark instead of floating in the middle of the bar.
523
+ * `.ahsuite-balance` keeps growing purely to hold the right-hand slack. */
524
+ .ahsuite-brand {
525
+ flex: 0 0 auto;
526
+ }
527
+
528
+ .ahsuite-balance {
529
+ flex: 1 1 auto;
530
+ }
531
+
488
532
  .ahsuite-panel {
489
533
  display: block;
490
534
  }
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@ export type { NavContextValue } from './context.js';
9
9
  export type { NavBarProps } from './nav-bar.js';
10
10
  export type { NavTriggerProps } from './nav-trigger.js';
11
11
  export type { NavProviderProps } from './provider.js';
12
- export type { NavAlignment, NavApp, NavAppClickArgs, NavConfigPayload, NavLogo, NavTheme, NavThemeMode, ResolvedNavApp, } from './types.js';
12
+ export type { NavApp, NavAppClickArgs, NavConfigPayload, ResolvedNavApp } from './types.js';
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACV,YAAY,EACZ,MAAM,EACN,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"nav-bar.d.ts","sourceRoot":"","sources":["../src/nav-bar.tsx"],"names":[],"mappings":"AASA,UAAU,WAAW;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsED,iBAAS,MAAM,CAAC,EAAE,MAAc,EAAE,SAAS,EAAE,EAAE,WAAW,2CA6HzD;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,YAAY,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"nav-bar.d.ts","sourceRoot":"","sources":["../src/nav-bar.tsx"],"names":[],"mappings":"AAsBA,UAAU,WAAW;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsED,iBAAS,MAAM,CAAC,EAAE,MAAc,EAAE,SAAS,EAAE,EAAE,WAAW,2CAkIzD;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,YAAY,EAAE,WAAW,EAAE,CAAC"}
package/dist/nav-bar.js CHANGED
@@ -4,7 +4,19 @@ import { ChevronDown, X } from 'lucide-react';
4
4
  import { useCallback, useEffect, useRef, useState } from 'react';
5
5
  import { useNav } from './context.js';
6
6
  import { NavIcon, WaffleIcon } from './icons.js';
7
- import { themeVars } from './theme.js';
7
+ /**
8
+ * The wordmark, compiled in. It used to come from the payload's `logo` block, which meant
9
+ * a static piece of chrome could disagree with the compiled-in fallback and shift on load
10
+ * like everything else fetched. Nobody renames the ArtHelper wordmark from an admin
11
+ * screen, so it belongs in the release.
12
+ *
13
+ * The href is the site root, NOT the resolved `home` catalog entry. Deriving it was
14
+ * considered and rejected twice over: it would put this back on config, and Home points at
15
+ * /community while the wordmark has always gone to the root — they are different
16
+ * destinations that happen to share a domain, not one destination expressed twice.
17
+ */
18
+ const WORDMARK_LABEL = 'arthelper';
19
+ const WORDMARK_HREF = 'https://www.arthelper.com';
8
20
  function chipClass(entry) {
9
21
  return entry.active ? 'ahsuite-chip ahsuite-chip--active' : 'ahsuite-chip';
10
22
  }
@@ -32,7 +44,7 @@ function Row({ entry, onActivate }) {
32
44
  return (_jsxs("a", { "aria-current": entry.active ? 'page' : undefined, className: className, href: entry.app.href, onClick: () => onActivate(entry), children: [_jsx(Chip, { entry: entry }), _jsx("span", { className: "ahsuite-row-label", children: entry.app.label })] }));
33
45
  }
34
46
  function NavBar({ sticky = false, className }) {
35
- const { config, apps, activeApp, collapsed, toggle, activate } = useNav();
47
+ const { apps, activeApp, collapsed, toggle, activate } = useNav();
36
48
  const [open, setOpen] = useState(false);
37
49
  const panelRef = useRef(null);
38
50
  const triggerRef = useRef(null);
@@ -85,7 +97,7 @@ function NavBar({ sticky = false, className }) {
85
97
  setOpen(false);
86
98
  }, [activate]);
87
99
  const classes = ['ahsuite-bar', sticky ? 'ahsuite-bar--sticky' : '', className ?? ''].filter(Boolean).join(' ');
88
- return (_jsxs("div", { "aria-label": "ArtHelper suite", className: classes, "data-collapsed": collapsed ? 'true' : 'false', "data-theme": config.theme.mode, role: "navigation", style: themeVars(config.theme), children: [_jsx("button", { "aria-expanded": !collapsed, "aria-label": collapsed ? 'Show the suite bar' : 'Hide the suite bar', className: "ahsuite-waffle", onClick: toggle, type: "button", children: _jsx(WaffleIcon, {}) }), config.logo.href ? (_jsx("a", { className: "ahsuite-wordmark", href: config.logo.href, children: config.logo.label })) : (_jsx("span", { className: "ahsuite-wordmark", children: config.logo.label })), _jsx("span", { "aria-hidden": "true", className: "ahsuite-divider" }), _jsx("div", { className: "ahsuite-tiles", children: apps.map((entry) => (_jsx(Tile, { entry: entry, onActivate: onActivate }, entry.app.id))) }), _jsxs("button", { "aria-controls": "ahsuite-panel", "aria-expanded": open, "aria-haspopup": "true", className: "ahsuite-trigger", onClick: () => setOpen((current) => !current), ref: triggerRef, type: "button", children: [activeApp ? _jsx(Chip, { entry: activeApp }) : null, _jsx("span", { className: "ahsuite-trigger-label", children: activeApp?.app.label ?? 'Apps' }), _jsx(ChevronDown, { "aria-hidden": "true", className: "ahsuite-chevron", height: 18, strokeWidth: 2, width: 18 })] }), _jsxs("div", { "aria-label": "Apps", className: "ahsuite-panel", hidden: !open, id: "ahsuite-panel", ref: panelRef, role: "dialog", children: [_jsxs("div", { className: "ahsuite-panel-head", children: [_jsx("span", { className: "ahsuite-panel-title", children: "Apps" }), _jsxs("button", { "aria-label": "Close", className: "ahsuite-close", onClick: () => setOpen(false), type: "button", children: [_jsx(X, { "aria-hidden": "true", className: "ahsuite-icon", height: 14, strokeWidth: 2, width: 14 }), _jsx("span", { children: "Close" })] })] }), apps.map((entry) => (_jsx(Row, { entry: entry, onActivate: onActivate }, entry.app.id)))] })] }));
100
+ return (_jsxs("div", { "aria-label": "ArtHelper suite", className: classes, "data-collapsed": collapsed ? 'true' : 'false', role: "navigation", children: [_jsxs("div", { className: "ahsuite-brand", children: [_jsx("button", { "aria-expanded": !collapsed, "aria-label": collapsed ? 'Show the suite bar' : 'Hide the suite bar', className: "ahsuite-waffle", onClick: toggle, type: "button", children: _jsx(WaffleIcon, {}) }), _jsx("a", { className: "ahsuite-wordmark", href: WORDMARK_HREF, children: WORDMARK_LABEL }), _jsx("span", { "aria-hidden": "true", className: "ahsuite-divider" })] }), _jsx("div", { className: "ahsuite-tiles", children: apps.map((entry) => (_jsx(Tile, { entry: entry, onActivate: onActivate }, entry.app.id))) }), _jsxs("button", { "aria-controls": "ahsuite-panel", "aria-expanded": open, "aria-haspopup": "true", className: "ahsuite-trigger", onClick: () => setOpen((current) => !current), ref: triggerRef, type: "button", children: [activeApp ? _jsx(Chip, { entry: activeApp }) : null, _jsx("span", { className: "ahsuite-trigger-label", children: activeApp?.app.label ?? 'Apps' }), _jsx(ChevronDown, { "aria-hidden": "true", className: "ahsuite-chevron", height: 18, strokeWidth: 2, width: 18 })] }), _jsx("span", { "aria-hidden": "true", className: "ahsuite-balance" }), _jsxs("div", { "aria-label": "Apps", className: "ahsuite-panel", hidden: !open, id: "ahsuite-panel", ref: panelRef, role: "dialog", children: [_jsxs("div", { className: "ahsuite-panel-head", children: [_jsx("span", { className: "ahsuite-panel-title", children: "Apps" }), _jsxs("button", { "aria-label": "Close", className: "ahsuite-close", onClick: () => setOpen(false), type: "button", children: [_jsx(X, { "aria-hidden": "true", className: "ahsuite-icon", height: 14, strokeWidth: 2, width: 14 }), _jsx("span", { children: "Close" })] })] }), apps.map((entry) => (_jsx(Row, { entry: entry, onActivate: onActivate }, entry.app.id)))] })] }));
89
101
  }
90
102
  export { NavBar };
91
103
  //# sourceMappingURL=nav-bar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nav-bar.js","sourceRoot":"","sources":["../src/nav-bar.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nav-bar.js","sourceRoot":"","sources":["../src/nav-bar.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"nav-trigger.d.ts","sourceRoot":"","sources":["../src/nav-trigger.tsx"],"names":[],"mappings":"AAMA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,EAAE,eAAe,2CAuBjD;AAED,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"nav-trigger.d.ts","sourceRoot":"","sources":["../src/nav-trigger.tsx"],"names":[],"mappings":"AAKA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,EAAE,eAAe,2CAqBjD;AAED,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,CAAC"}
@@ -2,7 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useNav } from './context.js';
4
4
  import { NavIcon, WaffleIcon } from './icons.js';
5
- import { themeVars } from './theme.js';
6
5
  /**
7
6
  * The waffle plus active-app chip a host places by hand in its own header, beside
8
7
  * its logo. This is the RESTORE control: while the bar is collapsed it is the only
@@ -13,9 +12,9 @@ import { themeVars } from './theme.js';
13
12
  * than swapped — the bar's own waffle is the hide control.
14
13
  */
15
14
  function NavTrigger({ className }) {
16
- const { config, activeApp, collapsed, toggle } = useNav();
15
+ const { activeApp, collapsed, toggle } = useNav();
17
16
  const classes = ['ahsuite-docked', className ?? ''].filter(Boolean).join(' ');
18
- return (_jsxs("button", { "aria-expanded": false, "aria-label": "Show the suite bar", className: classes, "data-collapsed": collapsed ? 'true' : 'false', "data-theme": config.theme.mode, onClick: toggle, style: themeVars(config.theme), title: "Show the suite bar", type: "button", children: [_jsx("span", { className: "ahsuite-chip ahsuite-chip--active", children: _jsx(NavIcon, { name: activeApp?.app.icon ?? '' }) }), _jsx(WaffleIcon, { className: "ahsuite-waffle-mini" })] }));
17
+ return (_jsxs("button", { "aria-expanded": false, "aria-label": "Show the suite bar", className: classes, "data-collapsed": collapsed ? 'true' : 'false', onClick: toggle, title: "Show the suite bar", type: "button", children: [_jsx("span", { className: "ahsuite-chip ahsuite-chip--active", children: _jsx(NavIcon, { name: activeApp?.app.icon ?? '' }) }), _jsx(WaffleIcon, { className: "ahsuite-waffle-mini" })] }));
19
18
  }
20
19
  export { NavTrigger };
21
20
  //# sourceMappingURL=nav-trigger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nav-trigger.js","sourceRoot":"","sources":["../src/nav-trigger.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAMvC;;;;;;;;GAQG;AACH,SAAS,UAAU,CAAC,EAAE,SAAS,EAAmB;IAChD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAE1D,MAAM,OAAO,GAAG,CAAC,gBAAgB,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE9E,OAAO,CACL,mCACiB,KAAK,gBACT,oBAAoB,EAC/B,SAAS,EAAE,OAAO,oBACF,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,gBAChC,MAAM,CAAC,KAAK,CAAC,IAAI,EAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAC9B,KAAK,EAAC,oBAAoB,EAC1B,IAAI,EAAC,QAAQ,aAEb,eAAM,SAAS,EAAC,mCAAmC,YACjD,KAAC,OAAO,IAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAI,GACvC,EACP,KAAC,UAAU,IAAC,SAAS,EAAC,qBAAqB,GAAG,IACvC,CACV,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"nav-trigger.js","sourceRoot":"","sources":["../src/nav-trigger.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMjD;;;;;;;;GAQG;AACH,SAAS,UAAU,CAAC,EAAE,SAAS,EAAmB;IAChD,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAElD,MAAM,OAAO,GAAG,CAAC,gBAAgB,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE9E,OAAO,CACL,mCACiB,KAAK,gBACT,oBAAoB,EAC/B,SAAS,EAAE,OAAO,oBACF,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAC5C,OAAO,EAAE,MAAM,EACf,KAAK,EAAC,oBAAoB,EAC1B,IAAI,EAAC,QAAQ,aAEb,eAAM,SAAS,EAAC,mCAAmC,YACjD,KAAC,OAAO,IAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAI,GACvC,EACP,KAAC,UAAU,IAAC,SAAS,EAAC,qBAAqB,GAAG,IACvC,CACV,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMlF,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAEpF,UAAU,gBAAgB;IACxB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,WAAW,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,iGAAiG;IACjG,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,wGAAwG;IACxG,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,QAAQ,GACT,EAAE,gBAAgB,2CAiDlB;AAED,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA8D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMnG,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAsBpF,UAAU,gBAAgB;IACxB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,WAAW,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,iGAAiG;IACjG,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,wGAAwG;IACxG,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,QAAQ,GACT,EAAE,gBAAgB,2CA+DlB;AAED,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
package/dist/provider.js CHANGED
@@ -1,17 +1,49 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useMemo, useState } from 'react';
4
- import { cached, fetchConfig } from './config.js';
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
4
+ import { cached, fetchConfig, restored } from './config.js';
5
5
  import { NavContext } from './context.js';
6
6
  import { readCollapsed, writeCollapsed } from './cookie.js';
7
7
  import { FALLBACK_CONFIG } from './fallback.js';
8
8
  import { resolveApps } from './resolve.js';
9
+ /**
10
+ * useLayoutEffect on the client, useEffect on the server — the standard isomorphic-layout
11
+ * pattern, and it is load-bearing in BOTH directions here.
12
+ *
13
+ * It cannot be the useState initialiser. town-square server-renders this bar, the server
14
+ * cannot read the viewer's localStorage, and a client first render that disagrees with the
15
+ * server HTML is a hydration mismatch — a tested-clean property of that mount today.
16
+ *
17
+ * It cannot be a plain useEffect either. Passive effects are flushed on a scheduler
18
+ * callback that can land AFTER the browser has painted, which puts the wrong catalog on
19
+ * screen for a frame — precisely the flash this whole change exists to remove. Layout
20
+ * effects run synchronously after commit and before paint, so the state swap and its
21
+ * re-render are both done before anything is shown.
22
+ *
23
+ * Picking the hook by `typeof window` keeps React from warning that useLayoutEffect does
24
+ * nothing on the server. Hydration builds the client fiber tree fresh, so the two paths
25
+ * never have to agree on which effect flavour occupied the slot.
26
+ */
27
+ const useBeforePaint = typeof window === 'undefined' ? useEffect : useLayoutEffect;
9
28
  function NavProvider({ configUrl, activeAppId, onAppClick, fallbackConfig, initialCollapsed, children, }) {
10
29
  const fallback = fallbackConfig ?? FALLBACK_CONFIG;
11
30
  // Stale-while-revalidate: paint the last good payload (or the embedded fallback)
12
31
  // synchronously, then swap in the fetched one. First paint never waits on the network.
13
32
  const [config, setConfig] = useState(() => cached(configUrl) ?? fallback);
14
33
  const [collapsed, setCollapsedState] = useState(() => initialCollapsed ?? readCollapsed() ?? false);
34
+ // Before the first paint, upgrade the compiled-in fallback to the last payload this
35
+ // browser actually saw. `cached` wins when it is populated: an in-memory hit means a
36
+ // fetch already landed in THIS page session, which is strictly fresher than the store.
37
+ useBeforePaint(() => {
38
+ if (cached(configUrl)) {
39
+ return;
40
+ }
41
+ const stored = restored();
42
+ if (!stored) {
43
+ return;
44
+ }
45
+ setConfig(stored);
46
+ }, [configUrl]);
15
47
  useEffect(() => {
16
48
  let live = true;
17
49
  void fetchConfig(configUrl).then((next) => {
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The last good payload, persisted per browser.
3
+ *
4
+ * Why this exists: the bar paints the compiled-in FALLBACK_CONFIG before the fetch
5
+ * resolves, and the catalog is the one field that can still legitimately differ from it —
6
+ * `enabled` states drift from the config row by design (see fallback.ts). Because the tile
7
+ * row is centred on the bar, that drift is expensive: every tile the fetch adds or removes
8
+ * shifts the whole row by half a tile, so a 2-tile fallback resolving to a 7-tile config
9
+ * moves the Home tile 230px. Persisting the last good payload makes the second and every
10
+ * subsequent visit paint the catalog the viewer actually saw last time.
11
+ *
12
+ * It cannot help the FIRST visit, and it cannot help the first paint of a server-rendered
13
+ * host: the server has no access to the browser's localStorage, so its HTML is always the
14
+ * compiled-in fallback. What this removes is the hydration-time swap on every return
15
+ * visit. See provider.tsx for how the read is sequenced against paint.
16
+ *
17
+ * EVERY access is wrapped, including the `localStorage` property read itself. Safari's
18
+ * private mode and Chrome with site data blocked throw a SecurityError on ACCESS, not just
19
+ * on setItem, so `typeof localStorage !== 'undefined'` is not a sufficient guard.
20
+ */
21
+ /**
22
+ * Versioned on the RENDERER's expected shape, not on the payload's `schema_version`. A
23
+ * renderer that changes what it stores bumps this key rather than trying to migrate, so
24
+ * an old blob is orphaned and ignored instead of misread. localStorage is origin-scoped
25
+ * and a host has exactly one configUrl, so the key needs no URL in it.
26
+ */
27
+ declare const STORAGE_KEY = "ahsuite-nav-config-v1";
28
+ /** Beyond this a dormant browser revalidates rather than painting something ancient. */
29
+ declare const MAX_AGE_MS: number;
30
+ /**
31
+ * The stored payload, or null. Returns the RAW payload deliberately — the caller runs it
32
+ * through `normalize` exactly as it would a network body, because a blob written by an
33
+ * older renderer is untrusted input of precisely the same kind.
34
+ */
35
+ declare function readStored(): unknown;
36
+ /** Best effort. A full quota or a blocked store is not a reason to fail the fetch. */
37
+ declare function writeStored(payload: unknown): void;
38
+ export { MAX_AGE_MS, STORAGE_KEY, readStored, writeStored };
39
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;GAKG;AACH,QAAA,MAAM,WAAW,0BAA0B,CAAC;AAE5C,wFAAwF;AACxF,QAAA,MAAM,UAAU,QAA2B,CAAC;AAU5C;;;;GAIG;AACH,iBAAS,UAAU,IAAI,OAAO,CAuB7B;AAED,sFAAsF;AACtF,iBAAS,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAM3C;AAED,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The last good payload, persisted per browser.
3
+ *
4
+ * Why this exists: the bar paints the compiled-in FALLBACK_CONFIG before the fetch
5
+ * resolves, and the catalog is the one field that can still legitimately differ from it —
6
+ * `enabled` states drift from the config row by design (see fallback.ts). Because the tile
7
+ * row is centred on the bar, that drift is expensive: every tile the fetch adds or removes
8
+ * shifts the whole row by half a tile, so a 2-tile fallback resolving to a 7-tile config
9
+ * moves the Home tile 230px. Persisting the last good payload makes the second and every
10
+ * subsequent visit paint the catalog the viewer actually saw last time.
11
+ *
12
+ * It cannot help the FIRST visit, and it cannot help the first paint of a server-rendered
13
+ * host: the server has no access to the browser's localStorage, so its HTML is always the
14
+ * compiled-in fallback. What this removes is the hydration-time swap on every return
15
+ * visit. See provider.tsx for how the read is sequenced against paint.
16
+ *
17
+ * EVERY access is wrapped, including the `localStorage` property read itself. Safari's
18
+ * private mode and Chrome with site data blocked throw a SecurityError on ACCESS, not just
19
+ * on setItem, so `typeof localStorage !== 'undefined'` is not a sufficient guard.
20
+ */
21
+ /**
22
+ * Versioned on the RENDERER's expected shape, not on the payload's `schema_version`. A
23
+ * renderer that changes what it stores bumps this key rather than trying to migrate, so
24
+ * an old blob is orphaned and ignored instead of misread. localStorage is origin-scoped
25
+ * and a host has exactly one configUrl, so the key needs no URL in it.
26
+ */
27
+ const STORAGE_KEY = 'ahsuite-nav-config-v1';
28
+ /** Beyond this a dormant browser revalidates rather than painting something ancient. */
29
+ const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
30
+ function store() {
31
+ try {
32
+ return globalThis.localStorage ?? null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /**
39
+ * The stored payload, or null. Returns the RAW payload deliberately — the caller runs it
40
+ * through `normalize` exactly as it would a network body, because a blob written by an
41
+ * older renderer is untrusted input of precisely the same kind.
42
+ */
43
+ function readStored() {
44
+ try {
45
+ const raw = store()?.getItem(STORAGE_KEY);
46
+ if (!raw) {
47
+ return null;
48
+ }
49
+ const envelope = JSON.parse(raw);
50
+ if (typeof envelope !== 'object' || envelope === null) {
51
+ return null;
52
+ }
53
+ const { saved_at: savedAt, payload } = envelope;
54
+ if (typeof savedAt !== 'number') {
55
+ return null;
56
+ }
57
+ // Absolute difference, so a clock moved BACKWARDS discards the entry rather than
58
+ // treating a future timestamp as permanently fresh.
59
+ if (Math.abs(Date.now() - savedAt) > MAX_AGE_MS) {
60
+ return null;
61
+ }
62
+ return payload ?? null;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ /** Best effort. A full quota or a blocked store is not a reason to fail the fetch. */
69
+ function writeStored(payload) {
70
+ try {
71
+ store()?.setItem(STORAGE_KEY, JSON.stringify({ saved_at: Date.now(), payload }));
72
+ }
73
+ catch {
74
+ return;
75
+ }
76
+ }
77
+ export { MAX_AGE_MS, STORAGE_KEY, readStored, writeStored };
78
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAE5C,wFAAwF;AACxF,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE5C,SAAS,KAAK;IACZ,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,YAAY,IAAI,IAAI,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,KAAK,EAAE,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,QAAQ,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,QAAqD,CAAC;QAC7F,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,iFAAiF;QACjF,oDAAoD;QACpD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,UAAU,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,WAAW,CAAC,OAAgB;IACnC,IAAI,CAAC;QACH,KAAK,EAAE,EAAE,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;AACH,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
package/dist/types.d.ts CHANGED
@@ -5,18 +5,21 @@
5
5
  * never removed or repurposed. Renderers ignore fields they do not recognise and
6
6
  * degrade rather than throw on a higher schema_version, so an asynchronously
7
7
  * deployed fleet can run mixed renderer versions against one payload.
8
+ *
9
+ * The payload used to carry a `theme` block (mode, accent_color, alignment) and a `logo`
10
+ * block (label, href). Both are gone, for one reason: the bar paints the compiled-in
11
+ * fallback before the fetch resolves, so EVERY configurable field is a field that can
12
+ * disagree with the fallback's guess and flash on load. The look and the wordmark are
13
+ * release decisions — nobody renames the ArtHelper wordmark from an admin screen — so
14
+ * they are baked in (see index.css and nav-bar.tsx). The app catalog is the one genuine
15
+ * operational switch, and it is all that is left here.
16
+ *
17
+ * Dropping a field IS breaking under the additive contract, so schema_version stays at 1
18
+ * only because no stable consumer was ever released against the old shape — the only
19
+ * publishes are 0.1.0-alpha.0 and 0.1.0-alpha.1, and both tolerate the fields' absence.
20
+ * AFTER THE FIRST STABLE PUBLISH the same removal would have to bump schema_version and
21
+ * keep the old branch alive until the fleet has rolled.
8
22
  */
9
- type NavThemeMode = 'dark' | 'light';
10
- type NavAlignment = 'left' | 'centered';
11
- interface NavTheme {
12
- mode: NavThemeMode;
13
- accent_color: string | null;
14
- alignment: NavAlignment;
15
- }
16
- interface NavLogo {
17
- label: string;
18
- href: string;
19
- }
20
23
  interface NavApp {
21
24
  id: string;
22
25
  label: string;
@@ -26,8 +29,6 @@ interface NavApp {
26
29
  }
27
30
  interface NavConfigPayload {
28
31
  schema_version: number;
29
- theme: NavTheme;
30
- logo: NavLogo;
31
32
  apps: NavApp[];
32
33
  }
33
34
  /**
@@ -46,5 +47,5 @@ interface NavAppClickArgs {
46
47
  activeAppId: string;
47
48
  collapsed: boolean;
48
49
  }
49
- export type { NavAlignment, NavApp, NavAppClickArgs, NavConfigPayload, NavLogo, NavTheme, NavThemeMode, ResolvedNavApp, };
50
+ export type { NavApp, NavAppClickArgs, NavConfigPayload, ResolvedNavApp };
50
51
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,KAAK,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;AAErC,KAAK,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAExC,UAAU,QAAQ;IAChB,IAAI,EAAE,YAAY,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,YAAY,CAAC;CACzB;AAED,UAAU,OAAO;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,QAAQ,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;GAIG;AACH,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,0FAA0F;AAC1F,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,YAAY,EACV,YAAY,EACZ,MAAM,EACN,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,cAAc,GACf,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;GAIG;AACH,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,0FAA0F;AAC1F,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC"}
package/dist/types.js CHANGED
@@ -5,6 +5,20 @@
5
5
  * never removed or repurposed. Renderers ignore fields they do not recognise and
6
6
  * degrade rather than throw on a higher schema_version, so an asynchronously
7
7
  * deployed fleet can run mixed renderer versions against one payload.
8
+ *
9
+ * The payload used to carry a `theme` block (mode, accent_color, alignment) and a `logo`
10
+ * block (label, href). Both are gone, for one reason: the bar paints the compiled-in
11
+ * fallback before the fetch resolves, so EVERY configurable field is a field that can
12
+ * disagree with the fallback's guess and flash on load. The look and the wordmark are
13
+ * release decisions — nobody renames the ArtHelper wordmark from an admin screen — so
14
+ * they are baked in (see index.css and nav-bar.tsx). The app catalog is the one genuine
15
+ * operational switch, and it is all that is left here.
16
+ *
17
+ * Dropping a field IS breaking under the additive contract, so schema_version stays at 1
18
+ * only because no stable consumer was ever released against the old shape — the only
19
+ * publishes are 0.1.0-alpha.0 and 0.1.0-alpha.1, and both tolerate the fields' absence.
20
+ * AFTER THE FIRST STABLE PUBLISH the same removal would have to bump schema_version and
21
+ * keep the old branch alive until the fleet has rolled.
8
22
  */
9
23
  export {};
10
24
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artstorefronts/arthelper-nav",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.",
5
5
  "license": "ISC",
6
6
  "type": "module",