@kal-elsam/kairo-runtime 0.13.0 → 0.14.0

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.
@@ -17,23 +17,33 @@ export const COCKPIT_REGIONS = {
17
17
 
18
18
  export const COCKPIT_NAV = [
19
19
  {
20
- id: "overview",
21
- label: "Overview",
20
+ id: "home",
21
+ label: "Home",
22
22
  view: ORCHESTRATOR_VIEWS.HOME,
23
- description: "Status, next action, activity, alerts, and tokens."
23
+ description: "What needs you, and what to do about it."
24
24
  },
25
+ {
26
+ id: "settings",
27
+ label: "Settings",
28
+ view: ORCHESTRATOR_VIEWS.PROFILE,
29
+ description: "Choose agents, connect Obsidian, add integrations."
30
+ },
31
+ {
32
+ id: "history",
33
+ label: "History",
34
+ view: ORCHESTRATOR_VIEWS.ACTIVITY,
35
+ description: "What Kairo changed, and how to undo it."
36
+ }
37
+ ];
38
+
39
+ /** Destinations reachable via the action palette, not the top nav. */
40
+ export const COCKPIT_SECONDARY = [
25
41
  {
26
42
  id: "governance",
27
43
  label: "Governance",
28
44
  view: ORCHESTRATOR_VIEWS.CHANGES,
29
45
  description: "Repair drift and apply governed changes."
30
46
  },
31
- {
32
- id: "activity",
33
- label: "Activity",
34
- view: ORCHESTRATOR_VIEWS.ACTIVITY,
35
- description: "Operations, backups, and recovery."
36
- },
37
47
  {
38
48
  id: "orchestration",
39
49
  label: "Orchestration",
@@ -45,12 +55,6 @@ export const COCKPIT_NAV = [
45
55
  label: "Usage",
46
56
  view: ORCHESTRATOR_VIEWS.USAGE,
47
57
  description: "Token and context pressure when auditable."
48
- },
49
- {
50
- id: "settings",
51
- label: "Settings",
52
- view: ORCHESTRATOR_VIEWS.PROFILE,
53
- description: "Profile, policy, and guided configuration."
54
58
  }
55
59
  ];
56
60
 
@@ -64,8 +68,8 @@ export function regionsForLayout(layoutMode) {
64
68
 
65
69
  export function navIndexForView(view, items = COCKPIT_NAV) {
66
70
  if (isRunsBranchView(view)) {
67
- const orchIndex = items.findIndex((item) => item.id === "orchestration");
68
- return orchIndex >= 0 ? orchIndex : 0;
71
+ const historyIndex = items.findIndex((item) => item.id === "history");
72
+ return historyIndex >= 0 ? historyIndex : 0;
69
73
  }
70
74
  const index = items.findIndex((item) => item.view === view);
71
75
  return index >= 0 ? index : 0;
@@ -105,6 +109,7 @@ export function resolveNavStatusSummary(item, {
105
109
  const backups = snapshot?.backups?.count ?? 0;
106
110
 
107
111
  switch (item.id) {
112
+ case "home":
108
113
  case "overview":
109
114
  return snapshot?.health?.replaceAll("_", " ")
110
115
  ?? resolveProjectReadiness({
@@ -114,6 +119,7 @@ export function resolveNavStatusSummary(item, {
114
119
  }).label;
115
120
  case "governance":
116
121
  return changes > 0 ? `${changes} pending` : "Clean";
122
+ case "history":
117
123
  case "activity":
118
124
  return backups > 0 ? `${backups} backups` : "No backups";
119
125
  case "orchestration":
@@ -174,6 +180,7 @@ export function buildNavModel({
174
180
  const mapped = items.map((item, index) => {
175
181
  const isSelected = index === navIndex;
176
182
  const isCurrent = item.view === currentView
183
+ || (item.id === "history" && isRunsBranchView(currentView))
177
184
  || (item.id === "orchestration" && isRunsBranchView(currentView));
178
185
  return {
179
186
  ...item,
@@ -185,11 +192,13 @@ export function buildNavModel({
185
192
  };
186
193
  });
187
194
 
195
+ const explanation = !selected || selected.id === "home" || selected.id === "overview"
196
+ ? ""
197
+ : (selected.description ?? "");
198
+
188
199
  return {
189
200
  title: "NAVIGATION",
190
- explanation: selected
191
- ? `${selected.description} (${resolveNavStatusSummary(selected, { dashboard, diagnostics, snapshot })})`
192
- : "",
201
+ explanation,
193
202
  items: mapped
194
203
  };
195
204
  }
@@ -320,7 +329,7 @@ export function buildFooterModel({
320
329
  // HOME footer must stay on one line at 80 cols (frame already near 24 rows).
321
330
  if (view === ORCHESTRATOR_VIEWS.HOME) {
322
331
  return {
323
- text: ["↑↓", "Enter", "Space", "R", "?", "/", "Esc"].join(` ${glyphs.bullet} `),
332
+ text: ["↑↓", "Enter", "Tab", "Space", "R", "?", "/", "Esc"].join(` ${glyphs.bullet} `),
324
333
  columns: footerColumns
325
334
  };
326
335
  }
@@ -1,6 +1,8 @@
1
- import { COCKPIT_NAV } from "./cockpit-models.js";
1
+ import { COCKPIT_NAV, COCKPIT_SECONDARY } from "./cockpit-models.js";
2
2
  import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
3
3
 
4
+ const PALETTE_NAV_ITEMS = [...COCKPIT_NAV, ...COCKPIT_SECONDARY];
5
+
4
6
  export const PALETTE_KINDS = Object.freeze({
5
7
  NAVIGATE: "navigate",
6
8
  REFRESH: "refresh",
@@ -12,10 +14,13 @@ const DESTINATION_VIEWS = Object.freeze({
12
14
  changes: ORCHESTRATOR_VIEWS.CHANGES,
13
15
  governance: ORCHESTRATOR_VIEWS.CHANGES,
14
16
  "control-center": ORCHESTRATOR_VIEWS.HOME,
17
+ home: ORCHESTRATOR_VIEWS.HOME,
15
18
  ides: ORCHESTRATOR_VIEWS.IDES,
16
19
  modules: ORCHESTRATOR_VIEWS.MODULES,
17
20
  activity: ORCHESTRATOR_VIEWS.ACTIVITY,
21
+ history: ORCHESTRATOR_VIEWS.ACTIVITY,
18
22
  profile: ORCHESTRATOR_VIEWS.PROFILE,
23
+ settings: ORCHESTRATOR_VIEWS.PROFILE,
19
24
  runs: ORCHESTRATOR_VIEWS.RUNS,
20
25
  orchestration: ORCHESTRATOR_VIEWS.RUNS,
21
26
  usage: ORCHESTRATOR_VIEWS.USAGE,
@@ -30,12 +35,12 @@ export function canOpenPalette({ loading = false, busy = false, confirming = fal
30
35
  return !loading && !busy && !confirming;
31
36
  }
32
37
 
33
- /** Optional CTA + six destinations + Refresh + Help. No write shortcuts. */
38
+ /** Optional CTA + primary/secondary destinations + Refresh + Help. No write shortcuts. */
34
39
  export function buildPaletteActions({
35
40
  ctaDestination = null,
36
41
  ctaTitle = null,
37
42
  ctaDetail = null,
38
- navItems = COCKPIT_NAV
43
+ navItems = PALETTE_NAV_ITEMS
39
44
  } = {}) {
40
45
  const actions = [];
41
46
  if (ctaDestination === "setup") {
@@ -100,7 +100,8 @@ export function renderCockpitView({
100
100
  governanceDetailsOpen = false,
101
101
  activityDetailsOpen = false,
102
102
  contentFocused = false,
103
- homeDir = null
103
+ homeDir = null,
104
+ hasGlobalState = false
104
105
  }) {
105
106
  if (palette) {
106
107
  return React.createElement(PalettePanel, { model: palette, colorEnabled });
@@ -112,7 +113,13 @@ export function renderCockpitView({
112
113
  detailsOpen: overviewDetailsOpen,
113
114
  colorEnabled,
114
115
  unicode,
115
- layoutMode
116
+ layoutMode,
117
+ selectedIndex: listIndex,
118
+ contentFocused,
119
+ hasGlobalState,
120
+ snapshot,
121
+ diagnostics,
122
+ dashboard
116
123
  });
117
124
  case ORCHESTRATOR_VIEWS.USAGE:
118
125
  return React.createElement(SemanticUsagePanel, {
@@ -46,6 +46,7 @@ import { LAYOUT_MODES } from "./layout.js";
46
46
  import { CHANGES_PHASE } from "./cockpit-changes.js";
47
47
  import { RECOVERY_PHASE, listRecoverySnapshots } from "./cockpit-recovery.js";
48
48
  import { SETTINGS_PHASE } from "./cockpit-settings.js";
49
+ import { buildOverviewButtons, OVERVIEW_BUTTON_COUNT } from "./ux/overview-actions.js";
49
50
 
50
51
  export function OrchestratorApp({
51
52
  homeDir,
@@ -245,7 +246,9 @@ export function OrchestratorApp({
245
246
  return;
246
247
  }
247
248
 
248
- const listLength = ui.view === ORCHESTRATOR_VIEWS.RUNS
249
+ const listLength = ui.view === ORCHESTRATOR_VIEWS.HOME
250
+ ? OVERVIEW_BUTTON_COUNT
251
+ : ui.view === ORCHESTRATOR_VIEWS.RUNS
249
252
  ? RUNS_HUB_ITEMS.length
250
253
  : ui.view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
251
254
  ? (data.dashboard?.activeRuns ?? []).length
@@ -301,6 +304,21 @@ export function OrchestratorApp({
301
304
  return;
302
305
  }
303
306
  }
307
+ if (routed.type === "enter-home-button") {
308
+ const buttons = buildOverviewButtons({
309
+ hasGlobalState,
310
+ snapshot: data.snapshot,
311
+ diagnostics: data.diagnostics,
312
+ dashboard: data.dashboard
313
+ });
314
+ const selected = buttons[Math.min(Math.max(0, ui.listIndex), buttons.length - 1)];
315
+ const intent = selected?.intent ?? null;
316
+ if (intent === "setup") {
317
+ finish({ cancelled: false, action: "setup" });
318
+ return;
319
+ }
320
+ if (intent && openDestination(intent)) return;
321
+ }
304
322
  dispatch(routed);
305
323
  return;
306
324
  }
@@ -583,7 +601,8 @@ export function OrchestratorApp({
583
601
  governanceDetailsOpen: ui.governanceDetailsOpen,
584
602
  activityDetailsOpen: ui.activityDetailsOpen,
585
603
  contentFocused: ui.region === COCKPIT_REGIONS.CONTENT,
586
- homeDir
604
+ homeDir,
605
+ hasGlobalState
587
606
  })
588
607
  )
589
608
  );
@@ -1,99 +1,121 @@
1
1
  /**
2
2
  * Live semantic Overview for Cockpit HOME — product cover.
3
- * Nav owns the sole focus mark this panel never renders `>`.
3
+ * Content owns the button focus mark when region=content.
4
4
  * ASCII wordmark only here (wide/compact); minimal is textual.
5
+ *
6
+ * Rule: show purpose + two buttons + a few plain-language needs.
7
+ * Machine/system noise stays out of the first screen (Details only).
5
8
  */
6
9
  import React from "react";
7
10
  import { Box, Text } from "ink";
8
- import { CONTROL_PLANE_HEALTH } from "../../control-plane-snapshot.js";
11
+ import { DASHBOARD_PURPOSE } from "../../dashboard-guidance.js";
9
12
  import { LAYOUT_MODES } from "../layout.js";
10
- import { COCKPIT_COLORS } from "../theme.js";
13
+ import { COCKPIT_COLORS, resolveGlyphs } from "../theme.js";
11
14
  import {
12
15
  overviewBrandTitle,
13
16
  shouldShowWordmark,
14
17
  wordmarkLines
15
18
  } from "../brand/wordmark.js";
16
19
  import { ActionList, Callout, Details } from "./semantic.js";
20
+ import {
21
+ humanizeDestination,
22
+ humanizeHealthTitle,
23
+ humanizePrimary,
24
+ mapHealthTone,
25
+ partitionCompanionLines
26
+ } from "./overview-needs.js";
27
+ import { buildOverviewButtons, OVERVIEW_BUTTON_COUNT } from "./overview-actions.js";
17
28
 
18
- const DESTINATION_LABELS = {
19
- setup: "Setup",
20
- changes: "Governance",
21
- ides: "Agents",
22
- runs: "Orchestration",
23
- "control-center": "Overview",
24
- activity: "Activity",
25
- usage: "Usage",
26
- profile: "Settings"
27
- };
28
-
29
- export function mapHealthTone(kind) {
30
- switch (kind) {
31
- case CONTROL_PLANE_HEALTH.CHECK_FAILED:
32
- return "danger";
33
- case CONTROL_PLANE_HEALTH.ACTION_REQUIRED:
34
- case CONTROL_PLANE_HEALTH.NOT_CONFIGURED:
35
- case CONTROL_PLANE_HEALTH.HEALTHY_WITH_NOTES:
36
- return "warn";
37
- case CONTROL_PLANE_HEALTH.HEALTHY:
38
- return "ready";
39
- default:
40
- return "warn";
41
- }
42
- }
29
+ export {
30
+ humanizeCompanionNeed,
31
+ humanizeHealthTitle,
32
+ humanizePrimary,
33
+ mapHealthTone,
34
+ partitionCompanionLines
35
+ } from "./overview-needs.js";
43
36
 
44
- function humanizeDestination(destination) {
45
- if (!destination) return null;
46
- return DESTINATION_LABELS[destination] ?? null;
47
- }
37
+ export { buildOverviewButtons, OVERVIEW_BUTTON_COUNT } from "./overview-actions.js";
48
38
 
49
- /** Safe Details lines only — never invent paths/IDs; honest empty when none. */
50
- export function buildOverviewDetails(model = {}) {
39
+ /** Safe Details lines — leftovers + raw signals; never invent paths/IDs. */
40
+ export function buildOverviewDetails(model = {}, companionRest = []) {
51
41
  const lines = [];
52
42
  const next = model.nextAction ?? model.cta ?? {};
53
43
  const dest = humanizeDestination(next.destination);
54
- if (dest) lines.push(`Next destination · ${dest}`);
55
- if (typeof model.alerts?.count === "number") {
44
+ if (dest) lines.push(`Opens · ${dest}`);
45
+ if (typeof model.alerts?.count === "number" && model.alerts.count > 0) {
56
46
  lines.push(`Open alerts · ${model.alerts.count}`);
57
47
  }
48
+ const tokens = model.tokens?.headline;
49
+ if (typeof tokens === "string" && tokens && !/unavailable/i.test(tokens)) {
50
+ lines.push(`Tokens · ${tokens}`);
51
+ }
58
52
  const secondary = model.companionNextAction;
59
53
  if (secondary?.title && secondary.kind !== "idle") {
60
54
  lines.push(`Companion · ${secondary.title}`);
61
55
  }
62
- if (lines.length === 0) return ["No extra evidence beyond the metrics above."];
56
+ for (const line of companionRest) {
57
+ if (typeof line === "string" && line.trim()) lines.push(line);
58
+ }
59
+ if (lines.length === 0) return ["Nothing else to show right now."];
63
60
  return lines;
64
61
  }
65
62
 
66
63
  /**
67
64
  * Pure adapter: buildControlCenterModel → semantic overview props.
68
- * Callout / CTA / metrics never include paths or IDs.
69
- * Primary action is always governance CTA — companion is secondary metrics/details only.
65
+ * First screen = purpose + two buttons + plain needs. Machine noise → Details.
70
66
  */
71
- export function adaptControlCenterToOverview(model = {}) {
67
+ export function adaptControlCenterToOverview(model = {}, options = {}) {
72
68
  const status = model.status ?? model.health ?? {};
73
69
  const next = model.nextAction ?? model.cta ?? {};
74
- const companionMetrics = (model.companion?.lines ?? []).map((label, i) => ({
75
- id: `companion-${i}`,
76
- label
77
- }));
70
+ const { needs, rest } = partitionCompanionLines(model.companion?.lines ?? []);
71
+ const primary = humanizePrimary(next);
72
+ const buttons = buildOverviewButtons({
73
+ hasGlobalState: options.hasGlobalState,
74
+ snapshot: options.snapshot,
75
+ diagnostics: options.diagnostics,
76
+ dashboard: options.dashboard
77
+ });
78
+
79
+ const activity = model.activity?.headline;
80
+ const metrics = [];
81
+ const hasUsefulActivity = typeof activity === "string"
82
+ && activity
83
+ && activity !== "Idle"
84
+ && activity !== "No activity yet";
85
+ if (hasUsefulActivity) {
86
+ metrics.push({ id: "activity", label: `Last activity · ${activity.replace(/^Last ·\s*/, "")}` });
87
+ }
88
+ for (let i = 0; i < needs.length; i += 1) {
89
+ metrics.push({ id: `need-${i}`, label: needs[i] });
90
+ }
91
+ if ((model.alerts?.count ?? 0) > 0) {
92
+ metrics.push({
93
+ id: "alerts",
94
+ label: `Alerts · ${model.alerts.headline ?? `${model.alerts.count} open`}`
95
+ });
96
+ }
97
+ if (rest.length > 0) {
98
+ metrics.push({
99
+ id: "more",
100
+ label: `${rest.length} more in Details · Space`
101
+ });
102
+ }
103
+ if (metrics.length === 0) {
104
+ metrics.push({ id: "quiet", label: "Nothing else needs you right now" });
105
+ }
106
+
78
107
  return {
79
108
  title: model.title ?? "Overview",
109
+ purpose: DASHBOARD_PURPOSE,
80
110
  callout: {
81
111
  tone: mapHealthTone(status.kind),
82
- title: status.label ?? "Unknown",
112
+ title: humanizeHealthTitle(status.kind, status.label),
83
113
  body: status.summaryLine ?? ""
84
114
  },
85
- primary: {
86
- label: next.actionTitle ?? "Review control plane",
87
- detail: next.actionDetail || null,
88
- hint: next.enterHint ?? null
89
- },
90
- metrics: [
91
- { id: "activity", label: `Activity · ${model.activity?.headline ?? "Idle"}` },
92
- { id: "alerts", label: `Alerts · ${model.alerts?.headline ?? "Alert data unavailable"}` },
93
- { id: "tokens", label: `Tokens · ${model.tokens?.headline ?? "Data unavailable"}` },
94
- ...companionMetrics
95
- ],
96
- details: buildOverviewDetails(model)
115
+ primary,
116
+ buttons,
117
+ metrics,
118
+ details: buildOverviewDetails(model, rest)
97
119
  };
98
120
  }
99
121
 
@@ -124,14 +146,30 @@ export function SemanticOverviewPanel({
124
146
  detailsOpen = false,
125
147
  colorEnabled = true,
126
148
  unicode = true,
127
- layoutMode = LAYOUT_MODES.COMPACT
149
+ layoutMode = LAYOUT_MODES.COMPACT,
150
+ selectedIndex = 0,
151
+ contentFocused = false,
152
+ hasGlobalState = false,
153
+ snapshot = null,
154
+ diagnostics = null,
155
+ dashboard = null
128
156
  }) {
129
- const view = adaptControlCenterToOverview(model);
157
+ const view = adaptControlCenterToOverview(model, {
158
+ hasGlobalState,
159
+ snapshot,
160
+ diagnostics,
161
+ dashboard
162
+ });
130
163
  const showArt = shouldShowWordmark(layoutMode);
131
164
  const brandTitle = overviewBrandTitle(layoutMode);
132
165
  const isWide = layoutMode === LAYOUT_MODES.WIDE;
133
166
  const mark = renderWordmark({ layoutMode, colorEnabled, unicode });
134
167
  const status = renderCallout(view, colorEnabled);
168
+ const safeIndex = Math.min(
169
+ Math.max(0, selectedIndex),
170
+ Math.max(0, view.buttons.length - 1)
171
+ );
172
+ const glyphs = resolveGlyphs(unicode);
135
173
 
136
174
  const hero = showArt
137
175
  ? (isWide
@@ -150,19 +188,29 @@ export function SemanticOverviewPanel({
150
188
 
151
189
  return React.createElement(Box, { flexDirection: "column" },
152
190
  hero,
191
+ React.createElement(Text, {
192
+ color: colorEnabled ? COCKPIT_COLORS.muted : undefined
193
+ }, view.purpose),
153
194
  React.createElement(Box, { marginTop: 1, flexDirection: "column" },
154
- React.createElement(Text, {
155
- bold: true,
156
- color: colorEnabled ? COCKPIT_COLORS.interactive : undefined
157
- }, view.primary.label),
158
- view.primary.detail
159
- ? React.createElement(Text, null, view.primary.detail)
160
- : null,
161
- view.primary.hint
162
- ? React.createElement(Text, {
163
- color: colorEnabled ? COCKPIT_COLORS.muted : undefined
164
- }, view.primary.hint)
165
- : null
195
+ ...view.buttons.map((button, index) => {
196
+ const selected = index === safeIndex;
197
+ const focused = contentFocused && selected;
198
+ return React.createElement(Box, {
199
+ key: button.id,
200
+ flexDirection: "column",
201
+ marginBottom: index === view.buttons.length - 1 ? 0 : 1
202
+ },
203
+ React.createElement(Text, {
204
+ bold: true,
205
+ color: focused && colorEnabled ? COCKPIT_COLORS.interactive : undefined
206
+ }, `${focused ? glyphs.focus : " "} ${button.label}`),
207
+ button.detail
208
+ ? React.createElement(Text, {
209
+ color: colorEnabled ? COCKPIT_COLORS.muted : undefined
210
+ }, ` ${button.detail}`)
211
+ : null
212
+ );
213
+ })
166
214
  ),
167
215
  React.createElement(Box, { marginTop: 1, flexDirection: "column" },
168
216
  React.createElement(ActionList, {
@@ -175,7 +223,7 @@ export function SemanticOverviewPanel({
175
223
  ),
176
224
  React.createElement(Details, {
177
225
  open: detailsOpen,
178
- summary: "Details",
226
+ summary: "More info",
179
227
  lines: view.details,
180
228
  colorEnabled,
181
229
  focused: false,
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Two explicit Home buttons — prepare + configure.
3
+ * No writes here: intents only open existing consent-gated flows.
4
+ */
5
+
6
+ export const OVERVIEW_BUTTON_COUNT = 2;
7
+
8
+ function changeCount(snapshot) {
9
+ if (!snapshot?.diff?.hasChanges) return 0;
10
+ return snapshot.diff.changeCount
11
+ ?? snapshot.diff.changes?.length
12
+ ?? 0;
13
+ }
14
+
15
+ function detectedAgentCount({ snapshot = null, diagnostics = null, dashboard = null } = {}) {
16
+ if (Number.isFinite(snapshot?.coverage?.detectedAgents)) {
17
+ return snapshot.coverage.detectedAgents;
18
+ }
19
+ if (Number.isFinite(diagnostics?.diagnostics?.detected)) {
20
+ return diagnostics.diagnostics.detected;
21
+ }
22
+ return (dashboard?.providers ?? []).filter((entry) => entry?.available).length;
23
+ }
24
+
25
+ function buildPrepareButton({
26
+ hasGlobalState = false,
27
+ snapshot = null,
28
+ diagnostics = null,
29
+ dashboard = null
30
+ } = {}) {
31
+ const detected = detectedAgentCount({ snapshot, diagnostics, dashboard });
32
+ const needsSetup = hasGlobalState === false || detected === 0;
33
+ if (needsSetup) {
34
+ return {
35
+ id: "prepare",
36
+ label: "Set up Kairo",
37
+ detail: "Detect your agents and install what is missing.",
38
+ intent: "setup"
39
+ };
40
+ }
41
+
42
+ const pending = changeCount(snapshot);
43
+ if (pending > 0) {
44
+ return {
45
+ id: "prepare",
46
+ label: `Repair ${pending} change${pending === 1 ? "" : "s"}`,
47
+ detail: "You will see the exact plan before anything is written.",
48
+ intent: "governance"
49
+ };
50
+ }
51
+
52
+ return {
53
+ id: "prepare",
54
+ label: "Everything is ready",
55
+ detail: "Open History to see what changed, or Configure to adjust.",
56
+ intent: "history"
57
+ };
58
+ }
59
+
60
+ const CONFIGURE_BUTTON = Object.freeze({
61
+ id: "configure",
62
+ label: "Configure",
63
+ detail: "Agents, Obsidian vault, integrations.",
64
+ intent: "settings"
65
+ });
66
+
67
+ /**
68
+ * Exactly two buttons for Home. First button changes face by state.
69
+ */
70
+ export function buildOverviewButtons({
71
+ hasGlobalState = false,
72
+ snapshot = null,
73
+ diagnostics = null,
74
+ dashboard = null
75
+ } = {}) {
76
+ return [
77
+ buildPrepareButton({ hasGlobalState, snapshot, diagnostics, dashboard }),
78
+ { ...CONFIGURE_BUTTON }
79
+ ];
80
+ }