@aircall/blocks 0.16.0 → 0.18.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/dist/globals.css +1 -1
- package/dist/index.d.ts +656 -44
- package/dist/index.js +668 -80
- package/dist/theme.css +103 -0
- package/package.json +6 -5
- package/skills/aircall-blocks/migrate-dashboard/SKILL.md +19 -2
- package/skills/aircall-blocks/migrate-dashboard/card/SKILL.md +43 -71
- package/skills/aircall-blocks/migrate-dashboard/catch-up-checklist/SKILL.md +109 -0
- package/skills/aircall-blocks/migrate-dashboard/combobox/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/copy-button/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/dashboard-page/SKILL.md +39 -1
- package/skills/aircall-blocks/migrate-dashboard/data-table/SKILL.md +10 -1
- package/skills/aircall-blocks/migrate-dashboard/empty-states/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +40 -1
- package/skills/aircall-blocks/migrate-dashboard/info-popup/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/layout/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/list/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/loading/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/page-header/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/setting-card/SKILL.md +258 -0
- package/skills/aircall-blocks/migrate-dashboard/tile/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/toggle-row/SKILL.md +0 -1
- package/skills/aircall-blocks/setup/SKILL.md +22 -1
|
@@ -7,7 +7,6 @@ description: >
|
|
|
7
7
|
FormWizard, useFormWizard, or FormField from @dashboard/library.
|
|
8
8
|
type: sub-skill
|
|
9
9
|
library: aircall-blocks
|
|
10
|
-
library_version: "0.5.1"
|
|
11
10
|
requires:
|
|
12
11
|
- aircall-blocks/setup
|
|
13
12
|
- aircall-blocks/migrate-dashboard
|
|
@@ -247,6 +246,46 @@ spread the control bundle onto the matching DS primitive.
|
|
|
247
246
|
| combobox (single) | `FormComboboxField` | switch | `FormSwitchField` |
|
|
248
247
|
| combobox (multi) | `FormMultiComboboxField` | number | `FormNumericField` |
|
|
249
248
|
| textarea | `FormTextareaField` | slider | `FormSliderField` |
|
|
249
|
+
| toggle group | `FormToggleGroupField` | | |
|
|
250
|
+
|
|
251
|
+
### Building a wrapper for an unlisted primitive (`FormFieldBase`)
|
|
252
|
+
|
|
253
|
+
For a DS primitive without a shipped `Form*Field`, build one on `FormFieldBase` — it owns the field binding + the shared `Field`/`Label`/`Description`/`Error` shell; you supply a `buildControl` that maps the typed field to the control bundle. This is the shape every shipped wrapper follows (here re-creating `FormSwitchField`, which already ships — copy the pattern for a genuinely new primitive):
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { FormFieldBase, type BoundForm, type FormFieldValidators, type TypedField } from '@aircall/blocks';
|
|
257
|
+
import { Switch } from '@aircall/ds';
|
|
258
|
+
import type { DeepKeysOfType } from '@tanstack/react-form';
|
|
259
|
+
|
|
260
|
+
type SwitchControl = {
|
|
261
|
+
switchProps: { checked: boolean; onCheckedChange: (checked: boolean) => void; onBlur: () => void };
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
function switchControl(field: TypedField<boolean>): SwitchControl {
|
|
265
|
+
return {
|
|
266
|
+
switchProps: {
|
|
267
|
+
checked: field.state.value,
|
|
268
|
+
onCheckedChange: checked => field.handleChange(checked),
|
|
269
|
+
onBlur: field.handleBlur,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function FormSwitchField<
|
|
275
|
+
TForm extends BoundForm,
|
|
276
|
+
TName extends DeepKeysOfType<TForm['state']['values'], boolean> // only boolean fields
|
|
277
|
+
>(props: {
|
|
278
|
+
form: TForm;
|
|
279
|
+
name: TName;
|
|
280
|
+
label: string;
|
|
281
|
+
validators?: FormFieldValidators<TForm['state']['values'], TName>;
|
|
282
|
+
children: (field: TypedField<boolean>, control: SwitchControl) => React.ReactNode;
|
|
283
|
+
}) {
|
|
284
|
+
return <FormFieldBase<boolean, SwitchControl> {...props} buildControl={switchControl} />;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`DeepKeysOfType<…, boolean>` restricts `name` to fields whose value type matches the control (here `boolean`), so the form stays type-safe.
|
|
250
289
|
|
|
251
290
|
## Multi-step gotchas
|
|
252
291
|
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/setting-card
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library Paper and PaperForm (settings-page surfaces) to the
|
|
5
|
+
@aircall/blocks SettingCard family (SettingCard, SettingCardHeader, SettingCardTitle,
|
|
6
|
+
SettingCardDescription, SettingCardAction, SettingCardContent) plus useForm + CardSaveBar
|
|
7
|
+
for the save bar. Load when a file imports Paper or PaperForm from @dashboard/library and
|
|
8
|
+
the surface is a settings section (title / description / optional toggle or action /
|
|
9
|
+
optional save bar). The errorBoundary is handled OUTSIDE the card — it does not migrate.
|
|
10
|
+
type: sub-skill
|
|
11
|
+
library: aircall-blocks
|
|
12
|
+
requires:
|
|
13
|
+
- aircall-blocks/setup
|
|
14
|
+
- aircall-blocks/migrate-dashboard
|
|
15
|
+
sources:
|
|
16
|
+
- "aircall/hydra:packages/blocks/src/components/setting-card.tsx"
|
|
17
|
+
- "aircall/hydra:packages/blocks/src/components/card-save-bar.tsx"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
This skill builds on aircall-blocks/migrate-dashboard.
|
|
21
|
+
|
|
22
|
+
`Paper` / `PaperForm` are the `@dashboard/library` settings-page surfaces (title, subtitle,
|
|
23
|
+
optional header action, content, optional save-bar footer). Their target is **`SettingCard`**
|
|
24
|
+
(`@aircall/blocks`) — a thin composition over the DS `Card` that adds the settings level
|
|
25
|
+
backgrounds and the level-aware title. For a **non-settings** card (a tile or generic
|
|
26
|
+
container with no title/toggle semantics) use the raw `Card` primitives instead — load
|
|
27
|
+
`@aircall/blocks#aircall-blocks/migrate-dashboard/card`.
|
|
28
|
+
|
|
29
|
+
## 1. Component mapping
|
|
30
|
+
|
|
31
|
+
| @dashboard/library | @aircall/blocks / @aircall/ds |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `Paper` (root surface) | `SettingCard` |
|
|
34
|
+
| `Paper` `title` prop | `SettingCardTitle` inside `SettingCardHeader` |
|
|
35
|
+
| `Paper` `subtitle` prop | `SettingCardDescription` inside `SettingCardHeader` |
|
|
36
|
+
| `Paper` `titleSide` prop | `SettingCardAction` inside `SettingCardHeader` |
|
|
37
|
+
| `Paper` `children` (body) | `SettingCardContent` |
|
|
38
|
+
| `Paper` `footer` (save/discard bar) | `useForm` + `CardSaveBar` (form on the outer level) |
|
|
39
|
+
| `Paper` `footer` (generic) | plain children after `SettingCardContent`, or a DS `CardFooter` |
|
|
40
|
+
| `Paper` `banner` prop | inline DS `Banner` before `SettingCardHeader` |
|
|
41
|
+
| `Paper` `disabled` / `disabledText` | app-level — dim the card + render a lock label; `SettingCard` has no `disabled` prop |
|
|
42
|
+
| `Paper` `fluid` prop | content is already full-width — drop it |
|
|
43
|
+
| `Paper` `errorBoundary` prop | **stays in the app, OUTSIDE `SettingCard`** — see §3. Does NOT migrate. |
|
|
44
|
+
| `Paper` `BoxProps` spread | drop — `className` for genuine one-offs only |
|
|
45
|
+
| `PaperForm` (form + save bar) | `<form>` (outer) + `useForm` + `SettingCard` + `CardSaveBar` |
|
|
46
|
+
| `PaperForm` `formProps` (`react-final-form`) | `useForm` (TanStack Form) — `defaultValues`, `onSubmit: async ({ value })` |
|
|
47
|
+
| `PaperForm` `getErrorMessage` / `submitError` | catch in `onSubmit`, store in state, render an `Alert` above the content |
|
|
48
|
+
| `PaperForm` `shouldBlockNavigation` | app-level (navigation blocker) — not part of blocks |
|
|
49
|
+
| `PaperForm` `submitButtonText` / `undoButtonText` | `CardSaveBar` `submitLabel` / `resetLabel` |
|
|
50
|
+
|
|
51
|
+
## 2. Imports
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// blocks — SettingCard family + TanStack Form + save bar
|
|
55
|
+
import {
|
|
56
|
+
SettingCard,
|
|
57
|
+
SettingCardHeader,
|
|
58
|
+
SettingCardTitle,
|
|
59
|
+
SettingCardDescription,
|
|
60
|
+
SettingCardAction,
|
|
61
|
+
SettingCardContent,
|
|
62
|
+
useForm,
|
|
63
|
+
CardSaveBar
|
|
64
|
+
} from '@aircall/blocks';
|
|
65
|
+
|
|
66
|
+
// DS primitives used inside (Button, Switch, Alert, Banner, fields …)
|
|
67
|
+
import { Button, Switch, Alert } from '@aircall/ds';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 3. The errorBoundary stays OUTSIDE SettingCard
|
|
71
|
+
|
|
72
|
+
`Paper` wrapped **its children** in `ScopedErrorBoundary` (plus an optional
|
|
73
|
+
`ErrorContextProvider`) via the `errorBoundary` prop. Those are **app-level**
|
|
74
|
+
`@dashboard/library` components — `ScopedErrorBoundary` is react-router-aware and reads
|
|
75
|
+
`ErrorContext`; neither migrates to blocks/ds. `SettingCard` is a presentational layout
|
|
76
|
+
primitive and deliberately has **no** `errorBoundary` prop.
|
|
77
|
+
|
|
78
|
+
So keep the boundary in the app and wrap the **content** (not the whole card), matching
|
|
79
|
+
`Paper`'s scope — a content crash still leaves the card title/description visible:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { ScopedErrorBoundary, ErrorContextProvider } from '@dashboard/library';
|
|
83
|
+
|
|
84
|
+
<SettingCard>
|
|
85
|
+
<SettingCardHeader>
|
|
86
|
+
<SettingCardTitle>Redaction</SettingCardTitle>
|
|
87
|
+
<SettingCardDescription>Mask sensitive data in transcripts.</SettingCardDescription>
|
|
88
|
+
</SettingCardHeader>
|
|
89
|
+
<SettingCardContent>
|
|
90
|
+
{/* errorBoundary lives here, around the fallible content — NOT on SettingCard */}
|
|
91
|
+
<ScopedErrorBoundary message="Couldn't load redaction settings" showRetryButton>
|
|
92
|
+
<RedactionCategories />
|
|
93
|
+
</ScopedErrorBoundary>
|
|
94
|
+
</SettingCardContent>
|
|
95
|
+
</SettingCard>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If `Paper`'s `errorBoundary` config carried a `context`, keep the
|
|
99
|
+
`ErrorContextProvider` wrapper too (same nesting as `Paper` did — provider outside the
|
|
100
|
+
boundary):
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<ErrorContextProvider value={ctx}>
|
|
104
|
+
<ScopedErrorBoundary {...cfg}>{content}</ScopedErrorBoundary>
|
|
105
|
+
</ErrorContextProvider>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> If the whole section (header included) should be replaced on error, wrap the entire
|
|
109
|
+
> `<SettingCard>` instead. Default to wrapping only the content — that is what `Paper` did.
|
|
110
|
+
|
|
111
|
+
## 4. Before / After
|
|
112
|
+
|
|
113
|
+
### 4a. Basic Paper — settings surface
|
|
114
|
+
|
|
115
|
+
**Before:**
|
|
116
|
+
```tsx
|
|
117
|
+
import { Paper } from '@dashboard/library';
|
|
118
|
+
|
|
119
|
+
<Paper title="General settings" subtitle="Manage your workspace preferences.">
|
|
120
|
+
<WorkspacePreferences />
|
|
121
|
+
</Paper>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**After:**
|
|
125
|
+
```tsx
|
|
126
|
+
import { SettingCard, SettingCardHeader, SettingCardTitle, SettingCardDescription, SettingCardContent } from '@aircall/blocks';
|
|
127
|
+
|
|
128
|
+
<SettingCard>
|
|
129
|
+
<SettingCardHeader>
|
|
130
|
+
<SettingCardTitle>General settings</SettingCardTitle>
|
|
131
|
+
<SettingCardDescription>Manage your workspace preferences.</SettingCardDescription>
|
|
132
|
+
</SettingCardHeader>
|
|
133
|
+
<SettingCardContent>
|
|
134
|
+
<WorkspacePreferences />
|
|
135
|
+
</SettingCardContent>
|
|
136
|
+
</SettingCard>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- `title` → `SettingCardTitle`, `subtitle` → `SettingCardDescription`, both inside `SettingCardHeader`.
|
|
140
|
+
- Body → `SettingCardContent`. Drop the `BoxProps` spread — `SettingCard` owns the surface.
|
|
141
|
+
|
|
142
|
+
### 4b. Paper with a header action / toggle (titleSide)
|
|
143
|
+
|
|
144
|
+
**Before:**
|
|
145
|
+
```tsx
|
|
146
|
+
<Paper
|
|
147
|
+
title="Call recording"
|
|
148
|
+
subtitle="Record inbound and outbound calls."
|
|
149
|
+
titleSide={<Switch checked={enabled} onChange={setEnabled} />}
|
|
150
|
+
>
|
|
151
|
+
{enabled && <RecordingOptions />}
|
|
152
|
+
</Paper>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**After:**
|
|
156
|
+
```tsx
|
|
157
|
+
<SettingCard>
|
|
158
|
+
<SettingCardHeader>
|
|
159
|
+
<SettingCardTitle>Call recording</SettingCardTitle>
|
|
160
|
+
<SettingCardDescription>Record inbound and outbound calls.</SettingCardDescription>
|
|
161
|
+
<SettingCardAction>
|
|
162
|
+
<Switch checked={enabled} onCheckedChange={setEnabled} />
|
|
163
|
+
</SettingCardAction>
|
|
164
|
+
</SettingCardHeader>
|
|
165
|
+
{enabled && (
|
|
166
|
+
<SettingCardContent>
|
|
167
|
+
<RecordingOptions />
|
|
168
|
+
</SettingCardContent>
|
|
169
|
+
)}
|
|
170
|
+
</SettingCard>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- `titleSide` → `SettingCardAction` (inside `SettingCardHeader`; the header grid pins it top-right).
|
|
174
|
+
- DS `Switch` uses `onCheckedChange`, not tractor's `onChange`.
|
|
175
|
+
- **Progressive disclosure is consumer-controlled**: render `SettingCardContent` only when the toggle is on (`{enabled && …}`). `SettingCard` has no internal toggle→content coupling, so gated data/fields never mount while off.
|
|
176
|
+
|
|
177
|
+
### 4c. Nested settings (levels are automatic)
|
|
178
|
+
|
|
179
|
+
A `SettingCard` rendered inside another card's `SettingCardContent` auto-detects **level 2**
|
|
180
|
+
(white background + border, 16px medium title); the top card is **level 1** (muted,
|
|
181
|
+
borderless, 18px bold). No prop needed. For a rare 3rd level, force `level={1}` on the
|
|
182
|
+
deepest card to reuse the level-1 background (its title stays 16px medium automatically).
|
|
183
|
+
|
|
184
|
+
### 4d. PaperForm — form with save/discard bar
|
|
185
|
+
|
|
186
|
+
**Before:**
|
|
187
|
+
```tsx
|
|
188
|
+
import { PaperForm } from '@dashboard/library';
|
|
189
|
+
|
|
190
|
+
<PaperForm
|
|
191
|
+
title="Profile"
|
|
192
|
+
subtitle="Update your display name."
|
|
193
|
+
formProps={{ initialValues: { name: '' }, onSubmit: async (values) => save(values) }}
|
|
194
|
+
submitButtonText="Save"
|
|
195
|
+
undoButtonText="Discard"
|
|
196
|
+
>
|
|
197
|
+
{({ values }) => <input name="name" defaultValue={values.name} />}
|
|
198
|
+
</PaperForm>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**After:**
|
|
202
|
+
```tsx
|
|
203
|
+
import { SettingCard, SettingCardHeader, SettingCardTitle, SettingCardDescription, SettingCardContent, useForm, FormInputField } from '@aircall/blocks';
|
|
204
|
+
import { FieldGroup, Input } from '@aircall/ds';
|
|
205
|
+
|
|
206
|
+
function ProfileForm() {
|
|
207
|
+
const form = useForm({
|
|
208
|
+
defaultValues: { name: '' },
|
|
209
|
+
onSubmit: async ({ value }) => { await save(value); }
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
// Form on the OUTER level — SettingCard stays presentational.
|
|
214
|
+
<form onSubmit={e => { e.preventDefault(); void form.handleSubmit(); }}>
|
|
215
|
+
<SettingCard>
|
|
216
|
+
<SettingCardHeader>
|
|
217
|
+
<SettingCardTitle>Profile</SettingCardTitle>
|
|
218
|
+
<SettingCardDescription>Update your display name.</SettingCardDescription>
|
|
219
|
+
</SettingCardHeader>
|
|
220
|
+
<SettingCardContent>
|
|
221
|
+
<FieldGroup>
|
|
222
|
+
<FormInputField form={form} name="name" label="Display name">
|
|
223
|
+
{(_field, { inputProps }) => <Input {...inputProps} />}
|
|
224
|
+
</FormInputField>
|
|
225
|
+
</FieldGroup>
|
|
226
|
+
</SettingCardContent>
|
|
227
|
+
<form.AppForm>
|
|
228
|
+
<form.CardSaveBar submitLabel="Save" resetLabel="Discard" />
|
|
229
|
+
</form.AppForm>
|
|
230
|
+
</SettingCard>
|
|
231
|
+
</form>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Key changes:
|
|
237
|
+
- `react-final-form` `formProps` + render-prop → `useForm` from `@aircall/blocks` (TanStack Form): `defaultValues` (not `initialValues`), `onSubmit` receives `{ value }`.
|
|
238
|
+
- Hand-wired inputs → `Form*Field` wrappers driven by the form (never per-field `useState`).
|
|
239
|
+
- Put the `<form>` on the **outer level** (wrapping `SettingCard`), and drop **`form.CardSaveBar`** in as the last child of `SettingCard`. It reads `isDirty`/`canSubmit`/`isSubmitting` from context, slides in when dirty, and carries `data-slot="card-footer"` so the card auto-collapses its bottom padding and the bar sits flush. **No `className` is needed on any part** - the card's own layout (row gap + auto `pb-0`) handles both the clean and dirty states.
|
|
240
|
+
- `submitButtonText`/`undoButtonText` → `CardSaveBar` `submitLabel`/`resetLabel`.
|
|
241
|
+
|
|
242
|
+
**Submit error (`getErrorMessage` / `submitError`).** No built-in equivalent — catch in
|
|
243
|
+
`onSubmit`, store it, and render an `Alert` above the content:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
247
|
+
const form = useForm({
|
|
248
|
+
defaultValues: { name: '' },
|
|
249
|
+
onSubmit: async ({ value }) => {
|
|
250
|
+
try { await save(value); setSubmitError(null); }
|
|
251
|
+
catch (e) { setSubmitError(getErrorMessage(e)); }
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// In JSX, above the fields inside SettingCardContent:
|
|
255
|
+
{submitError && <Alert variant="error">{submitError}</Alert>}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
> DS `Alert`'s error variant is `error` (not tractor's `critical`). Full set: `default` / `info` / `success` / `warning` / `error`.
|
|
@@ -9,7 +9,6 @@ description: >
|
|
|
9
9
|
consumers where the host already loads DS globals.
|
|
10
10
|
type: core
|
|
11
11
|
library: aircall-blocks
|
|
12
|
-
library_version: "0.5.1"
|
|
13
12
|
requires:
|
|
14
13
|
- aircall-ds/setup
|
|
15
14
|
sources:
|
|
@@ -58,6 +57,28 @@ import '@aircall/ds/globals.css';
|
|
|
58
57
|
import '@aircall/blocks/globals.css';
|
|
59
58
|
```
|
|
60
59
|
|
|
60
|
+
#### Single-compilation via `theme.css` (recommended for DS + blocks)
|
|
61
|
+
|
|
62
|
+
The two precompiled bundles above each re-emit the same Tailwind utilities into `@layer
|
|
63
|
+
utilities`; at equal specificity the last-loaded bundle can clobber the other's responsive
|
|
64
|
+
variants (e.g. blocks' plain `.text-base` beating DS's `md:text-sm` on `Textarea`), and **no
|
|
65
|
+
import order fixes it**. To avoid this, import the directive-preserving `theme.css` artifacts
|
|
66
|
+
(tokens + dark variant + reset + keyframes only — **no** Preflight, **no** utilities) and let a
|
|
67
|
+
single Tailwind pass generate the utilities by `@source`-scanning the library dist:
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
/* style.css */
|
|
71
|
+
@import 'tailwindcss'; /* consumer owns the single Preflight + engine */
|
|
72
|
+
@import '@aircall/ds/theme.css';
|
|
73
|
+
@import '@aircall/blocks/theme.css';
|
|
74
|
+
@source '../node_modules/@aircall/ds/dist/index.js';
|
|
75
|
+
@source '../node_modules/@aircall/blocks/dist/index.js';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Here the full `@import 'tailwindcss'` is **correct** (unlike the `globals.css` case) because
|
|
79
|
+
`theme.css` ships no Preflight/utilities. `globals.css` still ships unchanged for precompiled-
|
|
80
|
+
bundle consumers. Full rationale: `packages/ds/docs/single-compilation-theme-css.md`.
|
|
81
|
+
|
|
61
82
|
### Module-federation consumer (host already owns the Preflight)
|
|
62
83
|
|
|
63
84
|
When your app is a remote loaded inside a host that already imports DS globals
|