palmier 0.9.5 → 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 (258) hide show
  1. package/README.md +30 -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 +3 -4
  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/apple-touch-icon.png +0 -0
  91. package/dist/pwa/assets/index-D1bIhEbd.css +1 -0
  92. package/dist/pwa/assets/{index-Cvffaohh.js → index-DWvRAUiy.js} +31 -31
  93. package/dist/pwa/assets/{web-qdLcAD7T.js → web-C4iZbqTC.js} +1 -1
  94. package/dist/pwa/assets/{web-ChtbM4nv.js → web-CBFqJGX6.js} +1 -1
  95. package/dist/pwa/assets/{web-hExASsqW.js → web-DL4uXOpS.js} +1 -1
  96. package/dist/pwa/favicon.ico +0 -0
  97. package/dist/pwa/index.html +3 -3
  98. package/dist/pwa/manifest.webmanifest +1 -1
  99. package/dist/pwa/pwa-192x192.png +0 -0
  100. package/dist/pwa/pwa-512x512.png +0 -0
  101. package/dist/pwa/service-worker.js +1 -1
  102. package/dist/rpc-handler.d.ts +0 -1
  103. package/dist/rpc-handler.js +3 -10
  104. package/dist/sms-store.d.ts +0 -1
  105. package/dist/sms-store.js +0 -1
  106. package/dist/spawn-command.d.ts +0 -1
  107. package/dist/spawn-command.js +0 -1
  108. package/dist/task.d.ts +1 -1
  109. package/dist/task.js +14 -1
  110. package/dist/transports/http-transport.d.ts +0 -1
  111. package/dist/transports/http-transport.js +0 -1
  112. package/dist/transports/nats-transport.d.ts +0 -1
  113. package/dist/transports/nats-transport.js +0 -1
  114. package/dist/types.d.ts +0 -1
  115. package/dist/types.js +0 -1
  116. package/dist/update-checker.d.ts +0 -1
  117. package/dist/update-checker.js +0 -1
  118. package/package.json +5 -1
  119. package/.github/workflows/ci.yml +0 -16
  120. package/.github/workflows/publish.yml +0 -37
  121. package/CLAUDE.md +0 -22
  122. package/dist/pwa/assets/index-DBgOYBrB.css +0 -1
  123. package/palmier-server/.github/workflows/ci.yml +0 -21
  124. package/palmier-server/.github/workflows/deploy.yml +0 -38
  125. package/palmier-server/CLAUDE.md +0 -17
  126. package/palmier-server/PRODUCTION.md +0 -358
  127. package/palmier-server/README.md +0 -231
  128. package/palmier-server/nats.conf +0 -19
  129. package/palmier-server/package.json +0 -15
  130. package/palmier-server/pnpm-lock.yaml +0 -7639
  131. package/palmier-server/pnpm-workspace.yaml +0 -3
  132. package/palmier-server/pwa/index.html +0 -16
  133. package/palmier-server/pwa/logo/logo-prompt.md +0 -28
  134. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  135. package/palmier-server/pwa/package.json +0 -34
  136. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  137. package/palmier-server/pwa/public/favicon.ico +0 -0
  138. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  139. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  140. package/palmier-server/pwa/src/App.css +0 -3004
  141. package/palmier-server/pwa/src/App.tsx +0 -59
  142. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  143. package/palmier-server/pwa/src/api.ts +0 -67
  144. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  145. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -114
  146. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  147. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  148. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  149. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  150. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  151. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  152. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  153. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  154. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  155. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -764
  156. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  157. package/palmier-server/pwa/src/constants.ts +0 -2
  158. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  159. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  160. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  161. package/palmier-server/pwa/src/formatTime.ts +0 -44
  162. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  163. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  164. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  165. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  166. package/palmier-server/pwa/src/main.tsx +0 -14
  167. package/palmier-server/pwa/src/native/Device.ts +0 -49
  168. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  169. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  170. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  171. package/palmier-server/pwa/src/service-worker.ts +0 -142
  172. package/palmier-server/pwa/src/types.ts +0 -75
  173. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  174. package/palmier-server/pwa/tsconfig.json +0 -21
  175. package/palmier-server/pwa/tsconfig.node.json +0 -19
  176. package/palmier-server/pwa/vite.config.ts +0 -47
  177. package/palmier-server/server/.env.example +0 -20
  178. package/palmier-server/server/package.json +0 -36
  179. package/palmier-server/server/src/db.ts +0 -44
  180. package/palmier-server/server/src/fcm.ts +0 -74
  181. package/palmier-server/server/src/index.ts +0 -688
  182. package/palmier-server/server/src/nats-jwt.ts +0 -299
  183. package/palmier-server/server/src/nats-setup.ts +0 -48
  184. package/palmier-server/server/src/nats.ts +0 -33
  185. package/palmier-server/server/src/notify.ts +0 -34
  186. package/palmier-server/server/src/push.ts +0 -68
  187. package/palmier-server/server/src/routes/device.ts +0 -224
  188. package/palmier-server/server/src/routes/fcm.ts +0 -64
  189. package/palmier-server/server/src/routes/hosts.ts +0 -56
  190. package/palmier-server/server/src/routes/push.ts +0 -101
  191. package/palmier-server/server/tsconfig.json +0 -20
  192. package/palmier-server/spec.md +0 -533
  193. package/src/agents/agent-instructions.md +0 -28
  194. package/src/agents/agent.ts +0 -114
  195. package/src/agents/aider.ts +0 -35
  196. package/src/agents/claude.ts +0 -39
  197. package/src/agents/cline.ts +0 -35
  198. package/src/agents/codex.ts +0 -40
  199. package/src/agents/copilot.ts +0 -37
  200. package/src/agents/cursor.ts +0 -36
  201. package/src/agents/deepagents.ts +0 -36
  202. package/src/agents/droid.ts +0 -35
  203. package/src/agents/gemini.ts +0 -43
  204. package/src/agents/goose.ts +0 -33
  205. package/src/agents/hermes.ts +0 -36
  206. package/src/agents/kimi.ts +0 -35
  207. package/src/agents/kiro.ts +0 -36
  208. package/src/agents/openclaw.ts +0 -29
  209. package/src/agents/opencode.ts +0 -36
  210. package/src/agents/qoder.ts +0 -36
  211. package/src/agents/qwen.ts +0 -32
  212. package/src/agents/shared-prompt.ts +0 -30
  213. package/src/client-store.ts +0 -68
  214. package/src/commands/clients.ts +0 -29
  215. package/src/commands/info.ts +0 -29
  216. package/src/commands/init.ts +0 -165
  217. package/src/commands/pair.ts +0 -137
  218. package/src/commands/restart.ts +0 -6
  219. package/src/commands/run.ts +0 -608
  220. package/src/commands/serve.ts +0 -211
  221. package/src/commands/uninstall.ts +0 -9
  222. package/src/config.ts +0 -36
  223. package/src/cross-spawn.d.ts +0 -5
  224. package/src/event-queues.ts +0 -41
  225. package/src/events.ts +0 -29
  226. package/src/index.ts +0 -111
  227. package/src/linked-device.ts +0 -52
  228. package/src/mcp-handler.ts +0 -200
  229. package/src/mcp-tools.ts +0 -839
  230. package/src/nats-client.ts +0 -19
  231. package/src/network.ts +0 -96
  232. package/src/notification-store.ts +0 -30
  233. package/src/pending-requests.ts +0 -73
  234. package/src/platform/index.ts +0 -20
  235. package/src/platform/linux.ts +0 -296
  236. package/src/platform/macos.ts +0 -329
  237. package/src/platform/platform.ts +0 -31
  238. package/src/platform/windows.ts +0 -299
  239. package/src/rpc-handler.ts +0 -694
  240. package/src/sms-store.ts +0 -28
  241. package/src/spawn-command.ts +0 -123
  242. package/src/task.ts +0 -330
  243. package/src/transports/http-transport.ts +0 -478
  244. package/src/transports/nats-transport.ts +0 -76
  245. package/src/types.ts +0 -89
  246. package/src/update-checker.ts +0 -40
  247. package/test/agent-instructions.test.ts +0 -209
  248. package/test/agent-output-parsing.test.ts +0 -74
  249. package/test/linux-cron.test.ts +0 -41
  250. package/test/macos-plist.test.ts +0 -112
  251. package/test/notification-store.test.ts +0 -57
  252. package/test/pairing.test.ts +0 -35
  253. package/test/result-state.test.ts +0 -110
  254. package/test/task-parsing.test.ts +0 -82
  255. package/test/taskrun-messages.test.ts +0 -224
  256. package/test/tsconfig.json +0 -9
  257. package/test/windows-xml.test.ts +0 -89
  258. 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: "Send Email", 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,114 +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 WifiIcon() {
19
- return (
20
- <svg {...SVG_PROPS} aria-hidden="true">
21
- <path d="M5 12.55a11 11 0 0 1 14.08 0" />
22
- <path d="M1.42 9a16 16 0 0 1 21.16 0" />
23
- <path d="M8.53 16.11a6 6 0 0 1 6.95 0" />
24
- <line x1="12" y1="20" x2="12.01" y2="20" />
25
- </svg>
26
- );
27
- }
28
-
29
- function GlobeIcon() {
30
- return (
31
- <svg {...SVG_PROPS} aria-hidden="true">
32
- <circle cx="12" cy="12" r="10" />
33
- <line x1="2" y1="12" x2="22" y2="12" />
34
- <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" />
35
- </svg>
36
- );
37
- }
38
-
39
- function WarningIcon() {
40
- return (
41
- <svg {...SVG_PROPS} aria-hidden="true">
42
- <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" />
43
- <line x1="12" y1="9" x2="12" y2="13" />
44
- <line x1="12" y1="17" x2="12.01" y2="17" />
45
- </svg>
46
- );
47
- }
48
-
49
- export default function ConnectionStatusIcon() {
50
- const { mode } = useHostConnection();
51
- const [popoverOpen, setPopoverOpen] = useState(false);
52
- const containerRef = useRef<HTMLDivElement>(null);
53
-
54
- useEffect(() => {
55
- if (!popoverOpen) return;
56
- function onPointerDown(e: PointerEvent) {
57
- if (!containerRef.current?.contains(e.target as Node)) setPopoverOpen(false);
58
- }
59
- document.addEventListener("pointerdown", onPointerDown);
60
- const timer = window.setTimeout(() => setPopoverOpen(false), 3000);
61
- return () => {
62
- document.removeEventListener("pointerdown", onPointerDown);
63
- clearTimeout(timer);
64
- };
65
- }, [popoverOpen]);
66
-
67
- if (mode === "direct") return null;
68
-
69
- let icon: React.ReactNode;
70
- let label: string;
71
- let modifier: string;
72
- switch (mode) {
73
- case "lan":
74
- icon = <WifiIcon />;
75
- label = "Connected via LAN";
76
- modifier = "lan";
77
- break;
78
- case "nats":
79
- icon = <GlobeIcon />;
80
- label = isNative ? "Connected via relay" : "Connected";
81
- modifier = "relay";
82
- break;
83
- case "disconnected":
84
- icon = <WarningIcon />;
85
- label = "Disconnected";
86
- modifier = "disconnected";
87
- break;
88
- case "connecting":
89
- default:
90
- icon = <GlobeIcon />;
91
- label = "Connecting\u2026";
92
- modifier = "connecting";
93
- break;
94
- }
95
-
96
- return (
97
- <div ref={containerRef} className={`conn-status conn-status--${modifier}`}>
98
- <button
99
- type="button"
100
- className="conn-status-btn"
101
- aria-label={label}
102
- onClick={() => setPopoverOpen((v) => !v)}
103
- >
104
- {icon}
105
- </button>
106
- <div
107
- className={`conn-status-popover ${popoverOpen ? "conn-status-popover--open" : ""}`}
108
- role="tooltip"
109
- >
110
- {label}
111
- </div>
112
- </div>
113
- );
114
- }