@bardioc/create-bardioc-app 0.4.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +7 -13
  2. package/bin/create.mjs +4 -8
  3. package/package.json +1 -1
  4. package/src/scaffold.js +2 -2
  5. package/templates/_base/.changeset/README.md +3 -3
  6. package/templates/_base/.claude/commands/changeset-app.md +5 -5
  7. package/templates/_base/.claude/commands/refresh-bundle.md +5 -5
  8. package/templates/_base/README.md +10 -10
  9. package/templates/angular/package.json +6 -6
  10. package/templates/nextjs/.env.example +1 -1
  11. package/templates/nextjs/package.json +1 -1
  12. package/templates/preact/package.json +1 -1
  13. package/templates/solid/package.json +1 -1
  14. package/templates/svelte/package.json +1 -1
  15. package/templates/vite/.env.example +1 -1
  16. package/templates/vite/CLAUDE.md +198 -59
  17. package/templates/vite/package.json +7 -3
  18. package/templates/vite/public/app-manifest.json +25 -0
  19. package/templates/vite/src/App.tsx +11 -133
  20. package/templates/vite/src/auth/onboarding.tsx +67 -0
  21. package/templates/vite/src/climate/climate-data.ts +92 -0
  22. package/templates/vite/src/climate/conditions-chart.tsx +38 -0
  23. package/templates/vite/src/climate/location-card.tsx +50 -0
  24. package/templates/vite/src/climate/location-select.tsx +26 -0
  25. package/templates/vite/src/climate/metrics-grid.tsx +22 -0
  26. package/templates/vite/src/climate/overview-view.tsx +52 -0
  27. package/templates/vite/src/climate/stations-view.tsx +149 -0
  28. package/templates/vite/src/climate/types.ts +93 -0
  29. package/templates/vite/src/climate/use-climate.ts +123 -0
  30. package/templates/vite/src/dashboard-provider.tsx +27 -0
  31. package/templates/vite/src/events/live-events-view.tsx +74 -0
  32. package/templates/vite/src/graph/explorer-view.tsx +118 -0
  33. package/templates/vite/src/graph/requests.ts +142 -0
  34. package/templates/vite/src/i18n.tsx +68 -0
  35. package/templates/vite/src/index.css +12 -64
  36. package/templates/vite/src/locales/de.json +141 -0
  37. package/templates/vite/src/locales/en.json +141 -0
  38. package/templates/vite/src/main.tsx +10 -4
  39. package/templates/vite/src/profile/profile-view.tsx +75 -0
  40. package/templates/vite/src/profile/use-profile.ts +52 -0
  41. package/templates/vite/src/shell/about-dialog.tsx +129 -0
  42. package/templates/vite/src/shell/app-shell.tsx +160 -0
  43. package/templates/vite/src/shell/header.tsx +39 -0
  44. package/templates/vite/src/use-app-notify.ts +23 -0
  45. package/templates/vite/src/vite-env.d.ts +5 -5
  46. package/templates/vue/package.json +1 -1
@@ -1,6 +1,13 @@
1
1
  # {{DISPLAY_NAME}}
2
2
 
3
- Bardioc app. Vite + React 19 + TypeScript 6 + @bardioc/app-sdk.
3
+ Bardioc hosted app. Vite + React 19 + TypeScript 6 + `@bardioc/app-sdk`, with
4
+ `@bardioc/ui` (components + theme), `@bardioc/i18n` (translations), and `@bardioc/types`.
5
+
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), translations,
9
+ the SDK transport (graph + OS), notifications, app-scoped IndexedDB persistence, and an
10
+ explicit auth-onboarding screen. Build your app by editing these pieces.
4
11
 
5
12
  **Global rules apply.**
6
13
 
@@ -8,16 +15,53 @@ Bardioc app. Vite + React 19 + TypeScript 6 + @bardioc/app-sdk.
8
15
 
9
16
  ## Code style
10
17
 
