@bardioc/create-bardioc-app 0.5.6 → 0.5.8

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 (51) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/templates/_base/README.md +1 -1
  4. package/templates/angular/.env.example +4 -1
  5. package/templates/angular/package.json +2 -2
  6. package/templates/angular/scripts/dev-env.mjs +41 -0
  7. package/templates/angular/scripts/dev.mjs +27 -0
  8. package/templates/angular/scripts/sync-runtime-env.mjs +2 -28
  9. package/templates/angular/src/app/app.component.ts +5 -5
  10. package/templates/angular/src/styles.css +4 -0
  11. package/templates/nextjs/.env.example +4 -1
  12. package/templates/nextjs/app/page.tsx +11 -8
  13. package/templates/nextjs/next.config.ts +1 -1
  14. package/templates/nextjs/package.json +3 -3
  15. package/templates/nextjs/scripts/dev-env.mjs +41 -0
  16. package/templates/nextjs/scripts/dev.mjs +27 -0
  17. package/templates/preact/.env.example +3 -0
  18. package/templates/preact/package.json +2 -2
  19. package/templates/preact/src/App.tsx +5 -5
  20. package/templates/preact/vite.config.ts +4 -1
  21. package/templates/solid/.env.example +3 -0
  22. package/templates/solid/package.json +2 -2
  23. package/templates/solid/src/App.tsx +5 -5
  24. package/templates/solid/vite.config.ts +4 -1
  25. package/templates/svelte/.env.example +3 -0
  26. package/templates/svelte/package.json +2 -2
  27. package/templates/svelte/src/App.svelte +5 -5
  28. package/templates/svelte/vite.config.ts +4 -1
  29. package/templates/vite/.env.example +3 -0
  30. package/templates/vite/CLAUDE.md +34 -7
  31. package/templates/vite/package.json +3 -3
  32. package/templates/vite/src/auth/onboarding.tsx +4 -4
  33. package/templates/vite/src/climate/conditions-chart.tsx +2 -2
  34. package/templates/vite/src/climate/overview-view.tsx +1 -1
  35. package/templates/vite/src/climate/stations-view.tsx +3 -3
  36. package/templates/vite/src/climate/use-climate.ts +2 -2
  37. package/templates/vite/src/events/live-events-view.tsx +2 -2
  38. package/templates/vite/src/graph/explorer-view.tsx +4 -4
  39. package/templates/vite/src/locales/de.json +4 -4
  40. package/templates/vite/src/locales/en.json +44 -44
  41. package/templates/vite/src/profile/profile-view.tsx +5 -5
  42. package/templates/vite/src/shell/about-dialog.tsx +3 -3
  43. package/templates/vite/src/shell/app-shell.tsx +16 -12
  44. package/templates/vite/src/shell/header.tsx +24 -24
  45. package/templates/vite/vite.config.ts +4 -1
  46. package/templates/vue/.env.example +3 -0
  47. package/templates/vue/package.json +2 -2
  48. package/templates/vue/src/App.vue +5 -5
  49. package/templates/vue/vite.config.ts +4 -1
  50. package/templates/angular/scripts/dev-auth-proxy.mjs +0 -17
  51. package/templates/nextjs/scripts/dev-auth-proxy.mjs +0 -17
@@ -3,9 +3,12 @@ import { svelte } from '@sveltejs/vite-plugin-svelte';
3
3
  import tailwindcss from '@tailwindcss/vite';
4
4
  import { defineConfig, loadEnv } from 'vite';
5
5
 
