palmier 0.9.6 → 0.9.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 (250) hide show
  1. package/README.md +28 -13
  2. package/dist/agents/agent.d.ts +0 -1
  3. package/dist/agents/agent.js +0 -1
  4. package/dist/agents/aider.d.ts +0 -1
  5. package/dist/agents/aider.js +0 -1
  6. package/dist/agents/claude.d.ts +0 -1
  7. package/dist/agents/claude.js +0 -1
  8. package/dist/agents/cline.d.ts +0 -1
  9. package/dist/agents/cline.js +0 -1
  10. package/dist/agents/codex.d.ts +0 -1
  11. package/dist/agents/codex.js +0 -1
  12. package/dist/agents/copilot.d.ts +0 -1
  13. package/dist/agents/copilot.js +0 -1
  14. package/dist/agents/cursor.d.ts +0 -1
  15. package/dist/agents/cursor.js +0 -1
  16. package/dist/agents/deepagents.d.ts +0 -1
  17. package/dist/agents/deepagents.js +0 -1
  18. package/dist/agents/droid.d.ts +0 -1
  19. package/dist/agents/droid.js +0 -1
  20. package/dist/agents/gemini.d.ts +0 -1
  21. package/dist/agents/gemini.js +0 -1
  22. package/dist/agents/goose.d.ts +0 -1
  23. package/dist/agents/goose.js +0 -1
  24. package/dist/agents/hermes.d.ts +0 -1
  25. package/dist/agents/hermes.js +0 -1
  26. package/dist/agents/kimi.d.ts +0 -1
  27. package/dist/agents/kimi.js +0 -1
  28. package/dist/agents/kiro.d.ts +0 -1
  29. package/dist/agents/kiro.js +0 -1
  30. package/dist/agents/openclaw.d.ts +0 -1
  31. package/dist/agents/openclaw.js +0 -1
  32. package/dist/agents/opencode.d.ts +0 -1
  33. package/dist/agents/opencode.js +0 -1
  34. package/dist/agents/qoder.d.ts +0 -1
  35. package/dist/agents/qoder.js +0 -1
  36. package/dist/agents/qwen.d.ts +0 -1
  37. package/dist/agents/qwen.js +0 -1
  38. package/dist/agents/shared-prompt.d.ts +0 -1
  39. package/dist/agents/shared-prompt.js +0 -1
  40. package/dist/client-store.d.ts +0 -1
  41. package/dist/client-store.js +0 -1
  42. package/dist/commands/clients.d.ts +0 -1
  43. package/dist/commands/clients.js +0 -1
  44. package/dist/commands/info.d.ts +0 -1
  45. package/dist/commands/info.js +0 -1
  46. package/dist/commands/init.d.ts +0 -1
  47. package/dist/commands/init.js +1 -2
  48. package/dist/commands/pair.d.ts +0 -1
  49. package/dist/commands/pair.js +0 -1
  50. package/dist/commands/restart.d.ts +0 -1
  51. package/dist/commands/restart.js +0 -1
  52. package/dist/commands/run.d.ts +0 -1
  53. package/dist/commands/run.js +0 -1
  54. package/dist/commands/serve.d.ts +0 -1
  55. package/dist/commands/serve.js +0 -1
  56. package/dist/commands/uninstall.d.ts +0 -1
  57. package/dist/commands/uninstall.js +0 -1
  58. package/dist/config.d.ts +0 -1
  59. package/dist/config.js +0 -1
  60. package/dist/event-queues.d.ts +0 -1
  61. package/dist/event-queues.js +0 -1
  62. package/dist/events.d.ts +0 -1
  63. package/dist/events.js +0 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.js +0 -1
  66. package/dist/linked-device.d.ts +0 -1
  67. package/dist/linked-device.js +0 -1
  68. package/dist/mcp-handler.d.ts +0 -1
  69. package/dist/mcp-handler.js +0 -1
  70. package/dist/mcp-tools.d.ts +0 -1
  71. package/dist/mcp-tools.js +0 -1
  72. package/dist/nats-client.d.ts +0 -1
  73. package/dist/nats-client.js +0 -1
  74. package/dist/network.d.ts +0 -1
  75. package/dist/network.js +0 -1
  76. package/dist/notification-store.d.ts +0 -1
  77. package/dist/notification-store.js +0 -1
  78. package/dist/pending-requests.d.ts +0 -1
  79. package/dist/pending-requests.js +0 -1
  80. package/dist/platform/index.d.ts +0 -1
  81. package/dist/platform/index.js +0 -1
  82. package/dist/platform/linux.d.ts +0 -1
  83. package/dist/platform/linux.js +0 -1
  84. package/dist/platform/macos.d.ts +0 -1
  85. package/dist/platform/macos.js +0 -1
  86. package/dist/platform/platform.d.ts +0 -1
  87. package/dist/platform/platform.js +0 -1
  88. package/dist/platform/windows.d.ts +0 -1
  89. package/dist/platform/windows.js +0 -1
  90. package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
  91. package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
  92. package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
  93. package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
  94. package/dist/pwa/index.html +2 -2
  95. package/dist/pwa/service-worker.js +1 -1
  96. package/dist/rpc-handler.d.ts +0 -1
  97. package/dist/rpc-handler.js +0 -1
  98. package/dist/sms-store.d.ts +0 -1
  99. package/dist/sms-store.js +0 -1
  100. package/dist/spawn-command.d.ts +0 -1
  101. package/dist/spawn-command.js +0 -1
  102. package/dist/task.d.ts +0 -1
  103. package/dist/task.js +0 -1
  104. package/dist/transports/http-transport.d.ts +0 -1
  105. package/dist/transports/http-transport.js +0 -1
  106. package/dist/transports/nats-transport.d.ts +0 -1
  107. package/dist/transports/nats-transport.js +0 -1
  108. package/dist/types.d.ts +0 -1
  109. package/dist/types.js +0 -1
  110. package/dist/update-checker.d.ts +0 -1
  111. package/dist/update-checker.js +0 -1
  112. package/package.json +5 -1
  113. package/.github/workflows/ci.yml +0 -16
  114. package/.github/workflows/publish.yml +0 -37
  115. package/CLAUDE.md +0 -22
  116. package/palmier-server/.github/workflows/ci.yml +0 -21
  117. package/palmier-server/.github/workflows/deploy.yml +0 -38
  118. package/palmier-server/CLAUDE.md +0 -17
  119. package/palmier-server/PRODUCTION.md +0 -358
  120. package/palmier-server/README.md +0 -231
  121. package/palmier-server/nats.conf +0 -19
  122. package/palmier-server/package.json +0 -15
  123. package/palmier-server/pnpm-lock.yaml +0 -7639
  124. package/palmier-server/pnpm-workspace.yaml +0 -3
  125. package/palmier-server/pwa/index.html +0 -16
  126. package/palmier-server/pwa/logo/logo_20260421.png +0 -0
  127. package/palmier-server/pwa/package.json +0 -34
  128. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  129. package/palmier-server/pwa/public/favicon.ico +0 -0
  130. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  131. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  132. package/palmier-server/pwa/src/App.css +0 -3012
  133. package/palmier-server/pwa/src/App.tsx +0 -59
  134. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  135. package/palmier-server/pwa/src/api.ts +0 -67
  136. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  137. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
  138. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  139. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  140. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  141. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  142. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  143. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  144. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  145. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  146. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  147. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
  148. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  149. package/palmier-server/pwa/src/constants.ts +0 -2
  150. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  151. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  152. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  153. package/palmier-server/pwa/src/formatTime.ts +0 -44
  154. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  155. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  156. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  157. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  158. package/palmier-server/pwa/src/main.tsx +0 -14
  159. package/palmier-server/pwa/src/native/Device.ts +0 -49
  160. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  161. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  162. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  163. package/palmier-server/pwa/src/service-worker.ts +0 -142
  164. package/palmier-server/pwa/src/types.ts +0 -75
  165. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  166. package/palmier-server/pwa/tsconfig.json +0 -21
  167. package/palmier-server/pwa/tsconfig.node.json +0 -19
  168. package/palmier-server/pwa/vite.config.ts +0 -47
  169. package/palmier-server/server/.env.example +0 -20
  170. package/palmier-server/server/package.json +0 -36
  171. package/palmier-server/server/src/db.ts +0 -44
  172. package/palmier-server/server/src/fcm.ts +0 -74
  173. package/palmier-server/server/src/index.ts +0 -688
  174. package/palmier-server/server/src/nats-jwt.ts +0 -299
  175. package/palmier-server/server/src/nats-setup.ts +0 -48
  176. package/palmier-server/server/src/nats.ts +0 -33
  177. package/palmier-server/server/src/notify.ts +0 -34
  178. package/palmier-server/server/src/push.ts +0 -68
  179. package/palmier-server/server/src/routes/device.ts +0 -224
  180. package/palmier-server/server/src/routes/fcm.ts +0 -64
  181. package/palmier-server/server/src/routes/hosts.ts +0 -56
  182. package/palmier-server/server/src/routes/push.ts +0 -101
  183. package/palmier-server/server/tsconfig.json +0 -20
  184. package/palmier-server/spec.md +0 -533
  185. package/src/agents/agent-instructions.md +0 -28
  186. package/src/agents/agent.ts +0 -114
  187. package/src/agents/aider.ts +0 -35
  188. package/src/agents/claude.ts +0 -39
  189. package/src/agents/cline.ts +0 -35
  190. package/src/agents/codex.ts +0 -40
  191. package/src/agents/copilot.ts +0 -37
  192. package/src/agents/cursor.ts +0 -36
  193. package/src/agents/deepagents.ts +0 -36
  194. package/src/agents/droid.ts +0 -35
  195. package/src/agents/gemini.ts +0 -43
  196. package/src/agents/goose.ts +0 -33
  197. package/src/agents/hermes.ts +0 -36
  198. package/src/agents/kimi.ts +0 -35
  199. package/src/agents/kiro.ts +0 -36
  200. package/src/agents/openclaw.ts +0 -29
  201. package/src/agents/opencode.ts +0 -36
  202. package/src/agents/qoder.ts +0 -36
  203. package/src/agents/qwen.ts +0 -32
  204. package/src/agents/shared-prompt.ts +0 -30
  205. package/src/client-store.ts +0 -68
  206. package/src/commands/clients.ts +0 -29
  207. package/src/commands/info.ts +0 -29
  208. package/src/commands/init.ts +0 -165
  209. package/src/commands/pair.ts +0 -137
  210. package/src/commands/restart.ts +0 -6
  211. package/src/commands/run.ts +0 -608
  212. package/src/commands/serve.ts +0 -211
  213. package/src/commands/uninstall.ts +0 -9
  214. package/src/config.ts +0 -36
  215. package/src/cross-spawn.d.ts +0 -5
  216. package/src/event-queues.ts +0 -41
  217. package/src/events.ts +0 -29
  218. package/src/index.ts +0 -111
  219. package/src/linked-device.ts +0 -52
  220. package/src/mcp-handler.ts +0 -200
  221. package/src/mcp-tools.ts +0 -839
  222. package/src/nats-client.ts +0 -19
  223. package/src/network.ts +0 -96
  224. package/src/notification-store.ts +0 -30
  225. package/src/pending-requests.ts +0 -73
  226. package/src/platform/index.ts +0 -20
  227. package/src/platform/linux.ts +0 -296
  228. package/src/platform/macos.ts +0 -329
  229. package/src/platform/platform.ts +0 -31
  230. package/src/platform/windows.ts +0 -299
  231. package/src/rpc-handler.ts +0 -691
  232. package/src/sms-store.ts +0 -28
  233. package/src/spawn-command.ts +0 -123
  234. package/src/task.ts +0 -343
  235. package/src/transports/http-transport.ts +0 -478
  236. package/src/transports/nats-transport.ts +0 -76
  237. package/src/types.ts +0 -89
  238. package/src/update-checker.ts +0 -40
  239. package/test/agent-instructions.test.ts +0 -209
  240. package/test/agent-output-parsing.test.ts +0 -74
  241. package/test/linux-cron.test.ts +0 -41
  242. package/test/macos-plist.test.ts +0 -112
  243. package/test/notification-store.test.ts +0 -57
  244. package/test/pairing.test.ts +0 -35
  245. package/test/result-state.test.ts +0 -110
  246. package/test/task-parsing.test.ts +0 -82
  247. package/test/taskrun-messages.test.ts +0 -224
  248. package/test/tsconfig.json +0 -9
  249. package/test/windows-xml.test.ts +0 -89
  250. package/tsconfig.json +0 -19
