@xpufx/paseo-helper-demo 0.2.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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/client/pill.tsx +1249 -0
  4. package/client/suite-settings.tsx +77 -0
  5. package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
  6. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
  7. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
  8. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
  9. package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
  10. package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
  11. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
  12. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
  13. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
  14. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
  15. package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
  16. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
  17. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
  18. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
  19. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
  20. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
  21. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
  22. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
  23. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
  24. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
  25. package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
  26. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
  27. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
  28. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
  29. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
  30. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
  31. package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
  32. package/client/vendor/paseo-plugin-helper/core/index.ts +64 -0
  33. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
  34. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
  35. package/client/vendor/paseo-plugin-helper/host.ts +277 -0
  36. package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
  37. package/client/vendor/paseo-plugin-helper/index.ts +28 -0
  38. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
  39. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
  40. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
  41. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
  42. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
  43. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
  44. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
  45. package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
  46. package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
  47. package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
  48. package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
  49. package/client/vendor/paseo-plugin-helper/query.ts +66 -0
  50. package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
  51. package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
  52. package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
  53. package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
  54. package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
  55. package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
  56. package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
  57. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
  58. package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
  59. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
  60. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
  61. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
  62. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
  63. package/client/vendor/paseo-plugin-helper/ui/index.ts +25 -0
  64. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +105 -0
  65. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  66. package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
  67. package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
  68. package/package.json +35 -0
  69. package/paseo-plugin.json +6 -0
  70. package/server/demo.ts +195 -0
  71. package/server/suite-settings.ts +23 -0
  72. package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
  73. package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
  74. package/server/vendor/paseo-plugin-helper/index.ts +18 -0
  75. package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
  76. package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
  77. package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
  78. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
  79. package/server/vendor/paseo-plugin-helper/network.ts +91 -0
  80. package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
  81. package/server/vendor/paseo-plugin-helper/process.ts +186 -0
  82. package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
  83. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
  84. package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
  85. package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
  86. package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
  87. package/server/vendor/paseo-plugin-helper/system.ts +128 -0
  88. package/server/vendor/paseo-plugin-helper/task.ts +116 -0
  89. package/server/vendor/paseo-plugin-helper/version.ts +153 -0
  90. package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
  91. package/shared/demo.ts +155 -0
  92. package/shared/vendor/paseo-plugin-helper/README.md +11 -0
  93. package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
  94. package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
  95. package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
  96. package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
  97. package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
  98. package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
  99. package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
  100. package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
  101. package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
  102. package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
  103. package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
  104. package/shared/version.ts +2 -0