11
- ### Comments
18
+ - Single-line comments only (`// WHY, not WHAT`); no `/* */` blocks; no task refs.
19
+ - 150–200 LOC per component, 600 LOC hard max per file. Decompose early.
20
+ - Constants: `SCREAMING_SNAKE_CASE`. Prefer `switch`/early-return over long `else-if`.
21
+
22
+ ---
12
23
 
13
- - Single-line: `// WHY, not WHAT`
14
- - No `/* */` blocks
15
- - No task refs
24
+ ## Structure
16
25
 
17
- ### Constraints
26
+ ```
27
+ src/
28
+ main.tsx # providers: AppSdkProvider (in host) + AppI18nProvider
29
+ App.tsx # auth gate → DashboardProvider+AppShell, or Onboarding
30
+ index.css # Tailwind v4 + @bardioc/ui theme tokens
31
+ i18n.tsx # I18nProvider, OS-locale sync, useTranslation/useI18n
32
+ locales/ # en.json / de.json translation strings
33
+ dashboard-provider.tsx # context: climate state + profile, via useDashboard()
34
+ use-app-notify.ts # OS host notification (useNotify) mirrored to a local toast
35
+ auth/
36
+ onboarding.tsx # Empty full-page screen with the CLI dev-session steps
37
+ shell/
38
+ app-shell.tsx # Sidebar + view routing + Toaster + OS top-menu integration (useCommandMenu)
39
+ about-dialog.tsx # "About" Dialog — reads public/app-manifest.json at runtime (manifest example)
40
+ header.tsx # location combobox + language switcher + sync
41
+ climate/
42
+ types.ts # Station/ClimateState + METRICS config (icon, unit, color)
43
+ climate-data.ts # demo-data generator (replace with your real source)
44
+ use-climate.ts # hook: IndexedDB load/persist + OS-probe sync + history
45
+ overview-view.tsx # location card + metrics + chart + persistence indicator
46
+ stations-view.tsx # DataTable (search + FilterDropdown + row select)
47
+ location-card.tsx # Card + BadgeStatus widget
48
+ metrics-grid.tsx # GenericMetrics widget (5 readings)
49
+ conditions-chart.tsx # BarChartMultiple widget (7-day outlook)
50
+ location-select.tsx # Combobox station switcher
51
+ events/
52
+ live-events-view.tsx # live graph events via useGraphEvents (SSE, permission "events")
53
+ graph/
54
+ requests.ts # catalog of every SDK transport request (graph + OS)
55
+ explorer-view.tsx # run real requests; mock fallback when unauthorized
56
+ profile/
57
+ use-profile.ts # os.profile fetch (+ operatorLabel helper)
58
+ profile-view.tsx # real profile data + error state
59
+ ```
18
60
 
19
- - 600 LOC max per file
20
- - Constants: `SCREAMING_SNAKE_CASE`
61
+ Views are selected from the sidebar (`ViewId` in `climate/types.ts`); each view consumes
62
+ shared state from `useDashboard()`. The same sections are also driven from the **OS top menu**
63
+ (`useCommandMenu` in `app-shell.tsx`): each view is a checkable command under a `View` menu, plus
64
+ an `app`-kind root next to the host's About / Version entries — the example of OS menu integration.
21
65
 
22
66
  ---
23
67
 
@@ -30,85 +74,180 @@ pnpm build # → dist/
30
74
  pnpm bundle # → {{APP_NAME}}.zip
