@bardioc/create-bardioc-app 0.5.5 → 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 (79) hide show
  1. package/README.md +1 -3
  2. package/bin/create.mjs +2 -4
  3. package/package.json +1 -1
  4. package/templates/_base/README.md +17 -3
  5. package/templates/_base/scripts/stamp-manifest.mjs +1 -1
  6. package/templates/_opt-link/scripts/link-source.mjs +7 -5
  7. package/templates/angular/.env.example +7 -2
  8. package/templates/angular/_gitignore +1 -0
  9. package/templates/angular/package.json +2 -4
  10. package/templates/angular/scripts/dev-env.mjs +41 -0
  11. package/templates/angular/scripts/dev.mjs +27 -0
  12. package/templates/angular/scripts/sync-runtime-env.mjs +3 -48
  13. package/templates/angular/src/app/app.component.ts +6 -6
  14. package/templates/angular/src/styles.css +4 -0
  15. package/templates/angular/tsconfig.json +1 -0
  16. package/templates/nextjs/.env.example +7 -2
  17. package/templates/nextjs/_gitignore +1 -0
  18. package/templates/nextjs/app/layout.tsx +2 -0
  19. package/templates/nextjs/app/page.tsx +13 -7
  20. package/templates/nextjs/eslint.config.mjs +4 -0
  21. package/templates/nextjs/next.config.ts +2 -21
  22. package/templates/nextjs/package.json +8 -3
  23. package/templates/nextjs/prettier.config.mjs +1 -0
  24. package/templates/nextjs/scripts/dev-env.mjs +41 -0
  25. package/templates/nextjs/scripts/dev.mjs +27 -0
  26. package/templates/nextjs/tsconfig.json +3 -18
  27. package/templates/preact/.env.example +3 -0
  28. package/templates/preact/eslint.config.mjs +3 -0
  29. package/templates/preact/package.json +8 -3
  30. package/templates/preact/prettier.config.mjs +1 -0
  31. package/templates/preact/src/App.tsx +6 -6
  32. package/templates/preact/tsconfig.json +4 -10
  33. package/templates/preact/vite.config.ts +9 -1
  34. package/templates/solid/.env.example +3 -0
  35. package/templates/solid/eslint.config.mjs +3 -0
  36. package/templates/solid/package.json +8 -4
  37. package/templates/solid/prettier.config.mjs +1 -0
  38. package/templates/solid/src/App.tsx +7 -7
  39. package/templates/solid/tsconfig.json +4 -9
  40. package/templates/solid/vite.config.ts +10 -2
  41. package/templates/svelte/.env.example +3 -0
  42. package/templates/svelte/_gitignore +1 -0
  43. package/templates/svelte/eslint.config.mjs +3 -0
  44. package/templates/svelte/package.json +8 -3
  45. package/templates/svelte/prettier.config.mjs +1 -0
  46. package/templates/svelte/src/App.svelte +6 -6
  47. package/templates/svelte/tsconfig.json +4 -4
  48. package/templates/svelte/vite.config.ts +9 -1
  49. package/templates/vite/.env.example +3 -0
  50. package/templates/vite/CLAUDE.md +40 -8
  51. package/templates/vite/eslint.config.mjs +3 -0
  52. package/templates/vite/package.json +9 -3
  53. package/templates/vite/prettier.config.mjs +1 -0
  54. package/templates/vite/src/auth/onboarding.tsx +4 -4
  55. package/templates/vite/src/climate/climate-data.ts +13 -6
  56. package/templates/vite/src/climate/conditions-chart.tsx +2 -2
  57. package/templates/vite/src/climate/overview-view.tsx +1 -1
  58. package/templates/vite/src/climate/stations-view.tsx +3 -3
  59. package/templates/vite/src/climate/use-climate.ts +2 -2
  60. package/templates/vite/src/events/live-events-view.tsx +2 -2
  61. package/templates/vite/src/graph/explorer-view.tsx +4 -4
  62. package/templates/vite/src/graph/requests.ts +2 -1
  63. package/templates/vite/src/locales/de.json +4 -4
  64. package/templates/vite/src/locales/en.json +44 -44
  65. package/templates/vite/src/profile/profile-view.tsx +5 -5
  66. package/templates/vite/src/shell/about-dialog.tsx +3 -3
  67. package/templates/vite/src/shell/app-shell.tsx +16 -12
  68. package/templates/vite/src/shell/header.tsx +24 -24
  69. package/templates/vite/tsconfig.json +4 -10
  70. package/templates/vite/vite.config.ts +10 -2
  71. package/templates/vue/.env.example +3 -0
  72. package/templates/vue/_gitignore +1 -0
  73. package/templates/vue/eslint.config.mjs +3 -0
  74. package/templates/vue/package.json +8 -3
  75. package/templates/vue/prettier.config.mjs +1 -0
  76. package/templates/vue/src/App.vue +6 -6
  77. package/templates/vue/tsconfig.json +5 -9
  78. package/templates/vue/vite.config.ts +10 -2
  79. package/templates/angular/scripts/dev-auth-proxy.mjs +0 -91
@@ -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>
@@ -1,17 +1,20 @@
1
1
  import type { ClimateReading, ClimateState, Condition, ForecastDay, Station } from './types';
2
2
 