@@ -0,0 +1,1249 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Text, View } from "react-native";
3
+ import { useRpc } from "@getpaseo/plugin/client";
4
+ import { Icon, Modal, useToast, ScrollView, FlatList, TextInput as HostTextInput, copyText } from "@getpaseo/plugin/client/react-native";
5
+ import {
6
+ initClientHelpers,
7
+ type ComposerPillRegistrar,
8
+ } from "paseo-plugin-helper/client";
9
+
10
+ initClientHelpers({ Icon, Modal, useRpc, useToast, copyText, ScrollView, FlatList, TextInput: HostTextInput });
11
+ import {
12
+ registerComposerPill,
13
+ PluginThemeProvider,
14
+ ActionBar,
15
+ Card,
16
+ Badge,
17
+ StatusDot,
18
+ Button,
19
+ KeyValue,
20
+ KeyValueGroup,
21
+ MetricGauge,
22
+ ProgressBar,
23
+ DataTable,
24
+ Tabs,
25
+ SearchInput,
26
+ EmptyState,
27
+ CodeBlock,
28
+ Toggle,
29
+ FormRow,
30
+ TextInput,
31
+ AboutSection,
32
+ AttentionBeacon,
33
+ Collapsible,
34
+ SectionHeader,
35
+ triggerHaptic,
36
+ usePluginTheme,
37
+ useResponsive,
38
+ type RenderModalProps,
39
+ type RenderPillProps,
40
+ type VisualFlair,
41
+ type AttentionBeaconMode,
42
+ type AttentionBeaconTone,
43
+ } from "paseo-plugin-helper/client";
44
+ import {
45
+ useAutoRefreshQuery,
46
+ useRpcMutation,
47
+ usePluginSettings,
48
+ } from "paseo-plugin-helper/core";
49
+ import { HostModalSection } from "paseo-plugin-helper/ui";
50
+ import { formatBytes, formatUptime } from "paseo-plugin-helper/shared";
51
+ import {
52
+ getDemoDataRpc,
53
+ triggerDemoActionRpc,
54
+ demoAgentIdentityContract,
55
+ demoBeaconSetContract,
56
+ demoBeaconBlinkContract,
57
+ demoBeaconClearContract,
58
+ demoSettingsContract,
59
+ type DemoData,
60
+ } from "../shared/demo.js";
61
+ import { SharedSuiteCard } from "./suite-settings.js";
62
+ import { PLUGIN_VERSION } from "../shared/version.js";
63
+
64
+ const EMPTY_PARAMS = {};
65
+
66
+ function DemoPill({ isOpen }: RenderPillProps) {
67
+ const { colors, typography } = usePluginTheme();
68
+ const { isCompact } = useResponsive();
69
+ const { settings } = usePluginSettings(demoSettingsContract);
70
+ const { data, isLoading } = useAutoRefreshQuery(getDemoDataRpc, EMPTY_PARAMS, {
71
+ defaultRate: settings.pollingRate,
72
+ });
73
+
74
+ const cpu = data?.cpuUsagePercent ?? 0;
75
+ const isAlert = cpu > settings.highCpuThreshold;
76
+
77
+ // Raw View/Text composition: the host composer bar has no helper surface for
78
+ // pill content, so only the compositor row and inline text spans stay local.
79
+ return (
80
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 6, paddingHorizontal: 6 }}>
81
+ <StatusDot variant={isAlert ? "danger" : "success"} pulse={isAlert} />
82
+ {isCompact ? (
83
+ // Mobile / Compact track: ultra-compact layout to prevent truncation!
84
+ <Text
85
+ numberOfLines={1}
86
+ style={[
87
+ { ...typography.caption, color: colors.foreground, fontWeight: "600", flexShrink: 1 },
88
+ isOpen && { opacity: 0.85 },
89
+ ]}
90
+ >
91
+ {isLoading ? "..." : `${cpu}%`}
92
+ </Text>
93
+ ) : (
94
+ // Desktop wide track: full descriptive label
95
+ <Text
96
+ numberOfLines={1}
97
+ style={[{ ...typography.caption, flexShrink: 1 }, isOpen && { opacity: 0.85 }]}
98
+ >
99
+ <Text style={{ color: colors.accent, fontWeight: "600" }}>
100
+ {settings.accentPillLabel}
101
+ </Text>
102
+ {settings.showCpuUsage && (
103
+ <>
104
+ <Text style={{ color: colors.foregroundMuted }}>{" · "}</Text>
105
+ <Text style={{ color: colors.foreground }}>{isLoading ? "..." : `${cpu}% CPU`}</Text>
106
+ </>
107
+ )}
108
+ </Text>
109
+ )}
110
+ </View>
111
+ );
112
+ }
113
+
114
+ function DemoModal({ close, workspaceId }: RenderModalProps) {
115
+ const { colors, theme, layout, typography, touchTargetMin } = usePluginTheme();
116
+ const { isCompact } = useResponsive();
117
+ const toast = useToast();
118
+ const [activeTab, setActiveTab] = useState<string>("gauges");
119
+ const [navigationOpen, setNavigationOpen] = useState<boolean>(false);
120
+
121
+ const showcaseTabs = [
122
+ { id: "gauges", label: "Gauges & Hardware", shortLabel: "Gauges" },
123
+ { id: "flair", label: "Visual Flair Studio", shortLabel: "Flair" },
124
+ { id: "data", label: "Data Table", shortLabel: "Data" },
125
+ { id: "controls", label: "Interactive Controls", shortLabel: "Controls" },
126
+ { id: "attention", label: "Attention & Beacons", shortLabel: "Beacon" },
127
+ { id: "settings", label: "Plugin Settings", shortLabel: "Settings" },
128
+ { id: "network", label: "Network Diagnostics", shortLabel: "Net" },
129
+ { id: "logs", label: "System Logs", shortLabel: "Logs" },
130
+ { id: "about", label: "About Plugin", shortLabel: "About" },
131
+ ];
132
+ const [searchQuery, setSearchQuery] = useState<string>("");
133
+ const [liveStream, setLiveStream] = useState<boolean>(true);
134
+ const [actionFeedback, setActionFeedback] = useState<string | null>(null);
135
+ const [attentionOn, setAttentionOn] = useState<boolean>(true);
136
+ const [beaconMode, setBeaconMode] = useState<AttentionBeaconMode>("radar");
137
+ const [beaconTone, setBeaconTone] = useState<AttentionBeaconTone>("warning");
138
+ const [beaconPresses, setBeaconPresses] = useState<number>(0);
139
+
140
+ const {
141
+ settings,
142
+ updateSettings,
143
+ resetSettings,
144
+ isUpdating: isSettingsUpdating,
145
+ } = usePluginSettings(demoSettingsContract);
146
+
147
+ // An open in-flow dropdown must not survive a navigation-style switch with
148
+ // stale state; the menu unmounts but the flag would reopen it on return.
149
+ useEffect(() => {
150
+ setNavigationOpen(false);
151
+ }, [settings.navigationStyle]);
152
+
153
+ const {
154
+ data,
155
+ isLoading,
156
+ refetch,
157
+ rate,
158
+ setRate,
159
+ isPolling,
160
+ } = useAutoRefreshQuery(getDemoDataRpc, EMPTY_PARAMS, {
161
+ defaultRate: "2s",
162
+ });
163
+
164
+ const { mutate: runAction, isPending: isActionPending } = useRpcMutation(
165
+ triggerDemoActionRpc,
166
+ {
167
+ onSuccess: (res) => {
168
+ triggerHaptic("success");
169
+ setActionFeedback(res.message);
170
+ toast.show(res.message, { variant: "success" });
171
+ refetch();
172
+ },
173
+ onError: (err) => {
174
+ toast.error(err.message);
175
+ },
176
+ }
177
+ );
178
+
179
+ const [beaconFeedback, setBeaconFeedback] = useState<string | null>(null);
180
+ const [showEnvelope, setShowEnvelope] = useState<boolean>(false);
181
+
182
+ const { data: agentData, isLoading: isAgentLoading } = useAutoRefreshQuery(
183
+ demoAgentIdentityContract,
184
+ EMPTY_PARAMS,
185
+ { defaultRate: "5s" }
186
+ );
187
+ const agentIdentity = agentData?.identity ?? null;
188
+
189
+ const { mutate: setBeacon, isPending: isBeaconSetPending } = useRpcMutation(
190
+ demoBeaconSetContract,
191
+ {
192
+ onSuccess: (res) => {
193
+ triggerHaptic("success");
194
+ setBeaconFeedback(res.message);
195
+ toast.show(res.message, { variant: "success" });
196
+ },
197
+ onError: (err) => {
198
+ toast.error(err.message);
199
+ },
200
+ }
201
+ );
202
+
203
+ const { mutate: blinkBeacon, isPending: isBeaconBlinkPending } = useRpcMutation(
204
+ demoBeaconBlinkContract,
205
+ {
206
+ onSuccess: (res) => {
207
+ triggerHaptic("success");
208
+ setBeaconFeedback(res.message);
209
+ toast.show(res.message, { variant: "success" });
210
+ },
211
+ onError: (err) => {
212
+ toast.error(err.message);
213
+ },
214
+ }
215
+ );
216
+
217
+ const { mutate: clearBeacon, isPending: isBeaconClearPending } = useRpcMutation(
218
+ demoBeaconClearContract,
219
+ {
220
+ onSuccess: (res) => {
221
+ triggerHaptic("light");
222
+ setBeaconFeedback(res.message);
223
+ toast.show(res.message, { variant: "info" });
224
+ },
225
+ onError: (err) => {
226
+ toast.error(err.message);
227
+ },
228
+ }
229
+ );
230
+
231
+ const items = data?.items ?? [];
232
+ const filteredItems = items.filter(
233
+ (item) =>
234
+ item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
235
+ item.category.toLowerCase().includes(searchQuery.toLowerCase())
236
+ );
237
+
238
+ const activeFlair: Partial<VisualFlair> = {
239
+ radius: settings.flairRadius,
240
+ density: settings.flairDensity,
241
+ surfaceStyle: settings.flairSurface,
242
+ borderWidth: settings.flairBorderWidth,
243
+ headingTransform: settings.flairUppercase ? "uppercase" : "none",
244
+ accentColor: settings.flairAccentColor,
245
+ };
246
+
247
+ return (
248
+ <PluginThemeProvider
249
+ theme={{
250
+ ...theme,
251
+ colors: {
252
+ ...theme.colors,
253
+ accent: settings.flairAccentColor,
254
+ },
255
+ }}
256
+ layout={layout}
257
+ flair={activeFlair}
258
+ >
259
+ {/* Pill-embedded content (#219): the pill host already provides the one
260
+ <Modal.Content> (modal paths) or no modal at all (0.8 popover), so
261
+ this must stay a plain fluid section — a nested <Modal.Content>
262
+ violates the host contract and crashes the plugin. hostScroll: true
263
+ below leaves the scroller to the host; manual refresh lives in the
264
+ banner card. */}
265
+ <HostModalSection>
266
+ <View
267
+ style={{
268
+ width: "100%",
269
+ backgroundColor: colors.surface0,
270
+ paddingHorizontal: 12,
271
+ paddingTop: 12,
272
+ paddingBottom: 6,
273
+ }}
274
+ >
275
+ {settings.navigationStyle === "dropdown" ? (
276
+ // Dropdown navigation is composed from helper primitives: the
277
+ // helper has no Select/menu primitive, so Collapsible provides the
278
+ // disclosure header and Button rows make the options selectable.
279
+ <Collapsible
280
+ title={showcaseTabs.find((tab) => tab.id === activeTab)?.label ?? activeTab}
281
+ icon="Sliders"
282
+ isExpanded={navigationOpen}
283
+ onToggle={(expanded) => {
284
+ triggerHaptic("light");
285
+ setNavigationOpen(expanded);
286
+ }}
287
+ style={{ width: "100%", marginTop: 10 }}
288
+ >
289
+ <ActionBar direction="column" align="flex-start" style={{ marginTop: 0, gap: 4 }}>
290
+ {showcaseTabs.map((tab) => (
291
+ <Button
292
+ key={tab.id}
293
+ label={tab.label}
294
+ size="sm"
295
+ variant={tab.id === activeTab ? "primary" : "ghost"}
296
+ accessibilityLabel={`Show ${tab.label}`}
297
+ style={{ width: "100%" }}
298
+ onPress={() => {
299
+ triggerHaptic("light");
300
+ setActiveTab(tab.id);
301
+ setNavigationOpen(false);
302
+ }}
303
+ />
304
+ ))}
305
+ </ActionBar>
306
+ </Collapsible>
307
+ ) : (
308
+ <Tabs
309
+ tabs={showcaseTabs}
310
+ activeTab={activeTab}
311
+ onTabChange={(tab) => {
312
+ triggerHaptic("light");
313
+ setActiveTab(tab);
314
+ }}
315
+ mode="scroll"
316
+ />
317
+ )}
318
+ </View>
319
+ {/* Top Banner Card */}
320
+ <Card variant="elevated">
321
+ <Card.Header
322
+ title="Helper Showcase"
323
+ subtitle="v0.2 UI Design System & Daemon Primitives"
324
+ badge={<Badge variant="accent" label={`Polling ${rate}`} />}
325
+ />
326
+
327
+ {/* Refresh Interval Selector */}
328
+ <FormRow label="Auto Refresh" description={`Polling ${rate}`}>
329
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
330
+ {(["1s", "2s", "5s", "paused"] as const).map((r) => (
331
+ <Button
332
+ key={r}
333
+ label={r}
334
+ size="sm"
335
+ variant={rate === r ? "primary" : "ghost"}
336
+ onPress={() => {
337
+ triggerHaptic("light");
338
+ setRate(r);
339
+ }}
340
+ />
341
+ ))}
342
+ <Button
343
+ label={isLoading ? "Refreshing..." : "Refresh"}
344
+ size="sm"
345
+ variant="secondary"
346
+ onPress={async () => {
347
+ triggerHaptic("light");
348
+ setNavigationOpen(false);
349
+ await refetch();
350
+ }}
351
+ />
352
+ </ActionBar>
353
+ </FormRow>
354
+ </Card>
355
+
356
+ {/* TAB 1: GAUGES & HARDWARE */}
357
+ {activeTab === "gauges" && (
358
+ <>
359
+ <Card variant="elevated">
360
+ <Card.Header title="Metric Gauges" />
361
+ <ActionBar align="center" direction="row" style={{ marginTop: 0 }}>
362
+ <MetricGauge
363
+ value={data?.cpuUsagePercent ?? 0}
364
+ label="CPU Load"
365
+ size={74}
366
+ />
367
+ <MetricGauge
368
+ value={data?.memoryUsedPercent ?? 0}
369
+ label="RAM Used"
370
+ size={74}
371
+ />
372
+ <MetricGauge
373
+ value={38}
374
+ label="Disk I/O"
375
+ size={74}
376
+ />
377
+ </ActionBar>
378
+ </Card>
379
+
380
+ <Card variant="elevated">
381
+ <Card.Header title="Linear Progress" />
382
+ <ProgressBar
383
+ value={data?.cpuUsagePercent ?? 0}
384
+ height={8}
385
+ />
386
+ <ProgressBar
387
+ value={data?.memoryUsedPercent ?? 0}
388
+ height={8}
389
+ />
390
+ </Card>
391
+
392
+ <Card variant="elevated">
393
+ <Card.Header title="Host & Network" />
394
+ <KeyValueGroup columns={isCompact ? 1 : 2}>
395
+ <KeyValue
396
+ label="Hostname"
397
+ value={data?.hostname ?? "..."}
398
+ copyable
399
+ />
400
+ <KeyValue
401
+ label="Verified Daemon Port"
402
+ value={data?.daemonPort ? String(data.daemonPort) : "..."}
403
+ copyable
404
+ />
405
+ </KeyValueGroup>
406
+ <KeyValue
407
+ label="Processor"
408
+ value={data?.cpuModel ?? "..."}
409
+ subValue="Multi-line wrap verified"
410
+ copyable
411
+ />
412
+ <KeyValue
413
+ label="System Uptime"
414
+ value={data ? formatUptime(data.uptimeSeconds) : "..."}
415
+ />
416
+ </Card>
417
+ </>
418
+ )}
419
+
420
+ {/* TAB: VISUAL FLAIR STUDIO */}
421
+ {activeTab === "flair" && (
422
+ <>
423
+ <Card variant={settings.flairSurface}>
424
+ <Card.Header
425
+ title="Visual Flair Studio"
426
+ subtitle="Live theme & flair customizer backed by atomic settings"
427
+ />
428
+
429
+ {/* Corner Radius Selector */}
430
+ <FormRow
431
+ label="Corner Radius"
432
+ description={`Active preset: "${settings.flairRadius}"`}
433
+ >
434
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
435
+ {(["sharp", "rounded", "pill"] as const).map((r) => (
436
+ <Button
437
+ key={r}
438
+ size="sm"
439
+ label={r.toUpperCase()}
440
+ variant={settings.flairRadius === r ? "primary" : "ghost"}
441
+ onPress={() => {
442
+ triggerHaptic("light");
443
+ updateSettings({ flairRadius: r });
444
+ }}
445
+ />
446
+ ))}
447
+ </ActionBar>
448
+ </FormRow>
449
+
450
+ {/* Information Density Selector */}
451
+ <FormRow
452
+ label="Layout Density"
453
+ description={`Active density: "${settings.flairDensity}"`}
454
+ >
455
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
456
+ {(["compact", "comfortable", "spacious"] as const).map((d) => (
457
+ <Button
458
+ key={d}
459
+ size="sm"
460
+ label={d.charAt(0).toUpperCase() + d.slice(1)}
461
+ variant={settings.flairDensity === d ? "primary" : "ghost"}
462
+ onPress={() => {
463
+ triggerHaptic("light");
464
+ updateSettings({ flairDensity: d });
465
+ }}
466
+ />
467
+ ))}
468
+ </ActionBar>
469
+ </FormRow>
470
+
471
+ {/* Surface Styling Selector */}
472
+ <FormRow
473
+ label="Surface Treatment"
474
+ description={`Active surface: "${settings.flairSurface}"`}
475
+ >
476
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
477
+ {(["flat", "tinted", "elevated"] as const).map((s) => (
478
+ <Button
479
+ key={s}
480
+ size="sm"
481
+ label={s.charAt(0).toUpperCase() + s.slice(1)}
482
+ variant={settings.flairSurface === s ? "primary" : "ghost"}
483
+ onPress={() => {
484
+ triggerHaptic("light");
485
+ updateSettings({ flairSurface: s });
486
+ }}
487
+ />
488
+ ))}
489
+ </ActionBar>
490
+ </FormRow>
491
+
492
+ {/* Border Width Stepper / Selector */}
493
+ <FormRow
494
+ label="Border Width"
495
+ description={`Container outline stroke: ${settings.flairBorderWidth}px`}
496
+ >
497
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
498
+ {[0, 1, 2, 3].map((w) => (
499
+ <Button
500
+ key={w}
501
+ size="sm"
502
+ label={`${w}px`}
503
+ variant={settings.flairBorderWidth === w ? "primary" : "ghost"}
504
+ onPress={() => {
505
+ triggerHaptic("light");
506
+ updateSettings({ flairBorderWidth: w });
507
+ }}
508
+ />
509
+ ))}
510
+ </ActionBar>
511
+ </FormRow>
512
+
513
+ {/* Brand Accent Color Swatches */}
514
+ <FormRow
515
+ label="Brand Accent Color"
516
+ description={`Current accent: ${settings.flairAccentColor}`}
517
+ >
518
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
519
+ {[
520
+ { label: "Indigo", color: "#6366f1" },
521
+ { label: "Emerald", color: "#10b981" },
522
+ { label: "Violet", color: "#8b5cf6" },
523
+ { label: "Amber", color: "#f59e0b" },
524
+ { label: "Rose", color: "#f43f5e" },
525
+ { label: "Cyan", color: "#06b6d4" },
526
+ ].map((swatch) => {
527
+ const isSelected = settings.flairAccentColor.toLowerCase() === swatch.color.toLowerCase();
528
+ return (
529
+ <Button
530
+ key={swatch.color}
531
+ size="sm"
532
+ accessibilityLabel={`Select ${swatch.label} accent color`}
533
+ onPress={() => {
534
+ triggerHaptic("light");
535
+ updateSettings({ flairAccentColor: swatch.color });
536
+ }}
537
+ style={{
538
+ backgroundColor: swatch.color,
539
+ borderColor: isSelected ? colors.foreground : "transparent",
540
+ borderWidth: 2,
541
+ borderRadius: 9999,
542
+ width: touchTargetMin,
543
+ height: touchTargetMin,
544
+ minWidth: touchTargetMin,
545
+ minHeight: touchTargetMin,
546
+ paddingHorizontal: 0,
547
+ paddingVertical: 0,
548
+ }}
549
+ />
550
+ );
551
+ })}
552
+ </ActionBar>
553
+ </FormRow>
554
+
555
+ {/* Uppercase Header Switch */}
556
+ <FormRow
557
+ label="Uppercase Section Headings"
558
+ description="Transform component section titles to uppercase"
559
+ >
560
+ <Toggle
561
+ value={settings.flairUppercase}
562
+ onValueChange={(val) => {
563
+ triggerHaptic("light");
564
+ updateSettings({ flairUppercase: val });
565
+ }}
566
+ />
567
+ </FormRow>
568
+
569
+ {/* Live Component Preview Card */}
570
+ <Card variant={settings.flairSurface} style={{ marginTop: 8 }}>
571
+ <Card.Header
572
+ title="Live Component Preview"
573
+ subtitle="Reflects your active Visual Flair in real-time"
574
+ />
575
+ <KeyValueGroup columns={isCompact ? 1 : 2}>
576
+ <KeyValue label="Status" value="Production Ready" />
577
+ <KeyValue label="Radius Mode" value={settings.flairRadius} />
578
+ </KeyValueGroup>
579
+ <ActionBar align="flex-start">
580
+ <Button label="Primary Button" variant="primary" size="sm" />
581
+ <Button label="Secondary" variant="secondary" size="sm" />
582
+ <Badge label="Adaptive Badge" variant="accent" />
583
+ </ActionBar>
584
+ </Card>
585
+ </Card>
586
+ </>
587
+ )}
588
+
589
+ {/* TAB 2: DATA TABLE */}
590
+ {activeTab === "data" && (
591
+ <Card variant="elevated">
592
+ <Card.Header title={`Services Table (${isCompact ? "Compact 2-Col" : "Desktop 3-Col"})`} />
593
+ <SearchInput
594
+ value={searchQuery}
595
+ onChangeText={setSearchQuery}
596
+ placeholder="Search service name or category..."
597
+ />
598
+
599
+ <DataTable
600
+ data={filteredItems}
601
+ keyExtractor={(item) => item.id}
602
+ emptyState={
603
+ <EmptyState
604
+ icon="Search"
605
+ title="No Services Found"
606
+ description="Try clearing your search query."
607
+ />
608
+ }
609
+ columns={
610
+ isCompact
611
+ ? [
612
+ {
613
+ key: "name",
614
+ header: "Service",
615
+ flex: 2,
616
+ render: (item) => (
617
+ // Raw View/Text: DataTable column render slots have no
618
+ // helper cell-text primitive, so this composite stays local.
619
+ <View>
620
+ <Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
621
+ {item.name}
622
+ </Text>
623
+ <Text style={[{ color: colors.foregroundMuted, ...typography.caption }]}>
624
+ {item.category}
625
+ </Text>
626
+ </View>
627
+ ),
628
+ },
629
+ {
630
+ key: "load",
631
+ header: "Load",
632
+ align: "right",
633
+ render: (item) => (
634
+ <Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
635
+ {item.loadPercent}%
636
+ </Text>
637
+ ),
638
+ },
639
+ ]
640
+ : [
641
+ {
642
+ key: "name",
643
+ header: "Service",
644
+ flex: 2,
645
+ render: (item) => (
646
+ // Raw View/Text: DataTable column render slots have no
647
+ // helper cell-text primitive, so this composite stays local.
648
+ <View>
649
+ <Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
650
+ {item.name}
651
+ </Text>
652
+ <Text style={[{ color: colors.foregroundMuted, ...typography.caption }]}>
653
+ {item.category}
654
+ </Text>
655
+ </View>
656
+ ),
657
+ },
658
+ {
659
+ key: "status",
660
+ header: "Status",
661
+ align: "center",
662
+ render: (item) => (
663
+ <Badge
664
+ label={item.status}
665
+ variant={item.status === "running" ? "success" : "warning"}
666
+ />
667
+ ),
668
+ },
669
+ {
670
+ key: "load",
671
+ header: "Load",
672
+ align: "right",
673
+ render: (item) => (
674
+ <Text style={[{ color: colors.foreground, ...typography.bodyStrong }]}>
675
+ {item.loadPercent}%
676
+ </Text>
677
+ ),
678
+ },
679
+ ]
680
+ }
681
+ />
682
+ </Card>
683
+ )}
684
+
685
+ {/* TAB 3: CONTROLS & CODE */}
686
+ {activeTab === "controls" && (
687
+ <>
688
+ <Card variant="elevated">
689
+ <Card.Header title="Toggles & Actions" />
690
+ <Toggle
691
+ label="Live Log Streaming"
692
+ description="Stream background task ticks to console"
693
+ value={liveStream}
694
+ onValueChange={(val) => {
695
+ triggerHaptic("medium");
696
+ setLiveStream(val);
697
+ }}
698
+ />
699
+
700
+ <Button
701
+ label={isActionPending ? "Running RPC..." : "Trigger Background RPC Action"}
702
+ variant="primary"
703
+ onPress={() => runAction({ actionName: "Showcase Trigger" })}
704
+ />
705
+
706
+ {actionFeedback ? (
707
+ <Text style={{ color: colors.statusSuccess, ...typography.bodyStrong, marginTop: 6 }}>
708
+ {actionFeedback}
709
+ </Text>
710
+ ) : null}
711
+ </Card>
712
+
713
+ <Card variant="elevated">
714
+ <Card.Header title="Sample Code Block" />
715
+ <CodeBlock
716
+ language="typescript"
717
+ code={`import { createPluginPill, MetricGauge } from "paseo-plugin-helper/client";\n\n// Renders circular ring\n<MetricGauge value={75} label="CPU Load" />`}
718
+ copyable
719
+ />
720
+ </Card>
721
+ </>
722
+ )}
723
+
724
+ {/* TAB: ATTENTION & BEACONS */}
725
+ {activeTab === "attention" && (
726
+ <>
727
+ <Card variant="elevated">
728
+ <Card.Header
729
+ title="Workspace Beacon"
730
+ subtitle="Live workspace-row status ticker driven by createWorkspaceBeacon()"
731
+ />
732
+ {/* Raw Text: the helper has no standalone caption/paragraph
733
+ primitive, so explanatory copy stays local and uses typography. */}
734
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
735
+ Targets this workspace ({workspaceId}). Watch the workspace row chip and title while
736
+ triggering.
737
+ </Text>
738
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
739
+ <Button
740
+ label={isBeaconSetPending ? "Setting..." : "Set Status Beacon"}
741
+ variant="primary"
742
+ size="sm"
743
+ onPress={() => {
744
+ triggerHaptic("medium");
745
+ setBeacon({ workspaceId, name: "DEMO:ACTIVE", color: "sky" });
746
+ }}
747
+ />
748
+ <Button
749
+ label={isBeaconBlinkPending ? "Blinking..." : "Blink Beacon (5 rounds)"}
750
+ variant="secondary"
751
+ size="sm"
752
+ onPress={() => {
753
+ triggerHaptic("medium");
754
+ blinkBeacon({ workspaceId, rounds: 5 });
755
+ }}
756
+ />
757
+ <Button
758
+ label={isBeaconClearPending ? "Clearing..." : "Clear Beacon"}
759
+ variant="ghost"
760
+ size="sm"
761
+ onPress={() => {
762
+ triggerHaptic("light");
763
+ clearBeacon({ workspaceId, name: "DEMO:ACTIVE" });
764
+ }}
765
+ />
766
+ </ActionBar>
767
+ {beaconFeedback ? (
768
+ <Text style={{ color: colors.statusSuccess, ...typography.bodyStrong, marginTop: 6 }}>
769
+ {beaconFeedback}
770
+ </Text>
771
+ ) : null}
772
+ </Card>
773
+
774
+ <Card variant="elevated">
775
+ <Card.Header
776
+ title="Attention Playground"
777
+ subtitle="Live toggles for beacon animation, mode, and tone"
778
+ />
779
+ <FormRow
780
+ label="Attention Active"
781
+ description={attentionOn ? "Beacons animating" : "Static wrappers (reduced-motion safe)"}
782
+ >
783
+ <Toggle
784
+ value={attentionOn}
785
+ onValueChange={(val) => {
786
+ triggerHaptic("light");
787
+ setAttentionOn(val);
788
+ }}
789
+ />
790
+ </FormRow>
791
+
792
+ <FormRow
793
+ label="Beacon Mode"
794
+ description={`Active mode: "${beaconMode}"`}
795
+ >
796
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
797
+ {(["radar", "glow", "badge", "bounce"] as const).map((m) => (
798
+ <Button
799
+ key={m}
800
+ size="sm"
801
+ label={m}
802
+ variant={beaconMode === m ? "primary" : "ghost"}
803
+ onPress={() => {
804
+ triggerHaptic("light");
805
+ setBeaconMode(m);
806
+ }}
807
+ />
808
+ ))}
809
+ </ActionBar>
810
+ </FormRow>
811
+
812
+ <FormRow
813
+ label="Beacon Tone"
814
+ description={`Active tone token: "${beaconTone === "warning" ? "statusWarning" : beaconTone}"`}
815
+ >
816
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
817
+ {(
818
+ [
819
+ { token: "statusWarning", tone: "warning" },
820
+ { token: "accent", tone: "accent" },
821
+ { token: "statusDanger", tone: "danger" },
822
+ ] as const
823
+ ).map(({ token, tone }) => (
824
+ <Button
825
+ key={token}
826
+ size="sm"
827
+ label={token}
828
+ variant={beaconTone === tone ? "primary" : "ghost"}
829
+ onPress={() => {
830
+ triggerHaptic("light");
831
+ setBeaconTone(tone);
832
+ }}
833
+ />
834
+ ))}
835
+ </ActionBar>
836
+ </FormRow>
837
+ </Card>
838
+
839
+ <Card variant="elevated">
840
+ <Card.Header
841
+ title="Button attention prop"
842
+ subtitle="Direct attention across button variants"
843
+ />
844
+ <SectionHeader title="Primary" />
845
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
846
+ <Button label="Radar" variant="primary" size="sm" attention={attentionOn ? "radar" : undefined} />
847
+ <Button label="Glow" variant="primary" size="sm" attention={attentionOn ? "glow" : undefined} />
848
+ <Button label="Bounce" variant="primary" size="sm" attention={attentionOn ? "bounce" : undefined} />
849
+ </ActionBar>
850
+ <SectionHeader title="Danger" />
851
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
852
+ <Button label="Radar" variant="danger" size="sm" attention={attentionOn ? "radar" : undefined} />
853
+ <Button label="Glow" variant="danger" size="sm" attention={attentionOn ? "glow" : undefined} />
854
+ <Button label="Bounce" variant="danger" size="sm" attention={attentionOn ? "bounce" : undefined} />
855
+ </ActionBar>
856
+ <SectionHeader title="Secondary / Ghost" />
857
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
858
+ <Button label="Radar" variant="secondary" size="sm" attention={attentionOn ? "radar" : undefined} />
859
+ <Button label="Default (true)" variant="ghost" size="sm" attention={attentionOn ? true : undefined} />
860
+ </ActionBar>
861
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
862
+ Button accepts boolean | radar | glow | bounce. badge and ring are wrapper-only via AttentionBeacon.
863
+ </Text>
864
+ </Card>
865
+
866
+ <Card variant="elevated">
867
+ <Card.Header
868
+ title="Beacon Wrappers"
869
+ subtitle="AttentionBeacon around arbitrary components (playground-driven)"
870
+ />
871
+ <AttentionBeacon mode={beaconMode} tone={beaconTone} active={attentionOn}>
872
+ <Card>
873
+ <Card.Header
874
+ title="Urgent Review Required"
875
+ subtitle="Simulated verdict awaiting operator"
876
+ />
877
+ <KeyValue
878
+ label="Awaiting"
879
+ value="Command execution verdict"
880
+ subValue="twofado-style urgent prompt"
881
+ />
882
+ <ActionBar align="flex-start">
883
+ <Button label="Approve" variant="primary" size="sm" />
884
+ <Button label="Deny" variant="danger" size="sm" />
885
+ </ActionBar>
886
+ </Card>
887
+ </AttentionBeacon>
888
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
889
+ <AttentionBeacon mode={beaconMode} tone={beaconTone} active={attentionOn}>
890
+ <Badge label="Wrapped Badge" variant="warning" />
891
+ </AttentionBeacon>
892
+ <AttentionBeacon mode={beaconMode} tone={beaconTone} active={attentionOn}>
893
+ <Button
894
+ label={`Custom clickable (${beaconPresses})`}
895
+ variant="secondary"
896
+ size="sm"
897
+ accessibilityLabel="Custom beacon-wrapped clickable"
898
+ onPress={() => {
899
+ triggerHaptic("medium");
900
+ setBeaconPresses((n) => n + 1);
901
+ }}
902
+ />
903
+ </AttentionBeacon>
904
+ </ActionBar>
905
+ </Card>
906
+
907
+ <Card variant="elevated">
908
+ <Card.Header
909
+ title="Mode x Tone Matrix"
910
+ subtitle="All four modes side by side in the playground tone"
911
+ />
912
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
913
+ {(["radar", "glow", "badge", "bounce"] as const).map((m) => (
914
+ <AttentionBeacon key={m} mode={m} tone={beaconTone} active={attentionOn}>
915
+ <Badge label={m} variant="accent" />
916
+ </AttentionBeacon>
917
+ ))}
918
+ </ActionBar>
919
+ <SectionHeader title="Tones in playground mode" />
920
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
921
+ {(["warning", "accent", "danger"] as const).map((t) => (
922
+ <AttentionBeacon key={t} mode={beaconMode} tone={t} active={attentionOn}>
923
+ <Badge label={t === "warning" ? "statusWarning" : t} variant={t} />
924
+ </AttentionBeacon>
925
+ ))}
926
+ </ActionBar>
927
+ <SectionHeader title="Ring alias equivalence" />
928
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
929
+ <AttentionBeacon mode="radar" tone={beaconTone} active={attentionOn}>
930
+ <Badge label="radar" variant="accent" />
931
+ </AttentionBeacon>
932
+ <AttentionBeacon mode="ring" tone={beaconTone} active={attentionOn}>
933
+ <Badge label="ring" variant="accent" />
934
+ </AttentionBeacon>
935
+ </ActionBar>
936
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
937
+ ring normalizes to radar; both halos pulse identically.
938
+ </Text>
939
+ <SectionHeader title="Badge with icon" />
940
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
941
+ Badge accepts string host icons and custom emoji/graphic nodes via the icon prop, plus a dot variant.
942
+ </Text>
943
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
944
+ <Badge icon="Bell" label="Bell Icon" variant="danger" />
945
+ <Badge icon="Flame" label="Flame Icon" variant="warning" />
946
+ <Badge
947
+ icon={<Text style={{ fontSize: typography.caption.fontSize }}>🚀</Text>}
948
+ label="Emoji Graphic"
949
+ variant="accent"
950
+ />
951
+ <Badge
952
+ icon={<Text style={{ fontSize: typography.caption.fontSize }}>⚡</Text>}
953
+ label="Zap Graphic"
954
+ variant="success"
955
+ />
956
+ <Badge dot label="Status Dot" variant="info" />
957
+ </ActionBar>
958
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
959
+ <AttentionBeacon mode="badge" tone="danger" active={attentionOn}>
960
+ <Badge icon="Bell" label="Bell Icon" variant="danger" />
961
+ </AttentionBeacon>
962
+ <AttentionBeacon mode="badge" tone="warning" active={attentionOn}>
963
+ <Badge icon="Flame" label="Flame Icon" variant="warning" />
964
+ </AttentionBeacon>
965
+ <AttentionBeacon mode="badge" tone="accent" active={attentionOn}>
966
+ <Badge
967
+ icon={<Text style={{ fontSize: typography.caption.fontSize }}>🚀</Text>}
968
+ label="Emoji Graphic"
969
+ variant="accent"
970
+ />
971
+ </AttentionBeacon>
972
+ <AttentionBeacon mode="badge" tone="accent" active={attentionOn}>
973
+ <Badge
974
+ icon={<Text style={{ fontSize: typography.caption.fontSize }}>⚡</Text>}
975
+ label="Zap Graphic"
976
+ variant="success"
977
+ />
978
+ </AttentionBeacon>
979
+ <AttentionBeacon mode="badge" tone={beaconTone} active={attentionOn}>
980
+ <Badge dot label="Status Dot" variant="info" />
981
+ </AttentionBeacon>
982
+ </ActionBar>
983
+ </Card>
984
+ </>
985
+ )}
986
+
987
+ {/* TAB: SETTINGS & STORAGE */}
988
+ {activeTab === "settings" && (
989
+ <>
990
+ <SharedSuiteCard />
991
+ <Card variant="elevated">
992
+ <Card.Header
993
+ title="Plugin Settings"
994
+ subtitle="Type-safe Zod storage with optimistic React Query updates"
995
+ />
996
+ <FormRow
997
+ label="Show CPU in Pill"
998
+ description="Toggle whether the CPU usage percent is visible in the composer bar"
999
+ >
1000
+ <Toggle
1001
+ value={settings.showCpuUsage}
1002
+ onValueChange={(val) => {
1003
+ triggerHaptic("light");
1004
+ updateSettings({ showCpuUsage: val });
1005
+ }}
1006
+ />
1007
+ </FormRow>
1008
+
1009
+ <FormRow
1010
+ label="Navigation Style"
1011
+ description="Choose tabs or a readable dropdown for the showcase navbar"
1012
+ >
1013
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
1014
+ <Button
1015
+ label="Tabs"
1016
+ size="sm"
1017
+ variant={settings.navigationStyle === "tabs" ? "primary" : "secondary"}
1018
+ onPress={() => {
1019
+ triggerHaptic("light");
1020
+ updateSettings({ navigationStyle: "tabs" });
1021
+ }}
1022
+ />
1023
+ <Button
1024
+ label="Dropdown"
1025
+ size="sm"
1026
+ variant={settings.navigationStyle === "dropdown" ? "primary" : "secondary"}
1027
+ onPress={() => {
1028
+ triggerHaptic("light");
1029
+ updateSettings({ navigationStyle: "dropdown" });
1030
+ }}
1031
+ />
1032
+ </ActionBar>
1033
+ </FormRow>
1034
+
1035
+ <FormRow
1036
+ label="Pill Accent Label"
1037
+ description="Custom label displayed at the front of the composer pill"
1038
+ >
1039
+ <TextInput
1040
+ value={settings.accentPillLabel}
1041
+ onChangeText={(text) => updateSettings({ accentPillLabel: text })}
1042
+ placeholder="demo"
1043
+ />
1044
+ </FormRow>
1045
+
1046
+ <FormRow
1047
+ label="Alert Threshold"
1048
+ description={`Turns the status dot red when CPU exceeds this percent (Current: ${settings.highCpuThreshold}%)`}
1049
+ >
1050
+ <TextInput
1051
+ value={String(settings.highCpuThreshold)}
1052
+ keyboardType="numeric"
1053
+ onChangeText={(text) => {
1054
+ const val = parseInt(text, 10);
1055
+ if (!isNaN(val)) updateSettings({ highCpuThreshold: val });
1056
+ }}
1057
+ />
1058
+ </FormRow>
1059
+
1060
+ <ActionBar align="space-between">
1061
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption }}>
1062
+ {isSettingsUpdating ? "Saving to disk..." : "Saved to settings.json atomically"}
1063
+ </Text>
1064
+ <Button
1065
+ label="Reset Defaults"
1066
+ variant="secondary"
1067
+ size="sm"
1068
+ onPress={async () => {
1069
+ triggerHaptic("warning");
1070
+ await resetSettings();
1071
+ }}
1072
+ />
1073
+ </ActionBar>
1074
+ </Card>
1075
+ </>
1076
+ )}
1077
+
1078
+ {/* TAB 4: NETWORK DIAGNOSTICS */}
1079
+ {activeTab === "network" && (
1080
+ <Card variant="elevated">
1081
+ <Card.Header
1082
+ title="Network & Ports"
1083
+ subtitle="Daemon network diagnostics"
1084
+ />
1085
+ <KeyValueGroup>
1086
+ <KeyValue
1087
+ label="Daemon TCP Port"
1088
+ value={`${data?.daemonPort ?? 4280}`}
1089
+ subValue="Local verified socket"
1090
+ copyable
1091
+ />
1092
+ <KeyValue
1093
+ label="Network Connectivity"
1094
+ value="Active (Loopback)"
1095
+ />
1096
+ </KeyValueGroup>
1097
+ </Card>
1098
+ )}
1099
+
1100
+ {/* TAB 5: SYSTEM LOGS */}
1101
+ {activeTab === "logs" && (
1102
+ <Card variant="elevated">
1103
+ <Card.Header
1104
+ title="System Logs"
1105
+ subtitle={`Background task tick #${data?.backgroundTicks ?? 0}`}
1106
+ />
1107
+ <CodeBlock
1108
+ language="bash"
1109
+ code={`[INFO] Server running on ${data?.hostname} (${data?.platform})\n[INFO] Background task alive, uptime: ${Math.round(data?.uptimeSeconds ?? 0)}s\n[INFO] Periodic health check OK`}
1110
+ />
1111
+ </Card>
1112
+ )}
1113
+
1114
+ {/* TAB 6: ABOUT PLUGIN */}
1115
+ {activeTab === "about" && (
1116
+ <>
1117
+ <Card variant="elevated">
1118
+ <Card.Header
1119
+ title="Agent Identity & Session"
1120
+ subtitle="Self-inspection via getAgentIdentity()"
1121
+ />
1122
+ {isAgentLoading ? (
1123
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption, marginTop: 8 }}>
1124
+ Resolving agent identity...
1125
+ </Text>
1126
+ ) : !agentIdentity ? (
1127
+ <EmptyState
1128
+ icon="Bot"
1129
+ title="No Active Agent Session"
1130
+ description="Running outside an active agent session. Identity resolves via getAgentIdentity() when a session envelope is present."
1131
+ />
1132
+ ) : (
1133
+ <>
1134
+ <KeyValueGroup columns={isCompact ? 1 : 2}>
1135
+ <KeyValue
1136
+ label="Active Agent ID"
1137
+ value={agentIdentity.id ?? "—"}
1138
+ copyable={Boolean(agentIdentity.id)}
1139
+ />
1140
+ <KeyValue
1141
+ label="Session Name"
1142
+ value={agentIdentity.name ?? "—"}
1143
+ copyable={Boolean(agentIdentity.name)}
1144
+ />
1145
+ <KeyValue
1146
+ label="Model"
1147
+ value={agentIdentity.model ?? "—"}
1148
+ copyable={Boolean(agentIdentity.model)}
1149
+ />
1150
+ <KeyValue
1151
+ label="Provider"
1152
+ value={agentIdentity.provider ?? "—"}
1153
+ copyable={Boolean(agentIdentity.provider)}
1154
+ />
1155
+ <KeyValue
1156
+ label="Repo"
1157
+ value={agentIdentity.repo ?? "—"}
1158
+ copyable={Boolean(agentIdentity.repo)}
1159
+ />
1160
+ <KeyValue
1161
+ label="Branch"
1162
+ value={agentIdentity.branch ?? "—"}
1163
+ copyable={Boolean(agentIdentity.branch)}
1164
+ />
1165
+ </KeyValueGroup>
1166
+ {agentIdentity.envelopeText ? (
1167
+ <>
1168
+ <ActionBar align="flex-start" direction="row" style={{ marginTop: 0 }}>
1169
+ <Badge label="Audit envelope present" variant="success" />
1170
+ <Button
1171
+ label={showEnvelope ? "Hide Envelope" : "Inspect Envelope"}
1172
+ size="sm"
1173
+ variant="secondary"
1174
+ onPress={() => {
1175
+ triggerHaptic("light");
1176
+ setShowEnvelope((v) => !v);
1177
+ }}
1178
+ />
1179
+ </ActionBar>
1180
+ {showEnvelope ? (
1181
+ <CodeBlock
1182
+ language="bash"
1183
+ code={agentIdentity.envelopeText}
1184
+ copyable
1185
+ />
1186
+ ) : null}
1187
+ </>
1188
+ ) : null}
1189
+ </>
1190
+ )}
1191
+ </Card>
1192
+ <AboutSection
1193
+ name="Paseo Helper Demo"
1194
+ description="Interactive design system showcase and daemon runtime verification suite for paseo-plugin-helper."
1195
+ version={data?.version ?? PLUGIN_VERSION}
1196
+ author="xpufx"
1197
+ repository="https://github.com/xpufx/paseo-helper-demo"
1198
+ issues="https://github.com/xpufx/paseo-helper-demo/issues"
1199
+ license="MIT"
1200
+ extraItems={[
1201
+ { label: "Daemon Verified Port", value: `${data?.daemonPort ?? 4280}`, copyable: true },
1202
+ { label: "Host Platform", value: `${data?.platform ?? "unknown"}`, copyable: true },
1203
+ { label: "Background Uptime", value: `${Math.round(data?.uptimeSeconds ?? 0)}s` },
1204
+ ]}
1205
+ />
1206
+ </>
1207
+ )}
1208
+
1209
+ {/* Footer */}
1210
+ <ActionBar align="flex-end">
1211
+ <Button
1212
+ label="Close"
1213
+ variant="ghost"
1214
+ onPress={() => {
1215
+ triggerHaptic("light");
1216
+ close();
1217
+ }}
1218
+ />
1219
+ </ActionBar>
1220
+
1221
+ <View style={{ alignItems: "center", paddingVertical: 8 }}>
1222
+ <Text style={{ color: colors.foregroundMuted, ...typography.caption, fontFamily: "monospace" }}>
1223
+ helper-demo v{data?.version ?? PLUGIN_VERSION} (tick #{data?.backgroundTicks ?? 0})
1224
+ </Text>
1225
+ </View>
1226
+ </HostModalSection>
1227
+ </PluginThemeProvider>
1228
+ );
1229
+ }
1230
+
1231
+ export function contributeClient(client: ComposerPillRegistrar) {
1232
+ const removePill = registerComposerPill(client, {
1233
+ id: "helper-demo",
1234
+ title: "demo",
1235
+ modalTitle: "Showcase Demo",
1236
+ modalIcon: "Sliders",
1237
+ hostScroll: true,
1238
+ flair: {
1239
+ radius: "rounded",
1240
+ density: "comfortable",
1241
+ accentColor: "#6366f1",
1242
+ },
1243
+ renderPill: (props) => <DemoPill {...props} />,
1244
+ renderModal: (props) => <DemoModal {...props} />,
1245
+ });
1246
+ return () => {
1247
+ removePill();
1248
+ };
1249
+ }