31
75
  ```
32
76
 
33
- `.env`:
34
-
35
- ```bash
36
- VITE_APP_NAME={{APP_NAME}}
37
- VITE_APP_ID=your-app-id
38
- VITE_ENABLE_DEV_AUTH=true
39
- DEV_AUTH_HOST_URL=https://your-host
40
- DEV_SESSION_KEY=
41
- ```
42
-
43
- Standalone host-backed dev sessions are opt-in. Without `VITE_ENABLE_DEV_AUTH=true`, local `pnpm dev` runs outside the host iframe as a preview shell only and will not call the dev-session flow. Run `bardioc login` once, then `pnpm dev` will mint and refresh `DEV_SESSION_KEY` automatically.
77
+ `npm run dev` runs plain Vite — a local preview shell (no transport): the dashboard renders, but
78
+ live SDK data comes only when the app is opened inside the host. `.env` (see `.env.example`): set
79
+ `VITE_APP_ID` (from the BardiocOS AppStore) when you wire the app up to the host.
44
80
 
45
81
  ---
46
82
 
47
- ## SDK API
83
+ ## UI — `@bardioc/ui`
48
84
 
49
- **Graph API**
50
- Access the graph database via `bridge.transport.graph.*`:
85
+ Tailwind v4 + the Bardioc design system. **Never restyle with raw hex/oklch** — use the theme
86
+ tokens (`bg-background`, `text-foreground`, `text-muted-foreground`, `bg-card`, `border-border`,
87
+ `bg-primary`, and chart tokens `var(--chart-1..5)`). Theme is wired in `src/index.css`:
51
88
 
52
- ```typescript
53
- const bridge = useSdk();
54
- await bridge.transport.graph.get('node-id');
55
- await bridge.transport.graph.gremlin('node-id', 'g.V().limit(10)');
89
+ ```css
90
+ @import 'tailwindcss';
91
+ @import '@bardioc/ui/styles/theme.css';
92
+ @import '@bardioc/ui/styles/components.css';
93
+ @source './**/*.{ts,tsx}';
56
94
  ```
57
95
 
58
- **OS API**
59
- Access OS resources via `bridge.transport.os.*`:
96
+ Import components from the public subpaths (never reach into `@bardioc/ui/src`):
60
97
 
61
98
  ```typescript
62
- await bridge.transport.os.profile.get();
63
- await bridge.transport.os.organization.getStructure();
99
+ import {
100
+ Badge,
101
+ BadgeStatus,
102
+ Button,
103
+ Card,
104
+ Combobox,
105
+ CheckboxGroup,
106
+ ConnectedLanguageSwitcher,
107
+ DataTable,
108
+ Empty,
109
+ FilterDropdown,
110
+ Kbd,
111
+ SidebarBasic,
112
+ SidebarContainedProvider,
113
+ SortableColumnHeader,
114
+ Toaster,
115
+ TruncatedCellText,
116
+ toast,
117
+ } from '@bardioc/ui/components';
118
+ import { GenericMetrics } from '@bardioc/ui/components/metrics';
119
+ import { BarChartMultiple } from '@bardioc/ui/components/charts';
120
+ import type { ChartConfig } from '@bardioc/ui/shadcn/chart';
121
+ import type { TReactTable } from '@bardioc/ui/types'; // TReactTable.ColumnDef<Row>
64
122
  ```
65
123
 
66
- **Notifications**
124
+ Prefer a Bardioc component over a raw HTML element. Icons come from `lucide-react`. The
125
+ sidebar uses `SidebarBasic` inside a `SidebarContainedProvider` (the contained variant for
126
+ OS windows); the language switcher is `ConnectedLanguageSwitcher useI18n={useI18n}`.
67
127
 
68
- ```typescript
69
- const notify = useNotify();
70
- notify('Done', 'success');
71
- ```
128
+ ---
129
+
130
+ ## i18n — `@bardioc/i18n`
72
131
 
73
- **Provider** (only in iframe)
132
+ All user-facing strings go through `t()`. Locales: `en`, `de` (`@bardioc/types`).
74
133
 
75
134
  ```typescript
76
- const isInsideIframe = window.parent !== window;
77
- {isInsideIframe && (
78
- <AppSdkProvider appId={APP_ID}><App /></AppSdkProvider>
79
- )}
135
+ import { useTranslation } from './i18n';
136
+
137
+ const { t } = useTranslation();
138
+ t('metrics.oxygen', { fallback: 'Oxygen' });
139
+ t('overview.updated', { fallback: 'Updated {time}', params: { time } });
80
140
  ```
81
141
 