3
- const STATIONS: Array<Pick<Station, 'id' | 'name' | 'region'>> = [
3
+ type StationBase = Pick<Station, 'id' | 'name' | 'region'>;
4
+
5
+ // Non-empty tuples, so indexing them stays type-safe under noUncheckedIndexedAccess.
6
+ const STATIONS: [StationBase, ...StationBase[]] = [
4
7
  { id: 'lagoon', name: 'Lagoon Station', region: 'Equatorial Belt' },
5
8
  { id: 'verdant', name: 'Verdant Ridge', region: 'Highlands' },
6
9
  { id: 'aurora', name: 'Aurora Flats', region: 'Polar Rim' },
7
10
  { id: 'cinder', name: 'Cinder Basin', region: 'Volcanic Plains' },
8
11
  { id: 'mistral', name: 'Mistral Cove', region: 'Coastal Shelf' },
9
12
  ];
10
- const CONDITIONS: Condition[] = ['sunny', 'cloudy', 'rainy'];
11
- const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
13
+ const CONDITIONS: [Condition, ...Condition[]] = ['sunny', 'cloudy', 'rainy'];
14
+ const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const;
12
15
 
13
- function pick<T>(items: readonly T[]): T {
14
- return items[Math.floor(Math.random() * items.length)];
16
+ function pick<T>(items: readonly [T, ...T[]]): T {
17
+ return items[Math.floor(Math.random() * items.length)] ?? items[0];
15
18
  }
16
19
 
17
20
  function round(value: number, decimals: number): number {
@@ -63,7 +66,11 @@ export function generateState(
63
66
  activeStationId?: string,
64
67
  source: ClimateState['source'] = 'mock'
65
68
  ): ClimateState {
66
- const stations = STATIONS.map(buildStation);
69
+ const [firstBase, ...restBases] = STATIONS;
70
+ const stations: [Station, ...Station[]] = [
71
+ buildStation(firstBase),
72
+ ...restBases.map(buildStation),
73
+ ];
67
74
  const active = stations.find(station => station.id === activeStationId) ?? stations[0];
68
75
  return {
69
76
  stations,
@@ -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>
@@ -42,7 +42,8 @@ async function sampleNodes(bridge: HostBridge, limit: number): Promise<GraphNode
42
42
  // query by a type that actually exists, a node lookup, and a Gremlin neighbor traversal — the
43
43
  // lookup/traversal presets discover a real node from the wildcard query first, so nothing is
44
44
  // hard-coded to a specific instance.
45
- export const SDK_REQUESTS: SdkRequest[] = [
45
+ // Non-empty tuple, so the Explorer can index the first preset as its default.
46
+ export const SDK_REQUESTS: [SdkRequest, ...SdkRequest[]] = [
46
47
  {
47
48
  id: 'graph.query.all',
48
49
  category: 'graph',
@@ -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" />
@@ -4,35 +4,35 @@ import { LocationSelect } from '../climate/location-select';
4
4
  import { useDashboard } from '../dashboard-provider';
5
5
  import { SUPPORTED_LOCALES, useI18n, useTranslation } from '../i18n';
6
6
 
7
+ // Goes into Page's `actions` slot, so it renders bare controls: Page owns the header row, its
8
+ // padding and its border, and lines all three up with the body below.
7
9
  export function Header() {
8
10
  const { t } = useTranslation();
9
11
  const { state, syncing, refresh, setActiveStation } = useDashboard();
10
12
 
11
13
  return (
12
- <header className="border-border flex shrink-0 items-center justify-end gap-3 border-b px-4 py-2">
13
- <div className="flex shrink-0 items-center gap-2">
14
- {state?.stations && (
15
- <LocationSelect
16
- stations={state.stations}
17
- value={state.activeStationId}
18
- onSelect={setActiveStation}
19
- />
20
- )}
21
- <ConnectedLanguageSwitcher
22
- useI18n={useI18n}
23
- supportedLocales={[...SUPPORTED_LOCALES]}
24
- flags={{ en: 'EN', de: 'DE' }}
14
+ <>
15
+ {state?.stations && (
16
+ <LocationSelect
17
+ stations={state.stations}
18
+ value={state.activeStationId}
19
+ onSelect={setActiveStation}
25
20
  />
26
- <Button
27
- className="gap-2"
28
- size="sm"
29
- onClick={() => void refresh()}
30
- disabled={syncing || !state}
31
- >
32
- <RefreshCw className={`size-4 ${syncing ? 'animate-spin' : ''}`} />
33
- {t('header.sync', { fallback: 'Sync' })}
34
- </Button>
35
- </div>
36
- </header>
21
+ )}
22
+ <ConnectedLanguageSwitcher
23
+ useI18n={useI18n}
24
+ supportedLocales={[...SUPPORTED_LOCALES]}
25
+ flags={{ en: 'EN', de: 'DE' }}
26
+ />
27
+ <Button
28
+ className="gap-2"
29
+ size="sm"
30
+ onClick={() => void refresh()}
31
+ disabled={syncing || !state}
32
+ >
33
+ <RefreshCw className={`size-4 ${syncing ? 'animate-spin' : ''}`} />
34
+ {t('header.sync', { fallback: 'Sync' })}
35
+ </Button>
36
+ </>
37
37
  );
38
38
  }
@@ -1,17 +1,11 @@
1
1
  {
2
+ "extends": "@bardioc/configs/typescript/react-library.json",
2
3
  "compilerOptions": {
3
- "target": "ES2022",
4
+ "declaration": false,
5
+ "declarationMap": false,
4
6
  "module": "ESNext",
5
7
  "moduleResolution": "Bundler",
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "jsx": "react-jsx",
8
- "strict": true,
9
- "noEmit": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "resolveJsonModule": true,
14
- "isolatedModules": true
8
+ "noEmit": true
15
9
  },
16
10
  "include": ["src"],
17
11
  "exclude": ["node_modules", "dist"]