@bardioc/create-bardioc-app 0.7.0 → 0.7.1
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/package.json +2 -2
- package/templates/_base/.changeset/README.md +4 -6
- package/templates/_base/.claude/commands/changeset-app.md +11 -12
- package/templates/_base/.claude/commands/refresh-bundle.md +1 -1
- package/templates/_base/AGENTS.md +128 -0
- package/templates/_base/CLAUDE.md +1 -0
- package/templates/_base/README.md +11 -17
- package/templates/_opt-link/scripts/link-source.mjs +1 -1
- package/templates/_opt-pipeline/bitbucket-pipelines.yml +12 -56
- package/templates/angular/package.json +1 -1
- package/templates/nextjs/package.json +1 -1
- package/templates/preact/package.json +1 -1
- package/templates/solid/package.json +1 -1
- package/templates/svelte/package.json +1 -1
- package/templates/vite/CLAUDE.md +149 -135
- package/templates/vite/package.json +3 -3
- package/templates/vite/src/App.tsx +6 -3
- package/templates/vite/src/auth/onboarding.tsx +19 -23
- package/templates/vite/src/climate/climate-data.ts +19 -0
- package/templates/vite/src/climate/conditions-chart.tsx +11 -6
- package/templates/vite/src/climate/location-card.tsx +2 -5
- package/templates/vite/src/climate/location-select.tsx +7 -2
- package/templates/vite/src/climate/metrics-grid.tsx +6 -1
- package/templates/vite/src/climate/overview-view.tsx +26 -7
- package/templates/vite/src/climate/stations-view.tsx +73 -9
- package/templates/vite/src/climate/types.ts +5 -5
- package/templates/vite/src/dashboard-provider.tsx +15 -5
- package/templates/vite/src/events/live-events-view.tsx +67 -37
- package/templates/vite/src/events/mock-events.ts +33 -0
- package/templates/vite/src/graph/explorer-view.tsx +81 -63
- package/templates/vite/src/i18n.tsx +6 -2
- package/templates/vite/src/index.css +0 -6
- package/templates/vite/src/locales/de.json +55 -51
- package/templates/vite/src/locales/en.json +44 -40
- package/templates/vite/src/preview-state.tsx +29 -0
- package/templates/vite/src/profile/profile-view.tsx +43 -25
- package/templates/vite/src/shell/about-dialog.tsx +6 -5
- package/templates/vite/src/shell/app-shell.tsx +46 -45
- package/templates/vite/src/shell/debug-menu.tsx +86 -0
- package/templates/vite/src/shell/header.tsx +6 -4
- package/templates/vite/src/shell/view-content.tsx +69 -0
- package/templates/vue/package.json +1 -1
package/templates/vite/CLAUDE.md
CHANGED
|
@@ -4,20 +4,32 @@ Bardioc hosted app. Vite + React 19 + TypeScript 6 + `@bardioc/app-sdk`, with
|
|
|
4
4
|
`@bardioc/ui` (components + theme) and `@bardioc/i18n-core` (translations).
|
|
5
5
|
|
|
6
6
|
The starter ships a **Climate Dashboard** — a worked example of how a real Bardioc app
|
|
7
|
-
is wired: a sidebar shell with multiple views, `@bardioc/ui` components (cards, metrics,
|
|
8
|
-
charts, a searchable/filterable `DataTable`, combobox, language switcher),
|
|
9
|
-
the SDK
|
|
10
|
-
|
|
7
|
+
is wired: a searchable sidebar shell with multiple views, `@bardioc/ui` components (cards, metrics,
|
|
8
|
+
charts, a searchable/filterable/resizable `DataTable`, items, tabs, combobox, language switcher),
|
|
9
|
+
translations, the SDK (graph + OS calls, live graph events, OS menu, notifications), app-scoped
|
|
10
|
+
IndexedDB persistence, a debug menu that previews loading/empty/error states, and an explicit
|
|
11
|
+
auth-onboarding screen. Build your app by editing these pieces.
|
|
11
12
|
|
|
12
13
|
**Global rules apply.**
|
|
13
14
|
|
|
15
|
+
What every Bardioc app shares (commands, dev auth, SDK, errors, manifest, copy, shipping):
|
|
16
|
+
@AGENTS.md
|
|
17
|
+
|
|
18
|
+
This file adds the Climate Dashboard, `@bardioc/ui`, i18n and the React side of the SDK.
|
|
19
|
+
|
|
14
20
|
---
|
|
15
21
|
|
|
16
22
|
## Code style
|
|
17
23
|
|
|
18
|
-
- Single-line comments only (`// WHY, not WHAT`); no `/* */` blocks
|
|
24
|
+
- Single-line comments only (`// WHY, not WHAT`); no `/* */` blocks, also not as JSX
|
|
25
|
+
`{/* */}` — put a `//` line above the element or between its attributes. No task refs.
|
|
19
26
|
- 150–200 LOC per component, 600 LOC hard max per file. Decompose early.
|
|
20
|
-
- Constants: `SCREAMING_SNAKE_CASE`.
|
|
27
|
+
- Constants: `SCREAMING_SNAKE_CASE`. Keep a named constant even when it has one caller (`IDB_STORE`,
|
|
28
|
+
`MAX_EVENTS`): the name says what the value means. Inline it only when it holds nothing but CSS
|
|
29
|
+
classes.
|
|
30
|
+
- Prefer `switch`/early-return over long `else-if`.
|
|
31
|
+
- Compose class names with `cn` from `@bardioc/ui/lib`, never template strings or `+`. Write whole
|
|
32
|
+
Tailwind class names (`ok ? 'text-success' : 'text-error'`), never build them (`text-${tone}`).
|
|
21
33
|
|
|
22
34
|
---
|
|
23
35
|
|
|
@@ -25,63 +37,64 @@ explicit auth-onboarding screen. Build your app by editing these pieces.
|
|
|
25
37
|
|
|
26
38
|
```
|
|
27
39
|
src/
|
|
28
|
-
main.tsx #
|
|
29
|
-
App.tsx # auth gate → DashboardProvider+AppShell, or Onboarding
|
|
40
|
+
main.tsx # installDevHost (standalone dev) + AppSdkProvider + AppI18nProvider
|
|
41
|
+
App.tsx # auth gate → DashboardProvider + AppShell, or Onboarding
|
|
30
42
|
index.css # Tailwind v4 + @bardioc/ui theme tokens
|
|
31
|
-
i18n.tsx # I18nProvider, OS-locale sync, useTranslation/useI18n
|
|
32
|
-
locales/ # en.json / de.json
|
|
43
|
+
i18n.tsx # I18nProvider, @bardioc/ui catalog merge, OS-locale sync, useTranslation/useI18n
|
|
44
|
+
locales/ # en.json / de.json — the app's own strings
|
|
33
45
|
dashboard-provider.tsx # context: climate state + profile, via useDashboard()
|
|
46
|
+
preview-state.tsx # debug preview state (live | mock | loading | empty | error), via usePreviewState()
|
|
34
47
|
use-app-notify.ts # OS host notification (useNotify) mirrored to a local toast
|
|
35
48
|
auth/
|
|
36
49
|
onboarding.tsx # Empty full-page screen with the DEV_SECRET dev-auth steps
|
|
37
50
|
shell/
|
|
38
|
-
app-shell.tsx #
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
app-shell.tsx # SidebarBasic (search + nav + footer) + Page + OS top menu (useCommandMenu)
|
|
52
|
+
view-content.tsx # view switch + the preview state (skeleton / error) around it
|
|
53
|
+
debug-menu.tsx # sidebar footer, dev only: debug info toggle + preview state
|
|
54
|
+
header.tsx # Page actions: station combobox + language switcher + sync
|
|
55
|
+
about-dialog.tsx # "About" Dialog — reads public/app-manifest.json at runtime
|
|
41
56
|
climate/
|
|
42
|
-
types.ts # Station/ClimateState
|
|
43
|
-
climate-data.ts # demo-data generator (replace with your real source)
|
|
57
|
+
types.ts # ViewId, Station/ClimateState, METRICS config (icon, unit, color), IDB keys
|
|
58
|
+
climate-data.ts # demo-data generator, 24 stations (replace with your real source)
|
|
44
59
|
use-climate.ts # hook: IndexedDB load/persist + OS-probe sync + history
|
|
45
60
|
overview-view.tsx # location card + metrics + chart + persistence indicator
|
|
46
|
-
stations-view.tsx # DataTable
|
|
47
|
-
location-card.tsx #
|
|
61
|
+
stations-view.tsx # DataTable: search, FilterDropdown, paging, resizable columns, actions column
|
|
62
|
+
location-card.tsx # MediaTile + BadgeStatus widget
|
|
48
63
|
metrics-grid.tsx # GenericMetrics widget (5 readings)
|
|
49
64
|
conditions-chart.tsx # BarChartMultiple widget (7-day outlook)
|
|
50
65
|
location-select.tsx # Combobox station switcher
|
|
51
66
|
events/
|
|
52
|
-
live-events-view.tsx # live graph events via useGraphEvents
|
|
67
|
+
live-events-view.tsx # live graph events via useGraphEvents, as Item rows
|
|
68
|
+
mock-events.ts # sample events for the "Mock Data" preview
|
|
53
69
|
graph/
|
|
54
|
-
requests.ts # catalog of
|
|
55
|
-
explorer-view.tsx # run real requests;
|
|
70
|
+
requests.ts # catalog of SDK requests (graph + OS) shown in the Explorer
|
|
71
|
+
explorer-view.tsx # run real requests; simulated data when a call fails
|
|
56
72
|
profile/
|
|
57
73
|
use-profile.ts # os.profile fetch (+ operatorLabel helper)
|
|
58
74
|
profile-view.tsx # real profile data + error state
|
|
59
75
|
```
|
|
60
76
|
|
|
61
|
-
Views are
|
|
62
|
-
shared state from `useDashboard()`. The same
|
|
63
|
-
(`
|
|
64
|
-
|
|
77
|
+
Views are listed in `NAV` (`shell/app-shell.tsx`) and rendered by `ViewContent`
|
|
78
|
+
(`shell/view-content.tsx`); each view reads shared state from `useDashboard()`. The same `NAV`
|
|
79
|
+
drives the sidebar (filtered by its search, `⌘F` / `Ctrl+F`) and the **OS top menu**
|
|
80
|
+
(`useCommandMenu`): each view is a checkable command under a `View` menu, plus an `app`-kind root
|
|
81
|
+
next to the host's About / Version entries.
|
|
65
82
|
|
|
66
83
|
---
|
|
67
84
|
|
|
68
85
|
## Development
|
|
69
86
|
|
|
70
|
-
```bash
|
|
71
|
-
pnpm dev # http://localhost:{{PORT}}
|
|
72
|
-
pnpm build # → dist/
|
|
73
|
-
pnpm bundle # → {{APP_NAME}}.zip
|
|
74
|
-
pnpm check-types # tsc
|
|
75
|
-
pnpm lint # eslint (@bardioc/configs preset)
|
|
76
|
-
pnpm format # prettier (@bardioc/configs preset)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
87
|
ESLint, Prettier and the `tsconfig.json` base come from `@bardioc/configs` — edit
|
|
80
88
|
`eslint.config.mjs` / `prettier.config.mjs` only to add app-specific overrides on top of the preset.
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
Standalone, `main.tsx` installs the dev host and `vite.config.ts` the `bardiocDevAuth` proxy; without
|
|
91
|
+
dev auth the views show their error or fallback states. A build opened outside the host shows the
|
|
92
|
+
onboarding screen (`auth/onboarding.tsx`) instead.
|
|
93
|
+
|
|
94
|
+
**Debug menu** (sidebar footer, `npm run dev` only): _Show Debug Info_ toggles the same debug flag
|
|
95
|
+
as the OS dock; _Preview State_ forces every view into `Mock Data`, `Loading`, `Empty` or `Error`,
|
|
96
|
+
so you can check skeletons, empty states and errors without breaking the backend. Give every new
|
|
97
|
+
view all of these states.
|
|
85
98
|
|
|
86
99
|
---
|
|
87
100
|
|
|
@@ -89,8 +102,8 @@ live SDK data comes only when the app is opened inside the host, or when `DEV_SE
|
|
|
89
102
|
|
|
90
103
|
Tailwind v4 + the Bardioc design system. **Before writing any UI, read
|
|
91
104
|
`node_modules/@bardioc/ui/docs/INDEX.md`** — component choice, design tokens, layout and spacing
|
|
92
|
-
patterns, and copy rules ship with the installed package, so they match its version.
|
|
93
|
-
in `src/index.css`:
|
|
105
|
+
patterns, and copy rules ship with the installed package, so they match its version. Check a
|
|
106
|
+
component's props in its `.d.ts` before using one. Theme is wired in `src/index.css`:
|
|
94
107
|
|
|
95
108
|
```css
|
|
96
109
|
@import 'tailwindcss';
|
|
@@ -99,44 +112,51 @@ in `src/index.css`:
|
|
|
99
112
|
@source './**/*.{ts,tsx}';
|
|
100
113
|
```
|
|
101
114
|
|
|
102
|
-
Import
|
|
115
|
+
Import from the public subpaths (never reach into `@bardioc/ui/src`):
|
|
103
116
|
|
|
104
117
|
```typescript
|
|
105
|
-
import {
|
|
106
|
-
Badge,
|
|
107
|
-
BadgeStatus,
|
|
108
|
-
Button,
|
|
109
|
-
Card,
|
|
110
|
-
Combobox,
|
|
111
|
-
CheckboxGroup,
|
|
112
|
-
ConnectedLanguageSwitcher,
|
|
113
|
-
DataTable,
|
|
114
|
-
Empty,
|
|
115
|
-
FilterDropdown,
|
|
116
|
-
Kbd,
|
|
117
|
-
Page,
|
|
118
|
-
SidebarBasic,
|
|
119
|
-
SidebarContainedProvider,
|
|
120
|
-
SortableColumnHeader,
|
|
121
|
-
Toaster,
|
|
122
|
-
TruncatedCellText,
|
|
123
|
-
toast,
|
|
124
|
-
} from '@bardioc/ui/components';
|
|
118
|
+
import { Button, Card, DataTable, Empty, Page, SidebarBasic } from '@bardioc/ui/components';
|
|
125
119
|
import { GenericMetrics } from '@bardioc/ui/components/metrics';
|
|
126
120
|
import { BarChartMultiple } from '@bardioc/ui/components/charts';
|
|
121
|
+
import { useElementWidth, useFuzzySearch } from '@bardioc/ui/hooks';
|
|
122
|
+
import { cn, useDebugMode } from '@bardioc/ui/lib';
|
|
123
|
+
import * as Shadcn from '@bardioc/ui/shadcn'; // only parts with no Bardioc version
|
|
127
124
|
import type { ChartConfig } from '@bardioc/ui/shadcn/chart';
|
|
128
125
|
import type { TReactTable } from '@bardioc/ui/types'; // TReactTable.ColumnDef<Row>
|
|
129
126
|
```
|
|
130
127
|
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
**Component choice.** Use the Bardioc component from `@bardioc/ui/components` wherever one exists
|
|
129
|
+
(`Button`, `Badge`, `Tabs`, `Item*`, `DropdownMenuItem`, `SidebarMenuButton`, …). Import from
|
|
130
|
+
`@bardioc/ui/shadcn` only the parts Bardioc doesn't override (`DropdownMenu`, `DropdownMenuTrigger`,
|
|
131
|
+
`DropdownMenuContent`, `SidebarMenu`, `SidebarMenuItem`) — that's correct, not a gap. Don't rebuild
|
|
132
|
+
what a component already does: a list of rows is `ItemGroup` + `Item`, a "nothing here" message is
|
|
133
|
+
`Empty`, a table is `DataTable`.
|
|
134
|
+
|
|
135
|
+
**Use the props, not classes.** Size, variant, truncation and layout come from component props
|
|
136
|
+
(`size="sm"`, `variant="ghost"`, `ItemTitle truncate`, `Card contentClassName`). If a component
|
|
137
|
+
can't do what you need, that's a `@bardioc/ui` gap to report — not a reason for an arbitrary
|
|
138
|
+
`h-8`, `[&>span]:…` selector or a raw color.
|
|
139
|
+
|
|
140
|
+
**Rules from the design system:**
|
|
141
|
+
|
|
142
|
+
- No false affordances: hover shadows, lifts or highlights only on things that are clickable.
|
|
143
|
+
- `Empty` goes on the page, not inside a `Card` (its background shows as a block in dark mode).
|
|
144
|
+
- Page-header actions use the small sizes: `size="sm"` buttons and inputs, `icon-sm` icon buttons.
|
|
145
|
+
- An icon-only button needs a `title` (it becomes the tooltip and accessible name) or `aria-label`.
|
|
146
|
+
- Selected/forced state is shown with the component's own state (`isActive`, `aria-pressed`, a
|
|
147
|
+
filled `variant`), e.g. the debug entry and the Stations actions column.
|
|
148
|
+
- Filter triggers read "Filter" / "2 Filters" (`common.filter.*` from the `@bardioc/ui` catalog),
|
|
149
|
+
wrapped in `StableWidthText` so the button keeps its width.
|
|
150
|
+
- Theme tokens only (`bg-card`, `text-muted-foreground`, `var(--chart-1)`), never raw colors.
|
|
133
151
|
|
|
134
152
|
### Layout — the container contract
|
|
135
153
|
|
|
136
154
|
The app root opens `@container/app`; `Page` opens its own `@container/page` inside it. Keep both:
|
|
137
155
|
sizing that should follow the whole app reads `@container/app`, and anything inside the page column
|
|
138
156
|
— the page header included — reads `@container/page`, so it responds to the column rather than the
|
|
139
|
-
window.
|
|
157
|
+
window. Use container variants (`@xl/page:flex`, `@3xl/page:grid-cols-2`), not viewport
|
|
158
|
+
breakpoints (`md:`), and `useElementWidth` when JS has to switch a layout (the Explorer flips its
|
|
159
|
+
tabs to vertical when wide).
|
|
140
160
|
|
|
141
161
|
`Page` is the scrolling column, a **sibling of the sidebar** — it owns the header, the scroller and
|
|
142
162
|
the padding that lines them up. Don't compose a header and a `ScrollArea` by hand, and don't wrap
|
|
@@ -151,9 +171,9 @@ the sidebar in it:
|
|
|
151
171
|
</div>
|
|
152
172
|
```
|
|
153
173
|
|
|
154
|
-
Collapse your own `actions` against the same container (
|
|
155
|
-
on what "wide enough" means. `sticky` keeps the header
|
|
156
|
-
|
|
174
|
+
Collapse your own `actions` against the same container (the station combobox is
|
|
175
|
+
`hidden @xl/page:flex`) so both sides agree on what "wide enough" means. `sticky` keeps the header
|
|
176
|
+
at the top while the body scrolls under it, compacting once it does.
|
|
157
177
|
|
|
158
178
|
---
|
|
159
179
|
|
|
@@ -170,114 +190,108 @@ t('overview.updated', { fallback: 'Updated {time}', params: { time } });
|
|
|
170
190
|
```
|
|
171
191
|
|
|
172
192
|
- Add a string → add the key to **both** `src/locales/en.json` and `src/locales/de.json`.
|
|
193
|
+
- Always pass a `fallback`. It is interpolated like a translation, so `{param}`s and `{count}` are
|
|
194
|
+
filled in (`fallback: 'Debug: {state}', params: { state }`).
|
|
195
|
+
- `src/i18n.tsx` lays the app's catalog over `@bardioc/ui/i18n` (`mergeTranslations`), so
|
|
196
|
+
`@bardioc/ui` component strings and its `common.*` keys work out of the box and the app only
|
|
197
|
+
carries its own keys.
|
|
173
198
|
- `AppI18nProvider syncWithOs` follows the OS language (`useOsConfig().language`); the header's
|
|
174
199
|
`ConnectedLanguageSwitcher` lets the user switch it manually.
|
|
175
|
-
-
|
|
176
|
-
- `@bardioc/ui` component strings ship with the `@bardioc/ui` package itself — the app only
|
|
177
|
-
carries its own `en/de.json`.
|
|
178
|
-
- Copy style (Title Case labels, sentence-case messages, American English):
|
|
179
|
-
`node_modules/@bardioc/ui/docs/copy.md`.
|
|
200
|
+
- Copy rules are in `AGENTS.md`; details and exceptions in `node_modules/@bardioc/ui/docs/copy.md`.
|
|
180
201
|
|
|
181
202
|
---
|
|
182
203
|
|
|
183
204
|
## Persistence — app-scoped IndexedDB
|
|
184
205
|
|
|
185
|
-
The SDK gives every app a sandboxed IndexedDB via `
|
|
186
|
-
|
|
206
|
+
The SDK gives every app a sandboxed IndexedDB via `bardioc.idb(storeName)`. Standalone, it lives
|
|
207
|
+
in this browser's IndexedDB.
|
|
187
208
|
|
|
188
209
|
```typescript
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await store.
|
|
210
|
+
const bardioc = useSdk();
|
|
211
|
+
const store = bardioc.idb(IDB_STORE);
|
|
212
|
+
await store.put(IDB_STATE_KEY, value);
|
|
213
|
+
const saved = await store.get(IDB_STATE_KEY); // { key, value, updatedAt } | null
|
|
214
|
+
await store.query({ prefix: IDB_LOG_PREFIX, limit: 100 }); // history — see use-climate.ts
|
|
193
215
|
```
|
|
194
216
|
|
|
195
217
|
The Overview shows a live "Persisted to IndexedDB · Saved … · N snapshots" indicator so the
|
|
196
|
-
write path is visible.
|
|
197
|
-
|
|
218
|
+
write path is visible. Persisted state outlives code changes: after changing the demo data, clear
|
|
219
|
+
the site data (DevTools → Application) to see it.
|
|
198
220
|
|
|
199
221
|
---
|
|
200
222
|
|
|
201
|
-
## SDK
|
|
223
|
+
## SDK in React — `@bardioc/app-sdk/react`
|
|
224
|
+
|
|
225
|
+
The `Bardioc` object and its API are in `AGENTS.md`; this app reaches it through hooks.
|
|
202
226
|
|
|
203
227
|
```typescript
|
|
204
|
-
const
|
|
228
|
+
const bardioc = useSdk(); // the connected SDK (only inside AppSdkProvider)
|
|
205
229
|
const notify = useAppNotify(); // OS host notification + local toast (src/use-app-notify.ts)
|
|
206
|
-
const osConfig = useOsConfig(); // { theme, language,
|
|
230
|
+
const osConfig = useOsConfig(); // { theme, language, ... }
|
|
207
231
|
```
|
|
208
232
|
|
|
209
|
-
|
|
210
|
-
`
|
|
211
|
-
resolves to a `{ items: [...] }` envelope at runtime (not a bare array) — normalize before
|
|
212
|
-
indexing, and escape `/` in Lucene as `\/` (`ogit\/_type:"…"`). Hard-coded ids/types won't exist
|
|
213
|
-
in every instance: discover them from a wildcard `query('*')` first (see `graph/requests.ts`).
|
|
214
|
-
|
|
215
|
-
**OS** — `bridge.transport.os.*`: `profile.get()`, `organization.getStructure()`, `applications.list()`.
|
|
233
|
+
The `{ items }` envelope is normalized by `toItems` in `graph/requests.ts`. Error states:
|
|
234
|
+
`use-climate.ts`, `profile-view.tsx`, and `explorer-view.tsx`, which shows labeled simulated data.
|
|
216
235
|
|
|
217
|
-
**Live events** — `useGraphEvents({ types }, handler)` (`@bardioc/app-sdk/react`) opens a
|
|
218
|
-
|
|
219
|
-
`
|
|
220
|
-
|
|
236
|
+
**Live events** — `useGraphEvents({ types }, handler)` (`@bardioc/app-sdk/react`) opens a graph
|
|
237
|
+
event stream while mounted. Each `handler` receives a `GraphEvent` (`id`, `type`, `timestamp`,
|
|
238
|
+
`body?`). See `events/live-events-view.tsx`. No events arrive outside a valid session — render an
|
|
239
|
+
empty state.
|
|
221
240
|
|
|
222
241
|
**OS menu** — `useCommandMenu(commands, menu)` (`@bardioc/app-sdk/react`) pushes an app menu tree
|
|
223
|
-
into the host menu bar. `commands` are `{ id, title, checked?, handler }
|
|
224
|
-
|
|
225
|
-
label?, leftIcon? }`. `leftIcon` is a lucide export name
|
|
226
|
-
`shell/app-shell.tsx` — view switching is mirrored
|
|
227
|
-
|
|
228
|
-
The **Graph Explorer** view runs the
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return live data instead of empties. Network calls fail (401/timeout) outside a valid session —
|
|
233
|
-
always `try/catch` and fall back (see `use-climate.ts` and `explorer-view.tsx`, which show
|
|
234
|
-
simulated data).
|
|
242
|
+
into the host menu bar. `commands` are `AppCommand`s (`{ id, title, checked?, handler }`); `menu`
|
|
243
|
+
is `MenuRoot[]`, each root with a `label`, optional `kind: 'app'`, and `items` of
|
|
244
|
+
`{ type: 'command', commandId, label?, leftIcon? }`. `leftIcon` is a lucide export name the host
|
|
245
|
+
resolves. See `shell/app-shell.tsx` — view switching is mirrored there as checkable commands.
|
|
246
|
+
|
|
247
|
+
The **Graph Explorer** view runs the catalog in `graph/requests.ts` against the live SDK: a
|
|
248
|
+
wildcard `query('*')`, a query by the type of a real node, a node `get`, a Gremlin `both()`
|
|
249
|
+
traversal, and the OS calls. The typed/get/gremlin presets pull a real id and type from a wildcard
|
|
250
|
+
query first (`sampleNodes`), so they return live data instead of empties.
|
|
235
251
|
|
|
236
252
|
---
|
|
237
253
|
|
|
238
254
|
## Manifest — `public/app-manifest.json`
|
|
239
255
|
|
|
240
|
-
The starter ships a full manifest as a showcase of
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
- `permissions`: `notify`, `transport`, `indexdb`, `events` (the APIs this app actually uses).
|
|
244
|
-
Other valid values: `storage` (host-deferred) and the deprecated `kernel-proxy` — do not add
|
|
245
|
-
`kernel-proxy` for hosted apps.
|
|
246
|
-
- `window`: `size` (`xs|sm|md|lg|xl` or a custom `{ width, height, minWidth?, minHeight? }`),
|
|
247
|
-
`resizable`, `maximizable`, `minimizable`, `customScroll`, `centered`, `isHeaderless`,
|
|
248
|
-
`isInstanceAware`, `canHaveMultipleWindows`.
|
|
249
|
-
- Metadata: `author`, `categories`, `showInDock`, `showInDockAppsMenu`.
|
|
250
|
-
|
|
251
|
-
Trim any field you don't need — only `manifestVersion`, `id`, `name`, `version` are required.
|
|
256
|
+
The starter ships a full manifest as a showcase of the supported fields (listed in `AGENTS.md`).
|
|
257
|
+
Trim any field you don't need.
|
|
252
258
|
|
|
253
259
|
---
|
|
254
260
|
|
|
255
261
|
## How to…
|
|
256
262
|
|
|
257
263
|
- **Add a metric tile** → add a `MetricDef` (key, icon, unit, color) to `METRICS` in `types.ts`,
|
|
258
|
-
add the field to `ClimateReading` + the generator, and add `metrics.*` strings to
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`
|
|
264
|
+
add the field to `ClimateReading` + the generator, and add `metrics.*` strings to
|
|
265
|
+
`src/locales/{en,de}.json`.
|
|
266
|
+
- **Add a view** → add a `ViewId` (`climate/types.ts`), a `NAV` entry in `shell/app-shell.tsx`, a
|
|
267
|
+
`case` in `shell/view-content.tsx`, a `nav.*` string in `src/locales/{en,de}.json`, and the view
|
|
268
|
+
component. The `NAV` entry (with its `iconName`) is auto-wired into the sidebar, its search and
|
|
269
|
+
the OS `View` menu. Check the view in every debug preview state.
|
|
262
270
|
- **Add an OS menu command** → add an `AppCommand` (`{ id, title, handler }`) and a matching
|
|
263
|
-
`{ type: 'command', commandId }` item under a `
|
|
271
|
+
`{ type: 'command', commandId }` item under a `MenuRoot` in `app-shell.tsx`; set `checked` for
|
|
264
272
|
toggles. Add any `menu.*` label to `src/locales/{en,de}.json`.
|
|
265
|
-
- **Add a table column** → push a `TReactTable.ColumnDef<Station>` in `stations-view.tsx
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
273
|
+
- **Add a table column** → push a `TReactTable.ColumnDef<Station>` in `stations-view.tsx`, with a
|
|
274
|
+
`size` (resizing resets to it). Row actions go in the trailing `actions` column: fixed 40px, no
|
|
275
|
+
header, not sortable or resizable, `icon-xs` buttons.
|
|
276
|
+
- **Add an SDK request** → add an entry to `SDK_REQUESTS` in `graph/requests.ts` (it shows in the
|
|
277
|
+
Explorer).
|
|
278
|
+
- **Use real data** → replace `generateState()` with `bardioc.graph.*` calls in `use-climate.ts`;
|
|
279
|
+
keep the IndexedDB write so it persists across reloads.
|
|
280
|
+
- **Read the manifest** → ``fetch(`${import.meta.env.BASE_URL}app-manifest.json`)`` (served at
|
|
281
|
+
the Vite base in dev and inside the host). See `shell/about-dialog.tsx`.
|
|
272
282
|
|
|
273
283
|
---
|
|
274
284
|
|
|
275
285
|
## Anti-patterns
|
|
276
286
|
|
|
277
|
-
| ❌ Don't
|
|
278
|
-
|
|
|
279
|
-
| `
|
|
280
|
-
| `<AppSdkProvider>`
|
|
281
|
-
| raw `#1a1a1a` / `oklch(...)` colors
|
|
282
|
-
|
|
|
283
|
-
|
|
|
287
|
+
| ❌ Don't | ✅ Do |
|
|
288
|
+
| ------------------------------------------------- | ------------------------------------------------------------- |
|
|
289
|
+
| `bardioc.ui.notify(...)` in a component | `const notify = useAppNotify()` |
|
|
290
|
+
| `<AppSdkProvider>` without a host or dev session | gate on `isInsideIframe()` / `isDevStandalone()` (`main.tsx`) |
|
|
291
|
+
| raw `#1a1a1a` / `oklch(...)` colors | theme tokens (`bg-card`, `var(--chart-1)`) |
|
|
292
|
+
| `h-8`, `[&>span]:truncate` to restyle a component | its props (`size`, `variant`, `truncate`) |
|
|
293
|
+
| viewport breakpoints (`md:`) inside the page | container variants (`@xl/page:`) |
|
|
294
|
+
| hover shadow on something not clickable | no hover, or make it a real action |
|
|
295
|
+
| hand-built rows / empty messages | `ItemGroup` + `Item`, `Empty` |
|
|
296
|
+
| hard-code a label string | `t('key', { fallback })` in `en` **and** `de` |
|
|
297
|
+
| reach into `@bardioc/ui/src/...` | import from `@bardioc/ui/components` etc. |
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"release:publish": "changeset publish"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@bardioc/app-sdk": "^1.0.
|
|
26
|
-
"@bardioc/i18n-core": "^0.1.
|
|
27
|
-
"@bardioc/ui": "^0.7.
|
|
25
|
+
"@bardioc/app-sdk": "^1.0.3",
|
|
26
|
+
"@bardioc/i18n-core": "^0.1.2",
|
|
27
|
+
"@bardioc/ui": "^0.7.31",
|
|
28
28
|
"lucide-react": "^0.544.0",
|
|
29
29
|
"react": "19.2.6",
|
|
30
30
|
"react-dom": "19.2.6"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
2
2
|
import { Onboarding } from './auth/onboarding';
|
|
3
3
|
import { DashboardProvider } from './dashboard-provider';
|
|
4
|
+
import { PreviewStateProvider } from './preview-state';
|
|
4
5
|
import { AppShell } from './shell/app-shell';
|
|
5
6
|
|
|
6
7
|
export function App() {
|
|
@@ -11,8 +12,10 @@ export function App() {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
|
|
15
|
+
<PreviewStateProvider>
|
|
16
|
+
<DashboardProvider>
|
|
17
|
+
<AppShell />
|
|
18
|
+
</DashboardProvider>
|
|
19
|
+
</PreviewStateProvider>
|
|
17
20
|
);
|
|
18
21
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isInsideIframe } from '@bardioc/app-sdk/dev';
|
|
2
|
-
import { Button, Empty, Kbd } from '@bardioc/ui/components';
|
|
2
|
+
import { Button, Card, Empty, Kbd } from '@bardioc/ui/components';
|
|
3
3
|
import { PlugZap } from 'lucide-react';
|
|
4
4
|
import { useTranslation } from '../i18n';
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ function DetectedRow({ label, ok }: Readonly<{ label: string; ok: boolean }>) {
|
|
|
9
9
|
<div className="flex items-center justify-between gap-4">
|
|
10
10
|
<span className="text-muted-foreground">{label}</span>
|
|
11
11
|
<span className={ok ? 'text-success' : 'text-muted-foreground'}>
|
|
12
|
-
{ok ? t('auth.yes', { fallback: '
|
|
12
|
+
{ok ? t('auth.yes', { fallback: 'Yes' }) : t('auth.no', { fallback: 'No' })}
|
|
13
13
|
</span>
|
|
14
14
|
</div>
|
|
15
15
|
);
|
|
@@ -36,31 +36,27 @@ export function Onboarding() {
|
|
|
36
36
|
</Button>
|
|
37
37
|
}
|
|
38
38
|
>
|
|
39
|
-
<div className="
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<div className="border-border mt-3 space-y-1 border-t pt-3">
|
|
56
|
-
<p className="text-foreground font-medium">
|
|
57
|
-
{t('auth.detectedTitle', { fallback: 'Detected Environment' })}
|
|
58
|
-
</p>
|
|
39
|
+
<div className="flex w-full max-w-md flex-col gap-4 text-left">
|
|
40
|
+
<Card title={t('auth.stepsTitle', { fallback: 'Run Standalone with Dev Auth' })}>
|
|
41
|
+
<ol className="text-muted-foreground flex list-decimal flex-col gap-2 pl-4">
|
|
42
|
+
<li>
|
|
43
|
+
{t('auth.step1', {
|
|
44
|
+
fallback:
|
|
45
|
+
'Set DEV_SECRET=<your-secret> and API_BASE_URL=<your-host> in the .env file.',
|
|
46
|
+
})}
|
|
47
|
+
</li>
|
|
48
|
+
<li>
|
|
49
|
+
<div>{t('auth.step2', { fallback: 'Start the dev server.' })}</div>
|
|
50
|
+
<Kbd>npm run dev</Kbd>
|
|
51
|
+
</li>
|
|
52
|
+
</ol>
|
|
53
|
+
</Card>
|
|
54
|
+
<Card title={t('auth.detectedTitle', { fallback: 'Detected Environment' })}>
|
|
59
55
|
<DetectedRow
|
|
60
56
|
label={t('auth.insideHost', { fallback: 'Inside Host Iframe' })}
|
|
61
57
|
ok={insideHost}
|
|
62
58
|
/>
|
|
63
|
-
</
|
|
59
|
+
</Card>
|
|
64
60
|
</div>
|
|
65
61
|
</Empty>
|
|
66
62
|
);
|
|
@@ -9,6 +9,25 @@ const STATIONS: [StationBase, ...StationBase[]] = [
|
|
|
9
9
|
{ id: 'aurora', name: 'Aurora Flats', region: 'Polar Rim' },
|
|
10
10
|
{ id: 'cinder', name: 'Cinder Basin', region: 'Volcanic Plains' },
|
|
11
11
|
{ id: 'mistral', name: 'Mistral Cove', region: 'Coastal Shelf' },
|
|
12
|
+
{ id: 'ember', name: 'Ember Spire', region: 'Volcanic Plains' },
|
|
13
|
+
{ id: 'glacier', name: 'Glacier Gate', region: 'Polar Rim' },
|
|
14
|
+
{ id: 'saffron', name: 'Saffron Dunes', region: 'Arid Expanse' },
|
|
15
|
+
{ id: 'tidewater', name: 'Tidewater Point', region: 'Coastal Shelf' },
|
|
16
|
+
{ id: 'canopy', name: 'Canopy Hollow', region: 'Equatorial Belt' },
|
|
17
|
+
{ id: 'granite', name: 'Granite Pass', region: 'Highlands' },
|
|
18
|
+
{ id: 'halcyon', name: 'Halcyon Bay', region: 'Coastal Shelf' },
|
|
19
|
+
{ id: 'obsidian', name: 'Obsidian Field', region: 'Volcanic Plains' },
|
|
20
|
+
{ id: 'frostline', name: 'Frostline Camp', region: 'Polar Rim' },
|
|
21
|
+
{ id: 'mirage', name: 'Mirage Basin', region: 'Arid Expanse' },
|
|
22
|
+
{ id: 'fernwood', name: 'Fernwood Glade', region: 'Equatorial Belt' },
|
|
23
|
+
{ id: 'summit', name: 'Summit Watch', region: 'Highlands' },
|
|
24
|
+
{ id: 'driftwood', name: 'Driftwood Reach', region: 'Coastal Shelf' },
|
|
25
|
+
{ id: 'cobalt', name: 'Cobalt Crater', region: 'Volcanic Plains' },
|
|
26
|
+
{ id: 'tundra', name: 'Tundra Outpost', region: 'Polar Rim' },
|
|
27
|
+
{ id: 'sandstone', name: 'Sandstone Mesa', region: 'Arid Expanse' },
|
|
28
|
+
{ id: 'monsoon', name: 'Monsoon Delta', region: 'Equatorial Belt' },
|
|
29
|
+
{ id: 'windward', name: 'Windward Crest', region: 'Highlands' },
|
|
30
|
+
{ id: 'coral', name: 'Coral Lagoon', region: 'Coastal Shelf' },
|
|
12
31
|
];
|
|
13
32
|
const CONDITIONS: [Condition, ...Condition[]] = ['sunny', 'cloudy', 'rainy'];
|
|
14
33
|
const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Card } from '@bardioc/ui/components';
|
|
1
2
|
import { BarChartMultiple } from '@bardioc/ui/components/charts';
|
|
2
3
|
import type { ChartConfig } from '@bardioc/ui/shadcn/chart';
|
|
3
4
|
import { useTranslation } from '../i18n';
|
|
@@ -24,13 +25,17 @@ export function ConditionsChart({ forecast }: Readonly<ConditionsChartProps>) {
|
|
|
24
25
|
}));
|
|
25
26
|
|
|
26
27
|
return (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
xKey="day"
|
|
30
|
-
chartConfig={chartConfig}
|
|
28
|
+
// BarChartMultiple is a bare chart; the Card around it carries the heading.
|
|
29
|
+
<Card
|
|
31
30
|
title={t('chart.title', { fallback: '7-Day Outlook' })}
|
|
32
31
|
description={t('chart.description', { fallback: 'Daylight hours by condition.' })}
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
>
|
|
33
|
+
<BarChartMultiple
|
|
34
|
+
seriesData={seriesData}
|
|
35
|
+
xKey="day"
|
|
36
|
+
chartConfig={chartConfig}
|
|
37
|
+
heightClass="h-55"
|
|
38
|
+
/>
|
|
39
|
+
</Card>
|
|
35
40
|
);
|
|
36
41
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Badge, BadgeStatus, Card } from '@bardioc/ui/components';
|
|
1
|
+
import { Badge, BadgeStatus, Card, MediaTile } from '@bardioc/ui/components';
|
|
2
2
|
import { Cloud, CloudRain, Sun, type LucideIcon } from 'lucide-react';
|
|
3
3
|
import { useTranslation } from '../i18n';
|
|
4
4
|
import type { Condition, DataSource, Station } from './types';
|
|
@@ -20,15 +20,12 @@ interface LocationCardProps {
|
|
|
20
20
|
export function LocationCard({ station, updatedAt, source }: Readonly<LocationCardProps>) {
|
|
21
21
|
const { t } = useTranslation();
|
|
22
22
|
const condition = CONDITION[station.condition];
|
|
23
|
-
const Icon = condition.icon;
|
|
24
23
|
const time = new Date(updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
25
24
|
|
|
26
25
|
return (
|
|
27
26
|
<Card>
|
|
28
27
|
<div className="flex items-center gap-4">
|
|
29
|
-
<
|
|
30
|
-
<Icon className="size-6" />
|
|
31
|
-
</div>
|
|
28
|
+
<MediaTile size="md" icon={condition.icon} />
|
|
32
29
|
<div className="min-w-0 flex-1">
|
|
33
30
|
<div className="truncate text-lg font-semibold">{station.name}</div>
|
|
34
31
|
<div className="text-muted-foreground truncate text-xs">{station.region}</div>
|
|
@@ -16,11 +16,16 @@ export function LocationSelect({ stations, value, onSelect }: Readonly<LocationS
|
|
|
16
16
|
<Combobox
|
|
17
17
|
items={stations.map(station => station.id)}
|
|
18
18
|
value={value}
|
|
19
|
-
|
|
19
|
+
// Combobox reports null when cleared or when its value leaves the list; keep the station.
|
|
20
|
+
onValueChange={next => {
|
|
21
|
+
if (next) onSelect(next as string);
|
|
22
|
+
}}
|
|
20
23
|
itemToStringValue={item => item as string}
|
|
21
24
|
itemToStringLabel={item => nameById.get(item as string) ?? (item as string)}
|
|
22
25
|
placeholder={t('header.location', { fallback: 'Station' })}
|
|
23
|
-
|
|
26
|
+
size="sm"
|
|
27
|
+
// Too wide for a narrow page header; below `@xl` stations stay selectable from the Stations view.
|
|
28
|
+
className="hidden w-52 @xl/page:flex"
|
|
24
29
|
/>
|
|
25
30
|
);
|
|
26
31
|
}
|