142
+ - Add a string → add the key to **both** `src/locales/en.json` and `src/locales/de.json`.
143
+ - `AppI18nProvider syncWithOs` follows the OS language (`useOsConfig().language`); the header's
144
+ `ConnectedLanguageSwitcher` lets the user switch it manually.
145
+ - Always pass a `fallback` so a missing key degrades gracefully.
146
+ - `@bardioc/ui` components translate against this **same** provider — only the app's `en/de.json`
147
+ are loaded (the base `@bardioc/i18n` bundle is not merged). So shared-component keys must live in
148
+ the app locales: `common.button.previous/next` and the `genericTable.*` set (DataTable pagination
149
+ "1 to 5 of 5", Previous/Next). Without them those strings silently fall back to English.
150
+
82
151
  ---
83
152
 
84
- ## Anti-patterns
153
+ ## Persistence — app-scoped IndexedDB
85
154
 
86
- **❌ bridge.notify in components**
155
+ The SDK gives every app a sandboxed IndexedDB via `bridge.idb(storeName)`. Requires the
156
+ `"indexdb"` permission (already in `public/app-manifest.json`).
87
157
 
88
158
  ```typescript
89
- function MyComponent() {
90
- const bridge = useSdk();
91
- bridge.notify('Done', 'success');
92
- }
159
+ const store = bridge.idb('climate');
160
+ await store.put('state', value);
161
+ const saved = await store.get<ClimateState>('state');
162
+ await store.query({ prefix: 'log:', limit: 50 }); // history — see use-climate.ts
93
163
  ```
94
164
 
95
- **✅ useNotify hook**
165
+ The Overview shows a live "Persisted to IndexedDB · Saved … · N snapshots" indicator so the
166
+ write path is visible. For small key-value state, `bridge.storage.get/set/delete` (permission
167
+ `"storage"`) is simpler.
96
168
 
97
- ```typescript
98
- function MyComponent() {
99
- const notify = useNotify();
100
- notify('Done', 'success');
101
- }
102
- ```
169
+ ---
103
170
 
104
- **❌ provider outside iframe**
171
+ ## SDK API
105
172
 
106
173
  ```typescript
107
- <AppSdkProvider appId={APP_ID}><App /></AppSdkProvider>
174
+ const bridge = useSdk(); // host bridge (only inside the host/dev session)
175
+ const notify = useAppNotify(); // OS host notification + local toast (src/use-app-notify.ts)
176
+ const osConfig = useOsConfig(); // { theme, language, instanceId, ... }
108
177
  ```
109
178
 
110
- **✅ conditional rendering**
179
+ **Graph** `bridge.transport.graph.*`: `get(id)`, `getByXid`, `query(lucene, opts)`,
180
+ `gremlin(rootId, query)`, `create`, `update`, `connect`, `timeseries.get/query`. Note: `query`
181
+ resolves to a `{ items: [...] }` envelope at runtime (not a bare array) — normalize before
182
+ indexing, and escape `/` in Lucene as `\/` (`ogit\/_type:"…"`). Hard-coded ids/types won't exist
183
+ in every instance: discover them from a wildcard `query('*')` first (see `graph/requests.ts`).
184
+
185
+ **OS** — `bridge.transport.os.*`: `profile.get()`, `organization.getStructure()`, `applications.list()`.
186
+
187
+ **Live events** — `useGraphEvents({ types }, handler)` (`@bardioc/app-sdk/react`) opens a scoped
188
+ graph-event SSE stream while mounted (permission `"events"`). Each `handler` receives a
189
+ `GraphEvent` (`id`, `type`, `timestamp`, `body?`). See `events/live-events-view.tsx`. No events
190
+ arrive outside a valid session — render an empty state.
191
+
192
+ **OS menu** — `useCommandMenu(commands, menu)` (`@bardioc/app-sdk/react`) pushes an app menu tree
193
+ into the host menu bar. `commands` are `{ id, title, checked?, handler }`; `menu` is `SdkMenuRoot[]`
194
+ where each root has `label`, optional `kind: 'app'`, and `items` of `{ type: 'command', commandId,
195
+ label?, leftIcon? }`. `leftIcon` is a lucide export name (string) the host resolves. See
196
+ `shell/app-shell.tsx` — view switching is mirrored here as checkable commands.
197
+
198
+ The **Graph Explorer** view runs the full catalog in `graph/requests.ts` against the live
199
+ transport. The graph presets issue real calls — a wildcard `query('*')`, a query by the type of a
200
+ real node, a node `get`, and a Gremlin `both()` neighbor traversal. The typed/get/gremlin presets
201
+ each pull a real id and type from a wildcard query first (`sampleNodes` → `toItems`), so they
202
+ return live data instead of empties. Network calls fail (401/timeout) outside a valid session —
203
+ always `try/catch` and fall back (see `use-climate.ts` and `explorer-view.tsx`, which show
204
+ simulated data).
111
205
 
