@boost33/ui 0.8.0 → 0.9.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/CHANGELOG.md +214 -123
- package/dist/backdrop-icons.d.ts +53 -0
- package/dist/backdrop-icons.js +62 -0
- package/dist/backdrop-icons.js.map +1 -0
- package/dist/backdrop.css +1 -0
- package/dist/backdrop.d.ts +46 -0
- package/dist/backdrop.js +127 -0
- package/dist/backdrop.js.map +1 -0
- package/dist/components/AccountMenu/AccountMenu.d.ts +42 -32
- package/dist/components/AccountMenu/AccountMenu.js +66 -48
- package/dist/components/AccountMenu/AccountMenu.js.map +1 -1
- package/dist/components/AccountMenu/AccountMenu.module.css.js +44 -38
- package/dist/components/AccountMenu/AccountMenu.module.css.js.map +1 -1
- package/dist/components/AccountMenu/StandardAccountMenu.d.ts +87 -0
- package/dist/components/AccountMenu/StandardAccountMenu.js +86 -0
- package/dist/components/AccountMenu/StandardAccountMenu.js.map +1 -0
- package/dist/components/AccountMenu/icons.d.ts +2 -2
- package/dist/components/AccountMenu/icons.js +21 -8
- package/dist/components/AccountMenu/icons.js.map +1 -1
- package/dist/components/AccountMenu/initials.d.ts +11 -0
- package/dist/components/AccountMenu/initials.js +8 -0
- package/dist/components/AccountMenu/initials.js.map +1 -0
- package/dist/components/AccountMenu/labels.d.ts +64 -3
- package/dist/components/AccountMenu/labels.js +33 -5
- package/dist/components/AccountMenu/labels.js.map +1 -1
- package/dist/components/AccountMenu/use-menu.d.ts +34 -0
- package/dist/components/AccountMenu/use-menu.js +102 -0
- package/dist/components/AccountMenu/use-menu.js.map +1 -0
- package/dist/components/Backdrop/BackdropAppMenu.d.ts +55 -0
- package/dist/components/Backdrop/BackdropAppMenu.js +79 -0
- package/dist/components/Backdrop/BackdropAppMenu.js.map +1 -0
- package/dist/components/Backdrop/BackdropAppMenu.module.css.js +48 -0
- package/dist/components/Backdrop/BackdropAppMenu.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropBasics.d.ts +129 -0
- package/dist/components/Backdrop/BackdropBasics.js +130 -0
- package/dist/components/Backdrop/BackdropBasics.js.map +1 -0
- package/dist/components/Backdrop/BackdropBasics.module.css.js +71 -0
- package/dist/components/Backdrop/BackdropBasics.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropButton.d.ts +63 -0
- package/dist/components/Backdrop/BackdropButton.js +59 -0
- package/dist/components/Backdrop/BackdropButton.js.map +1 -0
- package/dist/components/Backdrop/BackdropButton.module.css.js +41 -0
- package/dist/components/Backdrop/BackdropButton.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropCharts.d.ts +158 -0
- package/dist/components/Backdrop/BackdropCharts.js +222 -0
- package/dist/components/Backdrop/BackdropCharts.js.map +1 -0
- package/dist/components/Backdrop/BackdropCharts.module.css.js +109 -0
- package/dist/components/Backdrop/BackdropCharts.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropChrome.d.ts +26 -0
- package/dist/components/Backdrop/BackdropChrome.js +35 -0
- package/dist/components/Backdrop/BackdropChrome.js.map +1 -0
- package/dist/components/Backdrop/BackdropConversation.d.ts +67 -0
- package/dist/components/Backdrop/BackdropConversation.js +76 -0
- package/dist/components/Backdrop/BackdropConversation.js.map +1 -0
- package/dist/components/Backdrop/BackdropConversation.module.css.js +44 -0
- package/dist/components/Backdrop/BackdropConversation.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropData.d.ts +195 -0
- package/dist/components/Backdrop/BackdropData.js +204 -0
- package/dist/components/Backdrop/BackdropData.js.map +1 -0
- package/dist/components/Backdrop/BackdropData.module.css.js +135 -0
- package/dist/components/Backdrop/BackdropData.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropDisplay.d.ts +122 -0
- package/dist/components/Backdrop/BackdropDisplay.js +80 -0
- package/dist/components/Backdrop/BackdropDisplay.js.map +1 -0
- package/dist/components/Backdrop/BackdropDisplay.module.css.js +80 -0
- package/dist/components/Backdrop/BackdropDisplay.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropFields.d.ts +122 -0
- package/dist/components/Backdrop/BackdropFields.js +196 -0
- package/dist/components/Backdrop/BackdropFields.js.map +1 -0
- package/dist/components/Backdrop/BackdropFields.module.css.js +104 -0
- package/dist/components/Backdrop/BackdropFields.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropFlows.d.ts +128 -0
- package/dist/components/Backdrop/BackdropFlows.js +156 -0
- package/dist/components/Backdrop/BackdropFlows.js.map +1 -0
- package/dist/components/Backdrop/BackdropFlows.module.css.js +80 -0
- package/dist/components/Backdrop/BackdropFlows.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropFrame.d.ts +36 -0
- package/dist/components/Backdrop/BackdropFrame.js +15 -0
- package/dist/components/Backdrop/BackdropFrame.js.map +1 -0
- package/dist/components/Backdrop/BackdropFrame.module.css.js +14 -0
- package/dist/components/Backdrop/BackdropFrame.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.d.ts +272 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.js +264 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.js.map +1 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.module.css.js +201 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropHub.d.ts +80 -0
- package/dist/components/Backdrop/BackdropHub.js +92 -0
- package/dist/components/Backdrop/BackdropHub.js.map +1 -0
- package/dist/components/Backdrop/BackdropHub.module.css.js +40 -0
- package/dist/components/Backdrop/BackdropHub.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropLists.d.ts +165 -0
- package/dist/components/Backdrop/BackdropLists.js +162 -0
- package/dist/components/Backdrop/BackdropLists.js.map +1 -0
- package/dist/components/Backdrop/BackdropLists.module.css.js +114 -0
- package/dist/components/Backdrop/BackdropLists.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropOverlays.d.ts +175 -0
- package/dist/components/Backdrop/BackdropOverlays.js +252 -0
- package/dist/components/Backdrop/BackdropOverlays.js.map +1 -0
- package/dist/components/Backdrop/BackdropOverlays.module.css.js +104 -0
- package/dist/components/Backdrop/BackdropOverlays.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropPage.d.ts +73 -0
- package/dist/components/Backdrop/BackdropPage.js +65 -0
- package/dist/components/Backdrop/BackdropPage.js.map +1 -0
- package/dist/components/Backdrop/BackdropPage.module.css.js +46 -0
- package/dist/components/Backdrop/BackdropPage.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropPatterns.d.ts +224 -0
- package/dist/components/Backdrop/BackdropPatterns.js +202 -0
- package/dist/components/Backdrop/BackdropPatterns.js.map +1 -0
- package/dist/components/Backdrop/BackdropPatterns.module.css.js +114 -0
- package/dist/components/Backdrop/BackdropPatterns.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropRail.d.ts +63 -0
- package/dist/components/Backdrop/BackdropRail.js +98 -0
- package/dist/components/Backdrop/BackdropRail.js.map +1 -0
- package/dist/components/Backdrop/BackdropRail.module.css.js +42 -0
- package/dist/components/Backdrop/BackdropRail.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropSheets.d.ts +68 -0
- package/dist/components/Backdrop/BackdropSheets.js +75 -0
- package/dist/components/Backdrop/BackdropSheets.js.map +1 -0
- package/dist/components/Backdrop/BackdropSheets.module.css.js +42 -0
- package/dist/components/Backdrop/BackdropSheets.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropTabs.d.ts +20 -0
- package/dist/components/Backdrop/BackdropTabs.js +31 -0
- package/dist/components/Backdrop/BackdropTabs.js.map +1 -0
- package/dist/components/Backdrop/BackdropTabs.module.css.js +16 -0
- package/dist/components/Backdrop/BackdropTabs.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropTooltip.d.ts +21 -0
- package/dist/components/Backdrop/BackdropTooltip.js +15 -0
- package/dist/components/Backdrop/BackdropTooltip.js.map +1 -0
- package/dist/components/Backdrop/BackdropTooltip.module.css.js +18 -0
- package/dist/components/Backdrop/BackdropTooltip.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropTopStrip.d.ts +65 -0
- package/dist/components/Backdrop/BackdropTopStrip.js +46 -0
- package/dist/components/Backdrop/BackdropTopStrip.js.map +1 -0
- package/dist/components/Backdrop/BackdropTopStrip.module.css.js +40 -0
- package/dist/components/Backdrop/BackdropTopStrip.module.css.js.map +1 -0
- package/dist/components/Backdrop/cx.d.ts +2 -0
- package/dist/components/Backdrop/cx.js +7 -0
- package/dist/components/Backdrop/cx.js.map +1 -0
- package/dist/components/Backdrop/icons.d.ts +70 -0
- package/dist/components/Backdrop/icons.js +103 -0
- package/dist/components/Backdrop/icons.js.map +1 -0
- package/dist/components/TopBar/TopBar.d.ts +8 -1
- package/dist/components/TopBar/TopBar.js +19 -16
- package/dist/components/TopBar/TopBar.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +40 -36
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +23 -4
package/CHANGELOG.md
CHANGED
|
@@ -15,9 +15,10 @@ merged without one is not announced late, it is not announced at all — the
|
|
|
15
15
|
release cut dates the section, it does not write it.
|
|
16
16
|
|
|
17
17
|
**Not everything that lands belongs here.** `package.json` ships `dist/` and this
|
|
18
|
-
file, and `dist/` is built from `src/index.ts`
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
file, and `dist/` is built from two entry points, `src/index.ts` (`@boost33/ui`)
|
|
19
|
+
and, from 0.9.0, `src/backdrop.ts` (`@boost33/ui/backdrop`), so the specimen app,
|
|
20
|
+
the Storybook stories, the scripts, the tests and anything in `src/` reachable
|
|
21
|
+
from neither never reach a consumer. Those changes are the repo's business, not the changelog's. Each
|
|
21
22
|
release below names them separately, briefly, so the omission reads as a decision
|
|
22
23
|
rather than a gap.
|
|
23
24
|
|
|
@@ -27,9 +28,9 @@ why the entry matters more than the number.
|
|
|
27
28
|
|
|
28
29
|
## Unreleased
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Two components are built to Stage A on main and are not releasable until each
|
|
32
|
+
passes its fidelity gate. Their entries wait here for the release that ships
|
|
33
|
+
them.
|
|
33
34
|
|
|
34
35
|
### Added — `CommandPalette`, Stage A only. **Not releasable: it has not passed its fidelity gate** (B33-495)
|
|
35
36
|
|
|
@@ -93,123 +94,6 @@ Not in `dist/` and not a consumer's business: the specimen route showing the fou
|
|
|
93
94
|
states, their `SPECIMEN_ONLY` records (none of them is screenshot-gated, because
|
|
94
95
|
a *designed* baseline is the gate's to capture and never an agent's), and
|
|
95
96
|
`tests/command-palette.test.tsx`.
|
|
96
|
-
### Added — `AccountMenu`, Stage A only, **not yet through its fidelity gate** (B33-493)
|
|
97
|
-
|
|
98
|
-
> [!danger] Do not include this component in a release until a human has approved
|
|
99
|
-
> it. `AccountMenu` has **not passed its fidelity gate.** Nobody has yet looked at
|
|
100
|
-
> it beside a reference and said yes, no baseline exists for it, and no measurement
|
|
101
|
-
> it draws with has been written into the spec. `CLAUDE.md` is explicit about what
|
|
102
|
-
> that means for the three components the Wallet never rendered: "Before the gate,
|
|
103
|
-
> nothing is authoritative", and the gate is where the referent is created rather
|
|
104
|
-
> than checked. Until that session has happened — a person opening the specimen
|
|
105
|
-
> route, judging the component, capturing the first baseline themselves, and
|
|
106
|
-
> recording the approved measurements in the spec in the same pass — every number
|
|
107
|
-
> this component renders is provisional and may change in a way that moves pixels
|
|
108
|
-
> in your app. **If you are cutting a release, either hold this entry back or get
|
|
109
|
-
> the gate done first.**
|
|
110
|
-
|
|
111
|
-
The twelfth component. The account menu's **panel**: the signed-in person, the
|
|
112
|
-
organisation they are acting in, and the rows into Settings and out of the
|
|
113
|
-
session. Exported as `AccountMenu`, with `AccountMenuProps`,
|
|
114
|
-
`AccountMenuPerson`, `AccountMenuOrg`, `AccountMenuGroup`, `AccountMenuItem`,
|
|
115
|
-
`AccountMenuIcon`, `AccountMenuLabels` and `defaultAccountMenuLabels`.
|
|
116
|
-
|
|
117
|
-
**It is the panel and nothing else — no trigger, no open state, no dismissal.**
|
|
118
|
-
That is not an unfinished half of trigger-plus-panel; it is the part of this
|
|
119
|
-
component the two reference drawings agree about. Paper's 004 Components artboard
|
|
120
|
-
and its origin on 103 Identity are identical in every property of the panel, and
|
|
121
|
-
they disagree about the anchor above it and about where the panel sits relative to
|
|
122
|
-
that anchor. Choosing between them is the gate-holder's, so the component takes no
|
|
123
|
-
anchor, does no positioning, and leaves `TopBar` untouched. Nothing in this release
|
|
124
|
-
changes `TopBar`.
|
|
125
|
-
|
|
126
|
-
**What a consumer does with it today: nothing.** Render it and you get a static
|
|
127
|
-
panel. It holds no state, so it cannot be opened or closed, and it wires no
|
|
128
|
-
handlers, so `Sign out` is a `<button>` that does nothing when pressed. Wiring it
|
|
129
|
-
to `useAnchoredOverlay`, giving the anchor its `aria-expanded`, and deciding what
|
|
130
|
-
each row does are Stage B, after the gate (`CLAUDE.md`, "Presentational-first").
|
|
131
|
-
|
|
132
|
-
**The shape, so it is legible from here:**
|
|
133
|
-
|
|
134
|
-
- `person` is `{ name, email, avatarUrl? }`. With no `avatarUrl` the circle draws
|
|
135
|
-
bare, which is what the drawings show; with one, the image is decorative and
|
|
136
|
-
takes an empty `alt`, because the person's name is on the line beside it.
|
|
137
|
-
- `org` is `{ name, initials, role? }`. The initials are decorative; omitting
|
|
138
|
-
`role` draws no pill rather than an empty one.
|
|
139
|
-
- `groups` is a list of `{ id, items }`, and **a hairline is drawn above every
|
|
140
|
-
group** — which is what separates the identity blocks from the first group as
|
|
141
|
-
well as each group from the last. Grouped rather than an `items` array plus a
|
|
142
|
-
named `footer`, because "footer" would be this package inventing a meaning for
|
|
143
|
-
the second group.
|
|
144
|
-
- an item is `{ id, label, description?, href?, icon? }`. **Whether a row
|
|
145
|
-
navigates is decided by `href` and nothing else** (ADR-0011): with an `href` the
|
|
146
|
-
row is a link and renders through `linkComponent`; without one it is an action
|
|
147
|
-
and stays a `<button type="button">`. So the package never has to know which of
|
|
148
|
-
Profile, Settings, Switch account and Sign out your product treats as a
|
|
149
|
-
navigation. `icon` names one of four glyphs the package draws
|
|
150
|
-
(`'person' | 'settings' | 'switch' | 'signOut'`); omit it and the glyph lane
|
|
151
|
-
stays, empty, so the row labels still line up in one column.
|
|
152
|
-
- `linkComponent` is the ADR-0011 escape hatch, unchanged from `NavItem` and
|
|
153
|
-
`AppSwitcher`: an `ElementType` defaulting to `'a'`, receiving `href`,
|
|
154
|
-
`className` and the row's `data-*`/`aria-*` through unchanged.
|
|
155
|
-
- `labels` is `Partial<AccountMenuLabels>` and holds **one** field, `panel`,
|
|
156
|
-
default `"Account"` — the panel's accessible name, which a disclosure panel has
|
|
157
|
-
no other way to get (ADR-0010). Everything else a person reads is row data you
|
|
158
|
-
supply, and therefore translates by construction.
|
|
159
|
-
|
|
160
|
-
**The label contract is the `WalletChip`'s, deliberately and in full.** `labels`
|
|
161
|
-
is a `Partial`, a field supplied as `undefined` (what a missed next-intl key looks
|
|
162
|
-
like) falls back to English rather than blanking the panel's name, and
|
|
163
|
-
`defaultAccountMenuLabels` is frozen with no type annotation so the freeze
|
|
164
|
-
survives into the emitted `.d.ts`. That is three of this repo's own fixed bugs
|
|
165
|
-
(B33-873, B33-877, B33-916) taken as the starting point rather than repeated.
|
|
166
|
-
B33-1007 records `AppSwitcher` still diverging on the last of the three; this
|
|
167
|
-
component does not add a fourth dialect, and the switcher's remaining divergence
|
|
168
|
-
is unchanged here and still that ticket's.
|
|
169
|
-
|
|
170
|
-
**A disclosure, not a menu** (ADR-0010): no widget role on the panel, no role on
|
|
171
|
-
the rows, every row the real `<a>` or `<button>` it already is, so Tab walks them
|
|
172
|
-
in the document's own order.
|
|
173
|
-
|
|
174
|
-
**Three new tokens, all three read off an unapproved drawing.** They reach your
|
|
175
|
-
app through `./style.css` and `./theme.css` like every other token, and the
|
|
176
|
-
derivation comment on each says in as many words that the value is not settled:
|
|
177
|
-
|
|
178
|
-
- `--container-account-menu: 312px` — the panel's width.
|
|
179
|
-
- `--spacing-3_5: 14px` — the person block's block padding.
|
|
180
|
-
- `--spacing-9_5: 38px` — the person block's avatar, the largest in the package.
|
|
181
|
-
|
|
182
|
-
Nothing else in the package reads any of the three, so no existing rendering
|
|
183
|
-
moves, and **no baseline moved**: all 27 gated states come back unchanged, because
|
|
184
|
-
every token here is additive and no shared value was touched.
|
|
185
|
-
|
|
186
|
-
**Four places where this does not reproduce its drawing**, each named at its rule
|
|
187
|
-
in `AccountMenu.module.css`, and each because the drawn value is not a step this
|
|
188
|
-
package's token set holds and the nearest step is within about a pixel — the trade
|
|
189
|
-
`PageHeader` already records for `--leading-tight` against Paper's 30px. The row's
|
|
190
|
-
glyph-to-text gap (11px drawn, `--spacing-2_5` here); the person block's
|
|
191
|
-
name-to-email gap (3px drawn, `--spacing-0_5`, which is what the rows draw for the
|
|
192
|
-
same relationship); two 10px type sizes (`--text-2xs`); and every line-height,
|
|
193
|
-
because the drawing sets them in px and this package holds only percentages.
|
|
194
|
-
|
|
195
|
-
**One open design question, recorded rather than decided.** This panel picks a row
|
|
196
|
-
out with `--color-neutral-50`, where every other popover in the package uses
|
|
197
|
-
`--color-surface-muted`, one neutral step darker. Reproduced as drawn and raised
|
|
198
|
-
at the gate — the drawing was cloned from an Identity screen that predates the
|
|
199
|
-
shared token pass, which is why it is the only popover in its own file still bound
|
|
200
|
-
to literals.
|
|
201
|
-
|
|
202
|
-
#### Not in `dist/`, and so not your concern
|
|
203
|
-
|
|
204
|
-
The specimen gains an `AccountMenu` route with two sections, neither screenshot-gated
|
|
205
|
-
and both recorded in `SPECIMEN_ONLY` with reasons: the panel, whose first baseline
|
|
206
|
-
is a *designed* baseline only the person at the fidelity gate may capture, and a
|
|
207
|
-
row rendered through a stand-in link component, which draws identically to the
|
|
208
|
-
default by design. `tests/account-menu.test.tsx` adds 28 cases covering the
|
|
209
|
-
structure, the row elements, the hairline-per-group rule, the label fallbacks and
|
|
210
|
-
the ADR-0011 pass-through — none of them asserting geometry, which jsdom cannot
|
|
211
|
-
see.
|
|
212
|
-
|
|
213
97
|
### Added — `NotificationCenter`, Stage A only, **not releasable yet** (B33-494)
|
|
214
98
|
|
|
215
99
|
> [!danger] This component has not passed its fidelity gate and must not be
|
|
@@ -299,6 +183,213 @@ most consumers meet first.
|
|
|
299
183
|
still gains no `aria-haspopup`, and it still opens nothing. Connecting the two is
|
|
300
184
|
Stage B.
|
|
301
185
|
|
|
186
|
+
### Internal — Storybook and Chromatic. No consumer-visible change
|
|
187
|
+
|
|
188
|
+
**Nothing a consumer imports from `@boost33/ui` changes.** None of the files
|
|
189
|
+
below are reachable from either entry point. This entry exists so the omission
|
|
190
|
+
reads as a decision, and so whoever cuts the next version knows what the repo
|
|
191
|
+
gained. The Backdrop components themselves moved out of this section into 0.9.0,
|
|
192
|
+
the version that publishes them.
|
|
193
|
+
|
|
194
|
+
- **Storybook is the preview surface, and Chromatic holds its snapshots.**
|
|
195
|
+
`npm run storybook` opens it; `.storybook/` configures it with the Backdrop
|
|
196
|
+
light and black themes as a global. CI renders every story in Chromium on
|
|
197
|
+
each PR (`npx vitest run --project storybook`, in `screenshot-diff.yml`). That
|
|
198
|
+
step compares no pixels and writes no baseline: Chromatic does the comparing,
|
|
199
|
+
and only a person accepts a snapshot. The Chromatic project token is a GitHub
|
|
200
|
+
secret and appears in no file. The specimen app and its screenshot diff are
|
|
201
|
+
unchanged and still binding (`CLAUDE.md`, "Storybook is the preview surface").
|
|
202
|
+
- Two deliberate changes to accepted snapshots inside Backdrop before it was
|
|
203
|
+
published: the textarea pads 14 at the sides, like the input and select
|
|
204
|
+
(Batch 4 gate), and the Hub page stories draw the Hub Final "Day 20" board
|
|
205
|
+
(Batch 5 gate). Neither touched an 0.8 component.
|
|
206
|
+
- `Boost33Mark` and `Wordmark` gain stories. Their components are unchanged, and
|
|
207
|
+
the stories' type declarations are kept out of `dist/`.
|
|
208
|
+
- New dev dependencies only: Storybook 10 and its addons, `chromatic`,
|
|
209
|
+
`playwright` and the vitest browser runner. No runtime dependency and no peer
|
|
210
|
+
dependency changes.
|
|
211
|
+
|
|
212
|
+
## 0.9.0 — 2026-11-12
|
|
213
|
+
|
|
214
|
+
**Not breaking. Nothing changes unless you import the new entry point.** This
|
|
215
|
+
version adds Backdrop, the next app shell, as a second entry point,
|
|
216
|
+
`@boost33/ui/backdrop`, beside the 0.8 shell. `@boost33/ui` itself is unchanged:
|
|
217
|
+
every 0.8 export, its JavaScript, its types and `style.css` are byte for byte
|
|
218
|
+
what 0.8.1 shipped (checked by diffing `dist/` against the published 0.8.1
|
|
219
|
+
tarball). An app that bumps to 0.9.0 and imports nothing new renders exactly what
|
|
220
|
+
it rendered on 0.8.1.
|
|
221
|
+
|
|
222
|
+
**Held back, as before:** `CommandPalette` and `NotificationCenter` under
|
|
223
|
+
Unreleased have still not passed their fidelity gate.
|
|
224
|
+
|
|
225
|
+
### Added — `@boost33/ui/backdrop`, the Backdrop shell, for each app's preview build (B33-1760)
|
|
226
|
+
|
|
227
|
+
**Build it behind your app's preview switch. Do not show it to customers yet.**
|
|
228
|
+
Every app moves to Backdrop on the same day, and each app is all or nothing:
|
|
229
|
+
until launch, an app renders its Backdrop screens only when
|
|
230
|
+
`isBackdropPreview(claims)` says so (the `design` claim Identity serves in
|
|
231
|
+
`b33_ctx`, B33-1759), and renders the 0.8 shell otherwise (Linear,
|
|
232
|
+
"Switching every app to Backdrop at once"; ADR-0017). Launch is one change in
|
|
233
|
+
Identity, not a release of this package. This replaces the earlier plan, in
|
|
234
|
+
this file's history, of adopting Backdrop screen by screen.
|
|
235
|
+
|
|
236
|
+
**One install, both shells.** The two entry points share no export name: every
|
|
237
|
+
Backdrop name carries the `Backdrop` prefix (`useBackdropPanel` for the one
|
|
238
|
+
hook), so an app imports both without aliases:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
import { AppShell } from '@boost33/ui' // switch off
|
|
242
|
+
import { BackdropChrome, BackdropFrame } from '@boost33/ui/backdrop' // switch on
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Load the stylesheet yourself, once, in your root layout**, the same way you
|
|
246
|
+
load `style.css` today. Nothing is injected by the JavaScript:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import '@boost33/ui/style.css' // the 0.8 shell, as today
|
|
250
|
+
import '@boost33/ui/backdrop.css' // Backdrop
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- `backdrop.css` stands alone. It carries the design tokens Backdrop reads, so a
|
|
254
|
+
page that renders only Backdrop does not need `style.css`.
|
|
255
|
+
- Loading both is safe. The 161 token names they share hold the same values in
|
|
256
|
+
both files, every token Backdrop adds is named `--backdrop-*`, every class is
|
|
257
|
+
scoped to its component, and `backdrop.css` styles no bare element. Neither
|
|
258
|
+
sheet changes how the other shell renders.
|
|
259
|
+
- Fonts work as in 0.8: set `--boost33-font-display`, `--boost33-font-sans` and
|
|
260
|
+
`--boost33-font-mono` to the faces your app loads.
|
|
261
|
+
- The light and black frames: `BackdropFrame`'s `theme` prop, or
|
|
262
|
+
`data-backdrop-theme="light" | "black"` on an ancestor.
|
|
263
|
+
|
|
264
|
+
**Server Components can import the barrel.** Six modules open with
|
|
265
|
+
`"use client"`: `BackdropChrome`, `BackdropFields`, `BackdropOverlays`,
|
|
266
|
+
`BackdropPatterns`, `BackdropRail` and `BackdropSheets`. Everything else renders
|
|
267
|
+
on the server. Checked by packing this version and building a Next.js 16 App
|
|
268
|
+
Router page that renders the frame, strip, rail, page header, card, field,
|
|
269
|
+
input, switch, button and pill from a Server Component, plus `useBackdropPanel`
|
|
270
|
+
in a client child. The usual rule holds: a function prop (`onClick`,
|
|
271
|
+
`onChange`, `linkComponent`) has to come from a client component of yours.
|
|
272
|
+
|
|
273
|
+
**`BackdropChrome`: one open panel at a time.** Put it around the frame, once per
|
|
274
|
+
layout. It holds which chrome panel is open, so opening one closes the other,
|
|
275
|
+
and a modal dialog closes an open popover (ADR-0009 addendum). Its children may
|
|
276
|
+
be Server Components.
|
|
277
|
+
|
|
278
|
+
- An overlay joins by naming a key: `panel` on the popovers, dialog and side
|
|
279
|
+
panel; `allAppsPanel` on the rail.
|
|
280
|
+
- `useBackdropPanel(key)` opens a panel from anywhere inside it, such as a ⌘K
|
|
281
|
+
handler or the Hub's "All apps" card. It throws outside a `BackdropChrome`,
|
|
282
|
+
on purpose: an opener that silently does nothing would pass every review.
|
|
283
|
+
- An overlay with no key keeps its own open state, as before.
|
|
284
|
+
|
|
285
|
+
**Icons are names, not components.** `backdropIconNames` maps each meaning to one
|
|
286
|
+
Tabler icon name (ADR-0017). The package does not depend on `@tabler/icons-react`
|
|
287
|
+
and installs no icon library: your app installs it and renders the named icon.
|
|
288
|
+
No new runtime or peer dependency in this version.
|
|
289
|
+
|
|
290
|
+
**What is in it, and how finished each part is.** Every part passed its fidelity
|
|
291
|
+
gate in Chromatic (builds 3 to 11, 10–12 Nov 2026), and the approved measurements
|
|
292
|
+
are in the spec, "The shared app shell". ADR-0017 records one size per job across
|
|
293
|
+
all the apps. **Several parts are still Stage A: they draw the approved view and
|
|
294
|
+
do not yet fully respond.** Stage B (keyboard, focus and state) for batches 2 to
|
|
295
|
+
5 is the remaining package work; build against them, and expect their behaviour
|
|
296
|
+
to arrive in later 0.9.x or 0.10 versions without their props changing shape.
|
|
297
|
+
|
|
298
|
+
| | Parts | Responds? |
|
|
299
|
+
| --- | --- | --- |
|
|
300
|
+
| Batch 0 | the `--backdrop-` tokens, `backdropIconNames` | n/a |
|
|
301
|
+
| Batch 1 | the frame, top strip, rail, sheets, side panel, app menu; global panels (account menu, tokens, mission, get started, All apps, search); overlays (popover, menu list, segmented, dialog, toast); buttons, tooltip, tabs; patterns (disclosure, overflow menu, filter, checkbox, bulk bar, next step, all set, availability); basics, page and display parts | **Yes, for the overlays**: menus, popovers, the dialog and the side panel have their keyboard and focus. The rest is static by nature or Stage A |
|
|
302
|
+
| Batch 2 | fields and flows: field, input, textarea, select, search field, switch, radios and radio cards, settings rows and section, save footer, flow header, result | **Stage A** |
|
|
303
|
+
| Batch 3 | lists and data: list, list row, key/value list, data table, list toolbar, status mark, meter, summary card, checklist, review card, board | **Stage A** |
|
|
304
|
+
| Batch 4 | conversation and charts: thread, message, day separator, composer, bar chart, bar list, line chart | **Stage A** |
|
|
305
|
+
| Batch 5 | the Hub parts: quick action, app card, profile strip, map card | **Stage A**. The search dialog's input takes `value` and `onChange`; the rail's All apps tile can open a popover (`allAppsPopover`) |
|
|
306
|
+
| Batch 6 | fixes from the Hub's build: the search dialog takes `onClose` (Escape and the scrim); a side popover stays inside the window | Yes |
|
|
307
|
+
| Batch 7 | `BackdropChrome` and `useBackdropPanel`, above | Yes |
|
|
308
|
+
| Batch 8 | fixes from the Hub's build: `BackdropTokensPopover`'s `primaryAction` is optional (no live wallet, nowhere to buy); `backdropIconNames` gains seven shared chrome meanings (mission, docs, shortcuts, language, billing, switch account, sign out); `BackdropAppCard` and `BackdropQuickAction` pass the click event to `onClick`, so a host can let a new-tab click through without a launch screen; a `BackdropDialog` with a title and no `label` is named by its title for screen readers | Yes |
|
|
309
|
+
|
|
310
|
+
The full list of names is `src/backdrop.ts`, pinned in
|
|
311
|
+
`tests/public-api.test.ts`: 101 values and 181 types.
|
|
312
|
+
|
|
313
|
+
## 0.8.1 — 2026-11-05
|
|
314
|
+
|
|
315
|
+
**Not breaking. Add one element to your top bar.** The account menu, the Hub's,
|
|
316
|
+
for every app: the avatar opens the same menu in all ten products (Jamal, 5 Nov
|
|
317
|
+
2026). New exports only; an app that passes nothing new renders exactly what
|
|
318
|
+
0.8.0 rendered.
|
|
319
|
+
|
|
320
|
+
**Held back, as before:** `CommandPalette` and `NotificationCenter` under
|
|
321
|
+
Unreleased have still not passed their fidelity gate.
|
|
322
|
+
|
|
323
|
+
### Added — `StandardAccountMenu`, the account menu behind every app's avatar, and `topBar.account` (B33-493)
|
|
324
|
+
|
|
325
|
+
The Hub's working menu (boost33-hub `src/app/account-menu.tsx`), moved into the
|
|
326
|
+
kit so every app draws the same one. It passed its fidelity gate on 5 Nov 2026
|
|
327
|
+
against a render of the Hub's own component.
|
|
328
|
+
|
|
329
|
+
What a consumer does:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { AppShell, StandardAccountMenu } from '@boost33/ui'
|
|
333
|
+
|
|
334
|
+
<AppShell
|
|
335
|
+
topBar={{
|
|
336
|
+
actions: <NotificationButton … />,
|
|
337
|
+
account: (
|
|
338
|
+
<StandardAccountMenu
|
|
339
|
+
person={{ name: claims.name, email: claims.email, avatarUrl }}
|
|
340
|
+
org={orgName ? { name: orgName } : undefined}
|
|
341
|
+
role={role} // 'admin' | 'member' | null
|
|
342
|
+
identityUrl="https://id.boost33.com"
|
|
343
|
+
hubUrl="https://app.boost33.com"
|
|
344
|
+
otherAccountsCount={claims.accounts.length - 1}
|
|
345
|
+
/>
|
|
346
|
+
),
|
|
347
|
+
}}
|
|
348
|
+
…
|
|
349
|
+
/>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- **Safe from a Server Component layout.** Every prop is plain data. You pass no
|
|
353
|
+
`linkComponent` and no callback, so nothing crosses the client boundary as a
|
|
354
|
+
function. `AccountMenu` is a client component inside the package;
|
|
355
|
+
`StandardAccountMenu` is not.
|
|
356
|
+
- **Remove your own avatar** from `topBar.actions`. `topBar.account` (new on
|
|
357
|
+
`TopBar` and `AppShell`) draws after `actions`, at the far right of the bar.
|
|
358
|
+
- **The rows, the Hub's, with absolute links**, so they work from any app and
|
|
359
|
+
from the Hub itself:
|
|
360
|
+
- Profile, to `{hubUrl}/settings/profile`
|
|
361
|
+
- Security, to `{identityUrl}/account/security`
|
|
362
|
+
- Settings, to `{hubUrl}/settings/org/general`, described by role
|
|
363
|
+
- Switch account, to `{identityUrl}/choose-account`, only when
|
|
364
|
+
`otherAccountsCount` is above 0, described by `otherAccountNames` when every
|
|
365
|
+
account is named, or else by the count
|
|
366
|
+
- Feedback (`mailto:support@boost33.com`) and Docs (`https://docs.boost33.com`)
|
|
367
|
+
- Sign out, to `{identityUrl}/signout`
|
|
368
|
+
- **The Hub's production URL is `https://app.boost33.com`** (Identity's
|
|
369
|
+
`HUB_URL`). `hub.boost33.com` does not resolve.
|
|
370
|
+
- **The trigger is the Hub's**: a 26px avatar with the person's initials, or their
|
|
371
|
+
photo from `person.avatarUrl`, named "Your account". The panel opens 10px below
|
|
372
|
+
it, aligned to its right edge.
|
|
373
|
+
- **Keyboard and focus are the Hub's `useMenu`**: a WAI-ARIA menu button.
|
|
374
|
+
- Opening moves focus to the first row (ArrowUp on the trigger: the last).
|
|
375
|
+
- The arrows wrap; Home and End jump.
|
|
376
|
+
- Escape closes and returns focus to the avatar; Tab leaves and closes.
|
|
377
|
+
- A press outside closes it, and choosing a row closes it.
|
|
378
|
+
- **Words**: `labels` takes the rows' words (`StandardAccountMenuLabels`, English
|
|
379
|
+
defaults the Hub's); `menuLabels` takes the menu's accessible names.
|
|
380
|
+
- **Lower level**: `AccountMenu` takes `groups` of rows of your own, and
|
|
381
|
+
`standardAccountMenuGroups(options)` returns the standard rows as data to
|
|
382
|
+
extend. Do not add per-app rows without asking: one menu everywhere is the
|
|
383
|
+
point.
|
|
384
|
+
|
|
385
|
+
Changed from the Stage A `AccountMenu` that 0.8.0 exported but did not release:
|
|
386
|
+
|
|
387
|
+
- It now draws its own trigger and opens a `role="menu"`, not a static panel.
|
|
388
|
+
- `person.name`, `org` and `org.initials` are optional.
|
|
389
|
+
- The default accessible name is "Your account", not "Account".
|
|
390
|
+
- Rows are `menuitem`s with `tabIndex={-1}`.
|
|
391
|
+
- `AccountMenuIcon` gains `security`, `feedback` and `docs`.
|
|
392
|
+
|
|
302
393
|
## 0.8.0 — 2026-11-05
|
|
303
394
|
|
|
304
395
|
**Breaking. Read the B33-1712 entry before you bump.** The 3 November 2026
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare const backdropIconNames: {
|
|
2
|
+
readonly home: "home";
|
|
3
|
+
readonly inbox: "inbox";
|
|
4
|
+
readonly apps: "apps";
|
|
5
|
+
readonly settings: "settings";
|
|
6
|
+
readonly help: "help-circle";
|
|
7
|
+
readonly ask: "message-circle";
|
|
8
|
+
readonly search: "search";
|
|
9
|
+
readonly tokens: "coins";
|
|
10
|
+
readonly wallet: "wallet";
|
|
11
|
+
readonly back: "arrow-left";
|
|
12
|
+
readonly forward: "chevron-right";
|
|
13
|
+
readonly expand: "chevron-down";
|
|
14
|
+
readonly externalLink: "external-link";
|
|
15
|
+
readonly mission: "trophy";
|
|
16
|
+
readonly docs: "book";
|
|
17
|
+
readonly shortcuts: "keyboard";
|
|
18
|
+
readonly language: "language";
|
|
19
|
+
readonly billing: "credit-card";
|
|
20
|
+
readonly switchAccount: "switch-horizontal";
|
|
21
|
+
readonly signOut: "logout";
|
|
22
|
+
readonly add: "plus";
|
|
23
|
+
readonly close: "x";
|
|
24
|
+
readonly more: "dots";
|
|
25
|
+
readonly edit: "pencil";
|
|
26
|
+
readonly copy: "copy";
|
|
27
|
+
readonly link: "link";
|
|
28
|
+
readonly attach: "paperclip";
|
|
29
|
+
readonly upload: "upload";
|
|
30
|
+
readonly download: "download";
|
|
31
|
+
readonly filter: "filter";
|
|
32
|
+
readonly send: "send";
|
|
33
|
+
readonly approveAll: "checks";
|
|
34
|
+
readonly company: "building";
|
|
35
|
+
readonly person: "user";
|
|
36
|
+
readonly team: "users";
|
|
37
|
+
readonly document: "file-text";
|
|
38
|
+
readonly invoice: "file-invoice";
|
|
39
|
+
readonly email: "mail";
|
|
40
|
+
readonly message: "message";
|
|
41
|
+
readonly calendar: "calendar";
|
|
42
|
+
readonly location: "map-pin";
|
|
43
|
+
readonly website: "world";
|
|
44
|
+
readonly done: "circle-check";
|
|
45
|
+
readonly verified: "circle-check";
|
|
46
|
+
readonly warning: "alert-circle";
|
|
47
|
+
readonly blocking: "alert-triangle";
|
|
48
|
+
readonly info: "info-circle";
|
|
49
|
+
readonly time: "clock";
|
|
50
|
+
readonly private: "lock";
|
|
51
|
+
};
|
|
52
|
+
export type BackdropIconMeaning = keyof typeof backdropIconNames;
|
|
53
|
+
export type BackdropIconName = (typeof backdropIconNames)[BackdropIconMeaning];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const e = {
|
|
2
|
+
// Shell and navigation
|
|
3
|
+
home: "home",
|
|
4
|
+
inbox: "inbox",
|
|
5
|
+
apps: "apps",
|
|
6
|
+
settings: "settings",
|
|
7
|
+
help: "help-circle",
|
|
8
|
+
ask: "message-circle",
|
|
9
|
+
search: "search",
|
|
10
|
+
tokens: "coins",
|
|
11
|
+
wallet: "wallet",
|
|
12
|
+
back: "arrow-left",
|
|
13
|
+
forward: "chevron-right",
|
|
14
|
+
expand: "chevron-down",
|
|
15
|
+
externalLink: "external-link",
|
|
16
|
+
// The strip's mission chip, the help menu and the account menu (Batch 8,
|
|
17
|
+
// from the Hub app's gap 5): the Hub's picks, so the next app does not
|
|
18
|
+
// pick differently.
|
|
19
|
+
mission: "trophy",
|
|
20
|
+
docs: "book",
|
|
21
|
+
shortcuts: "keyboard",
|
|
22
|
+
language: "language",
|
|
23
|
+
billing: "credit-card",
|
|
24
|
+
switchAccount: "switch-horizontal",
|
|
25
|
+
signOut: "logout",
|
|
26
|
+
// Actions
|
|
27
|
+
add: "plus",
|
|
28
|
+
close: "x",
|
|
29
|
+
more: "dots",
|
|
30
|
+
edit: "pencil",
|
|
31
|
+
copy: "copy",
|
|
32
|
+
link: "link",
|
|
33
|
+
attach: "paperclip",
|
|
34
|
+
upload: "upload",
|
|
35
|
+
download: "download",
|
|
36
|
+
filter: "filter",
|
|
37
|
+
send: "send",
|
|
38
|
+
approveAll: "checks",
|
|
39
|
+
// Things
|
|
40
|
+
company: "building",
|
|
41
|
+
person: "user",
|
|
42
|
+
team: "users",
|
|
43
|
+
document: "file-text",
|
|
44
|
+
invoice: "file-invoice",
|
|
45
|
+
email: "mail",
|
|
46
|
+
message: "message",
|
|
47
|
+
calendar: "calendar",
|
|
48
|
+
location: "map-pin",
|
|
49
|
+
website: "world",
|
|
50
|
+
// States
|
|
51
|
+
done: "circle-check",
|
|
52
|
+
verified: "circle-check",
|
|
53
|
+
warning: "alert-circle",
|
|
54
|
+
blocking: "alert-triangle",
|
|
55
|
+
info: "info-circle",
|
|
56
|
+
time: "clock",
|
|
57
|
+
private: "lock"
|
|
58
|
+
};
|
|
59
|
+
export {
|
|
60
|
+
e as backdropIconNames
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=backdrop-icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backdrop-icons.js","sources":["../src/backdrop-icons.ts"],"sourcesContent":["/*\n * One Tabler icon per meaning (ADR-0017, D18).\n *\n * The cross-app review of 11 Nov 2026 found twelve meanings drawn with two or\n * three different icons (\"company\" as both `building` and\n * `building-skyscraper`, on the same page) and three icons each standing for\n * two things. This map is the single answer for the meanings every product\n * shares. A consumer renders the named icon from its own copy of Tabler\n * (`@tabler/icons-react`, `IconBuilding` for `building`); the package ships the\n * names only, so it takes no icon dependency -- the same choice icons.tsx made.\n *\n * Only generic meanings belong here. Which icon stands for \"Offers\" or \"Cost\n * profile\" is a product decision and lives in that product's repo (CLAUDE.md:\n * no component here knows what a lead, a campaign or a prospect is). ADR-0017\n * records the product-level picks for the designers.\n *\n * Values are Tabler's own icon names, outline style. tests/backdrop-icons.test.ts\n * fails if a name is not a real Tabler icon.\n */\nexport const backdropIconNames = {\n // Shell and navigation\n home: 'home',\n inbox: 'inbox',\n apps: 'apps',\n settings: 'settings',\n help: 'help-circle',\n ask: 'message-circle',\n search: 'search',\n tokens: 'coins',\n wallet: 'wallet',\n back: 'arrow-left',\n forward: 'chevron-right',\n expand: 'chevron-down',\n externalLink: 'external-link',\n // The strip's mission chip, the help menu and the account menu (Batch 8,\n // from the Hub app's gap 5): the Hub's picks, so the next app does not\n // pick differently.\n mission: 'trophy',\n docs: 'book',\n shortcuts: 'keyboard',\n language: 'language',\n billing: 'credit-card',\n switchAccount: 'switch-horizontal',\n signOut: 'logout',\n\n // Actions\n add: 'plus',\n close: 'x',\n more: 'dots',\n edit: 'pencil',\n copy: 'copy',\n link: 'link',\n attach: 'paperclip',\n upload: 'upload',\n download: 'download',\n filter: 'filter',\n send: 'send',\n approveAll: 'checks',\n\n // Things\n company: 'building',\n person: 'user',\n team: 'users',\n document: 'file-text',\n invoice: 'file-invoice',\n email: 'mail',\n message: 'message',\n calendar: 'calendar',\n location: 'map-pin',\n website: 'world',\n\n // States\n done: 'circle-check',\n verified: 'circle-check',\n warning: 'alert-circle',\n blocking: 'alert-triangle',\n info: 'info-circle',\n time: 'clock',\n private: 'lock',\n} as const\n\nexport type BackdropIconMeaning = keyof typeof backdropIconNames\nexport type BackdropIconName = (typeof backdropIconNames)[BackdropIconMeaning]\n"],"names":["backdropIconNames"],"mappings":"AAmBO,MAAMA,IAAoB;AAAA;AAAA,EAE/B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,cAAc;AAAA;AAAA;AAAA;AAAA,EAId,SAAS;AAAA,EACT,MAAM;AAAA,EACN,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,eAAe;AAAA,EACf,SAAS;AAAA;AAAA,EAGT,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA;AAAA,EAGZ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AAAA;AAAA,EAGT,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AACX;"}
|