@@ -1,59 +0,0 @@
1
- import { useEffect } from "react";
2
- import { Routes, Route, useNavigate, useParams, Navigate } from "react-router-dom";
3
- import { HostStoreProvider, useHostStore } from "./contexts/HostStoreContext";
4
- import { HostConnectionProvider } from "./contexts/HostConnectionContext";
5
- import { Device } from "./native/Device";
6
- import Dashboard from "./pages/Dashboard";
7
- import PairHost from "./pages/PairHost";
8
- import PairSetup from "./pages/PairSetup";
9
-
10
- function DeepLinkRouter() {
11
- const navigate = useNavigate();
12
- useEffect(() => {
13
- if (!Device) return;
14
- const handle = Device.addListener("deepLink", ({ path }) => navigate(path));
15
- return () => { handle.then((h) => h.remove()); };
16
- }, [navigate]);
17
- return null;
18
- }
19
-
20
- function RootRedirect() {
21
- const { pairedHosts } = useHostStore();
22
- if (pairedHosts.length === 0) return <Navigate to="/pair" replace />;
23
- return <Navigate to={`/hosts/${encodeURIComponent(pairedHosts[0].hostId)}`} replace />;
24
- }
25
-
26
- function HostScope() {
27
- const { hostId } = useParams<{ hostId: string }>();
28
- const { pairedHosts } = useHostStore();
29
- const host = pairedHosts.find((h) => h.hostId === hostId) ?? null;
30
-
31
- if (!host) return <Navigate to="/" replace />;
32
-
33
- return (
34
- <HostConnectionProvider activeHost={host}>
35
- <Routes>
36
- <Route index element={<Dashboard />} />
37
- <Route path="tasks" element={<Dashboard />} />
38
- <Route path="runs/:taskId" element={<Dashboard />} />
39
- <Route path="runs/:taskId/:runId" element={<Dashboard />} />
40
- <Route path="pair/setup" element={<PairSetup />} />
41
- <Route path="*" element={<Navigate to="." replace />} />
42
- </Routes>
43
- </HostConnectionProvider>
44
- );
45
- }
46
-
47
- export default function App() {
48
- return (
49
- <HostStoreProvider>
50
- <DeepLinkRouter />
51
- <Routes>
52
- <Route path="/" element={<RootRedirect />} />
53
- <Route path="/pair" element={<PairHost />} />
54
- <Route path="/hosts/:hostId/*" element={<HostScope />} />
55
- <Route path="*" element={<Navigate to="/" replace />} />
56
- </Routes>
57
- </HostStoreProvider>
58
- );
59
- }
@@ -1,11 +0,0 @@
1
- import type { AgentInfo } from "./types";
2
-
3
- const labels: Record<string, string> = {};
4
-
5
- export function setAgentLabels(agents: AgentInfo[]): void {
6
- for (const a of agents) labels[a.key] = a.label;
7
- }
8
-
9
- export function getAgentLabel(key: string): string {
10
- return labels[key] ?? key;
11
- }
@@ -1,67 +0,0 @@
1
- import { Capacitor } from "@capacitor/core";
2
-
3
- /** On native platforms, API calls go to the production server. On web, they're relative (same-origin). */
4
- export const SERVER_URL = Capacitor.isNativePlatform() ? "https://app.palmier.me" : "";
5
-
6
- async function request<T>(
7
- method: string,
8
- path: string,
9
- body?: unknown,
10
- token?: string
11
- ): Promise<T> {
12
- const url = `${SERVER_URL}${path}`;
13
- console.log(`[API] ${method} ${url}`);
14
- const headers: Record<string, string> = {
15
- "Content-Type": "application/json",
16
- };
17
- if (token) {
18
- headers["Authorization"] = `Bearer ${token}`;
19
- }
20
- const res = await fetch(url, {
21
- method,
22
- headers,
23
- body: body != null ? JSON.stringify(body) : undefined,
24
- });
25
- if (!res.ok) {
26
- const text = await res.text();
27
- let message: string;
28
- try {
29
- const json = JSON.parse(text);
30
- message = json.error ?? json.message ?? text;
31
- } catch {
32
- message = text;
33
- }
34
- console.error(`[API] ${method} ${path} failed:`, res.status, message);
35
- throw new Error(message || `Request failed with status ${res.status}`);
36
- }
37
- console.log(`[API] ${method} ${path} ->`, res.status);
38
- return res.json() as Promise<T>;
39
- }
40
-
41
- export function apiPost<T = unknown>(
42
- path: string,
43
- body: unknown,
44
- token?: string
45
- ): Promise<T> {
46
- return request<T>("POST", path, body, token);
47
- }
48
-
49
- export function apiGet<T = unknown>(path: string, token?: string): Promise<T> {
50
- return request<T>("GET", path, undefined, token);
51
- }
52
-
53
- export function apiPatch<T = unknown>(
54
- path: string,
55
- body: unknown,
56
- token?: string
57
- ): Promise<T> {
58
- return request<T>("PATCH", path, body, token);
59
- }
60
-
61
- export function apiDelete<T = unknown>(
62
- path: string,
63
- body?: unknown,
64
- token?: string
65
- ): Promise<T> {
66
- return request<T>("DELETE", path, body, token);
67
- }
@@ -1,170 +0,0 @@
1
- import { useState, useEffect, useCallback } from "react";
2
- import { createPortal } from "react-dom";
3
- import { Capacitor } from "@capacitor/core";
4
- import { App as CapacitorApp } from "@capacitor/app";
5
- import { Device, type CapabilityStatus } from "../native/Device";
6
-
7
- const isNative = Capacitor.isNativePlatform();
8
-
9
- type CapabilityGroup = "Messaging" | "Data" | "Device";
10
-
11
- const CAPABILITY_GROUPS: CapabilityGroup[] = ["Device", "Data", "Messaging"];
12
-
13
- interface CapabilityDefinition {
14
- capability: string;
15
- label: string;
16
- group: CapabilityGroup;
17
- }
18
-
19
- const CAPABILITIES: CapabilityDefinition[] = [
20
- { capability: "sms-read", label: "Read SMS", group: "Messaging" },
21
- { capability: "sms-send", label: "Send SMS", group: "Messaging" },
22
- { capability: "send-email", label: "Prompt Email to Send", group: "Messaging" },
23
- { capability: "notifications", label: "Notifications from Other Apps", group: "Data" },
24
- { capability: "contacts", label: "Manage Contacts", group: "Data" },
25
- { capability: "calendar", label: "Manage Calendar", group: "Data" },
26
- { capability: "location", label: "Get Location", group: "Device" },
27
- { capability: "dnd", label: "Set Ringer Mode", group: "Device" },
28
- { capability: "alarm", label: "Trigger Alarms", group: "Device" },
29
- ];
30
-
31
- export async function loadEnabledCapabilities(): Promise<Set<string>> {
32
- if (!isNative || !Device) return new Set();
33
- try {
34
- const { capabilities } = await Device.getCapabilityStatus();
35
- return new Set(capabilities.filter((c) => c.enabled).map((c) => c.name));
36
- } catch {
37
- return new Set();
38
- }
39
- }
40
-
41
- interface CapabilityTogglesProps {
42
- onChange?(enabled: Set<string>): void;
43
- /** When true, disabling a currently-on capability prompts a confirmation
44
- * mentioning that all linked hosts will lose it. */
45
- confirmDisable?: boolean;
46
- }
47
-
48
- export default function CapabilityToggles({ onChange, confirmDisable = false }: CapabilityTogglesProps) {
49
- const [statuses, setStatuses] = useState<Map<string, CapabilityStatus>>(new Map());
50
- const [busyCapability, setBusyCapability] = useState<string | null>(null);
51
- const [confirmingDisable, setConfirmingDisable] = useState<CapabilityDefinition | null>(null);
52
-
53
- const refresh = useCallback(async () => {
54
- if (!isNative || !Device) return;
55
- try {
56
- const { capabilities } = await Device.getCapabilityStatus();
57
- const next = new Map<string, CapabilityStatus>();
58
- for (const c of capabilities) next.set(c.name, c);
59
- setStatuses(next);
60
- onChange?.(new Set(capabilities.filter((c) => c.enabled).map((c) => c.name)));
61
- } catch (err) {
62
- console.error("Failed to read capability status:", err);
63
- }
64
- }, [onChange]);
65
-
66
- useEffect(() => { refresh(); }, [refresh]);
67
-
68
- useEffect(() => {
69
- if (!isNative) return;
70
- const listener = CapacitorApp.addListener("resume", refresh);
71
- return () => { listener.then((h) => h.remove()); };
72
- }, [refresh]);
73
-
74
- async function applyToggle(definition: CapabilityDefinition, enabled: boolean) {
75
- if (!Device) return;
76
- setBusyCapability(definition.capability);
77
- try {
78
- const result = await Device.setCapabilityEnabled({ capability: definition.capability, enabled });
79
- if (!result.enabled && result.reason === "no-email-client") {
80
- alert("No email app is installed on this device. Install one (e.g. Gmail) before enabling Send Email.");
81
- }
82
- await refresh();
83
- } catch (err) {
84
- console.error(`Failed to toggle ${definition.capability}:`, err);
85
- } finally {
86
- setBusyCapability(null);
87
- }
88
- }
89
-
90
- function toggleCapability(definition: CapabilityDefinition) {
91
- const status = statuses.get(definition.capability);
92
- if (!status) return;
93
- if (status.enabled && confirmDisable) {
94
- setConfirmingDisable(definition);
95
- return;
96
- }
97
- applyToggle(definition, !status.enabled);
98
- }
99
-
100
- function confirmAndDisable() {
101
- const definition = confirmingDisable;
102
- if (!definition) return;
103
- setConfirmingDisable(null);
104
- applyToggle(definition, false);
105
- }
106
-
107
- if (!isNative) return null;
108
-
109
- const visibleGroups = CAPABILITY_GROUPS
110
- .map((group) => ({
111
- group,
112
- items: CAPABILITIES.filter((d) => {
113
- const s = statuses.get(d.capability);
114
- return d.group === group && s?.supported;
115
- }),
116
- }))
117
- .filter((g) => g.items.length > 0);
118
-
119
- if (visibleGroups.length === 0) {
120
- return (
121
- <div className="capability-toggles-loading">
122
- <span className="spinner" />
123
- </div>
124
- );
125
- }
126
-
127
- const disableModal = confirmingDisable && createPortal(
128
- <div className="confirm-modal-overlay" onClick={() => setConfirmingDisable(null)}>
129
- <div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
130
- <h2 className="confirm-modal-title">Disable {confirmingDisable.label}?</h2>
131
- <p className="confirm-modal-message">
132
- All hosts linked to this device will no longer be able to use {confirmingDisable.label}.
133
- </p>
134
- <div className="confirm-modal-actions">
135
- <button className="btn btn-secondary" onClick={() => setConfirmingDisable(null)}>Cancel</button>
136
- <button className="btn btn-danger" onClick={confirmAndDisable}>Disable</button>
137
- </div>
138
- </div>
139
- </div>,
140
- document.body,
141
- );
142
-
143
- return (
144
- <>
145
- {visibleGroups.map(({ group, items }, index) => (
146
- <div key={group} className={index > 0 ? "drawer-toggle-group drawer-toggle-group-divided" : "drawer-toggle-group"}>
147
- {items.map((definition) => {
148
- const status = statuses.get(definition.capability);
149
- const on = !!status?.enabled;
150
- return (
151
- <label key={definition.capability} className="drawer-toggle">
152
- <span className="drawer-toggle-label">{definition.label}</span>
153
- <button
154
- className={`toggle-switch ${on ? "toggle-switch-on" : ""}`}
155
- onClick={() => toggleCapability(definition)}
156
- disabled={busyCapability === definition.capability}
157
- role="switch"
158
- aria-checked={on}
159
- >
160
- <span className="toggle-switch-thumb" />
161
- </button>
162
- </label>
163
- );
164
- })}
165
- </div>
166
- ))}
167
- {disableModal}
168
- </>
169
- );
170
- }
@@ -1,113 +0,0 @@
1
- import { useState, useRef, useEffect } from "react";
2
- import { Capacitor } from "@capacitor/core";
3
- import { useHostConnection } from "../contexts/HostConnectionContext";
4
-
5
- const isNative = Capacitor.isNativePlatform();
6
-
7
- const SVG_PROPS = {
8
- width: 16,
9
- height: 16,
10
- viewBox: "0 0 24 24",
11
- fill: "none",
12
- stroke: "currentColor",
13
- strokeWidth: 2,
14
- strokeLinecap: "round" as const,
15
- strokeLinejoin: "round" as const,
16
- };
17
-
18
- function LinkedDevicesIcon() {
19
- return (
20
- <svg {...SVG_PROPS} aria-hidden="true">
21
- <rect x="3" y="6" width="5" height="12" rx="1" />
22
- <rect x="16" y="6" width="5" height="12" rx="1" />
23
- <line x1="8" y1="12" x2="16" y2="12" />
24
- </svg>
25
- );
26
- }
27
-
28
- function GlobeIcon() {
29
- return (
30
- <svg {...SVG_PROPS} aria-hidden="true">
31
- <circle cx="12" cy="12" r="10" />
32
- <line x1="2" y1="12" x2="22" y2="12" />
33
- <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
34
- </svg>
35
- );
36
- }
37
-
38
- function WarningIcon() {
39
- return (
40
- <svg {...SVG_PROPS} aria-hidden="true">
41
- <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
42
- <line x1="12" y1="9" x2="12" y2="13" />
43
- <line x1="12" y1="17" x2="12.01" y2="17" />
44
- </svg>
45
- );
46
- }
47
-
48
- export default function ConnectionStatusIcon() {
49
- const { mode } = useHostConnection();
50
- const [popoverOpen, setPopoverOpen] = useState(false);
51
- const containerRef = useRef<HTMLDivElement>(null);
52
-
53
- useEffect(() => {
54
- if (!popoverOpen) return;
55
- function onPointerDown(e: PointerEvent) {
56
- if (!containerRef.current?.contains(e.target as Node)) setPopoverOpen(false);
57
- }
58
- document.addEventListener("pointerdown", onPointerDown);
59
- const timer = window.setTimeout(() => setPopoverOpen(false), 3000);
60
- return () => {
61
- document.removeEventListener("pointerdown", onPointerDown);
62
- clearTimeout(timer);
63
- };
64
- }, [popoverOpen]);
65
-
66
- if (mode === "direct") return null;
67
-
68
- let icon: React.ReactNode;
69
- let label: string;
70
- let modifier: string;
71
- switch (mode) {
72
- case "lan":
73
- icon = <LinkedDevicesIcon />;
74
- label = "Connected via LAN";
75
- modifier = "lan";
76
- break;
77
- case "nats":
78
- icon = <GlobeIcon />;
79
- label = isNative ? "Connected via relay" : "Connected";
80
- modifier = "relay";
81
- break;
82
- case "disconnected":
83
- icon = <WarningIcon />;
84
- label = "Disconnected";
85
- modifier = "disconnected";
86
- break;
87
- case "connecting":
88
- default:
89
- icon = <GlobeIcon />;
90
- label = "Connecting\u2026";
91
- modifier = "connecting";
92
- break;
93
- }
94
-
95
- return (
96
- <div ref={containerRef} className={`conn-status conn-status--${modifier}`}>
97
- <button
98
- type="button"
99
- className="conn-status-btn"
100
- aria-label={label}
101
- onClick={() => setPopoverOpen((v) => !v)}
102
- >
103
- {icon}
104
- </button>
105
- <div
106
- className={`conn-status-popover ${popoverOpen ? "conn-status-popover--open" : ""}`}
107
- role="tooltip"
108
- >
109
- {label}
110
- </div>
111
- </div>
112
- );
113
- }