112
- ```typescript
113
- {isInsideIframe && <AppSdkProvider appId={APP_ID}><App /></AppSdkProvider>}
114
- ```
206
+ ---
207
+
208
+ ## Manifest — `public/app-manifest.json`
209
+
210
+ The starter ships a full manifest as a showcase of every supported field (validated by
211
+ `validateManifest` in `@bardioc/app-sdk`):
212
+
213
+ - `permissions`: `notify`, `transport`, `indexdb`, `events` (the APIs this app actually uses).
214
+ Other valid values: `storage` (host-deferred) and the deprecated `kernel-proxy` — do not add
215
+ `kernel-proxy` for hosted apps.
216
+ - `window`: `size` (`xs|sm|md|lg|xl` or a custom `{ width, height, minWidth?, minHeight? }`),
217
+ `resizable`, `maximizable`, `minimizable`, `customScroll`, `centered`, `isHeaderless`,
218
+ `isInstanceAware`, `canHaveMultipleWindows`.
219
+ - Metadata: `author`, `categories`, `showInDock`, `showInDockAppsMenu`.
220
+
221
+ Trim any field you don't need — only `manifestVersion`, `id`, `name`, `version` are required.
222
+
223
+ ---
224
+
225
+ ## How to…
226
+
227
+ - **Add a metric tile** → add a `MetricDef` (key, icon, unit, color) to `METRICS` in `types.ts`,
228
+ add the field to `ClimateReading` + the generator, and add `metrics.*` strings to `src/locales/{en,de}.json`.
229
+ - **Add a view** → add a `ViewId` (`climate/types.ts`), a `NAV` entry + `case` in `shell/app-shell.tsx`,
230
+ a `nav.*` string in `src/locales/{en,de}.json`, and the view component. The `NAV` entry (with its
231
+ `iconName`) is auto-wired into both the sidebar and the OS `View` menu.
232
+ - **Add an OS menu command** → add an `AppCommand` (`{ id, title, handler }`) and a matching
233
+ `{ type: 'command', commandId }` item under a `SdkMenuRoot` in `app-shell.tsx`; set `checked` for
234
+ toggles. Add any `menu.*` label to `src/locales/{en,de}.json`.
235
+ - **Add a table column** → push a `TReactTable.ColumnDef<Station>` in `stations-view.tsx`.
236
+ - **Add an SDK request** → add an entry to `SDK_REQUESTS` in `graph/requests.ts` (it shows in the Explorer).
237
+ - **Use real data** → replace `generateState()` with `bridge.transport.graph.*` calls in
238
+ `use-climate.ts`; keep the IndexedDB write so it persists across reloads.
239
+ - **Read the manifest** → `fetch(\`${import.meta.env.BASE_URL}app-manifest.json\`)`(served at the
240
+ Vite base in dev and inside the host). See`shell/about-dialog.tsx` — it shows version, id,
241
+ description, and permissions straight from the manifest.
242
+
243
+ ---
244
+
245
+ ## Anti-patterns
246
+
247
+ | ❌ Don't | ✅ Do |
248
+ | -------------------------------------------- | ----------------------------------------------------- |
249
+ | `bridge.notify(...)` in a component | `const notify = useNotify()` |
250
+ | `<AppSdkProvider>` rendered outside the host | gate on `isInsideIframe()` / dev session (`main.tsx`) |
251
+ | raw `#1a1a1a` / `oklch(...)` colors | theme tokens (`bg-card`, `var(--chart-1)`) |
252
+ | hard-code a label string | `t('key', { fallback })` in `en` **and** `de` |
253
+ | reach into `@bardioc/ui/src/...` | import from `@bardioc/ui/components` etc. |
@@ -4,19 +4,23 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "bardioc-dev-session -- vite",
8
- "dev:live": "BARDIOC_TUNNEL=1 bardioc-dev-session -- vite",
7
+ "dev": "vite",
8
+ "dev:live": "BARDIOC_TUNNEL=1 vite",
9
9
  "build": "tsc && vite build && node scripts/stamp-manifest.mjs dist",
