create-bluecopa-react-app 1.0.41 → 1.0.43
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 +7 -5
- package/package.json +1 -1
- package/templates/latest/.claude/settings.local.json +56 -0
- package/templates/latest/.env.example +8 -0
- package/templates/latest/Agent.md +598 -775
- package/templates/latest/CLAUDE.md +759 -0
- package/templates/latest/README.md +11 -2
- package/templates/latest/app/app.css +292 -85
- package/templates/latest/app/app.tsx +48 -39
- package/templates/latest/app/components/bluecopa-logo.tsx +20 -0
- package/templates/latest/app/components/charts/bar-chart.tsx +132 -0
- package/templates/latest/app/components/charts/base-chart.tsx +149 -0
- package/templates/latest/app/components/charts/chart-provider.tsx +71 -0
- package/templates/latest/app/components/charts/chart-theme.ts +262 -0
- package/templates/latest/app/components/charts/chart-utils.ts +142 -0
- package/templates/latest/app/components/charts/donut-chart.tsx +110 -0
- package/templates/latest/app/components/charts/index.ts +5 -0
- package/templates/latest/app/components/layouts/app-layout.tsx +22 -0
- package/templates/latest/app/components/layouts/app-sidebar.tsx +88 -0
- package/templates/latest/app/components/layouts/nav-main.tsx +50 -0
- package/templates/latest/app/components/layouts/nav-user.tsx +38 -0
- package/templates/latest/app/components/layouts/site-header.tsx +93 -0
- package/templates/latest/app/components/loading-screen.tsx +41 -0
- package/templates/latest/app/components/ui/ag-grid-table.tsx +79 -0
- package/templates/latest/app/components/ui/button.tsx +23 -23
- package/templates/latest/app/components/ui/card.tsx +20 -20
- package/templates/latest/app/components/ui/dropdown-menu.tsx +54 -49
- package/templates/latest/app/components/ui/input.tsx +8 -8
- package/templates/latest/app/components/ui/label.tsx +8 -8
- package/templates/latest/app/components/ui/separator.tsx +7 -7
- package/templates/latest/app/components/ui/sheet.tsx +43 -32
- package/templates/latest/app/components/ui/sidebar.tsx +240 -235
- package/templates/latest/app/components/ui/skeleton.tsx +4 -4
- package/templates/latest/app/components/ui/sonner.tsx +6 -9
- package/templates/latest/app/components/ui/tabs.tsx +15 -15
- package/templates/latest/app/components/ui/tooltip.tsx +18 -12
- package/templates/latest/app/constants/index.ts +31 -0
- package/templates/latest/app/contexts/app-context.tsx +201 -0
- package/templates/latest/app/hooks/use-mobile.ts +13 -12
- package/templates/latest/app/main.tsx +1 -1
- package/templates/latest/app/pages/dashboard.tsx +246 -0
- package/templates/latest/app/pages/payments.tsx +182 -0
- package/templates/latest/app/pages/settings.tsx +128 -0
- package/templates/latest/app/routes/index.tsx +19 -0
- package/templates/latest/app/single-spa.tsx +69 -186
- package/templates/latest/app/types/index.ts +37 -0
- package/templates/latest/app/utils/ag-grid-datasource.ts +63 -0
- package/templates/latest/app/utils/ag-grid-license.ts +12 -0
- package/templates/latest/app/utils/ag-grid-theme.ts +9 -0
- package/templates/latest/app/utils/component-style.ts +7 -0
- package/templates/latest/app/utils/style-drivers.ts +24 -0
- package/templates/latest/app/utils/utils.ts +10 -0
- package/templates/latest/components.json +3 -3
- package/templates/latest/index.html +30 -2
- package/templates/latest/package-lock.json +2717 -955
- package/templates/latest/package.json +19 -21
- package/templates/latest/preview/index.html +125 -285
- package/templates/latest/public/favicon.svg +1 -0
- package/templates/latest/vite.config.ts +2 -8
- package/templates/latest/app/components/app-sidebar.tsx +0 -182
- package/templates/latest/app/components/chart-area-interactive.tsx +0 -290
- package/templates/latest/app/components/data-table.tsx +0 -807
- package/templates/latest/app/components/nav-documents.tsx +0 -92
- package/templates/latest/app/components/nav-main.tsx +0 -40
- package/templates/latest/app/components/nav-secondary.tsx +0 -42
- package/templates/latest/app/components/nav-user.tsx +0 -111
- package/templates/latest/app/components/section-cards.tsx +0 -102
- package/templates/latest/app/components/site-header.tsx +0 -28
- package/templates/latest/app/components/ui/avatar.tsx +0 -53
- package/templates/latest/app/components/ui/badge.tsx +0 -46
- package/templates/latest/app/components/ui/breadcrumb.tsx +0 -109
- package/templates/latest/app/components/ui/chart.tsx +0 -352
- package/templates/latest/app/components/ui/checkbox.tsx +0 -30
- package/templates/latest/app/components/ui/drawer.tsx +0 -139
- package/templates/latest/app/components/ui/select.tsx +0 -183
- package/templates/latest/app/components/ui/table.tsx +0 -117
- package/templates/latest/app/components/ui/toggle-group.tsx +0 -73
- package/templates/latest/app/components/ui/toggle.tsx +0 -47
- package/templates/latest/app/data/data.json +0 -614
- package/templates/latest/app/data/mock-payments.json +0 -122
- package/templates/latest/app/data/mock-transactions.json +0 -128
- package/templates/latest/app/hooks/use-bluecopa-user.ts +0 -37
- package/templates/latest/app/lib/utils.ts +0 -6
- package/templates/latest/app/routes/apitest.tsx +0 -2118
- package/templates/latest/app/routes/comments.tsx +0 -588
- package/templates/latest/app/routes/dashboard.tsx +0 -36
- package/templates/latest/app/routes/payments.tsx +0 -342
- package/templates/latest/app/routes/statements.tsx +0 -493
- package/templates/latest/app/routes/websocket.tsx +0 -450
- package/templates/latest/app/routes.tsx +0 -22
- package/templates/latest/dist/assets/__federation_expose_App-D-lv9y21.js +0 -161
- package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +0 -438
- package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +0 -16
- package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +0 -17
- package/templates/latest/dist/assets/client-Dms8K6Dw.js +0 -78879
- package/templates/latest/dist/assets/index-BrhXrqF7.js +0 -60
- package/templates/latest/dist/assets/index-BzNimew1.js +0 -69
- package/templates/latest/dist/assets/index-DMFtQdNS.js +0 -412
- package/templates/latest/dist/assets/remoteEntry.css +0 -3996
- package/templates/latest/dist/assets/remoteEntry.js +0 -88
- package/templates/latest/dist/favicon.ico +0 -0
- package/templates/latest/dist/index.html +0 -19
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
# CLAUDE.md — React MFE Boilerplate
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code when working with the Bluecopa React MFE (Micro-Frontend) boilerplate.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
A production-ready React microfrontend template for building apps that run inside the Bluecopa platform. Uses single-spa for MFE orchestration, Module Federation for runtime loading, and the Dream Light design system for UI.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm dev # Dev server on port 8080
|
|
13
|
+
pnpm build # TypeScript check + production build
|
|
14
|
+
pnpm start # Serve built MFE on port 3001
|
|
15
|
+
pnpm typecheck # TypeScript validation only
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
### Entry Points
|
|
21
|
+
|
|
22
|
+
| File | Mode | Router | Use Case |
|
|
23
|
+
|------|------|--------|----------|
|
|
24
|
+
| `app/main.tsx` | Standalone | `BrowserRouter` | Local development |
|
|
25
|
+
| `app/single-spa.tsx` | MFE | `MemoryRouter` | Deployed inside Bluecopa shell |
|
|
26
|
+
|
|
27
|
+
### MFE Props Interface
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface MfeProps {
|
|
31
|
+
apiBaseUrl?: string; // Bluecopa API URL
|
|
32
|
+
workspaceId?: string; // Workspace context
|
|
33
|
+
accessToken?: string; // JWT auth token
|
|
34
|
+
userId?: string; // Current user ID
|
|
35
|
+
basename?: string; // Router basename
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Props are passed from the host app via single-spa `customProps` or `manualMount()`.
|
|
40
|
+
|
|
41
|
+
### Key Files
|
|
42
|
+
|
|
43
|
+
| File | Purpose |
|
|
44
|
+
|------|---------|
|
|
45
|
+
| `app/app.tsx` | Root component — QueryClient, ChartProvider, MFE config, AppProvider |
|
|
46
|
+
| `app/app.css` | Tailwind v4 config + Dream Light theme tokens |
|
|
47
|
+
| `app/contexts/app-context.tsx` | User, workspace settings, admin role context |
|
|
48
|
+
| `app/routes/index.tsx` | Route definitions (all wrapped in AppLayout) |
|
|
49
|
+
| `app/utils/utils.ts` | `cn()` utility with `copa` prefix support |
|
|
50
|
+
| `app/utils/component-style.ts` | Unprefixed style tokens (source of truth) |
|
|
51
|
+
| `app/utils/style-drivers.ts` | Auto-prefixes style tokens with `copa:` |
|
|
52
|
+
| `vite.config.ts` | Vite + Module Federation + Tailwind v4 |
|
|
53
|
+
| `components.json` | shadcn CLI config (prefix: `copa`) |
|
|
54
|
+
|
|
55
|
+
### Directory Layout
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
app/
|
|
59
|
+
├── components/
|
|
60
|
+
│ ├── ui/ # shadcn/Dream Light components (prefixed)
|
|
61
|
+
│ ├── charts/ # ECharts-based chart components (BarChart, DonutChart, etc.)
|
|
62
|
+
│ └── layouts/ # AppLayout, AppSidebar, SiteHeader, NavMain, NavUser
|
|
63
|
+
├── constants/ # Route paths, API defaults, query defaults, breakpoints
|
|
64
|
+
├── contexts/ # AppContext (user, workspace, settings)
|
|
65
|
+
├── hooks/ # use-mobile.ts (768px breakpoint)
|
|
66
|
+
├── pages/ # Page components (dashboard, settings, etc.)
|
|
67
|
+
├── routes/ # Route config
|
|
68
|
+
├── types/ # MfeProps, AppUser, WorkspaceDataSettings
|
|
69
|
+
└── utils/ # cn(), style drivers
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Layout Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
AppLayout (SidebarProvider)
|
|
76
|
+
├── AppSidebar (collapsible, Cmd/Ctrl+B toggle)
|
|
77
|
+
│ ├── NavMain (navigation items)
|
|
78
|
+
│ └── NavUser (user menu footer)
|
|
79
|
+
└── SidebarInset
|
|
80
|
+
├── SiteHeader (breadcrumbs, user dropdown)
|
|
81
|
+
└── <Outlet /> (page content)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Tailwind v4 + `copa:` Prefix — CRITICAL
|
|
85
|
+
|
|
86
|
+
All Tailwind classes MUST use the `copa:` prefix for MFE CSS isolation.
|
|
87
|
+
|
|
88
|
+
### Prefix Order Rule
|
|
89
|
+
|
|
90
|
+
**Prefix ALWAYS comes first**, then variants, then utility:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
{prefix}:{variants}:{utility}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// CORRECT
|
|
98
|
+
className="copa:flex copa:items-center copa:hover:bg-accent copa:md:block"
|
|
99
|
+
className="copa:group-data-[state=open]:block"
|
|
100
|
+
|
|
101
|
+
// WRONG — silently fails, no CSS generated, no errors
|
|
102
|
+
className="hover:copa:bg-accent" // ← variant before prefix
|
|
103
|
+
className="md:copa:block" // ← breakpoint before prefix
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### CSS Architecture (app.css)
|
|
107
|
+
|
|
108
|
+
Three layers in order:
|
|
109
|
+
|
|
110
|
+
1. **`@theme` (build-time)** — Resolved at compile time. Cannot use `var()` refs to runtime CSS variables. Must hardcode values.
|
|
111
|
+
2. **`@theme inline` (runtime)** — Binds to CSS variables from `.mfe-root`. Used for dynamic radius, colors.
|
|
112
|
+
3. **`.mfe-root` (scoped vars)** — All CSS variables scoped here, NOT `:root`. This prevents MFE style leaking.
|
|
113
|
+
|
|
114
|
+
### cn() Utility
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { cn } from "~/utils/utils";
|
|
118
|
+
|
|
119
|
+
// Handles copa: prefix correctly via extendTailwindMerge({ prefix: "copa" })
|
|
120
|
+
<div className={cn("copa:flex copa:gap-2", isActive && "copa:bg-accent", className)} />
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Style Drivers
|
|
124
|
+
|
|
125
|
+
For reusable style tokens, define unprefixed in `component-style.ts`, consume prefixed from `style-drivers.ts`:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// component-style.ts (write unprefixed)
|
|
129
|
+
export const styles = {
|
|
130
|
+
card: "rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// style-drivers.ts (auto-prefixes)
|
|
134
|
+
// "rounded-lg border ..." → "copa:rounded-lg copa:border ..."
|
|
135
|
+
|
|
136
|
+
// Usage
|
|
137
|
+
import { prefixedStyles } from "~/utils/style-drivers";
|
|
138
|
+
<div className={prefixedStyles.card} />
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## @bluecopa/react — API Integration
|
|
142
|
+
|
|
143
|
+
React Query hooks wrapping `@bluecopa/core`. All data fetching uses these hooks.
|
|
144
|
+
|
|
145
|
+
### Setup Flow
|
|
146
|
+
|
|
147
|
+
1. `copaSetConfig()` called in `app.tsx` useEffect with MFE props or env vars
|
|
148
|
+
2. `QueryClientProvider` wraps the app (staleTime: 5min, retry: 1)
|
|
149
|
+
3. Hooks become available in all child components
|
|
150
|
+
|
|
151
|
+
### Configuration
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { copaSetConfig } from "@bluecopa/react";
|
|
155
|
+
|
|
156
|
+
copaSetConfig({
|
|
157
|
+
apiBaseUrl: "https://develop.bluecopa.com/api/v1",
|
|
158
|
+
workspaceId: "your-workspace-id",
|
|
159
|
+
accessToken: "your-jwt-token",
|
|
160
|
+
userId: "user-id",
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Environment Variables
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
VITE_BLUECOPA_API_URL=https://develop.bluecopa.com/api/v1
|
|
168
|
+
VITE_BLUECOPA_WORKSPACE_ID=prod
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Available Hooks
|
|
172
|
+
|
|
173
|
+
#### User & Auth
|
|
174
|
+
- `useUser()` — Current user details
|
|
175
|
+
- `useGetAllUsers()` — All workspace users
|
|
176
|
+
|
|
177
|
+
#### Datasets & Tables
|
|
178
|
+
- `useDataset(id, { limit })` — Dataset records
|
|
179
|
+
- `useDatasetSample(id)` — Sample data
|
|
180
|
+
- `useRows(tableId, { filters, limit, offset, order, order_by })` — Supabase-style filtered rows
|
|
181
|
+
- `useInputTable(id, { limitParams, pageParams, sortParams })` — Input table data
|
|
182
|
+
- `useGetTableById(id)` — Table metadata
|
|
183
|
+
- `useInsertRow()` — Insert row (mutation, auto-invalidates)
|
|
184
|
+
- `useUpdateRow()` — Update row (mutation)
|
|
185
|
+
- `useDeleteRow()` — Delete row (mutation)
|
|
186
|
+
|
|
187
|
+
#### Metrics
|
|
188
|
+
- `useMetric(id)` — Metric data
|
|
189
|
+
|
|
190
|
+
#### Workbooks & Worksheets
|
|
191
|
+
- `useGetWorkbooksByType(type)` — Filter workbooks
|
|
192
|
+
- `useGetPublishedWorkbookById(id)` — Published workbook
|
|
193
|
+
- `useGetWorkbookDetails(id)` — Detailed info
|
|
194
|
+
- `useSaveWorkbook()` — Save (mutation)
|
|
195
|
+
- `usePublishWorkbook()` — Publish (mutation)
|
|
196
|
+
- `useGetWorksheets()` — All worksheets
|
|
197
|
+
- `useGetWorksheetsByType(type)` — Filter by type
|
|
198
|
+
|
|
199
|
+
#### Views & Runs
|
|
200
|
+
- `useGetViewById(id)` — View details
|
|
201
|
+
- `useGetViewsBySheetId(id)` — Views for sheet
|
|
202
|
+
- `useGetRunsByViewId(id)` — Run history
|
|
203
|
+
- `useGetRunResultById(id)` — Run result
|
|
204
|
+
|
|
205
|
+
#### Workflows
|
|
206
|
+
- `useTriggerWorkflow()` — Trigger execution (mutation)
|
|
207
|
+
- `useTriggerHttpWorkflow()` — HTTP trigger (mutation)
|
|
208
|
+
- `useGetWorkflowInstanceStatusById(id)` — Check status
|
|
209
|
+
- `useGetAllHttpTriggers()` — List triggers
|
|
210
|
+
|
|
211
|
+
#### Definitions
|
|
212
|
+
- `useRunDefinition()` — Execute definition (mutation)
|
|
213
|
+
- `useRunPublishedDefinition()` — Published definition (mutation)
|
|
214
|
+
- `useRunSampleDefinition()` — Sample definition (mutation)
|
|
215
|
+
|
|
216
|
+
#### Files
|
|
217
|
+
- `useFileUpload()` — Upload to S3 (mutation)
|
|
218
|
+
- `useFileDownload()` — Download file (mutation)
|
|
219
|
+
- `useGetFileUrlByFileId(id)` — Get file URL
|
|
220
|
+
|
|
221
|
+
#### Forms
|
|
222
|
+
- `useGetFormById(id)` — Form details
|
|
223
|
+
- `useGetFormSchema(id)` — Form schema
|
|
224
|
+
- `useGetFormData(id)` — Form data
|
|
225
|
+
- `useCreateOrUpdateForm()` — Create/update (mutation)
|
|
226
|
+
|
|
227
|
+
#### Chat & Comments
|
|
228
|
+
- `useCreateThread()` — Create thread (mutation)
|
|
229
|
+
- `useGetCommentsByThreadId(id)` — Thread comments
|
|
230
|
+
- `usePostComment()` — Post comment (mutation)
|
|
231
|
+
- `useUpdateComment()` / `useDeleteComment()` — Manage comments
|
|
232
|
+
|
|
233
|
+
#### Tasks & Inbox
|
|
234
|
+
- `useGetTaskDetails(id)` — Task details
|
|
235
|
+
- `useMarkTaskDone()` — Complete task (mutation)
|
|
236
|
+
- `useReassignTask()` — Reassign (mutation)
|
|
237
|
+
- `useGetAllInboxItems()` — Inbox items
|
|
238
|
+
- `useMarkItemAsRead()` / `useMarkItemAsUnread()` — Read state
|
|
239
|
+
|
|
240
|
+
#### Statements
|
|
241
|
+
- `useCreateStatementRun()` — Create run (mutation)
|
|
242
|
+
- `useGetStatementData(id)` — Statement data
|
|
243
|
+
|
|
244
|
+
#### Audit & Recon
|
|
245
|
+
- `useGetAuditLogs()` — Audit logs
|
|
246
|
+
- `useCreateAuditLog()` — Create entry (mutation)
|
|
247
|
+
- `useGetAllRecon()` — Reconciliations
|
|
248
|
+
- `useRunRecon()` — Run recon (mutation)
|
|
249
|
+
|
|
250
|
+
#### Pipelines
|
|
251
|
+
- `useGetAllTemplatedPipelines()` — Template pipelines
|
|
252
|
+
|
|
253
|
+
### Re-exports
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// Available from @bluecopa/react
|
|
257
|
+
export * as reactQuery from "@tanstack/react-query"; // QueryClient, useQuery, etc.
|
|
258
|
+
export * from "@bluecopa/core"; // copaSetConfig, copaApi, copaUtils
|
|
259
|
+
export { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Hook Usage Pattern
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import { useDataset } from "@bluecopa/react";
|
|
266
|
+
|
|
267
|
+
function MyPage() {
|
|
268
|
+
const { data, isLoading, error, refetch } = useDataset("dataset-id", { limit: 100 });
|
|
269
|
+
|
|
270
|
+
if (isLoading) return <Skeleton />;
|
|
271
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
272
|
+
|
|
273
|
+
// Parse response (handles multiple formats)
|
|
274
|
+
const rows = data?.records || data?.data || data?.rows || (Array.isArray(data) ? data : []);
|
|
275
|
+
return <DataTable data={rows} />;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## @bluecopa/core — API SDK
|
|
280
|
+
|
|
281
|
+
Low-level TypeScript SDK. Prefer `@bluecopa/react` hooks in components, use `copaApi` directly only when needed outside React.
|
|
282
|
+
|
|
283
|
+
### Authentication
|
|
284
|
+
|
|
285
|
+
Custom header-based auth (not Bearer tokens):
|
|
286
|
+
- `X-COPA-TOKEN`: Access token
|
|
287
|
+
- `X-COPA-WORKSPACE-ID`: Workspace context
|
|
288
|
+
|
|
289
|
+
### Direct API Usage
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { copaApi } from "@bluecopa/react";
|
|
293
|
+
|
|
294
|
+
const user = await copaApi.user.getLoggedInUserDetails();
|
|
295
|
+
const rows = await copaApi.inputTable.getRows("table-id", { limit: 50 });
|
|
296
|
+
await copaApi.inputTable.insertRow("table-id", { name: "New", value: 42 });
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### WebSocket Integration
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { copaUtils } from "@bluecopa/react";
|
|
303
|
+
|
|
304
|
+
const ws = copaUtils.websocketUtils.WebsocketContextFactory.create("centrifugo", {
|
|
305
|
+
connectionUrl: "wss://...",
|
|
306
|
+
token: "...",
|
|
307
|
+
userId: "...",
|
|
308
|
+
});
|
|
309
|
+
ws.connect();
|
|
310
|
+
ws.bind("channel", "event", (data) => console.log(data));
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## AppContext
|
|
314
|
+
|
|
315
|
+
Provides user data, workspace settings, and role info to all components.
|
|
316
|
+
|
|
317
|
+
### Available Values
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const {
|
|
321
|
+
user, // { userId, email, name, role, currentWorkspace, ... }
|
|
322
|
+
isLoading, // User fetch loading state
|
|
323
|
+
error, // User fetch error
|
|
324
|
+
refetch, // Refetch user data
|
|
325
|
+
isAdmin, // Derived: any team contains "admin"
|
|
326
|
+
workspaceIds, // User's workspace IDs
|
|
327
|
+
teams, // User's teams
|
|
328
|
+
users, // All workspace users
|
|
329
|
+
currentWorkspaceSettings, // { currency, timezone, dateFormat, fiscalYear, numberSettings, weekStartDay }
|
|
330
|
+
setCurrency, // Override currency
|
|
331
|
+
} = useAppContext();
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Workspace Settings Shape
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
{
|
|
338
|
+
currency: "USD",
|
|
339
|
+
timezone: "UTC",
|
|
340
|
+
dateFormat: "MM/DD/YYYY HH:mm:ss",
|
|
341
|
+
fiscalYear: { yearType: "calendar_year", startMonth?, startDate? },
|
|
342
|
+
numberSettings: { numberSystem: "intl", defaultPrecision: 2, nullPlaceholder? },
|
|
343
|
+
weekStartDay: "SUNDAY",
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Dream Light Design System
|
|
348
|
+
|
|
349
|
+
The UI design system. Source: `[https://blui.vercel.app/](https://blui.vercel.app/)`
|
|
350
|
+
Registry base URL: `https://blui.vercel.app/r/registry.json`
|
|
351
|
+
|
|
352
|
+
### Adding Components
|
|
353
|
+
|
|
354
|
+
From either registry via shadcn CLI:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
# Dream Light / Bluecopa UI (preferred)
|
|
358
|
+
pnpm dlx shadcn@latest add @bluecopa-ui/button
|
|
359
|
+
pnpm dlx shadcn@latest add @bluecopa-ui/dialog
|
|
360
|
+
pnpm dlx shadcn@latest add @bluecopa-ui/data-table
|
|
361
|
+
|
|
362
|
+
# Standard shadcn/ui (fallback)
|
|
363
|
+
pnpm dlx shadcn@latest add button
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Registry URL: `https://blui.vercel.app/r/{name}.json`
|
|
367
|
+
|
|
368
|
+
### Available Dream Light Components (70+)
|
|
369
|
+
|
|
370
|
+
**Inputs**: button, input, textarea, checkbox, radio-group, switch, select, select-advanced, combobox, number-input, pills, date-picker, date-range-picker, calendar, toggle
|
|
371
|
+
|
|
372
|
+
**Navigation**: tabs, horizontal-tabs, vertical-tabs, breadcrumb, pagination, dropdown-menu, command, sidebar, sidebar-with-icons
|
|
373
|
+
|
|
374
|
+
**Display**: text, badge, alert, avatar, toast, tooltip, spinner, loading-state, skeleton, empty-state, error-boundary, progress, kpi-card, ag-grid-table, data-table
|
|
375
|
+
|
|
376
|
+
**Containers**: card, section-card, dialog, sheet, side-panel, popover, accordion, scroll-area, separator, stack, kanban, tree-menu, topbar
|
|
377
|
+
|
|
378
|
+
**Layout**: scaffold, page-container, section, page-header
|
|
379
|
+
|
|
380
|
+
**Forms**: form-field, form-section, form-error-summary, label, search-box
|
|
381
|
+
|
|
382
|
+
**Advanced**: filter-builder, stepper, brand-logo, listing-view, listing-table, listing-header
|
|
383
|
+
|
|
384
|
+
### Design Tokens
|
|
385
|
+
|
|
386
|
+
#### Colors (OKLCH)
|
|
387
|
+
|
|
388
|
+
```css
|
|
389
|
+
--primary: #3548ff; /* Bluecopa blue */
|
|
390
|
+
--primary-foreground: oklch(100% 0 0); /* White on primary */
|
|
391
|
+
--background: oklch(0.975 0.005 280); /* Near-white */
|
|
392
|
+
--foreground: oklch(0.145 0 0); /* Near-black */
|
|
393
|
+
--destructive: oklch(0.577 0.245 27.325); /* Red */
|
|
394
|
+
--border: oklch(0.92 0.005 280); /* Light gray */
|
|
395
|
+
--muted: oklch(0.97 0.003 280);
|
|
396
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
397
|
+
--accent: oklch(0.97 0.003 280);
|
|
398
|
+
|
|
399
|
+
/* Semantic status */
|
|
400
|
+
--status-success: oklch(0.72 0.19 142);
|
|
401
|
+
--status-warning: oklch(0.80 0.18 85);
|
|
402
|
+
--status-error: oklch(0.63 0.24 25);
|
|
403
|
+
|
|
404
|
+
/* Sidebar */
|
|
405
|
+
--sidebar: oklch(1 0 0); /* White */
|
|
406
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
407
|
+
--sidebar-border: oklch(0.92 0.004 280);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### Typography Scale
|
|
411
|
+
|
|
412
|
+
```css
|
|
413
|
+
/* Fluid type scale */
|
|
414
|
+
--text-xs: 0.702rem; /* Small captions */
|
|
415
|
+
--text-sm: 0.835rem; /* Body text, labels */
|
|
416
|
+
--text-md: 1.004rem; /* Prominent body */
|
|
417
|
+
--text-lg: 1.21rem; /* Subheadings */
|
|
418
|
+
--text-xl: 1.452rem; /* Section headers */
|
|
419
|
+
--text-2xl: 1.742rem; /* Large headings */
|
|
420
|
+
--text-3xl: 2.093rem;
|
|
421
|
+
--text-4xl: 2.505rem;
|
|
422
|
+
--text-5xl: 3.013rem; /* Hero display */
|
|
423
|
+
|
|
424
|
+
/* Heading tokens */
|
|
425
|
+
--text-h1: 3rem; /* 700 weight */
|
|
426
|
+
--text-h2: 2.25rem; /* 700 */
|
|
427
|
+
--text-h3: 1.875rem; /* 600 */
|
|
428
|
+
--text-h4: 1.5rem; /* 400 */
|
|
429
|
+
--text-h5: 1.25rem; /* 600 */
|
|
430
|
+
--text-h6: 1rem; /* 400 */
|
|
431
|
+
|
|
432
|
+
/* Font: Satoshi (sans-serif) */
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### Custom Typography Utilities (defined in app.css)
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
.text-page-title → 1.5rem, semibold
|
|
439
|
+
.text-section-title → 0.9375rem, semibold
|
|
440
|
+
.text-label → 0.75rem, uppercase, tracked
|
|
441
|
+
.text-value → 1.75rem, bold
|
|
442
|
+
.text-nav-section → 0.6875rem, uppercase, tracked
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Spacing
|
|
446
|
+
|
|
447
|
+
```css
|
|
448
|
+
--radius: 1rem;
|
|
449
|
+
--card-radius: 1.25rem;
|
|
450
|
+
--rounded-input: 12px;
|
|
451
|
+
|
|
452
|
+
/* Motion */
|
|
453
|
+
--duration-fast: 100ms;
|
|
454
|
+
--duration-normal: 200ms;
|
|
455
|
+
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### Custom Shadow Utilities
|
|
459
|
+
|
|
460
|
+
```
|
|
461
|
+
.shadow-soft-xs → Subtle card shadow
|
|
462
|
+
.shadow-card → Multi-layer card shadow
|
|
463
|
+
.shadow-card-hover → Elevated hover state
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Chart Colors
|
|
467
|
+
|
|
468
|
+
```css
|
|
469
|
+
--chart-1: oklch(0.55 0.15 255); /* Blue */
|
|
470
|
+
--chart-2: oklch(0.62 0.12 175); /* Teal */
|
|
471
|
+
--chart-3: oklch(0.70 0.13 80); /* Green */
|
|
472
|
+
--chart-4: oklch(0.55 0.14 295); /* Purple */
|
|
473
|
+
--chart-5: oklch(0.62 0.12 15); /* Red */
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Build System
|
|
477
|
+
|
|
478
|
+
### Vite + Module Federation
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
Build Output:
|
|
482
|
+
dist/assets/remoteEntry.js ← Module Federation entry (SystemJS format)
|
|
483
|
+
dist/assets/remoteEntry.css ← Single bundled CSS file
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
- **Federation name**: `__copa_ext_app__react_route`
|
|
487
|
+
- **Exposed module**: `./App` → `./app/single-spa.tsx`
|
|
488
|
+
- **Shared**: `react`, `react-dom` (externalized in build)
|
|
489
|
+
- **Format**: SystemJS (for single-spa compatibility)
|
|
490
|
+
- **CSS**: Single file, no code splitting
|
|
491
|
+
|
|
492
|
+
### TypeScript Config
|
|
493
|
+
|
|
494
|
+
- Path alias: `~/` → `./app/` (via `tsconfig.json` paths + `vite-tsconfig-paths`)
|
|
495
|
+
- Target: ES2020, JSX: react-jsx, strict mode
|
|
496
|
+
|
|
497
|
+
### Docker
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
docker build -t bluecopa-mfe .
|
|
501
|
+
# Serves dist/ via lightweight HTTP server
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## MFE Integration
|
|
505
|
+
|
|
506
|
+
### How the Host App Loads This MFE
|
|
507
|
+
|
|
508
|
+
```javascript
|
|
509
|
+
// 1. Load Module Federation entry
|
|
510
|
+
System.import("__copa_ext_app__react_route/App");
|
|
511
|
+
|
|
512
|
+
// 2. Register with single-spa
|
|
513
|
+
registerApplication({
|
|
514
|
+
name: "bluecopa-preview",
|
|
515
|
+
app: () => System.import("__copa_ext_app__react_route/App"),
|
|
516
|
+
activeWhen: ["/app/external"],
|
|
517
|
+
customProps: {
|
|
518
|
+
apiBaseUrl: "https://develop.bluecopa.com/api/v1",
|
|
519
|
+
accessToken: "eyJ...",
|
|
520
|
+
workspaceId: "prod",
|
|
521
|
+
userId: "user-123",
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Single-spa Lifecycle
|
|
527
|
+
|
|
528
|
+
- **bootstrap**: No-op
|
|
529
|
+
- **mount**: Creates React root in `#single-spa-application:bluecopa-preview`, renders `<MemoryRouter><App {...props} /></MemoryRouter>`
|
|
530
|
+
- **unmount**: Calls `root.unmount()`
|
|
531
|
+
- **errorBoundary**: Catches errors, renders fallback UI
|
|
532
|
+
|
|
533
|
+
### Manual Mount API (for non-single-spa hosts)
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { manualMount, manualUnmount } from "./single-spa";
|
|
537
|
+
|
|
538
|
+
await manualMount({
|
|
539
|
+
domElement: document.getElementById("mfe-container"),
|
|
540
|
+
apiBaseUrl: "https://...",
|
|
541
|
+
accessToken: "...",
|
|
542
|
+
workspaceId: "...",
|
|
543
|
+
userId: "...",
|
|
544
|
+
basename: "/app",
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Charts (ECharts)
|
|
549
|
+
|
|
550
|
+
Chart components live in `app/components/charts/` and are powered by Apache ECharts via `echarts-for-react`. The `<ChartProvider>` in `app.tsx` bridges CSS design tokens to the ECharts theme.
|
|
551
|
+
|
|
552
|
+
### Architecture
|
|
553
|
+
|
|
554
|
+
| File | Purpose |
|
|
555
|
+
|------|---------|
|
|
556
|
+
| `chart-provider.tsx` | React context — registers ECharts theme from CSS tokens, auto-refreshes on theme change |
|
|
557
|
+
| `chart-theme.ts` | Reads `--chart-*` vars from `.mfe-root`, converts OKLCH to hex, builds ECharts theme object |
|
|
558
|
+
| `chart-utils.ts` | Composable option builders: `chartTooltip()`, `chartLegend()`, `chartGrid()`, `chartCategoryAxis()`, `chartValueAxis()`, `chartTitle()`, `chartAnimation()`, `chartColors()`, `formatChartValue()`, `formatChartCurrency()` |
|
|
559
|
+
| `base-chart.tsx` | Thin wrapper around `echarts-for-react` with loading/empty states, accessibility, responsive resize |
|
|
560
|
+
| `bar-chart.tsx` | Configurable bar chart (vertical/horizontal, stacked, click events, value labels) |
|
|
561
|
+
| `donut-chart.tsx` | Donut/pie chart with center text, legend, slice interactions |
|
|
562
|
+
| `index.ts` | Barrel exports |
|
|
563
|
+
|
|
564
|
+
### Available Chart Components
|
|
565
|
+
|
|
566
|
+
#### BarChart
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
import { BarChart } from "~/components/charts";
|
|
570
|
+
|
|
571
|
+
<BarChart
|
|
572
|
+
data={{
|
|
573
|
+
categories: ["Jan", "Feb", "Mar"],
|
|
574
|
+
series: [{ name: "Revenue", data: [4200, 3800, 5100] }],
|
|
575
|
+
}}
|
|
576
|
+
height={280}
|
|
577
|
+
horizontal={false} // swap to horizontal bars
|
|
578
|
+
stacked={false} // stack multiple series
|
|
579
|
+
showValues={false} // show value labels on bars
|
|
580
|
+
showLegend={true} // auto-shows if >1 series
|
|
581
|
+
formatValue={(v) => `$${(v / 1000).toFixed(1)}K`}
|
|
582
|
+
onBarClick={({ name, seriesName, value }) => console.log(name, value)}
|
|
583
|
+
loading={false}
|
|
584
|
+
empty={false}
|
|
585
|
+
/>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### DonutChart
|
|
589
|
+
|
|
590
|
+
```tsx
|
|
591
|
+
import { DonutChart } from "~/components/charts";
|
|
592
|
+
|
|
593
|
+
<DonutChart
|
|
594
|
+
data={{
|
|
595
|
+
items: [
|
|
596
|
+
{ name: "Payroll", value: 42 },
|
|
597
|
+
{ name: "Operations", value: 28 },
|
|
598
|
+
{ name: "Marketing", value: 16 },
|
|
599
|
+
],
|
|
600
|
+
}}
|
|
601
|
+
height={280}
|
|
602
|
+
centerText="100%" // large text in donut center
|
|
603
|
+
centerSubText="Total" // smaller text below center
|
|
604
|
+
thickness={60} // donut ring thickness (%)
|
|
605
|
+
showLegend={true}
|
|
606
|
+
showLabels={false} // show labels on slices
|
|
607
|
+
onSliceClick={(params) => console.log(params)}
|
|
608
|
+
/>
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
#### BaseChart (escape hatch)
|
|
612
|
+
|
|
613
|
+
For custom ECharts configurations not covered by BarChart/DonutChart:
|
|
614
|
+
|
|
615
|
+
```tsx
|
|
616
|
+
import { BaseChart } from "~/components/charts";
|
|
617
|
+
import { chartTooltip, chartGrid, chartCategoryAxis, chartValueAxis } from "~/components/charts/chart-utils";
|
|
618
|
+
|
|
619
|
+
<BaseChart
|
|
620
|
+
option={{
|
|
621
|
+
...chartTooltip(),
|
|
622
|
+
...chartGrid(),
|
|
623
|
+
xAxis: chartCategoryAxis(["A", "B", "C"]),
|
|
624
|
+
yAxis: chartValueAxis(),
|
|
625
|
+
series: [{ type: "line", data: [10, 20, 30], smooth: true }],
|
|
626
|
+
}}
|
|
627
|
+
height={300}
|
|
628
|
+
loading={false}
|
|
629
|
+
ariaLabel="Custom line chart"
|
|
630
|
+
/>
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Chart Colors
|
|
634
|
+
|
|
635
|
+
Defined as `--chart-1` through `--chart-5` in `.mfe-root` (app.css). Hardcoded hex fallbacks in `chart-theme.ts` ensure colors render even if CSS vars aren't resolved:
|
|
636
|
+
|
|
637
|
+
| Token | OKLCH | Hex Fallback | Color |
|
|
638
|
+
|-------|-------|-------------|-------|
|
|
639
|
+
| `--chart-1` | `oklch(0.55 0.15 255)` | `#2563eb` | Blue |
|
|
640
|
+
| `--chart-2` | `oklch(0.62 0.12 175)` | `#0d9488` | Teal |
|
|
641
|
+
| `--chart-3` | `oklch(0.70 0.13 80)` | `#ca8a04` | Gold |
|
|
642
|
+
| `--chart-4` | `oklch(0.55 0.14 295)` | `#7c3aed` | Purple |
|
|
643
|
+
| `--chart-5` | `oklch(0.62 0.12 15)` | `#dc2626` | Red |
|
|
644
|
+
|
|
645
|
+
The theme auto-extends to 10 colors via tints/shades of the base 5.
|
|
646
|
+
|
|
647
|
+
### Adding New Chart Types
|
|
648
|
+
|
|
649
|
+
To add a new chart type (e.g., LineChart, PieChart), follow the pattern in `bar-chart.tsx`:
|
|
650
|
+
|
|
651
|
+
1. Create `app/components/charts/my-chart.tsx`
|
|
652
|
+
2. Use `useMemo` to build an ECharts option from `chart-utils` helpers
|
|
653
|
+
3. Render via `<BaseChart option={option} ... />`
|
|
654
|
+
4. Export from `index.ts`
|
|
655
|
+
|
|
656
|
+
Source chart components from the Dream Light registry: `/Users/mahipat/projects/bluecopa/shadcn-ui-registry/src/components/charts/`
|
|
657
|
+
|
|
658
|
+
## Toast Notifications
|
|
659
|
+
|
|
660
|
+
Uses [Sonner](https://sonner.emilkowal.dev/) for toast notifications. The `<Toaster />` component is rendered in `app.tsx`.
|
|
661
|
+
|
|
662
|
+
### Usage
|
|
663
|
+
|
|
664
|
+
```tsx
|
|
665
|
+
import { toast } from "sonner";
|
|
666
|
+
|
|
667
|
+
// Success
|
|
668
|
+
toast.success("Record saved", { description: "Changes applied." });
|
|
669
|
+
|
|
670
|
+
// Error
|
|
671
|
+
toast.error("Failed to save", { description: "Check your connection." });
|
|
672
|
+
|
|
673
|
+
// Warning
|
|
674
|
+
toast.warning("Unsaved changes");
|
|
675
|
+
|
|
676
|
+
// Info
|
|
677
|
+
toast.info("New data available");
|
|
678
|
+
|
|
679
|
+
// With action
|
|
680
|
+
toast("Item deleted", {
|
|
681
|
+
action: { label: "Undo", onClick: () => undoDelete() },
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Promise (loading → success/error)
|
|
685
|
+
toast.promise(saveData(), {
|
|
686
|
+
loading: "Saving...",
|
|
687
|
+
success: "Saved!",
|
|
688
|
+
error: "Failed to save",
|
|
689
|
+
});
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Toaster Config
|
|
693
|
+
|
|
694
|
+
The `<Toaster />` component from `~/components/ui/sonner` is pre-configured with Dream Light theme tokens. It renders in the app root (`app.tsx`) and is available globally — no additional setup needed.
|
|
695
|
+
|
|
696
|
+
## Coding Conventions
|
|
697
|
+
|
|
698
|
+
### Component Pattern
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
import { cn } from "~/utils/utils";
|
|
702
|
+
|
|
703
|
+
interface MyComponentProps {
|
|
704
|
+
className?: string;
|
|
705
|
+
children: React.ReactNode;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export function MyComponent({ className, children }: MyComponentProps) {
|
|
709
|
+
return (
|
|
710
|
+
<div className={cn("copa:flex copa:items-center copa:gap-2", className)}>
|
|
711
|
+
{children}
|
|
712
|
+
</div>
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Adding a New Page
|
|
718
|
+
|
|
719
|
+
1. Create page in `app/pages/my-page.tsx`
|
|
720
|
+
2. Add route in `app/routes/index.tsx` inside the `<Route element={<AppLayout />}>` group
|
|
721
|
+
3. Add navigation item in `app/components/layouts/app-sidebar.tsx`
|
|
722
|
+
|
|
723
|
+
### Adding a New UI Component
|
|
724
|
+
|
|
725
|
+
```bash
|
|
726
|
+
# Preferred: Dream Light registry
|
|
727
|
+
pnpm dlx shadcn@latest add @bluecopa-ui/component-name
|
|
728
|
+
|
|
729
|
+
# Fallback: standard shadcn
|
|
730
|
+
pnpm dlx shadcn@latest add component-name
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
Components install to `app/components/ui/` with `copa:` prefix pre-configured.
|
|
734
|
+
|
|
735
|
+
### Icons
|
|
736
|
+
|
|
737
|
+
Use `lucide-react` or `@tabler/icons-react` (both available).
|
|
738
|
+
|
|
739
|
+
### State Management
|
|
740
|
+
|
|
741
|
+
- **Server state**: `@bluecopa/react` hooks (React Query)
|
|
742
|
+
- **App context**: `useAppContext()` for user/workspace/settings
|
|
743
|
+
- **Local state**: `useState` / `useReducer`
|
|
744
|
+
- **No global state library** — keep it simple
|
|
745
|
+
|
|
746
|
+
### Responsive Design
|
|
747
|
+
|
|
748
|
+
- Mobile breakpoint: 768px (`useIsMobile()` hook)
|
|
749
|
+
- Sidebar: Desktop = collapsible panel, Mobile = sheet overlay
|
|
750
|
+
- Use `copa:md:` prefix for desktop-only styles
|
|
751
|
+
|
|
752
|
+
### Important Gotchas
|
|
753
|
+
|
|
754
|
+
1. **Prefix order**: `copa:` MUST come before any variant. `copa:hover:bg-accent` not `hover:copa:bg-accent`
|
|
755
|
+
2. **`@theme` is build-time**: Cannot reference `.mfe-root` CSS variables in `@theme` blocks. Use `@theme inline` for runtime bindings.
|
|
756
|
+
3. **`.mfe-root` not `:root`**: All CSS variables scoped to `.mfe-root` for MFE isolation
|
|
757
|
+
4. **Breakpoints with prefix**: Must explicitly define `--breakpoint-*` vars in `@theme` (Tailwind v4 doesn't auto-include them with prefix)
|
|
758
|
+
5. **ReactQueryDevtools**: Only rendered in dev mode (`import.meta.env.DEV`)
|
|
759
|
+
6. **Config before queries**: `copaSetConfig()` must complete before React Query hooks fire. The app gates rendering behind `isConfigured` state.
|