@xpufx/paseo-top 0.4.4 → 0.4.7

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 (130) hide show
  1. package/OVERVIEW.md +18 -0
  2. package/client/host-ui.tsx +184 -0
  3. package/client/multi-host-view.tsx +57 -43
  4. package/client/multi-host.ts +36 -13
  5. package/client/pill-labels.ts +94 -1
  6. package/client/pill.tsx +692 -616
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +1 -1
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +308 -276
  11. package/client/telemetry-copy.ts +8 -2
  12. package/client/telemetry.tsx +372 -321
  13. package/client/turn-panel.tsx +126 -104
  14. package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +9 -3
  15. package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
  16. package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
  17. package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
  18. package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
  19. package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
  20. package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
  21. package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
  22. package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
  23. package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
  24. package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
  25. package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
  26. package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
  27. package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
  28. package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +51 -18
  29. package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
  30. package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
  31. package/client/vendor/paseo-plugin-helper/{surface.tsx → lifecycle/surface.tsx} +6 -6
  32. package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
  33. package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
  34. package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
  35. package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +50 -58
  36. package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
  37. package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
  38. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
  39. package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
  40. package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
  41. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  42. package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
  43. package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
  44. package/client/vendor/permission-audit/client.tsx +706 -0
  45. package/client/vendor/permission-audit/index.tsx +1 -0
  46. package/index.client.tsx +58 -37
  47. package/index.server.ts +256 -234
  48. package/npm-shrinkwrap.json +44 -0
  49. package/package.json +8 -4
  50. package/paseo-plugin.json +2 -0
  51. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  52. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  53. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  54. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  55. package/screenshots/paseo-top-dashboard.jpg +0 -0
  56. package/screenshots/paseo-top-general-view.jpg +0 -0
  57. package/server/resources.ts +98 -0
  58. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  59. package/server/vendor/paseo-plugin-helper/index.ts +2 -0
  60. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  61. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  62. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  63. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  64. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  65. package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
  66. package/server/vendor/permission-audit/index.ts +1 -0
  67. package/server/vendor/permission-audit/server.ts +839 -0
  68. package/shared/helper-version.ts +1 -1
  69. package/shared/multi-host.ts +153 -159
  70. package/shared/resources.ts +145 -0
  71. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  72. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  73. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  74. package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
  75. package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
  76. package/shared/vendor/permission-audit/filter.ts +61 -0
  77. package/shared/vendor/permission-audit/index.ts +2 -0
  78. package/shared/vendor/permission-audit/shared.ts +119 -0
  79. package/shared/version.ts +1 -1
  80. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  81. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  82. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
  83. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
  84. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
  85. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  86. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  87. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  88. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  89. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  90. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  91. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
  92. package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
  93. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  94. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  95. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  96. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  97. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
  100. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  101. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  102. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  103. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  104. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  105. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
  107. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  108. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  109. package/client/vendor/paseo-plugin-helper/index.ts +0 -30
  110. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  111. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  112. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  113. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
  114. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  115. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  117. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  118. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  119. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  120. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  121. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  122. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  123. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  124. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  125. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  126. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  127. package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
  128. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  130. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -2,57 +2,54 @@ import React, { useState } from "react";
2
2
  import { Text, View } from "react-native";
3
3
  import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
4
4
  import {
5
- AboutSection,
6
- Button,
7
- Card,
8
- KeyValue,
9
- KeyValueGroup,
10
- MetricGauge,
11
- ModalBody,
12
- Row,
13
- Stack,
14
- Tabs,
15
- Toggle,
16
- CardHeader,
17
- usePluginSettings,
18
- usePluginTheme,
19
- } from "./vendor/paseo-plugin-helper/index";
5
+ HostAboutSection,
6
+ HostButton,
7
+ HostCard,
8
+ HostCardHeader,
9
+ HostGrid,
10
+ HostKeyValue,
11
+ HostLayoutProvider,
12
+ HostMetricGauge,
13
+ HostRow,
14
+ HostScroll,
15
+ HostStack,
16
+ HostTabs,
17
+ HostThemeProvider,
18
+ HostToggle,
19
+ } from "./host-ui";
20
+ import { usePluginSettings } from "./vendor/paseo-plugin-helper/core/index";
20
21
  import { formatBytes, formatUptime } from "../shared/vendor/paseo-plugin-helper/index";