10
10
  "bundle": "tsc && vite build && node scripts/stamp-manifest.mjs dist && cd dist && zip -r ../{{APP_NAME}}.zip .",
11
11
  "preview": "vite preview",
12
12
  "check-types": "tsc --noEmit",
13
13
  "changeset": "changeset",
14
14
  "release:status": "changeset status --verbose",
15
- "release:version": "changeset version && pnpm install --lockfile-only",
15
+ "release:version": "changeset version && npm install",
16
16
  "release:publish": "changeset publish"
17
17
  },
18
18
  "dependencies": {
19
19
  "@bardioc/app-sdk": "latest",
20
+ "@bardioc/i18n": "^0.1.6",
21
+ "@bardioc/types": "^0.3.0",
22
+ "@bardioc/ui": "^0.2.11",
23
+ "lucide-react": "^0.544.0",
20
24
  "react": "19.2.6",
21
25
  "react-dom": "19.2.6"
22
26
  },
@@ -0,0 +1,25 @@
1
+ {
2
+ "manifestVersion": 2,
3
+ "id": "{{APP_NAME}}",
4
+ "name": "{{DISPLAY_NAME}}",
5
+ "description": "{{DISPLAY_NAME}} — a Bardioc hosted app",
6
+ "version": "0.0.1",
7
+ "entry": "index.html",
8
+ "icon": "icon.svg",
9
+ "author": { "name": "{{DISPLAY_NAME}} Team" },
10
+ "categories": ["productivity"],
11
+ "permissions": ["notify", "transport", "indexdb", "events"],
12
+ "showInDock": true,
13
+ "showInDockAppsMenu": true,
14
+ "window": {
15
+ "size": "lg",
16
+ "resizable": true,
17
+ "maximizable": true,
18
+ "minimizable": true,
19
+ "customScroll": false,
20
+ "centered": false,
21
+ "isHeaderless": false,
22
+ "isInstanceAware": false,
23
+ "canHaveMultipleWindows": false
24
+ }
25
+ }
@@ -1,141 +1,19 @@
1
1
  import { isDevStandalone, isInsideIframe } from '@bardioc/app-sdk/dev';
2
- import { useSdk, useNotify } from '@bardioc/app-sdk/react';
3
- import { useState } from 'react';
2
+ import { Onboarding } from './auth/onboarding';
3
+ import { DashboardProvider } from './dashboard-provider';
4
+ import { AppShell } from './shell/app-shell';
4
5
 