6
+ const DEFAULT_PORT = {{PORT}};
7
+
6
8
  export default defineConfig(({ mode }) => {
7
9
  const env = loadEnv(mode, __dirname, '');
8
10
  const appName = env.VITE_APP_NAME?.trim();
11
+ const port = Number(env.LOCALHOST_PORT) || DEFAULT_PORT;
9
12
 
10
13
  if (!appName) {
11
14
  throw new Error('Missing VITE_APP_NAME for Vite config');
@@ -15,7 +18,7 @@ export default defineConfig(({ mode }) => {
15
18
  plugins: [
16
19
  bardiocDevAuth({ appName }),
17
20
  svelte(),
18
- bardiocApp({ appName, port: {{PORT}} }),
21
+ bardiocApp({ appName, port }),
19
22
  tailwindcss(),
20
23
  ],
21
24
  };
@@ -7,3 +7,6 @@ DEV_SECRET=
7
7
 
8
8
  # Base URL of the Bardioc BFF / backend-for-frontend (e.g. https://your-host).
9
9
  API_BASE_URL=
10
+
11
+ # By default, the app will start on port {{PORT}}. Feel free to uncomment the following variable and change it to match your local setup preferences.
12
+ #LOCALHOST_PORT={{PORT}}
@@ -35,9 +35,9 @@ src/
35
35
  auth/
36
36
  onboarding.tsx # Empty full-page screen with the DEV_SECRET dev-auth steps
37
37
  shell/
38
- app-shell.tsx # Sidebar + view routing + Toaster + OS top-menu integration (useCommandMenu)
38
+ app-shell.tsx # Sidebar + Page + view routing + Toaster + OS top-menu (useCommandMenu)
39
39
  about-dialog.tsx # "About" Dialog — reads public/app-manifest.json at runtime (manifest example)
40
- header.tsx # location combobox + language switcher + sync
40
+ header.tsx # Page actions: location combobox + language switcher + sync
41
41
  climate/
42
42
  types.ts # Station/ClimateState + METRICS config (icon, unit, color)
43
43
  climate-data.ts # demo-data generator (replace with your real source)
@@ -87,9 +87,10 @@ live SDK data comes only when the app is opened inside the host, or when `DEV_SE
87
87
 
88
88
  ## UI — `@bardioc/ui`
89
89
 
90
- Tailwind v4 + the Bardioc design system. **Never restyle with raw hex/oklch** — use the theme
91
- tokens (`bg-background`, `text-foreground`, `text-muted-foreground`, `bg-card`, `border-border`,
92
- `bg-primary`, and chart tokens `var(--chart-1..5)`). Theme is wired in `src/index.css`:
90
+ Tailwind v4 + the Bardioc design system. **Before writing any UI, read
91
+ `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. Theme is wired
93
+ in `src/index.css`:
93
94
 
94
95
  ```css
95
96
  @import 'tailwindcss';
@@ -113,6 +114,7 @@ import {
113
114
  Empty,
114
115
  FilterDropdown,
115
116
  Kbd,
117
+ Page,
116
118
  SidebarBasic,
117
119
  SidebarContainedProvider,
118
120
  SortableColumnHeader,
@@ -126,10 +128,33 @@ import type { ChartConfig } from '@bardioc/ui/shadcn/chart';
126
128
  import type { TReactTable } from '@bardioc/ui/types'; // TReactTable.ColumnDef<Row>
127
129
  ```
128
130
 
129
- Prefer a Bardioc component over a raw HTML element. Icons come from `lucide-react`. The
130
- sidebar uses `SidebarBasic` inside a `SidebarContainedProvider` (the contained variant for
131
+ The sidebar uses `SidebarBasic` inside a `SidebarContainedProvider` (the contained variant for
131
132
  OS windows); the language switcher is `ConnectedLanguageSwitcher useI18n={useI18n}`.
132
133
 
134
+ ### Layout — the container contract
135
+
136
+ The app root opens `@container/app`; `Page` opens its own `@container/page` inside it. Keep both:
137
+ sizing that should follow the whole app reads `@container/app`, and anything inside the page column
138
+ — the page header included — reads `@container/page`, so it responds to the column rather than the
139
+ window.
140
+
141
+ `Page` is the scrolling column, a **sibling of the sidebar** — it owns the header, the scroller and
142
+ the padding that lines them up. Don't compose a header and a `ScrollArea` by hand, and don't wrap
143
+ the sidebar in it:
144
+
145
+ ```tsx
146
+ <div className="bg-background text-foreground @container/app flex h-full min-h-0">
147
+ <SidebarBasic … />
148
+ <Page title={pageTitle} actions={<Header />} actionsClassName="gap-2" sticky className="min-w-0">
149
+ <ViewContent view={view} />
150
+ </Page>
151
+ </div>
152
+ ```
153
+
154
+ Collapse your own `actions` against the same container (e.g. `@3xl/page:hidden`) so both sides agree
155
+ on what "wide enough" means. `sticky` keeps the header at the top while the body scrolls under it,
156
+ compacting once it does; drop it if you want the header to scroll away instead.
157
+
133
158
  ---
134
159
 
135
160
  ## i18n — `@bardioc/i18n-core`
@@ -150,6 +175,8 @@ t('overview.updated', { fallback: 'Updated {time}', params: { time } });
150
175
  - Always pass a `fallback` so a missing key degrades gracefully.
151
176
  - `@bardioc/ui` component strings ship with the `@bardioc/ui` package itself — the app only
152
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`.
153
180
 
154
181
  ---
155
182
 
@@ -18,15 +18,15 @@
18
18
  "release:publish": "changeset publish"
19
19
  },
20
20
  "dependencies": {
21
- "@bardioc/app-sdk": "^0.4.9",
21
+ "@bardioc/app-sdk": "^0.5.0",
22
22
  "@bardioc/i18n-core": "^0.1.1",
23
- "@bardioc/ui": "^0.3.1",
23
+ "@bardioc/ui": "^0.7.29",
24
24
  "lucide-react": "^0.544.0",
25
25
  "react": "19.2.6",
26
26
  "react-dom": "19.2.6"
27
27
  },
28
28
  "devDependencies": {
29
- "@bardioc/configs": "^0.2.1",
29
+ "@bardioc/configs": "0.2.1",
30
30
  "@changesets/cli": "^2.31.0",
31
31
  "@tailwindcss/vite": "^4.3.0",
32
32
  "@types/node": "^22.19.19",
@@ -25,7 +25,7 @@ export function Onboarding() {
25
25
  icon={PlugZap}
26
26
  iconVariant="chip"
27
27
  titleSize="lg"
28
- title={t('auth.title', { fallback: 'Connect to Bardioc OS' })}
28
+ title={t('auth.title', { fallback: 'Connect to Bardioc' })}
29
29
  description={t('auth.description', {
30
30
  fallback: 'This dashboard reads live data through the Bardioc SDK transport.',
31
31
  })}
@@ -42,7 +42,7 @@ export function Onboarding() {
42
42
  <ol className="text-muted-foreground list-decimal space-y-2 pl-4">
43
43
  <li>
44
44
  {t('auth.step1', {
45
- fallback: 'Set DEV_SECRET=<your-secret> and API_BASE_URL=<your-host> in .env',
45
+ fallback: 'Set DEV_SECRET=<your-secret> and API_BASE_URL=<your-host> in .env.',
46
46
  })}
47
47
  </li>
48
48
  <li>
@@ -52,10 +52,10 @@ export function Onboarding() {
52
52
  </ol>
53
53
  <div className="border-border mt-3 space-y-1 border-t pt-3">
54
54
  <p className="text-foreground font-medium">
55
- {t('auth.detectedTitle', { fallback: 'Detected environment' })}
55
+ {t('auth.detectedTitle', { fallback: 'Detected Environment' })}
56
56
  </p>
57
57
  <DetectedRow
58
- label={t('auth.insideHost', { fallback: 'Inside host iframe' })}
58
+ label={t('auth.insideHost', { fallback: 'Inside Host Iframe' })}
59
59
  ok={insideHost}
60
60
  />
61
61
  </div>
@@ -28,8 +28,8 @@ export function ConditionsChart({ forecast }: Readonly<ConditionsChartProps>) {
28
28
  seriesData={seriesData}
29
29
  xKey="day"
30
30
  chartConfig={chartConfig}
31
- title={t('chart.title', { fallback: '7-day outlook' })}
32
- description={t('chart.description', { fallback: 'Daylight hours by condition' })}
31
+ title={t('chart.title', { fallback: '7-Day Outlook' })}
32
+ description={t('chart.description', { fallback: 'Daylight hours by condition.' })}
33
33
  heightClass="h-[220px]"
34
34
  />
35
35
  );
@@ -42,7 +42,7 @@ export function OverviewView() {
42
42
  ) : (
43
43
  <span>
44
44
  {t('overview.notPersisted', {
45
- fallback: 'Preview — not persisted (open inside Bardioc OS to persist)',
45
+ fallback: 'Preview — not persisted (open inside Bardioc to persist)',
46
46
  })}
47
47
  </span>
48
48
  )}
@@ -122,7 +122,7 @@ export function StationsView() {
122
122
  <div className="flex flex-col gap-3">
123
123
  <div>
124
124
  <h2 className="text-lg font-semibold">
125
- {t('stations.title', { fallback: 'Monitoring stations' })}
125
+ {t('stations.title', { fallback: 'Monitoring Stations' })}
126
126
  </h2>
127
127
  <p className="text-muted-foreground text-xs">
128
128
  {t('stations.description', { fallback: '' })}
@@ -134,13 +134,13 @@ export function StationsView() {
134
134
  columns={columns}
135
135
  filterKey={['name', 'region']}
136
136
  showSearchBar
137
- filterPlaceholder={t('stations.search', { fallback: 'Search stations…' })}
137
+ filterPlaceholder={t('stations.search', { fallback: 'Search Stations…' })}
138
138
  itemLabel={t('stations.itemLabel', { fallback: 'stations' })}
139
139
  onRowClick={station => setActiveStation(station.id)}
140
140
  filterSlot={<ConditionFilter selected={conditions} onToggle={onToggle} />}
141
141
  emptyState={
142
142
  <p className="text-muted-foreground p-6 text-center text-sm">
143
- {t('stations.empty', { fallback: 'No stations match your filters' })}
143
+ {t('stations.empty', { fallback: 'No stations match your filters.' })}
144
144
  </p>
145
145
  }
146
146
  />
@@ -89,7 +89,7 @@ export function useClimate() {
89
89
 
90
90
  notify(
91
91
  t('notify.synced', {
92
- fallback: 'Synced {count} stations',
92
+ fallback: 'Synced {count} stations.',
93
93
  params: { count: next.stations.length },
94
94
  }),
95
95
  source === 'graph' ? 'success' : 'warning'
@@ -108,7 +108,7 @@ export function useClimate() {
108
108
  const station = next.stations.find(item => item.id === id);
109
109
  notify(
110
110
  t('notify.stationSelected', {
111
- fallback: 'Active station: {name}',
111
+ fallback: 'Active station: {name}.',
112
112
  params: { name: station?.name ?? id },
113
113
  }),
114
114
  'info'
@@ -37,14 +37,14 @@ export function LiveEventsView() {
37
37
  <Empty
38
38
  icon={Activity}
39
39
  iconVariant="chip"
40
- title={t('events.emptyTitle', { fallback: 'Waiting for events' })}
40
+ title={t('events.emptyTitle', { fallback: 'Waiting for Events' })}
41
41
  description={
42
42
  isInsideIframe()
43
43
  ? t('events.emptyHost', {
44
44
  fallback: 'No graph events yet — activity will appear here as it happens.',
45
45
  })
46
46
  : t('events.emptyStandalone', {
47
- fallback: 'Open inside Bardioc OS (or a dev session) to receive live events.',
47
+ fallback: 'Open inside Bardioc (or a dev session) to receive live events.',
48
48
  })
49
49
  }
50
50
  />
@@ -28,12 +28,12 @@ export function ExplorerView() {
28
28
  const data = await request.run(bridge);
29
29
  const ms = Date.now() - started;
30
30
  setResult({ status: 'done', live: true, ms, data });
31
- notify(t('notify.queryOk', { fallback: 'Request ok ({ms} ms)', params: { ms } }), 'success');
31
+ notify(t('notify.queryOk', { fallback: 'Request ok ({ms} ms).', params: { ms } }), 'success');
32
32
  } catch {
33
33
  const ms = Date.now() - started;
34
34
  setResult({ status: 'done', live: false, ms, data: request.mock() });
35
35
  notify(
36
- t('notify.queryFailed', { fallback: 'Request failed — showing simulated data' }),
36
+ t('notify.queryFailed', { fallback: 'Request failed — showing simulated data.' }),
37
37
  'warning'
38
38
  );
39
39
  }
@@ -85,7 +85,7 @@ export function ExplorerView() {
85
85
  >
86
86
  {result.status === 'running'
87
87
  ? t('explorer.running', { fallback: 'Running…' })
88
- : t('explorer.run', { fallback: 'Run request' })}
88
+ : t('explorer.run', { fallback: 'Run Request' })}
89
89
  </Button>
90
90
  </div>
91
91
 
@@ -107,7 +107,7 @@ export function ExplorerView() {
107
107
  {result.status === 'done'
108
108
  ? JSON.stringify(result.data, null, 2)
109
109
  : t('explorer.empty', {
110
- fallback: 'Select a request and run it to see the response',
110
+ fallback: 'Select a request and run it to see the response.',
111
111
  })}
112
112
  </pre>
113
113
  </div>
@@ -39,8 +39,8 @@
39
39
  "neverSaved": "Noch nicht gespeichert"
40
40
  },
41
41
  "auth": {
42
- "title": "Mit Bardioc OS verbinden",
43
- "description": "Dieses Dashboard liest Live-Daten über den Bardioc-SDK-Transport. Es läuft in Bardioc OS oder eigenständig mit einer CLI-Dev-Session.",
42
+ "title": "Mit Bardioc verbinden",
43
+ "description": "Dieses Dashboard liest Live-Daten über den Bardioc-SDK-Transport. Es läuft in Bardioc oder eigenständig mit einer CLI-Dev-Session.",
44
44
  "stepsTitle": "Eigenständig mit Dev-Session ausführen:",
45
45
  "step1": "Setze DEV_SECRET=<dein-secret> und API_BASE_URL=<dein-host> in .env",
46
46
  "step2": "Dev-Server starten:",
@@ -56,7 +56,7 @@
56
56
  "noOperator": "Betreiber nicht verfügbar",
57
57
  "updated": "Aktualisiert {time}",
58
58
  "persistence": "In IndexedDB gespeichert",
59
- "notPersisted": "Vorschau — nicht gespeichert (in Bardioc OS öffnen zum Speichern)",
59
+ "notPersisted": "Vorschau — nicht gespeichert (in Bardioc öffnen zum Speichern)",
60
60
  "historyOne": "{count} Snapshot im Verlauf",
61
61
  "historyOther": "{count} Snapshots im Verlauf",
62
62
  "source": "Quelle"
@@ -127,7 +127,7 @@
127
127
  "description": "Live-Graph-Ereignisse, vom OS über SSE gestreamt (Berechtigung \"events\").",
128
128
  "emptyTitle": "Warte auf Ereignisse",
129
129
  "emptyHost": "Noch keine Graph-Ereignisse — Aktivität erscheint hier, sobald sie auftritt.",
130
- "emptyStandalone": "In Bardioc OS (oder einer Dev-Session) öffnen, um Live-Ereignisse zu empfangen."
130
+ "emptyStandalone": "In Bardioc (oder einer Dev-Session) öffnen, um Live-Ereignisse zu empfangen."
131
131
  },
132
132
  "notify": {
133
133
  "synced": "{count} Stationen synchronisiert",
@@ -1,5 +1,5 @@
1
1
  {
2
- "app": { "title": "Weather Dashboard", "tagline": "Live planetary readings" },
2
+ "app": { "title": "Weather Dashboard", "tagline": "Live Planetary Readings" },
3
3
  "nav": {
4
4
  "overview": "Overview",
5
5
  "stations": "Stations",
@@ -9,77 +9,77 @@
9
9
  },
10
10
  "menu": {
11
11
  "view": "View",
12
- "about": "About this app"
12
+ "about": "About This App"
13
13
  },
14
14
  "about": {
15
- "title": "About this app",
15
+ "title": "About This App",
16
16
  "version": "Version",
17
17
  "id": "App ID",
18
18
  "description": "Description",
19
19
  "permissions": "Permissions",
20
- "error": "Could not read app-manifest.json",
21
- "source": "Read live from public/app-manifest.json"
20
+ "error": "Could not read app-manifest.json.",
21
+ "source": "Read live from public/app-manifest.json."
22
22
  },
23
23
  "genericTable": {
24
- "filterPlaceholder": "Filter results...",
24
+ "filterPlaceholder": "Filter Results...",
25
25
  "itemLabel": "rows",
26
26
  "showing": "{from} to {to} of {total}",
27
27
  "columns": "Columns",
28
28
  "noResults": "No results found.",
29
- "goToPreviousPage": "Go to previous page",
30
- "goToNextPage": "Go to next page"
29
+ "goToPreviousPage": "Go to Previous Page",
30
+ "goToNextPage": "Go to Next Page"
31
31
  },
32
32
  "common": { "button": { "previous": "Previous", "next": "Next" } },
33
33
  "header": {
34
- "search": "Search stations…",
34
+ "search": "Search Stations…",
35
35
  "sync": "Sync",
36
36
  "syncing": "Syncing…",
37
37
  "location": "Station",
38
38
  "savedAt": "Saved {time}",
39
- "neverSaved": "Not yet saved"
39
+ "neverSaved": "Not Yet Saved"
40
40
  },
41
41
  "auth": {
42
- "title": "Connect to Bardioc OS",
43
- "description": "This dashboard reads live data through the Bardioc SDK transport. It runs when opened inside Bardioc OS, or standalone with a CLI dev session.",
42
+ "title": "Connect to Bardioc",
43
+ "description": "This dashboard reads live data through the Bardioc SDK transport. It runs when opened inside Bardioc, or standalone with a CLI dev session.",
44
44
  "stepsTitle": "Run standalone with a dev session:",
45
- "step1": "Set DEV_SECRET=<your-secret> and API_BASE_URL=<your-host> in .env",
45
+ "step1": "Set DEV_SECRET=<your-secret> and API_BASE_URL=<your-host> in .env.",
46
46
  "step2": "Start the dev server:",
47
- "detectedTitle": "Detected environment",
48
- "insideHost": "Inside host iframe",
49
- "devAuth": "Dev auth enabled",
47
+ "detectedTitle": "Detected Environment",
48
+ "insideHost": "Inside Host Iframe",
49
+ "devAuth": "Dev Auth Enabled",
50
50
  "yes": "yes",
51
51
  "no": "no",
52
52
  "retry": "Reload"
53
53
  },
54
54
  "overview": {
55
55
  "operator": "Operator",
56
- "noOperator": "Operator unavailable",
56
+ "noOperator": "Operator Unavailable",
57
57
  "updated": "Updated {time}",
58
58
  "persistence": "Persisted to IndexedDB",
59
- "notPersisted": "Preview — not persisted (open inside Bardioc OS to persist)",
59
+ "notPersisted": "Preview — not persisted (open inside Bardioc to persist)",
60
60
  "historyOne": "{count} snapshot in history",
61
61
  "historyOther": "{count} snapshots in history",
62
62
  "source": "Source"
63
63
  },
64
64
  "conditions": { "sunny": "Sunny", "cloudy": "Cloudy", "rainy": "Rainy" },
65
- "source": { "graph": "Bardioc graph", "mock": "Simulated" },
65
+ "source": { "graph": "Bardioc Graph", "mock": "Simulated" },
66
66
  "metrics": {
67
67
  "oxygen": "Oxygen",
68
- "temperature": "Surface temp",
68
+ "temperature": "Surface Temp",
69
69
  "humidity": "Humidity",
70
- "windSpeed": "Wind speed",
70
+ "windSpeed": "Wind Speed",
71
71
  "vegetation": "Vegetation",
72
- "oxygenHint": "Atmospheric mix",
73
- "temperatureHint": "Ground level",
72
+ "oxygenHint": "Atmospheric Mix",
73
+ "temperatureHint": "Ground Level",
74
74
  "humidityHint": "Relative",
75
75
  "windSpeedHint": "Sustained",
76
- "vegetationHint": "Surface cover"
76
+ "vegetationHint": "Surface Cover"
77
77
  },
78
- "chart": { "title": "7-day outlook", "description": "Daylight hours by condition" },
78
+ "chart": { "title": "7-Day Outlook", "description": "Daylight hours by condition." },
79
79
  "stations": {
80
- "title": "Monitoring stations",
80
+ "title": "Monitoring Stations",
81
81
  "description": "All stations reporting to this instance. Search, filter, or select one.",
82
- "search": "Search stations…",
82
+ "search": "Search Stations…",
83
83
  "filter": "Condition",
84
84
  "colName": "Station",
85
85
  "colRegion": "Region",
@@ -88,16 +88,16 @@
88
88
  "colHumidity": "Humidity",
89
89
  "colVegetation": "Vegetation",
90
90
  "itemLabel": "stations",
91
- "empty": "No stations match your filters",
91
+ "empty": "No stations match your filters.",
92
92
  "apply": "Apply",
93
93
  "clear": "Clear"
94
94
  },
95
95
  "explorer": {
96
96
  "title": "Graph Explorer",
97
97
  "description": "Run real Bardioc SDK transport requests. Without a session, each request falls back to simulated data so you can still explore the shape.",
98
- "run": "Run request",
98
+ "run": "Run Request",
99
99
  "running": "Running…",
100
- "empty": "Select a request and run it to see the response",
100
+ "empty": "Select a request and run it to see the response.",
101
101
  "live": "Live",
102
102
  "mock": "Simulated",
103
103
  "categoryGraph": "Graph",
@@ -106,35 +106,35 @@
106
106
  "durationMs": "{ms} ms"
107
107
  },
108
108
  "profile": {
109
- "title": "Operator profile",
109
+ "title": "Operator Profile",
110
110
  "description": "Pulled live from the OS transport (os.profile).",
111
111
  "name": "Name",
112
- "displayName": "Display name",
113
- "firstName": "First name",
114
- "lastName": "Last name",
112
+ "displayName": "Display Name",
113
+ "firstName": "First Name",
114
+ "lastName": "Last Name",
115
115
  "email": "Email",
116
116
  "id": "ID",
117
117
  "organization": "Organization",
118
118
  "units": "Units",
119
119
  "members": "Members",
120
- "raw": "Raw response",
121
- "errorTitle": "Could not load profile",
120
+ "raw": "Raw Response",
121
+ "errorTitle": "Could Not Load Profile",
122
122
  "errorHint": "The transport returned an error. Authorize a session, then reload.",
123
123
  "unknown": "Unknown"
124
124
  },
125
125
  "events": {
126
126
  "title": "Live Events",
127
127
  "description": "Live graph events streamed from the OS over SSE (permission \"events\").",
128
- "emptyTitle": "Waiting for events",
128
+ "emptyTitle": "Waiting for Events",
129
129
  "emptyHost": "No graph events yet — activity will appear here as it happens.",
130
- "emptyStandalone": "Open inside Bardioc OS (or a dev session) to receive live events."
130
+ "emptyStandalone": "Open inside Bardioc (or a dev session) to receive live events."
131
131
  },
132
132
  "notify": {
133
- "synced": "Synced {count} stations",
134
- "saved": "Saved to IndexedDB",
135
- "stationSelected": "Active station: {name}",
136
- "localeChanged": "Language switched to {locale}",
137
- "queryOk": "Request ok ({ms} ms)",
138
- "queryFailed": "Request failed — showing simulated data"
133
+ "synced": "Synced {count} stations.",
134
+ "saved": "Saved to IndexedDB.",
135
+ "stationSelected": "Active station: {name}.",
136
+ "localeChanged": "Language switched to {locale}.",
137
+ "queryOk": "Request ok ({ms} ms).",
138
+ "queryFailed": "Request failed — showing simulated data."
139
139
  }
140
140
  }
@@ -31,7 +31,7 @@ export function ProfileView() {
31
31
  <Empty
32
32
  icon={UserX}
33
33
  iconVariant="chip"
34
- title={t('profile.errorTitle', { fallback: 'Could not load profile' })}
34
+ title={t('profile.errorTitle', { fallback: 'Could Not Load Profile' })}
35
35
  description={profile.error ?? t('profile.errorHint', { fallback: '' })}
36
36
  />
37
37
  );
@@ -44,7 +44,7 @@ export function ProfileView() {
44
44
  <div className="flex flex-col gap-4">
45
45
  <div>
46
46
  <h2 className="text-lg font-semibold">
47
- {t('profile.title', { fallback: 'Operator profile' })}
47
+ {t('profile.title', { fallback: 'Operator Profile' })}
48
48
  </h2>
49
49
  <p className="text-muted-foreground text-xs">
50
50
  {t('profile.description', { fallback: '' })}
@@ -56,15 +56,15 @@ export function ProfileView() {
56
56
  <Avatar firstName={name} size="lg" />
57
57
  <div className="flex min-w-0 flex-1 flex-col gap-1">
58
58
  <Row
59
- label={t('profile.displayName', { fallback: 'Display name' })}
59
+ label={t('profile.displayName', { fallback: 'Display Name' })}
60
60
  value={asString(data.display_name, unknown)}
61
61
  />
62
62
  <Row
63
- label={t('profile.firstName', { fallback: 'First name' })}
63
+ label={t('profile.firstName', { fallback: 'First Name' })}
64
64
  value={asString(data.first_name, unknown)}
65
65
  />
66
66
  <Row
67
- label={t('profile.lastName', { fallback: 'Last name' })}
67
+ label={t('profile.lastName', { fallback: 'Last Name' })}
68
68
  value={asString(data.last_name, unknown)}
69
69
  />
70
70
  </div>
@@ -81,7 +81,7 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
81
81
  modal
82
82
  showCloseOnHeader
83
83
  width={440}
84
- title={t('about.title', { fallback: 'About this app' })}
84
+ title={t('about.title', { fallback: 'About This App' })}
85
85
  >
86
86
  <div className="flex flex-col items-center gap-4 px-2 pb-2">
87
87
  <div className="flex flex-col items-center gap-2 py-2">
@@ -93,7 +93,7 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
93
93
 
94
94
  {failed ? (
95
95
  <p className="text-destructive text-sm">
96
- {t('about.error', { fallback: 'Could not read app-manifest.json' })}
96
+ {t('about.error', { fallback: 'Could not read app-manifest.json.' })}
97
97
  </p>
98
98
  ) : (
99
99
  <div className="w-full">
@@ -121,7 +121,7 @@ export function AboutDialog({ open, onOpenChange }: Readonly<AboutDialogProps>)
121
121
  )}
122
122
 
123
123
  <p className="text-muted-foreground text-center text-xs">
124
- {t('about.source', { fallback: 'Read live from public/app-manifest.json' })}
124
+ {t('about.source', { fallback: 'Read live from public/app-manifest.json.' })}
125
125
  </p>
126
126
  </div>
127
127
  </Dialog>
@@ -2,11 +2,11 @@ import type { SdkMenuRoot } from '@bardioc/app-sdk';
2
2
  import { useCommandMenu, type AppCommand } from '@bardioc/app-sdk/react';
3
3
  import {
4
4
  BardiocLogoIcon,
5
+ Page,
5
6
  SidebarBasic,
6
7
  SidebarContainedProvider,
7
8
  Toaster,
8
9
  } from '@bardioc/ui/components';
9
- import { ScrollArea } from '@bardioc/ui/shadcn';
10
10
  import {
11
11
  Activity,
12
12
  LayoutDashboard,
@@ -68,7 +68,7 @@ export function AppShell() {
68
68
  return;
69
69
  }
70
70
  notify(
71
- t('notify.localeChanged', { fallback: 'Language switched to {locale}', params: { locale } }),
71
+ t('notify.localeChanged', { fallback: 'Language switched to {locale}.', params: { locale } }),
72
72
  'info'
73
73
  );
74
74
  }, [locale, notify, t]);
@@ -86,7 +86,7 @@ export function AppShell() {
86
86
  () => [
87
87
  {
88
88
  id: 'app.about',
89
- title: t('menu.about', { fallback: 'About this app' }),
89
+ title: t('menu.about', { fallback: 'About This App' }),
90
90
  handler: () => setAboutOpen(true),
91
91
  },
92
92
  ...NAV.map(entry => ({
@@ -121,6 +121,9 @@ export function AppShell() {
121
121
 
122
122
  useCommandMenu(commands, menu);
123
123
 
124
+ const activeNav = NAV.find(entry => entry.id === view);
125
+ const pageTitle = activeNav ? t(activeNav.labelKey, { fallback: activeNav.id }) : undefined;
126
+
124
127
  const header = (
125
128
  <div className="flex items-center gap-2 px-2 py-1">
126
129
  <BardiocLogoIcon className="size-5" />
@@ -132,7 +135,7 @@ export function AppShell() {
132
135
 
133
136
  return (
134
137
  <SidebarContainedProvider defaultCollapsed={false}>
135
- <div className="bg-background text-foreground flex h-full min-h-0">
138
+ <div className="bg-background text-foreground @container/app flex h-full min-h-0">
136
139
  <SidebarBasic
137
140
  header={header}
138
141
  rail={false}
@@ -144,14 +147,15 @@ export function AppShell() {
144
147
  }}
145
148
  className="border-r"
146
149
  />
147
- <main className="flex min-w-0 flex-1 flex-col">
148
- <Header />
149
- <ScrollArea className="min-h-0 flex-1">
150
- <div className="p-4">
151
- <ViewContent view={view} />
152
- </div>
153
- </ScrollArea>
154
- </main>
150
+ <Page
151
+ title={pageTitle}
152
+ actions={<Header />}
153
+ actionsClassName="gap-2"
154
+ sticky
155
+ className="min-w-0"
156
+ >
157
+ <ViewContent view={view} />
158
+ </Page>
155
159
  </div>
156
160
  <AboutDialog open={aboutOpen} onOpenChange={setAboutOpen} />
157
161
  <Toaster position="bottom-right" />