21
- import { PermissionAuditView } from "permission-audit/client";
22
+ import { PermissionAuditView } from "./vendor/permission-audit";
22
23
  import {
23
24
  topSettingsContract,
24
25
  checkboxesFromTarget,
25
26
  targetFromCheckboxes,
27
+ isPillEnabled,
28
+ isTimelineEnabled,
26
29
  METRIC_DEFINITIONS,
30
+ CPU_THRESHOLDS,
31
+ MEM_THRESHOLDS,
32
+ SURFACE_TABS,
33
+ aboutExtraItems,
27
34
  type PillMode,
28
35
  type TopSettings,
29
36
  } from "../shared/resources";
30
37
  import { PLUGIN_VERSION } from "../shared/version";
31
38
  import { notifySettingsChanged } from "./settings-events";
32
- import { ChoiceChips } from "./settings-ui";
39
+ import { ChoiceChips, MultiChoiceChips } from "./settings-ui";
33
40
  import { useTopResourceQuery } from "./resources-query";
34
41
  import { FleetView } from "./multi-host-view";
35
42
 
36
43
  type SurfaceTab = "system" | "fleet" | "permissions" | "settings" | "about";
37
44
 
38
- const TABS = [
39
- { id: "system", label: "Activity", shortLabel: "Activity", icon: "Activity" },
40
- { id: "fleet", label: "Fleet", shortLabel: "Fleet", icon: "Server" },
41
- { id: "permissions", label: "Permissions", shortLabel: "Permissions", icon: "ShieldCheck" },
42
- { id: "settings", label: "Settings", shortLabel: "Settings", icon: "Sliders" },
43
- { id: "about", label: "About", shortLabel: "About", icon: "Info" },
44
- ];
45
-
46
45
  const TIMELINE_CADENCE_OPTIONS = Array.from({ length: 11 }, (_, n) => ({
47
46
  id: n,
48
47
  label: n === 0 ? "Never" : n === 1 ? "Every turn" : `${n}`,
49
48
  }));
50
49
 