5
- type ResultState =
6
- | { status: 'idle' }
7
- | { status: 'loading'; request: string }
8
- | { status: 'success'; request: string; data: unknown }
9
- | { status: 'error'; request: string; message: string };
10
-
11
- function SdkDemo() {
12
- const bridge = useSdk();
13
- const notify = useNotify();
14
- const [result, setResult] = useState<ResultState>({ status: 'idle' });
15
- const [running, setRunning] = useState(false);
16
-
17
- async function runGetProfile() {
18
- if (running) return;
19
- setRunning(true);
20
-
21
- const request = 'transport.os.profile.get()';
22
- setResult({ status: 'loading', request });
23
-
24
- const startTime = Date.now();
25
- notify('Getting user profile', 'info');
26
-
27
- try {
28
- const data = await bridge.transport.os.profile.get();
29
-
30
- const duration = Date.now() - startTime;
31
- notify(`Profile loaded (${duration}ms)`, 'success');
32
- setResult({ status: 'success', request, data });
33
- } catch (err) {
34
- const message = err instanceof Error ? err.message : String(err);
35
- notify(`Failed: ${message}`, 'error');
36
- setResult({ status: 'error', request, message });
37
- }
38
-
39
- setRunning(false);
40
- }
6
+ export function App() {
7
+ const authorized =
8
+ isInsideIframe() || (import.meta.env.VITE_ENABLE_DEV_AUTH === 'true' && isDevStandalone());
41
9
 
42
- function handleNotify() {
43
- notify('Hello from {{DISPLAY_NAME}}', 'info');
44
- setResult({
45
- status: 'success',
46
- request: 'SDK_NOTIFY',
47
- data: { ok: true, message: 'Notification sent to OS' },
48
- });
10
+ if (!authorized) {
11
+ return <Onboarding />;
49
12
  }
50
13
 
51
14
  return (
52
- <div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-3 p-4">
53
- <div className="flex flex-col gap-1">
54
- <h1 className="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
55
- <p className="text-muted-foreground m-0 text-xs leading-5">
56
- Minimal hosted-app demo for API transport and SDK APIs.
57
- </p>
58
- </div>
59
-
60
- <div className="flex flex-wrap gap-2">
61
- <button
62
- className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed"
63
- onClick={() => void runGetProfile()}
64
- disabled={running}
65
- >
66
- Get Profile
67
- </button>
68
- <button
69
- className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 rounded px-3 py-1 text-xs font-medium disabled:cursor-not-allowed"
70
- onClick={handleNotify}
71
- disabled={running}
72
- >
73
- Send Notification OS
74
- </button>
75
- <button
76
- className="border-border text-muted-foreground hover:text-foreground rounded border px-3 py-1 text-xs"
77
- onClick={() => setResult({ status: 'idle' })}
78
- disabled={running}
79
- >
80
- Clear
81
- </button>
82
- </div>
83
-
84
- <div className="border-border bg-background flex min-h-0 flex-1 flex-col overflow-hidden rounded border">
85
- {result.status === 'idle' && (
86
- <div className="text-muted-foreground flex h-full items-center justify-center p-3 text-center text-xs leading-5">
87
- Run a query to see results. If the host session is missing, the transport route will return 401.
88
- </div>
89
- )}
90
- {result.status === 'loading' && (
91
- <div className="flex h-full flex-col items-center justify-center gap-2 p-3">
92
- <div className="text-muted-foreground font-mono text-xs">{result.request}</div>
93
- <div className="text-muted-foreground text-xs">Loading…</div>
94
- </div>
95
- )}
96
- {result.status === 'error' && (
97
- <div className="flex h-full flex-col gap-2 p-3 text-xs">
98
- {result.request && (
99
- <div className="text-muted-foreground border-border border-b pb-2 font-mono text-xs">
100
- {result.request}
101
- </div>
102
- )}
103
- <div className="text-destructive whitespace-pre-wrap break-all">{result.message}</div>
104
- </div>
105
- )}
106
- {result.status === 'success' && (
107
- <div className="flex h-full flex-col gap-0 overflow-hidden">
108
- <div className="border-border text-muted-foreground shrink-0 border-b px-3 py-1 font-mono text-xs">
109
- {result.request}
110
- </div>
111
- <pre className="text-foreground flex-1 overflow-auto p-3 text-[11px] leading-5">
112
- {JSON.stringify(result.data, null, 2)}
113
- </pre>
114
- </div>
115
- )}
116
- </div>
117
- </div>
15
+ <DashboardProvider>
16
+ <AppShell />
17
+ </DashboardProvider>
118
18
  );
119
19
  }
120
-
121
- export function App() {
122
- const showSdkDemo = isInsideIframe() || (import.meta.env.VITE_ENABLE_DEV_AUTH === 'true' && isDevStandalone());
123
-
124
- return (
125
- <div className="bg-background text-foreground min-h-screen">
126
- {showSdkDemo ? (
127
- <SdkDemo />
128
- ) : (
129
- <div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-4 p-4">
130
- <h1 className="m-0 text-2xl font-semibold">Hello Bardioc App</h1>
131
- <p className="text-muted-foreground m-0 max-w-2xl text-xs leading-5">
132
- Open this app inside Bardioc OS to use transport requests and send host notifications.
133
- </p>
134
- <div className="border-border bg-muted/20 rounded-xl border p-3 text-xs leading-5">
135
- Outside the host iframe this page is only a local preview shell.
136
- </div>
137
- </div>
138
- )}
139
- </div>
140
- );
141
- }
@@ -0,0 +1,67 @@
1
+ import { isInsideIframe } from '@bardioc/app-sdk/dev';
2
+ import { Button, Empty, Kbd } from '@bardioc/ui/components';
3
+ import { PlugZap } from 'lucide-react';
4
+ import { useTranslation } from '../i18n';
5
+
6
+ function DetectedRow({ label, ok }: Readonly<{ label: string; ok: boolean }>) {
7
+ const { t } = useTranslation();
8
+ return (
9
+ <div className="flex items-center justify-between gap-4">
10
+ <span className="text-muted-foreground">{label}</span>
11
+ <span className={ok ? 'text-success' : 'text-muted-foreground'}>
12
+ {ok ? t('auth.yes', { fallback: 'yes' }) : t('auth.no', { fallback: 'no' })}
13
+ </span>
14
+ </div>
15
+ );
16
+ }
17
+
18
+ export function Onboarding() {
19
+ const { t } = useTranslation();
20
+ const insideHost = isInsideIframe();
21
+ const devAuth = import.meta.env.VITE_ENABLE_DEV_AUTH === 'true';
22
+
23
+ return (
24
+ <Empty
25
+ fullPage
26
+ icon={PlugZap}
27
+ iconVariant="chip"
28
+ titleSize="lg"
29
+ title={t('auth.title', { fallback: 'Connect to Bardioc OS' })}
30
+ description={t('auth.description', {
31
+ fallback: 'This dashboard reads live data through the Bardioc SDK transport.',
32
+ })}
33
+ actions={
34
+ <Button onClick={() => globalThis.location.reload()}>
35
+ {t('auth.retry', { fallback: 'Reload' })}
36
+ </Button>
37
+ }
38
+ >
39
+ <div className="border-border bg-card mt-4 w-full max-w-md rounded-lg border p-4 text-left text-xs leading-6">
40
+ <p className="text-foreground mb-2 font-medium">
41
+ {t('auth.stepsTitle', { fallback: 'Run standalone with a dev session:' })}
42
+ </p>
43
+ <ol className="text-muted-foreground list-decimal space-y-2 pl-4">
44
+ <li>{t('auth.step1', { fallback: 'Set VITE_ENABLE_DEV_AUTH=true in .env' })}</li>
45
+ <li>
46
+ <div>{t('auth.step2', { fallback: 'Authorize once in the app folder:' })}</div>
47
+ <Kbd>bardioc login</Kbd>
48
+ </li>
49
+ <li>
50
+ <div>{t('auth.step3', { fallback: 'Start the dev server:' })}</div>
51
+ <Kbd>pnpm dev</Kbd>
52
+ </li>
53
+ </ol>
54
+ <div className="border-border mt-3 space-y-1 border-t pt-3">
55
+ <p className="text-foreground font-medium">
56
+ {t('auth.detectedTitle', { fallback: 'Detected environment' })}
57
+ </p>
58
+ <DetectedRow
59
+ label={t('auth.insideHost', { fallback: 'Inside host iframe' })}
60
+ ok={insideHost}
61
+ />
62
+ <DetectedRow label={t('auth.devAuth', { fallback: 'Dev auth enabled' })} ok={devAuth} />
63
+ </div>
64
+ </div>
65
+ </Empty>
66
+ );
67
+ }