51
- const CPU_THRESHOLDS = { warning: 60, danger: 85 };
52
- const MEM_THRESHOLDS = { warning: 70, danger: 85 };
53
-
54
- export function TopDashboardSurface(_props: PluginSurfaceProps) {
55
- const { colors } = usePluginTheme();
50
+ export function TopDashboardSurface(props: PluginSurfaceProps) {
51
+ const { theme, layout } = props;
52
+ const { colors } = theme;
56
53
  const [activeTab, setActiveTab] = useState<SurfaceTab>("system");
57
54
  // No background settings poll: mount/focus verification plus mutation
58
55
  // invalidation keep the dashboard in sync without daemon churn.
@@ -78,266 +75,297 @@ export function TopDashboardSurface(_props: PluginSurfaceProps) {
78
75
  };
79
76
 
80
77
  return (
81
- <View style={[styles.root, { backgroundColor: colors.surface0 }]}>
82
- <ModalBody
83
- headerMode="pinned"
84
- header={
85
- <Tabs
86
- tabs={TABS}
78
+ <HostThemeProvider theme={theme}>
79
+ <HostLayoutProvider layout={layout}>
80
+ <View style={[styles.root, { backgroundColor: colors.surface0 }]}>
81
+ {/*
82
+ * The sidebar host supplies no scroller for plugin surfaces, so the
83
+ * dashboard owns exactly one: HostScroll below the pinned tab
84
+ * strip (the header stays fixed because it sits outside the scroller).
85
+ */}
86
+ <HostTabs
87
+ tabs={[...SURFACE_TABS]}
87
88
  activeTab={activeTab}
88
89
  onTabChange={(id) => setActiveTab(id as SurfaceTab)}
89
90
  />
90
- }
91
- headerStyle={{
92
- backgroundColor: colors.surface0,
93
- paddingHorizontal: 12,
94
- paddingTop: 12,
95
- paddingBottom: 6,
96
- }}
97
- contentContainerStyle={{
98
- gap: 12,
99
- paddingHorizontal: 12,
100
- paddingBottom: 12,
101
- paddingTop: 6,
102
- }}
103
- refreshing={isLoading}
104
- >
105
- {activeTab === "system" && (
106
- <Stack gap={12}>
107
- <Text style={{ fontSize: 16, fontWeight: "700", color: colors.foreground }}>
108
- Host System Resources
109
- </Text>
91
+ <HostScroll
92
+ style={styles.scroll}
93
+ contentContainerStyle={{
94
+ gap: 12,
95
+ paddingHorizontal: 12,
96
+ paddingBottom: 12,
97
+ paddingTop: 6,
98
+ }}
99
+ >
100
+ {activeTab === "system" && (
101
+ <HostStack gap={12}>
102
+ <Text style={{ fontSize: 16, fontWeight: "700", color: colors.foreground }}>
103
+ Host System Resources
104
+ </Text>
110
105
 
111
- {pillHidden && (
112
- <Card variant="elevated">
113
- <Text style={{ fontSize: 12, fontWeight: "600", color: colors.foregroundMuted }}>
114
- Composer pill is hidden in trackbar. You can re-enable it from the Settings tab above.
115
- </Text>
116
- </Card>
117
- )}
106
+ {pillHidden && (
107
+ <HostCard variant="elevated">
108
+ <Text
109
+ style={{ fontSize: 12, fontWeight: "600", color: colors.foregroundMuted }}
110
+ >
111
+ Composer pill is hidden. You can re-enable it from the Settings
112
+ tab above.
113
+ </Text>
114
+ </HostCard>
115
+ )}
118
116
 
119
- <Card variant="elevated">
120
- <Row justify="space-around" align="center" style={styles.gaugeContainer}>
121
- <MetricGauge
122
- value={data?.cpuUsagePercent ?? 0}
123
- thresholds={CPU_THRESHOLDS}
124
- label="CPU Load"
125
- size={72}
126
- />
127
- <MetricGauge
128
- value={data?.memoryUsedPercent ?? 0}
129
- thresholds={MEM_THRESHOLDS}
130
- label="RAM Used"
131
- size={72}
132
- />
133
- </Row>
134
- </Card>
117
+ <HostCard variant="elevated">
118
+ <HostRow justify="between" align="center" style={styles.gaugeContainer}>
119
+ <HostMetricGauge
120
+ value={data?.cpuUsagePercent ?? 0}
121
+ thresholds={CPU_THRESHOLDS}
122
+ label="CPU Load"
123
+ size={72}
124
+ />
125
+ <HostMetricGauge
126
+ value={data?.memoryUsedPercent ?? 0}
127
+ thresholds={MEM_THRESHOLDS}
128
+ label="RAM Used"
129
+ size={72}
130
+ />
131
+ </HostRow>
132
+ </HostCard>
135
133
 
136
- <Card variant="elevated">
137
- <KeyValueGroup>
138
- <KeyValue
139
- label="CPU"
140
- value={data?.cpuUsagePercent !== undefined ? `${data.cpuUsagePercent}%` : "--"}
141
- />
142
- <KeyValue
143
- label="Memory"
144
- value={
145
- data?.memoryUsedBytes !== undefined
146
- ? `${formatBytes(data.memoryUsedBytes, { compact: true, decimals: 1 })} (${data.memoryUsedPercent ?? "--"}%)`
147
- : "--"
148
- }
149
- />
150
- <KeyValue
151
- label="Load"
152
- value={data?.loadAvg?.[0] !== undefined ? data.loadAvg[0].toFixed(2) : "--"}
153
- />
154
- <KeyValue
155
- label="Uptime"
156
- value={data?.uptimeSeconds ? formatUptime(data.uptimeSeconds) : "--"}
157
- />
158
- <KeyValue label="Branch" value={data?.branch ?? "--"} copyable />
159
- <KeyValue
160
- label="MCP"
161
- value={
162
- data?.mcp && typeof data.mcp.total === "number"
163
- ? `${data.mcp.healthy}/${data.mcp.total} healthy`
164
- : "MCP --"
165
- }
166
- />
167
- </KeyValueGroup>
168
- </Card>
169
- {isLoading && !data ? (
170
- <Text style={{ fontSize: 12, color: colors.foregroundMuted }}>
171
- Loading live snapshot...
172
- </Text>
173
- ) : null}
174
- </Stack>
175
- )}
134
+ <HostCard variant="elevated">
135
+ <HostGrid columns={2} gap={12}>
136
+ <HostKeyValue
137
+ label="CPU"
138
+ value={data?.cpuUsagePercent !== undefined ? `${data.cpuUsagePercent}%` : "--"}
139
+ />
140
+ <HostKeyValue
141
+ label="Memory"
142
+ value={
143
+ data?.memoryUsedBytes !== undefined
144
+ ? `${formatBytes(data.memoryUsedBytes, { compact: true, decimals: 1 })} (${data.memoryUsedPercent ?? "--"}%)`
145
+ : "--"
146
+ }
147
+ />
148
+ <HostKeyValue
149
+ label="Load"
150
+ value={data?.loadAvg?.[0] !== undefined ? data.loadAvg[0].toFixed(2) : "--"}
151
+ />
152
+ <HostKeyValue
153
+ label="Uptime"
154
+ value={data?.uptimeSeconds ? formatUptime(data.uptimeSeconds) : "--"}
155
+ />
156
+ <HostKeyValue label="Branch" value={data?.branch ?? "--"} copyable />
157
+ <HostKeyValue
158
+ label="MCP"
159
+ value={
160
+ data?.mcp && typeof data.mcp.total === "number"
161
+ ? `${data.mcp.healthy}/${data.mcp.total} healthy`
162
+ : "MCP --"
163
+ }
164
+ />
165
+ </HostGrid>
166
+ </HostCard>
167
+ {isLoading && !data ? (
168
+ <Text style={{ fontSize: 12, color: colors.foregroundMuted }}>
169
+ Loading live snapshot...
170
+ </Text>
171
+ ) : null}
172
+ </HostStack>
173
+ )}
176
174
 
177
- {activeTab === "fleet" && <FleetView />}
175
+ {activeTab === "fleet" && <FleetView />}
178
176
 
179
- {activeTab === "permissions" && <PermissionAuditView variant="compact" />}
177
+ {activeTab === "permissions" && <PermissionAuditView variant="compact" theme={theme} layout={layout} />}
180
178
 
181
- {activeTab === "settings" && (
182
- <Stack gap={12}>
183
- <Card variant="elevated">
184
- <CardHeader
185
- title="Pill Display Mode"
186
- icon="LayoutGrid"
187
- subtitle="How active items appear in the composer trackbar"
188
- />
189
- <ChoiceChips
190
- options={[
191
- { id: "cycle", label: "Cycle", description: "Rotate one at a time" },
192
- { id: "all", label: "All in One", description: "Combined into one pill" },
193
- { id: "multiple", label: "Multiple", description: "Dedicated pills" },
194
- ]}
195
- value={settings.pillMode ?? "cycle"}
196
- showActiveDescription
197
- onChange={(pillMode: PillMode) => applySettings({ pillMode })}
198
- />
199
- <View style={styles.settingsSpacer}>
200
- <Toggle
201
- label="Show Composer Pill"
202
- description="Hide the composer pill entirely; the dashboard stays available from the sidebar"
203
- value={settings.showComposerPill ?? true}
204
- onValueChange={(val) => applySettings({ showComposerPill: val })}
205
- />
206
- </View>
207
- </Card>
208
-
209
- <Card variant="elevated">
210
- <CardHeader
211
- title="Metric Surfaces"
212
- icon="Sliders"
213
- subtitle="Choose where each metric appears (pill vs timeline)"
214
- />
215
- <Stack gap={12}>
216
- {METRIC_DEFINITIONS.map((def) => {
217
- const boxes = checkboxesFromTarget(settings.metricSurfaces?.[def.id]);
218
- const setBox = (which: "pill" | "timeline", val: boolean) => {
219
- const nextBoxes = { ...boxes, [which]: val };
220
- if (def.pillOnly) nextBoxes.timeline = false;
221
- applySettings({
222
- metricSurfaces: {
223
- ...settings.metricSurfaces,
224
- [def.id]: targetFromCheckboxes(nextBoxes.pill, nextBoxes.timeline),
225
- } as TopSettings["metricSurfaces"],
226
- });
227
- };
228
- return (
229
- <View key={def.id}>
230
- <Text style={{ fontSize: 11, fontWeight: "700", color: colors.foreground }}>
231
- {def.title}
232
- </Text>
233
- <Row gap={24} align="center" style={styles.metricTargets}>
234
- <Row gap={6} align="center">
235
- <Text style={[styles.metricTargetLabel, { color: colors.foreground }]}>
236
- Pill
237
- </Text>
238
- <Toggle value={boxes.pill} onValueChange={(val) => setBox("pill", val)} />
239
- </Row>
240
- <Row gap={6} align="center">
241
- <Text style={[styles.metricTargetLabel, { color: colors.foreground }]}>
242
- Timeline
243
- </Text>
244
- <Toggle
245
- value={boxes.timeline && !def.pillOnly}
246
- disabled={!!def.pillOnly}
247
- onValueChange={(val) => setBox("timeline", val)}
248
- />
249
- </Row>
250
- </Row>
179
+ {activeTab === "settings" && (
180
+ <HostStack gap={12}>
181
+ <HostCard variant="elevated">
182
+ <HostCardHeader
183
+ title="Pill Display Mode"
184
+ icon="LayoutGrid"
185
+ subtitle="How active items appear in the composer"
186
+ />
187
+ <ChoiceChips
188
+ options={[
189
+ { id: "cycle", label: "Cycle", description: "Rotate one at a time" },
190
+ { id: "all", label: "All in One", description: "Combined into one pill" },
191
+ { id: "multiple", label: "Multiple", description: "Dedicated pills" },
192
+ ]}
193
+ value={settings.pillMode ?? "cycle"}
194
+ showActiveDescription
195
+ onChange={(pillMode: PillMode) => applySettings({ pillMode })}
196
+ />
197
+ <View style={styles.settingsSpacer}>
198
+ <HostToggle
199
+ label="Show Composer Pill"
200
+ description="Hide the composer pill entirely; the dashboard stays available from the sidebar"
201
+ value={settings.showComposerPill ?? true}
202
+ onValueChange={(val) => applySettings({ showComposerPill: val })}
203
+ />
251
204
  </View>
252
- );
253
- })}
254
- </Stack>
255
- </Card>
205
+ </HostCard>
256
206
 
257
- <Card variant="elevated">
258
- <CardHeader
259
- title="Rotation Speed"
260
- icon="Clock"
261
- value={
262
- <Text style={{ color: colors.accent, fontWeight: "600" }}>
263
- {`${settings.intervalSeconds}s`}
264
- </Text>
265
- }
266
- />
267
- <ChoiceChips
268
- options={[2, 3, 4, 6].map((sec) => ({ id: sec, label: `${sec}s` }))}
269
- value={settings.intervalSeconds}
270
- onChange={(intervalSeconds) => applySettings({ intervalSeconds })}
271
- />
272
- </Card>
207
+ <HostCard variant="elevated">
208
+ <HostCardHeader
209
+ title="Active Pill Items"
210
+ icon="Sliders"
211
+ value={
212
+ <Text style={{ fontWeight: "600", color: colors.accent }}>
213
+ {`${METRIC_DEFINITIONS.filter((d) => isPillEnabled(settings.metricSurfaces?.[d.id])).length} active`}
214
+ </Text>
215
+ }
216
+ subtitle="Choose which items appear in the composer pill"
217
+ />
218
+ <MultiChoiceChips
219
+ options={METRIC_DEFINITIONS.map((def) => ({
220
+ id: def.id,
221
+ label: def.title,
222
+ icon: def.icon,
223
+ }))}
224
+ selected={METRIC_DEFINITIONS.filter((d) => isPillEnabled(settings.metricSurfaces?.[d.id])).map((d) => d.id)}
225
+ onToggle={(metricId) => {
226
+ const boxes = checkboxesFromTarget(settings.metricSurfaces?.[metricId]);
227
+ applySettings({
228
+ metricSurfaces: {
229
+ ...settings.metricSurfaces,
230
+ [metricId]: targetFromCheckboxes(!boxes.pill, boxes.timeline),
231
+ } as TopSettings["metricSurfaces"],
232
+ });
233
+ }}
234
+ onSelectAll={() => {
235
+ const next = { ...settings.metricSurfaces };
236
+ for (const def of METRIC_DEFINITIONS) {
237
+ const boxes = checkboxesFromTarget(settings.metricSurfaces?.[def.id]);
238
+ next[def.id] = targetFromCheckboxes(true, boxes.timeline);
239
+ }
240
+ applySettings({ metricSurfaces: next as TopSettings["metricSurfaces"] });
241
+ }}
242
+ onClearAll={() => {
243
+ const next = { ...settings.metricSurfaces };
244
+ for (const def of METRIC_DEFINITIONS) {
245
+ const boxes = checkboxesFromTarget(settings.metricSurfaces?.[def.id]);
246
+ next[def.id] = targetFromCheckboxes(false, boxes.timeline);
247
+ }
248
+ applySettings({ metricSurfaces: next as TopSettings["metricSurfaces"] });
249
+ }}
250
+ />
251
+ </HostCard>
273
252
 
274
- <Card variant="elevated">
275
- <CardHeader
276
- title="Timeline Cadence"
277
- icon="Clock"
278
- value={
279
- <Text style={{ color: colors.accent, fontWeight: "600" }}>
280
- {(settings.timelineCadence ?? 1) === 0
281
- ? "Never"
282
- : (settings.timelineCadence ?? 1) === 1
283
- ? "Every turn"
284
- : `Every ${(settings.timelineCadence ?? 1)} turns`}
285
- </Text>
286
- }
287
- subtitle="How often a card is stamped into the timeline view (0 = never)"
288
- />
289
- <ChoiceChips
290
- options={TIMELINE_CADENCE_OPTIONS}
291
- value={settings.timelineCadence ?? 1}
292
- onChange={(timelineCadence) => applySettings({ timelineCadence })}
293
- />
294
- </Card>
253
+ <HostCard variant="elevated">
254
+ <HostCardHeader
255
+ title="Timeline Card Metrics"
256
+ icon="GitCommitHorizontal"
257
+ value={
258
+ <Text style={{ fontWeight: "600", color: colors.accent }}>
259
+ {`${METRIC_DEFINITIONS.filter((d) => !d.pillOnly && isTimelineEnabled(settings.metricSurfaces?.[d.id])).length} active`}
260
+ </Text>
261
+ }
262
+ subtitle="Choose which metrics appear on turn timeline cards"
263
+ />
264
+ <MultiChoiceChips
265
+ options={METRIC_DEFINITIONS.filter((d) => !d.pillOnly).map((def) => ({
266
+ id: def.id,
267
+ label: def.title,
268
+ icon: def.icon,
269
+ }))}
270
+ selected={METRIC_DEFINITIONS.filter((d) => !d.pillOnly && isTimelineEnabled(settings.metricSurfaces?.[d.id])).map((d) => d.id)}
271
+ onToggle={(metricId) => {
272
+ const boxes = checkboxesFromTarget(settings.metricSurfaces?.[metricId]);
273
+ applySettings({
274
+ metricSurfaces: {
275
+ ...settings.metricSurfaces,
276
+ [metricId]: targetFromCheckboxes(boxes.pill, !boxes.timeline),
277
+ } as TopSettings["metricSurfaces"],
278
+ });
279
+ }}
280
+ onSelectAll={() => {
281
+ const next = { ...settings.metricSurfaces };
282
+ for (const def of METRIC_DEFINITIONS) {
283
+ if (def.pillOnly) continue;
284
+ const boxes = checkboxesFromTarget(settings.metricSurfaces?.[def.id]);
285
+ next[def.id] = targetFromCheckboxes(boxes.pill, true);
286
+ }
287
+ applySettings({ metricSurfaces: next as TopSettings["metricSurfaces"] });
288
+ }}
289
+ onClearAll={() => {
290
+ const next = { ...settings.metricSurfaces };
291
+ for (const def of METRIC_DEFINITIONS) {
292
+ if (def.pillOnly) continue;
293
+ const boxes = checkboxesFromTarget(settings.metricSurfaces?.[def.id]);
294
+ next[def.id] = targetFromCheckboxes(boxes.pill, false);
295
+ }
296
+ applySettings({ metricSurfaces: next as TopSettings["metricSurfaces"] });
297
+ }}
298
+ />
299
+ </HostCard>
295
300
 
296
- <Button
297
- label="Reset to Defaults"
298
- variant="secondary"
299
- size="sm"
300
- onPress={() => {
301
- void resetSettings().then((defaults) => {
302
- notifySettingsChanged(defaults ?? topSettingsContract.defaultSettings);
303
- });
304
- }}
305
- />
306
- </Stack>
307
- )}
301
+ <HostCard variant="elevated">
302
+ <HostCardHeader
303
+ title="Rotation Speed"
304
+ icon="Clock"
305
+ value={
306
+ <Text style={{ color: colors.accent, fontWeight: "600" }}>
307
+ {`${settings.intervalSeconds}s`}
308
+ </Text>
309
+ }
310
+ />
311
+ <ChoiceChips
312
+ options={[2, 3, 4, 6].map((sec) => ({ id: sec, label: `${sec}s` }))}
313
+ value={settings.intervalSeconds}
314
+ onChange={(intervalSeconds) => applySettings({ intervalSeconds })}
315
+ />
316
+ </HostCard>
308
317
 
309
- {activeTab === "about" && (
310
- <AboutSection
311
- name="paseo-top"
312
- description="Live host system and workspace monitor for Paseo composer trackbar."
313
- version={data?.version ?? PLUGIN_VERSION}
314
- author="xpufx"
315
- repository="https://github.com/xpufx/paseo-top"
316
- issues="https://github.com/xpufx/paseo-top/issues"
317
- license="MIT"
318
- density="tiny"
319
- extraItems={[
320
- {
321
- label: "Host Platform",
322
- value: data?.platform ? `${data.platform} (${data.arch ?? "unknown"})` : "Linux",
323
- copyable: true,
324
- },
325
- { label: "Host Name", value: data?.hostname ?? "localhost", copyable: true },
326
- { label: "CPU Model", value: data?.cpuModel ?? "unknown", copyable: true },
327
- { label: "CPU Cores", value: `${data?.cpuCores ?? 0} cores` },
328
- {
329
- label: "Total Memory",
330
- value: data?.memoryTotalBytes ? formatBytes(data.memoryTotalBytes) : "unknown",
331
- },
332
- {
333
- label: "Host Uptime",
334
- value: data?.uptimeSeconds ? formatUptime(data.uptimeSeconds) : "unknown",
335
- },
336
- ]}
337
- />
338
- )}
339
- </ModalBody>
340
- </View>
318
+ <HostCard variant="elevated">
319
+ <HostCardHeader
320
+ title="Timeline Cadence"
321
+ icon="Clock"
322
+ value={
323
+ <Text style={{ color: colors.accent, fontWeight: "600" }}>
324
+ {(settings.timelineCadence ?? 1) === 0
325
+ ? "Never"
326
+ : (settings.timelineCadence ?? 1) === 1
327
+ ? "Every turn"
328
+ : `Every ${(settings.timelineCadence ?? 1)} turns`}
329
+ </Text>
330
+ }
331
+ subtitle="How often a card is stamped into the timeline view (0 = never)"
332
+ />
333
+ <ChoiceChips
334
+ options={TIMELINE_CADENCE_OPTIONS}
335
+ value={settings.timelineCadence ?? 1}
336
+ onChange={(timelineCadence) => applySettings({ timelineCadence })}
337
+ />
338
+ </HostCard>
339
+
340
+ <HostButton
341
+ label="Reset to Defaults"
342
+ variant="secondary"
343
+ size="sm"
344
+ onPress={() => {
345
+ void resetSettings().then((defaults) => {
346
+ notifySettingsChanged(defaults ?? topSettingsContract.defaultSettings);
347
+ });
348
+ }}
349
+ />
350
+ </HostStack>
351
+ )}
352
+
353
+ {activeTab === "about" && (
354
+ <HostAboutSection
355
+ name="paseo-top"
356
+ description="Live host and session monitor across composer pills, timeline turns, and an interactive dashboard."
357
+ version={data?.version ?? PLUGIN_VERSION}
358
+ author="xpufx"
359
+ repository="https://github.com/xpufx/paseo-top"
360
+ issues="https://github.com/xpufx/paseo-top/issues"
361
+ license="MIT"
362
+ extraItems={aboutExtraItems(data)}
363
+ />
364
+ )}
365
+ </HostScroll>
366
+ </View>
367
+ </HostLayoutProvider>
368
+ </HostThemeProvider>
341
369
  );
342
370
  }
343
371
 
@@ -347,6 +375,10 @@ const styles = {
347
375
  minHeight: 0,
348
376
  width: "100%",
349
377
  },
378
+ scroll: {
379
+ flex: 1,
380
+ minHeight: 0,
381
+ },
350
382
  gaugeContainer: {
351
383
  paddingVertical: 12,
352
384
  width: "100%",