@peers-app/peers-ui 0.27.2 → 0.27.4

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 (309) hide show
  1. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +3 -1
  2. package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
  3. package/dist/screens/network-viewer/sync-recovery.js +103 -1
  4. package/dist/screens/variables/variable-details.js +2 -1
  5. package/dist/tabs-layout/network-status-indicator.js +26 -12
  6. package/dist/tabs-layout/use-network-status.d.ts +6 -1
  7. package/dist/tabs-layout/use-network-status.js +27 -4
  8. package/package.json +9 -3
  9. package/.claude/settings.local.json +0 -34
  10. package/.github/README.md +0 -52
  11. package/.github/workflows/delete-all-artifacts.yml +0 -49
  12. package/.github/workflows/publish.yml +0 -51
  13. package/babel.config.js +0 -7
  14. package/biome.json +0 -198
  15. package/dist/app-updates/app-updates-consumer.test.d.ts +0 -1
  16. package/dist/app-updates/app-updates-consumer.test.js +0 -56
  17. package/dist/app-updates/app-updates-settings.test.d.ts +0 -1
  18. package/dist/app-updates/app-updates-settings.test.js +0 -93
  19. package/dist/app-updates/remote-app-update-panel.test.d.ts +0 -1
  20. package/dist/app-updates/remote-app-update-panel.test.js +0 -165
  21. package/dist/components/access-level-field.test.d.ts +0 -1
  22. package/dist/components/access-level-field.test.js +0 -28
  23. package/dist/components/message-logs/tool-access-approval-card.test.d.ts +0 -1
  24. package/dist/components/message-logs/tool-access-approval-card.test.js +0 -102
  25. package/dist/components/responsive-section-nav.test.d.ts +0 -1
  26. package/dist/components/responsive-section-nav.test.js +0 -45
  27. package/dist/globals-hash.test.d.ts +0 -1
  28. package/dist/globals-hash.test.js +0 -16
  29. package/dist/hooks/use-observable-state.test.d.ts +0 -1
  30. package/dist/hooks/use-observable-state.test.js +0 -64
  31. package/dist/hooks/use-observable.test.d.ts +0 -1
  32. package/dist/hooks/use-observable.test.js +0 -191
  33. package/dist/hooks/use-rows.test.d.ts +0 -1
  34. package/dist/hooks/use-rows.test.js +0 -175
  35. package/dist/hooks/use-tool.test.d.ts +0 -1
  36. package/dist/hooks/use-tool.test.js +0 -190
  37. package/dist/host-shell/host-shell-consumer.test.d.ts +0 -1
  38. package/dist/host-shell/host-shell-consumer.test.js +0 -28
  39. package/dist/root-layout/root-layout-host.test.d.ts +0 -1
  40. package/dist/root-layout/root-layout-host.test.js +0 -86
  41. package/dist/screens/account/account-screen.test.d.ts +0 -1
  42. package/dist/screens/account/account-screen.test.js +0 -51
  43. package/dist/screens/assistants/assistant-config.test.d.ts +0 -1
  44. package/dist/screens/assistants/assistant-config.test.js +0 -57
  45. package/dist/screens/contacts/contact-list.test.d.ts +0 -1
  46. package/dist/screens/contacts/contact-list.test.js +0 -48
  47. package/dist/screens/device-pairing/device-pairing.test.d.ts +0 -1
  48. package/dist/screens/device-pairing/device-pairing.test.js +0 -119
  49. package/dist/screens/devices/devices-list.test.d.ts +0 -1
  50. package/dist/screens/devices/devices-list.test.js +0 -53
  51. package/dist/screens/groups/group-list.test.d.ts +0 -1
  52. package/dist/screens/groups/group-list.test.js +0 -38
  53. package/dist/screens/identity/identity-routes.test.d.ts +0 -1
  54. package/dist/screens/identity/identity-routes.test.js +0 -51
  55. package/dist/screens/identity/identity-screen.test.d.ts +0 -1
  56. package/dist/screens/identity/identity-screen.test.js +0 -103
  57. package/dist/screens/invites/accept-invite.test.d.ts +0 -1
  58. package/dist/screens/invites/accept-invite.test.js +0 -103
  59. package/dist/screens/invites/activity-view.test.d.ts +0 -1
  60. package/dist/screens/invites/activity-view.test.js +0 -108
  61. package/dist/screens/invites/invite-classify.test.d.ts +0 -1
  62. package/dist/screens/invites/invite-classify.test.js +0 -115
  63. package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +0 -1
  64. package/dist/screens/network-viewer/network-viewer-ipc.test.js +0 -61
  65. package/dist/screens/network-viewer/network-viewer.section.test.d.ts +0 -1
  66. package/dist/screens/network-viewer/network-viewer.section.test.js +0 -63
  67. package/dist/screens/network-viewer/sync-recovery.test.d.ts +0 -1
  68. package/dist/screens/network-viewer/sync-recovery.test.js +0 -104
  69. package/dist/screens/packages/package-helpers.test.d.ts +0 -1
  70. package/dist/screens/packages/package-helpers.test.js +0 -53
  71. package/dist/screens/packages/package-runtime-status.test.d.ts +0 -1
  72. package/dist/screens/packages/package-runtime-status.test.js +0 -50
  73. package/dist/screens/packages/packages-ipc.test.d.ts +0 -1
  74. package/dist/screens/packages/packages-ipc.test.js +0 -43
  75. package/dist/screens/settings/settings-page.test.d.ts +0 -1
  76. package/dist/screens/settings/settings-page.test.js +0 -19
  77. package/dist/screens/tools/tool-access-ipc.test.d.ts +0 -1
  78. package/dist/screens/tools/tool-access-ipc.test.js +0 -40
  79. package/dist/screens/tools/tool-catalog-ipc.test.d.ts +0 -1
  80. package/dist/screens/tools/tool-catalog-ipc.test.js +0 -52
  81. package/dist/tabs-layout/network-status-indicator.test.d.ts +0 -1
  82. package/dist/tabs-layout/network-status-indicator.test.js +0 -665
  83. package/dist/tabs-layout/tabs-state.test.d.ts +0 -1
  84. package/dist/tabs-layout/tabs-state.test.js +0 -110
  85. package/dist/tabs-layout/use-network-status.test.d.ts +0 -1
  86. package/dist/tabs-layout/use-network-status.test.js +0 -45
  87. package/dist/ui-router/ui-loader.test.d.ts +0 -1
  88. package/dist/ui-router/ui-loader.test.js +0 -91
  89. package/jest.config.js +0 -25
  90. package/release-guard.js +0 -10
  91. package/src/app-updates/app-update-controls.tsx +0 -73
  92. package/src/app-updates/app-updates-consumer.test.ts +0 -83
  93. package/src/app-updates/app-updates-consumer.ts +0 -121
  94. package/src/app-updates/app-updates-settings.test.tsx +0 -109
  95. package/src/app-updates/app-updates-settings.tsx +0 -136
  96. package/src/app-updates/remote-app-update-panel.test.tsx +0 -205
  97. package/src/app-updates/remote-app-update-panel.tsx +0 -210
  98. package/src/app-updates/use-app-update-status.ts +0 -16
  99. package/src/command-palette/command-palette-ui.tsx +0 -564
  100. package/src/command-palette/command-palette.ts +0 -242
  101. package/src/components/access-level-field.test.tsx +0 -32
  102. package/src/components/access-level-field.tsx +0 -68
  103. package/src/components/checkbox.tsx +0 -25
  104. package/src/components/group-switcher.tsx +0 -526
  105. package/src/components/input-date.tsx +0 -29
  106. package/src/components/input-number.tsx +0 -83
  107. package/src/components/input.tsx +0 -25
  108. package/src/components/inverse-lazy-list.tsx +0 -107
  109. package/src/components/io-schema-values.tsx +0 -103
  110. package/src/components/io-schema.tsx +0 -259
  111. package/src/components/lazy-list.tsx +0 -115
  112. package/src/components/list-screen.tsx +0 -126
  113. package/src/components/loading-indicator.tsx +0 -7
  114. package/src/components/markdown-editor/autolink-plugin.tsx +0 -29
  115. package/src/components/markdown-editor/editor-inline.tsx +0 -9
  116. package/src/components/markdown-editor/editor.tsx +0 -158
  117. package/src/components/markdown-editor/markdown-plugin.tsx +0 -202
  118. package/src/components/markdown-editor/mention-node.ts +0 -204
  119. package/src/components/markdown-editor/mentions-plugin.tsx +0 -354
  120. package/src/components/markdown-editor/move-line-plugin.tsx +0 -186
  121. package/src/components/markdown-editor/select-line-boundary-plugin.tsx +0 -95
  122. package/src/components/markdown-editor/theme.ts +0 -46
  123. package/src/components/markdown-editor/toolbar.tsx +0 -269
  124. package/src/components/markdown-with-mentions.tsx +0 -198
  125. package/src/components/message-logs/message-logs.tsx +0 -523
  126. package/src/components/message-logs/tool-access-approval-card.test.tsx +0 -127
  127. package/src/components/message-logs/tool-access-approval-card.tsx +0 -178
  128. package/src/components/messages/avatar.tsx +0 -171
  129. package/src/components/messages/channel-message-list.tsx +0 -189
  130. package/src/components/messages/channel-view.tsx +0 -92
  131. package/src/components/messages/message-compose.tsx +0 -192
  132. package/src/components/messages/message-display.tsx +0 -231
  133. package/src/components/messages/thread-message-list.tsx +0 -135
  134. package/src/components/qr-scanner.tsx +0 -180
  135. package/src/components/responsive-section-nav.test.tsx +0 -88
  136. package/src/components/responsive-section-nav.tsx +0 -92
  137. package/src/components/router.tsx +0 -175
  138. package/src/components/save-button.tsx +0 -134
  139. package/src/components/sortable-list.tsx +0 -130
  140. package/src/components/tabs.tsx +0 -76
  141. package/src/components/tooltip.tsx +0 -43
  142. package/src/components/trust-level-badge.tsx +0 -44
  143. package/src/components/trust-level-dropdown.tsx +0 -136
  144. package/src/components/typeahead.tsx +0 -357
  145. package/src/globals-hash.test.ts +0 -19
  146. package/src/globals.tsx +0 -226
  147. package/src/hooks/index.ts +0 -8
  148. package/src/hooks/use-host-contract.ts +0 -75
  149. package/src/hooks/use-observable-state.test.tsx +0 -101
  150. package/src/hooks/use-observable-state.ts +0 -29
  151. package/src/hooks/use-observable.test.tsx +0 -258
  152. package/src/hooks/use-observable.ts +0 -67
  153. package/src/hooks/use-on-screen.ts +0 -24
  154. package/src/hooks/use-promise.ts +0 -34
  155. package/src/hooks/use-rows.test.tsx +0 -196
  156. package/src/hooks/use-rows.ts +0 -120
  157. package/src/hooks/use-subscription.ts +0 -24
  158. package/src/hooks/use-tool.test.tsx +0 -225
  159. package/src/hooks/use-tool.ts +0 -116
  160. package/src/host-shell/host-shell-consumer.test.ts +0 -32
  161. package/src/host-shell/host-shell-consumer.ts +0 -53
  162. package/src/index.tsx +0 -42
  163. package/src/mention-configs.ts +0 -185
  164. package/src/operator-console/operator-console-state.ts +0 -119
  165. package/src/operator-console/operator-console.tsx +0 -233
  166. package/src/peers-services-registration.ts +0 -6
  167. package/src/root-layout/global-overlays.ts +0 -50
  168. package/src/root-layout/layout-picker.tsx +0 -47
  169. package/src/root-layout/layouts/full-screen-layout.tsx +0 -144
  170. package/src/root-layout/layouts/tabs-with-console-layout.tsx +0 -260
  171. package/src/root-layout/root-layout-host.test.tsx +0 -108
  172. package/src/root-layout/root-layout-host.tsx +0 -147
  173. package/src/root-layout/root-layout-registry.ts +0 -51
  174. package/src/root-layout/root-layout-state.ts +0 -55
  175. package/src/root-layout/root-layout.ts +0 -32
  176. package/src/screens/account/account-screen.test.tsx +0 -65
  177. package/src/screens/account/account-screen.tsx +0 -942
  178. package/src/screens/assistants/assistant-config.test.tsx +0 -54
  179. package/src/screens/assistants/assistant-config.tsx +0 -121
  180. package/src/screens/assistants/assistant-details.tsx +0 -89
  181. package/src/screens/assistants/assistant-info.tsx +0 -66
  182. package/src/screens/assistants/assistant-list.tsx +0 -145
  183. package/src/screens/assistants/assistant-tools.tsx +0 -82
  184. package/src/screens/console-logs/console-logs-list.tsx +0 -374
  185. package/src/screens/console-logs/log-display.tsx +0 -137
  186. package/src/screens/console-logs/log-filters.tsx +0 -64
  187. package/src/screens/console-logs/mobile-log-card.tsx +0 -161
  188. package/src/screens/console-logs/resizable-table-header.tsx +0 -110
  189. package/src/screens/contacts/contact-details.tsx +0 -123
  190. package/src/screens/contacts/contact-list.test.tsx +0 -59
  191. package/src/screens/contacts/contact-list.tsx +0 -286
  192. package/src/screens/contacts/index.ts +0 -2
  193. package/src/screens/data-explorer/data-explorer.tsx +0 -633
  194. package/src/screens/data-explorer/index.ts +0 -6
  195. package/src/screens/data-explorer/query-executor.tsx +0 -520
  196. package/src/screens/device-pairing/device-pairing.test.tsx +0 -143
  197. package/src/screens/device-pairing/device-pairing.tsx +0 -461
  198. package/src/screens/device-pairing/identity-ipc.ts +0 -25
  199. package/src/screens/device-pairing/index.ts +0 -2
  200. package/src/screens/device-pairing/use-device-pairing-state.ts +0 -15
  201. package/src/screens/devices/devices-list.test.tsx +0 -68
  202. package/src/screens/devices/devices-list.tsx +0 -365
  203. package/src/screens/devices/index.ts +0 -1
  204. package/src/screens/groups/group-details.tsx +0 -160
  205. package/src/screens/groups/group-invite-panel.tsx +0 -311
  206. package/src/screens/groups/group-list.test.tsx +0 -44
  207. package/src/screens/groups/group-list.tsx +0 -317
  208. package/src/screens/groups/group-members.tsx +0 -343
  209. package/src/screens/groups/index.ts +0 -3
  210. package/src/screens/identity/identity-routes.test.ts +0 -70
  211. package/src/screens/identity/identity-routes.ts +0 -301
  212. package/src/screens/identity/identity-screen.test.tsx +0 -138
  213. package/src/screens/identity/identity-screen.tsx +0 -282
  214. package/src/screens/identity/index.ts +0 -2
  215. package/src/screens/identity/invite-contact-panel.tsx +0 -45
  216. package/src/screens/identity/profile-invite.tsx +0 -93
  217. package/src/screens/invites/accept-invite.test.tsx +0 -115
  218. package/src/screens/invites/accept-invite.tsx +0 -318
  219. package/src/screens/invites/activity-view.test.tsx +0 -127
  220. package/src/screens/invites/activity-view.tsx +0 -162
  221. package/src/screens/invites/index.ts +0 -6
  222. package/src/screens/invites/invite-capture.ts +0 -104
  223. package/src/screens/invites/invite-classify.test.ts +0 -129
  224. package/src/screens/invites/invite-share.tsx +0 -104
  225. package/src/screens/invites/invites-ipc.ts +0 -25
  226. package/src/screens/invites/pending-invites-list.tsx +0 -256
  227. package/src/screens/network-viewer/connection-troubleshooter.tsx +0 -395
  228. package/src/screens/network-viewer/cpu-usage-graph.tsx +0 -294
  229. package/src/screens/network-viewer/device-details-modal.tsx +0 -339
  230. package/src/screens/network-viewer/group-details-modal.tsx +0 -421
  231. package/src/screens/network-viewer/index.ts +0 -8
  232. package/src/screens/network-viewer/network-viewer-ipc.test.ts +0 -72
  233. package/src/screens/network-viewer/network-viewer-ipc.ts +0 -39
  234. package/src/screens/network-viewer/network-viewer-section.ts +0 -12
  235. package/src/screens/network-viewer/network-viewer.section.test.tsx +0 -68
  236. package/src/screens/network-viewer/network-viewer.tsx +0 -708
  237. package/src/screens/network-viewer/remote-logs-panel.tsx +0 -190
  238. package/src/screens/network-viewer/sync-recovery.test.tsx +0 -114
  239. package/src/screens/network-viewer/sync-recovery.tsx +0 -461
  240. package/src/screens/network-viewer/usage-graph.tsx +0 -455
  241. package/src/screens/packages/local-folder-grants-panel.tsx +0 -290
  242. package/src/screens/packages/local-folders-ipc.ts +0 -28
  243. package/src/screens/packages/package-details.tsx +0 -166
  244. package/src/screens/packages/package-helpers.test.ts +0 -57
  245. package/src/screens/packages/package-helpers.ts +0 -137
  246. package/src/screens/packages/package-info.tsx +0 -354
  247. package/src/screens/packages/package-list.tsx +0 -519
  248. package/src/screens/packages/package-network-panel.tsx +0 -82
  249. package/src/screens/packages/package-new-local.tsx +0 -100
  250. package/src/screens/packages/package-runtime-status.test.tsx +0 -58
  251. package/src/screens/packages/package-runtime-status.tsx +0 -68
  252. package/src/screens/packages/package-versions.tsx +0 -298
  253. package/src/screens/packages/packages-ipc.test.ts +0 -47
  254. package/src/screens/packages/packages-ipc.ts +0 -125
  255. package/src/screens/peer-types/peer-type-details.tsx +0 -172
  256. package/src/screens/peer-types/peer-type-list.tsx +0 -64
  257. package/src/screens/search/global-search.tsx +0 -406
  258. package/src/screens/settings/color-mode-dropdown.tsx +0 -82
  259. package/src/screens/settings/settings-page.test.tsx +0 -24
  260. package/src/screens/settings/settings-page.tsx +0 -426
  261. package/src/screens/setup-user.tsx +0 -355
  262. package/src/screens/tools/tool-access-ipc.test.ts +0 -55
  263. package/src/screens/tools/tool-access-ipc.ts +0 -27
  264. package/src/screens/tools/tool-catalog-ipc.test.ts +0 -52
  265. package/src/screens/tools/tool-catalog-ipc.ts +0 -51
  266. package/src/screens/tools/tool-details.tsx +0 -52
  267. package/src/screens/tools/tool-info.tsx +0 -45
  268. package/src/screens/tools/tool-list.tsx +0 -106
  269. package/src/screens/tools/tool-schema.tsx +0 -44
  270. package/src/screens/tools/tool-test-details.tsx +0 -89
  271. package/src/screens/tools/tool-test-list.tsx +0 -73
  272. package/src/screens/variables/variable-details.tsx +0 -289
  273. package/src/screens/variables/variable-list.tsx +0 -110
  274. package/src/screens/welcome-modal.tsx +0 -294
  275. package/src/screens/workflows/workflow-details.tsx +0 -115
  276. package/src/screens/workflows/workflow-info.tsx +0 -22
  277. package/src/screens/workflows/workflow-instructions.tsx +0 -134
  278. package/src/screens/workflows/workflow-list.tsx +0 -117
  279. package/src/setupTests.ts +0 -38
  280. package/src/system-apps/assistants.app.ts +0 -7
  281. package/src/system-apps/console-logs.app.ts +0 -7
  282. package/src/system-apps/data-explorer.app.ts +0 -8
  283. package/src/system-apps/identity.app.ts +0 -9
  284. package/src/system-apps/index.ts +0 -77
  285. package/src/system-apps/mobile-settings.app.ts +0 -7
  286. package/src/system-apps/network-viewer.app.ts +0 -8
  287. package/src/system-apps/packages.app.ts +0 -7
  288. package/src/system-apps/search.app.ts +0 -8
  289. package/src/system-apps/settings.app.ts +0 -7
  290. package/src/system-apps/threads.app.ts +0 -7
  291. package/src/system-apps/tools.app.ts +0 -7
  292. package/src/system-apps/types.app.ts +0 -7
  293. package/src/system-apps/variables.app.ts +0 -7
  294. package/src/system-apps/workflows.app.ts +0 -7
  295. package/src/tabs-layout/network-status-indicator.test.tsx +0 -799
  296. package/src/tabs-layout/network-status-indicator.tsx +0 -756
  297. package/src/tabs-layout/tabs-layout.tsx +0 -857
  298. package/src/tabs-layout/tabs-state.test.ts +0 -152
  299. package/src/tabs-layout/tabs-state.ts +0 -587
  300. package/src/tabs-layout/use-network-status.test.tsx +0 -58
  301. package/src/tabs-layout/use-network-status.ts +0 -68
  302. package/src/ui-defaults/index.ts +0 -2
  303. package/src/ui-defaults/list-screen.tsx +0 -94
  304. package/src/ui-defaults/markdown-field.tsx +0 -87
  305. package/src/ui-router/routes-loader.ts +0 -193
  306. package/src/ui-router/ui-loader.test.tsx +0 -108
  307. package/src/ui-router/ui-loader.tsx +0 -606
  308. package/src/utils.ts +0 -95
  309. package/tsconfig.json +0 -18
@@ -1,587 +0,0 @@
1
- import {
2
- groupDeviceVar,
3
- groupUserVar,
4
- type IAppNav,
5
- newid,
6
- type Observable,
7
- observable,
8
- } from "@peers-app/peers-sdk";
9
- import { _mainContentPath } from "../globals";
10
- import {
11
- canonicalizeRoute,
12
- IDENTITY_APP_PATH,
13
- isIdentityPath,
14
- parseRoute,
15
- } from "../screens/identity/identity-routes";
16
- import { systemPackage } from "../system-apps";
17
- import { allPackages, type IPackageWithNavs } from "../ui-router/routes-loader";
18
-
19
- /** One top-level app tab. */
20
- export interface TabState {
21
- tabId: string;
22
- packageId?: string; // 'launcher' for app launcher tab
23
- path: string;
24
- title: string;
25
- iconClassName?: string;
26
- }
27
-
28
- /** The app launcher tab. It is always present and cannot be closed. */
29
- export const launcherApp: TabState = {
30
- tabId: "launcher",
31
- packageId: "launcher",
32
- path: "",
33
- title: "Apps",
34
- iconClassName: "bi-grid-3x3-gap",
35
- };
36
-
37
- // Persistent vars for storage (write-only, no subscription to avoid oscillation)
38
- const _persistentActiveTabs = groupDeviceVar<TabState[]>("activeTabs", {
39
- defaultValue: [launcherApp],
40
- });
41
-
42
- const _persistentActiveTabId = groupDeviceVar<string>("activeTabId", {
43
- defaultValue: "launcher",
44
- });
45
-
46
- const _persistentRecentlyUsedApps = groupUserVar<string[]>("recentlyUsedApps", {
47
- defaultValue: [],
48
- });
49
-
50
- let finishNavigationRestore: () => void = () => {};
51
- const navigationReady = new Promise<void>((resolve) => {
52
- finishNavigationRestore = resolve;
53
- });
54
-
55
- // In-memory observables for UI state (loaded once, then write-through to persistent vars)
56
- /** Open tabs. Writes through to device storage after the initial restore. */
57
- export const activeTabs: Observable<TabState[]> & { loadingPromise: Promise<void> } = (() => {
58
- const obs = observable<TabState[]>([launcherApp]);
59
-
60
- // Write-through to persistent var on change
61
- obs.subscribe((value) => {
62
- _persistentActiveTabs(value);
63
- });
64
-
65
- return Object.assign(obs, { loadingPromise: navigationReady });
66
- })();
67
-
68
- /** Id of the tab whose path is showing. */
69
- export const activeTabId: Observable<string> & { loadingPromise: Promise<void> } = (() => {
70
- const obs = observable<string>("launcher");
71
-
72
- // Write-through to persistent var on change
73
- obs.subscribe((value) => {
74
- _persistentActiveTabId(value);
75
- });
76
-
77
- return Object.assign(obs, { loadingPromise: navigationReady });
78
- })();
79
-
80
- /** App roots recently opened from the launcher, newest first. */
81
- export const recentlyUsedApps: Observable<string[]> & { loadingPromise: Promise<void> } = (() => {
82
- const obs = observable<string[]>([]);
83
-
84
- // Write-through to persistent var on change
85
- obs.subscribe((value) => {
86
- _persistentRecentlyUsedApps(value);
87
- });
88
-
89
- return Object.assign(obs, { loadingPromise: navigationReady });
90
- })();
91
-
92
- void Promise.all([
93
- _persistentActiveTabs.loadingPromise,
94
- _persistentActiveTabId.loadingPromise,
95
- _persistentRecentlyUsedApps.loadingPromise,
96
- ]).then(() => {
97
- const restored = restoreNavigationState(_persistentActiveTabs(), _persistentActiveTabId());
98
- activeTabs(restored.tabs);
99
- if (activeTabId() !== restored.activeId) {
100
- activeTabId(restored.activeId);
101
- }
102
- recentlyUsedApps(migrateRecentPaths(_persistentRecentlyUsedApps()));
103
- finishNavigationRestore();
104
- });
105
-
106
- /** Tab ids whose bodies have been mounted at least once. */
107
- export const initializedTabs = new Set<string>();
108
-
109
- /** Options for {@link navigateTo}. */
110
- export interface NavigateOptions {
111
- /**
112
- * When the requested path is an app's root and that app is already open,
113
- * activate the existing tab without resetting its section.
114
- */
115
- preserveSectionOnAppRoot?: boolean;
116
- }
117
-
118
- /**
119
- * Open a path in the tab that owns its app.
120
- *
121
- * Legacy identity aliases are rewritten first. A route owned by the current
122
- * app replaces that tab's path. A route owned by another open app activates
123
- * that app's tab. Otherwise a new tab is opened.
124
- *
125
- * @param rawPath - Requested route, with or without a query string.
126
- * @param options - App-root behavior. Launcher opens preserve the current section.
127
- */
128
- export function navigateTo(rawPath: string, options?: NavigateOptions) {
129
- const requestedPathname = parseRoute(rawPath).pathname;
130
- if (!requestedPathname) {
131
- const launcher = activeTabs().find((tab) => tab.tabId === "launcher") ?? activeTabs()[0];
132
- if (launcher && activeTabId() !== launcher.tabId) {
133
- activeTabId(launcher.tabId);
134
- }
135
- return;
136
- }
137
-
138
- const next = canonicalizeRoute(rawPath);
139
- const app = determineAppFromPath(next);
140
- if (options?.preserveSectionOnAppRoot && app && isAppRootRequest(requestedPathname, app)) {
141
- const existing = findTabForApp(app);
142
- if (existing) {
143
- if (activeTabId() !== existing.tabId) {
144
- activeTabId(existing.tabId);
145
- }
146
- rememberRecentApp(existing.path);
147
- return;
148
- }
149
- }
150
-
151
- if (!app || app.package.packageId === "launcher") {
152
- openUnresolved(next);
153
- return;
154
- }
155
-
156
- const current = activeTabs().find((tab) => tab.tabId === activeTabId());
157
- const currentApp = current ? determineAppFromPath(current.path) : undefined;
158
- if (current && current.packageId !== "launcher" && currentApp && sameApp(currentApp, app)) {
159
- showTab(current.tabId, next);
160
- return;
161
- }
162
-
163
- const existing = findTabForApp(app);
164
- if (existing) {
165
- showTab(existing.tabId, next);
166
- return;
167
- }
168
-
169
- openNewTab({
170
- path: next,
171
- title: app.navItem.displayName || app.navItem.name,
172
- packageId: app.package.packageId,
173
- iconClassName: app.navItem.iconClassName,
174
- });
175
- }
176
-
177
- /**
178
- * Open an app or path from the launcher.
179
- *
180
- * Choosing an app that is already open keeps the section the user was on.
181
- *
182
- * @param path - App root or deep link.
183
- */
184
- export function goToTabPath(path: string) {
185
- navigateTo(path, { preserveSectionOnAppRoot: true });
186
- }
187
-
188
- /**
189
- * Apply a {@link mainContentPath} write to the open tabs.
190
- *
191
- * @param _oldPath - Previous route. Retained for existing call sites.
192
- * @param newPath - Requested route.
193
- * @param _setNewMainPath - Direct path setter. Navigation updates the path itself.
194
- */
195
- export const handleMainPathChanged = (
196
- _oldPath: string,
197
- newPath: string,
198
- _setNewMainPath: (path: string) => void,
199
- ) => {
200
- navigateTo(newPath);
201
- };
202
-
203
- /**
204
- * Rewrite stored tabs onto canonical routes.
205
- *
206
- * Contacts, Groups, and Devices tabs from before the Identity app become one
207
- * Identity tab. The active tab is kept when it is one of those duplicates.
208
- *
209
- * @param tabs - Persisted tabs.
210
- * @param activeId - Persisted active tab id.
211
- */
212
- export function restoreNavigationState(
213
- tabs: TabState[],
214
- activeId: string,
215
- ): { tabs: TabState[]; activeId: string } {
216
- const migrated = (tabs.length > 0 ? tabs : [launcherApp]).map(migrateStoredTab);
217
- const merged = mergeIdentityTabs(migrated, activeId);
218
- const activeExists = merged.tabs.some((tab) => tab.tabId === merged.activeId);
219
- return {
220
- tabs: merged.tabs,
221
- activeId: activeExists ? merged.activeId : merged.tabs[0].tabId,
222
- };
223
- }
224
-
225
- /**
226
- * Map a stored recent-app path onto the current app root.
227
- *
228
- * @param path - Persisted recent path, possibly a legacy alias.
229
- */
230
- export function migrateRecentPath(path: string): string {
231
- const canonical = canonicalizeRoute(path);
232
- if (isIdentityPath(canonical)) {
233
- return IDENTITY_APP_PATH;
234
- }
235
- return canonical;
236
- }
237
-
238
- type AppInfo = { navItem: IAppNav; package: IPackageWithNavs };
239
-
240
- /**
241
- * Resolve the app that owns a route.
242
- *
243
- * @param path - Route, with or without a query string.
244
- * @returns The matching app, the launcher for an empty path, or `undefined`.
245
- */
246
- export function determineAppFromPath(path: string): AppInfo | undefined {
247
- const launcherInfo = {
248
- navItem: {
249
- name: "Apps",
250
- navigationPath: "",
251
- iconClassName: launcherApp.iconClassName || "bi-grid-3x3-gap",
252
- },
253
- package: systemPackage,
254
- };
255
- if (!path || path === "" || path === "/") {
256
- return launcherInfo;
257
- }
258
-
259
- // Remove leading/trailing slashes and query params
260
- path = path.toLowerCase().trim().split("?")[0];
261
- if (path.startsWith("/")) {
262
- path = path.substring(1);
263
- }
264
- const rootPath = path.split("/")[0];
265
- const _allPackages = [systemPackage, ...allPackages()]; // WARNING allPackages might not be loaded yet
266
-
267
- const allAppInfos: AppInfo[] = _allPackages
268
- .filter((pkg) => (pkg.appNavs?.length ?? 0) > 0)
269
- .flatMap((pkg) => {
270
- const navs = pkg.appNavs;
271
- if (!navs) return [];
272
- return navs.map((navItem) => ({ navItem, package: pkg }));
273
- });
274
-
275
- // Find the most relevant app based on path
276
- for (const appInfo of allAppInfos) {
277
- const navPath = appInfo.navItem.navigationPath || appInfo.navItem.name.toLowerCase();
278
-
279
- // Check for exact match or path starting with nav path
280
- if (rootPath === navPath || rootPath.startsWith(`${navPath}/`)) {
281
- // Return the first matching app
282
- return {
283
- navItem: appInfo.navItem,
284
- package: appInfo.package,
285
- };
286
- }
287
- }
288
-
289
- // look for package-specific routes
290
- // uri in format of "package-nav/{packageId}/{path}"
291
- const packageNavPrefix = "package-nav/";
292
- if (path.startsWith(packageNavPrefix)) {
293
- const parts = path.substring(packageNavPrefix.length).split("/");
294
- if (parts.length > 0) {
295
- const packageId = parts[0];
296
- const pkg = _allPackages.find((p) => p.packageId === packageId);
297
- if (pkg) {
298
- const subPath = parts.slice(1).join("/");
299
- const navItem = pkg.appNavs?.find((n) => {
300
- const navPath = n.navigationPath || n.name.toLowerCase();
301
- return subPath === navPath || subPath.startsWith(`${navPath}/`);
302
- });
303
- if (navItem) {
304
- return {
305
- navItem,
306
- package: pkg,
307
- };
308
- }
309
- }
310
- }
311
- }
312
-
313
- console.warn(`No app found for path: ${path}`);
314
- return undefined;
315
- }
316
-
317
- function appKey(app: AppInfo): string {
318
- const nav = app.navItem.navigationPath || app.navItem.name.toLowerCase();
319
- return `${app.package.packageId}:${nav}`;
320
- }
321
-
322
- function sameApp(left: AppInfo, right: AppInfo): boolean {
323
- return appKey(left) === appKey(right);
324
- }
325
-
326
- function isAppRootRequest(pathname: string, app: AppInfo): boolean {
327
- const nav = (app.navItem.navigationPath || app.navItem.name).toLowerCase();
328
- return pathname.toLowerCase() === nav;
329
- }
330
-
331
- function findTabForApp(app: AppInfo): TabState | undefined {
332
- const key = appKey(app);
333
- return activeTabs().find((tab) => {
334
- const info = determineAppFromPath(tab.path);
335
- return info ? appKey(info) === key : false;
336
- });
337
- }
338
-
339
- /**
340
- * Replace a tab's path immutably so the memoized tab body re-renders.
341
- * Activating a different tab lets the tab subscription publish the path.
342
- */
343
- function showTab(tabId: string, path: string) {
344
- const tabs = activeTabs();
345
- const index = tabs.findIndex((tab) => tab.tabId === tabId);
346
- if (index < 0) {
347
- return;
348
- }
349
- const current = tabs[index];
350
- if (current.path !== path) {
351
- const nextTabs = [...tabs];
352
- nextTabs[index] = { ...current, path };
353
- activeTabs(nextTabs);
354
- }
355
- if (activeTabId() !== tabId) {
356
- activeTabId(tabId);
357
- } else if (_mainContentPath() !== path) {
358
- _mainContentPath(path);
359
- }
360
- rememberRecentApp(path);
361
- }
362
-
363
- function openUnresolved(path: string) {
364
- const current = activeTabs().find((tab) => tab.tabId === activeTabId());
365
- if (current && current.packageId === "system-apps") {
366
- showTab(current.tabId, path);
367
- return;
368
- }
369
- openNewTab({
370
- path,
371
- title: "New Tab",
372
- packageId: "system-apps",
373
- iconClassName: "bi-question-circle",
374
- });
375
- }
376
-
377
- function rememberRecentApp(path: string) {
378
- const app = determineAppFromPath(canonicalizeRoute(path));
379
- if (!app || app.package.packageId === "launcher") {
380
- return;
381
- }
382
- const recentPath =
383
- app.package.packageId === systemPackage.packageId ? app.navItem.navigationPath || path : path;
384
- if (!recentPath) {
385
- return;
386
- }
387
- const recent = recentlyUsedApps();
388
- const filtered = recent.filter((entry) => entry !== recentPath);
389
- const next = [recentPath, ...filtered].slice(0, 8);
390
- if (next.length === recent.length && next.every((entry, index) => entry === recent[index])) {
391
- return;
392
- }
393
- recentlyUsedApps(next);
394
- }
395
-
396
- function migrateStoredTab(tab: TabState): TabState {
397
- const path = canonicalizeRoute(tab.path);
398
- if (path === tab.path) {
399
- return tab;
400
- }
401
- const app = determineAppFromPath(path);
402
- const identity = app?.navItem.navigationPath === IDENTITY_APP_PATH;
403
- return {
404
- ...tab,
405
- path,
406
- packageId: app?.package.packageId ?? tab.packageId,
407
- title: identity ? app?.navItem.displayName || app?.navItem.name || tab.title : tab.title,
408
- iconClassName: identity ? app?.navItem.iconClassName || tab.iconClassName : tab.iconClassName,
409
- };
410
- }
411
-
412
- function mergeIdentityTabs(
413
- tabs: TabState[],
414
- activeId: string,
415
- ): { tabs: TabState[]; activeId: string } {
416
- const identityTabs = tabs.filter((tab) => isIdentityPath(tab.path));
417
- if (identityTabs.length <= 1) {
418
- return { tabs, activeId };
419
- }
420
- const keeper = identityTabs.find((tab) => tab.tabId === activeId) ?? identityTabs[0];
421
- const remove = new Set(
422
- identityTabs.filter((tab) => tab.tabId !== keeper.tabId).map((tab) => tab.tabId),
423
- );
424
- return {
425
- tabs: tabs.filter((tab) => !remove.has(tab.tabId)),
426
- activeId: remove.has(activeId) ? keeper.tabId : activeId,
427
- };
428
- }
429
-
430
- function migrateRecentPaths(paths: string[]): string[] {
431
- const seen = new Set<string>();
432
- const next: string[] = [];
433
- for (const path of paths) {
434
- const migrated = migrateRecentPath(path);
435
- if (!migrated || seen.has(migrated)) {
436
- continue;
437
- }
438
- seen.add(migrated);
439
- next.push(migrated);
440
- }
441
- return next.slice(0, 8);
442
- }
443
-
444
- /**
445
- * Set the active tab's title.
446
- *
447
- * @param newTitle - Title shown on the tab strip.
448
- */
449
- export const updateActiveTabTitle = (newTitle: string) => {
450
- // TODO Fix this to use a more reliable update mechanism
451
- // using setTimeout to get around a race condition for setting title on new tabs
452
- setTimeout(() => {
453
- const currentTabs = activeTabs();
454
- const activeTab = currentTabs.find((t) => t.tabId === activeTabId());
455
- if (activeTab) {
456
- activeTab.title = newTitle;
457
- activeTabs([...currentTabs]);
458
- }
459
- }, 100);
460
- };
461
-
462
- // Single-tab mode: when enabled, opening an app replaces the current screen instead of
463
- // appending a tab. Used by the full-screen root layout so it shows one screen at a time.
464
- let singleTabModeEnabled = false;
465
-
466
- /**
467
- * Enable or disable single-tab mode.
468
- *
469
- * When enabled, {@link openNewTab} replaces the active (non-launcher) screen in place
470
- * rather than appending a new tab, so single-screen layouts (e.g. full-screen) never
471
- * accumulate tabs. This is non-destructive: any pre-existing tabs are left untouched and
472
- * reappear when a multi-tab layout becomes active again. Layouts should enable it on mount
473
- * and disable it on unmount.
474
- */
475
- export const setSingleTabMode = (enabled: boolean) => {
476
- singleTabModeEnabled = enabled;
477
- };
478
-
479
- /**
480
- * Open a tab, or activate an existing tab with the same package and path.
481
- *
482
- * @param tab - Tab contents. `tabId` is assigned here.
483
- * @param forceNew - When true, always append a tab instead of reusing one.
484
- */
485
- export const openNewTab = (tab: Omit<TabState, "tabId">, forceNew = false) => {
486
- const newTab: TabState = {
487
- ...tab,
488
- tabId: newid(),
489
- };
490
-
491
- const currentTabs = activeTabs();
492
- const existingTabIndex = currentTabs.findIndex(
493
- (t) => t.packageId === tab.packageId && t.path === tab.path,
494
- );
495
-
496
- if (existingTabIndex >= 0 && !forceNew) {
497
- // Switch to existing tab
498
- activeTabId(currentTabs[existingTabIndex].tabId);
499
- } else if (singleTabModeEnabled) {
500
- // Single-tab mode: replace the active non-launcher screen in place. From the launcher
501
- // (home), append once so the launcher stays put and the new app becomes the single
502
- // app slot.
503
- const activeIndex = currentTabs.findIndex((t) => t.tabId === activeTabId());
504
- const canReplace = activeIndex >= 0 && currentTabs[activeIndex].tabId !== "launcher";
505
- if (canReplace) {
506
- const newTabs = [...currentTabs];
507
- newTabs[activeIndex] = newTab;
508
- activeTabs(newTabs);
509
- } else {
510
- activeTabs([...currentTabs, newTab]);
511
- }
512
- activeTabId(newTab.tabId);
513
- } else {
514
- // Always add new tab (for app launcher or when forceNew is true)
515
- activeTabs([...currentTabs, newTab]);
516
- activeTabId(newTab.tabId);
517
- }
518
-
519
- if (tab.path && tab.packageId !== "launcher") {
520
- rememberRecentApp(tab.path);
521
- }
522
- };
523
-
524
- /** Close the active tab. The launcher tab stays open. */
525
- export const closeCurrentTab = () => {
526
- const currentTabs = activeTabs();
527
- const _activeTabId = activeTabId();
528
- const tabToClose = currentTabs.find((t) => t.tabId === _activeTabId);
529
-
530
- if (!tabToClose || tabToClose.tabId === "launcher") return;
531
-
532
- const closedIndex = currentTabs.findIndex((t) => t.tabId === _activeTabId);
533
- const newTabs = currentTabs.filter((t) => t.tabId !== _activeTabId);
534
- activeTabs(newTabs);
535
- initializedTabs.delete(_activeTabId);
536
-
537
- if (newTabs.length === 0) {
538
- activeTabId("launcher");
539
- } else if (closedIndex < newTabs.length) {
540
- activeTabId(newTabs[closedIndex].tabId);
541
- } else {
542
- activeTabId(newTabs[newTabs.length - 1].tabId);
543
- }
544
- };
545
-
546
- /** Activate the next open tab, wrapping around. */
547
- export const switchToNextTab = () => {
548
- const currentTabs = activeTabs();
549
- const activeTab = activeTabId();
550
- const currentIndex = currentTabs.findIndex((tab) => tab.tabId === activeTab);
551
-
552
- if (currentIndex === -1) return;
553
-
554
- const nextIndex = currentIndex === currentTabs.length - 1 ? 0 : currentIndex + 1;
555
- activeTabId(currentTabs[nextIndex].tabId);
556
- };
557
-
558
- /** Activate the previous open tab, wrapping around. */
559
- export const switchToPreviousTab = () => {
560
- const currentTabs = activeTabs();
561
- const activeTab = activeTabId();
562
- const currentIndex = currentTabs.findIndex((tab) => tab.tabId === activeTab);
563
-
564
- if (currentIndex === -1) return;
565
-
566
- const prevIndex = currentIndex === 0 ? currentTabs.length - 1 : currentIndex - 1;
567
- activeTabId(currentTabs[prevIndex].tabId);
568
- };
569
-
570
- // Keyboard shortcuts are now handled by the command palette system
571
-
572
- activeTabId.subscribe((newActiveTab) => {
573
- const currentTabs = activeTabs();
574
- const activeTab = currentTabs.find((t) => t.tabId === newActiveTab);
575
-
576
- if (!activeTab) {
577
- console.warn("Active tab not found:", newActiveTab);
578
- return;
579
- }
580
-
581
- // Update mainContentPath for the new active tab
582
- if (activeTab.packageId !== "launcher") {
583
- _mainContentPath(activeTab.path);
584
- } else {
585
- _mainContentPath(""); // Launcher or fallback
586
- }
587
- });
@@ -1,58 +0,0 @@
1
- import {
2
- type INetworkDiagnosticsConsumer,
3
- type INetworkStatusSummary,
4
- inProcessTransportPair,
5
- observable,
6
- registerHostContractTransport,
7
- } from "@peers-app/peers-sdk";
8
- import { act, renderHook, waitFor } from "@testing-library/react";
9
- import { getNetworkDiagnosticsConsumer } from "../screens/network-viewer/network-viewer-ipc";
10
- import { useNetworkStatus } from "./use-network-status";
11
-
12
- jest.mock("../screens/network-viewer/network-viewer-ipc", () => ({
13
- getNetworkDiagnosticsConsumer: jest.fn(),
14
- }));
15
-
16
- const mockGetDiagnostics = getNetworkDiagnosticsConsumer as jest.MockedFunction<
17
- typeof getNetworkDiagnosticsConsumer
18
- >;
19
-
20
- function makeStatus(overrides: Partial<INetworkStatusSummary> = {}): INetworkStatusSummary {
21
- return {
22
- state: "offline",
23
- connectionCount: 0,
24
- syncingContextCount: 0,
25
- errorCount: 0,
26
- disconnectedContextCount: 0,
27
- connectedContextCount: 0,
28
- stuckRecoveryCount: 0,
29
- errors: [],
30
- ...overrides,
31
- };
32
- }
33
-
34
- describe("useNetworkStatus", () => {
35
- it("streams status updates and reports host disconnection", async () => {
36
- const status = observable(makeStatus());
37
- mockGetDiagnostics.mockResolvedValue({
38
- observables: { status },
39
- } as INetworkDiagnosticsConsumer);
40
- const [transport] = inProcessTransportPair();
41
- const disposeTransport = registerHostContractTransport(transport);
42
- const hook = renderHook(() => useNetworkStatus());
43
-
44
- await waitFor(() => expect(hook.result.current.availability).toBe("ready"));
45
- expect(hook.result.current.status?.state).toBe("offline");
46
-
47
- act(() => {
48
- status(makeStatus({ state: "syncing", connectionCount: 2, syncingContextCount: 1 }));
49
- });
50
- await waitFor(() => expect(hook.result.current.status?.state).toBe("syncing"));
51
-
52
- act(() => {
53
- disposeTransport();
54
- });
55
- await waitFor(() => expect(hook.result.current.availability).toBe("unavailable"));
56
- hook.unmount();
57
- });
58
- });
@@ -1,68 +0,0 @@
1
- import {
2
- type INetworkStatusSummary,
3
- type Subscription,
4
- subscribeHostContractTransport,
5
- } from "@peers-app/peers-sdk";
6
- import { useEffect, useState } from "react";
7
- import { getNetworkDiagnosticsConsumer } from "../screens/network-viewer/network-viewer-ipc";
8
-
9
- /** Host availability plus the latest reactive network status snapshot. */
10
- export interface INetworkStatusViewState {
11
- availability: "loading" | "ready" | "unavailable";
12
- status?: INetworkStatusSummary;
13
- }
14
-
15
- /** Subscribe to the current host's System Network Diagnostics status observable. */
16
- export function useNetworkStatus(): INetworkStatusViewState {
17
- const [viewState, setViewState] = useState<INetworkStatusViewState>({
18
- availability: "loading",
19
- });
20
-
21
- useEffect(() => {
22
- let active = true;
23
- let generation = 0;
24
- let statusSubscription: Subscription | undefined;
25
- let hadTransport = false;
26
- const unsubscribeTransport = subscribeHostContractTransport((transport) => {
27
- generation += 1;
28
- const currentGeneration = generation;
29
- statusSubscription?.dispose();
30
- statusSubscription = undefined;
31
- if (!transport) {
32
- setViewState({ availability: hadTransport ? "unavailable" : "loading" });
33
- return;
34
- }
35
- hadTransport = true;
36
- setViewState({ availability: "loading" });
37
- void getNetworkDiagnosticsConsumer()
38
- .then((diagnostics) => {
39
- if (!active || generation !== currentGeneration) {
40
- return;
41
- }
42
- setViewState({
43
- availability: "ready",
44
- status: diagnostics.observables.status(),
45
- });
46
- statusSubscription = diagnostics.observables.status.subscribe((status) => {
47
- if (active && generation === currentGeneration) {
48
- setViewState({ availability: "ready", status });
49
- }
50
- });
51
- })
52
- .catch(() => {
53
- if (active && generation === currentGeneration) {
54
- setViewState({ availability: "unavailable" });
55
- }
56
- });
57
- });
58
-
59
- return () => {
60
- active = false;
61
- generation += 1;
62
- statusSubscription?.dispose();
63
- unsubscribeTransport();
64
- };
65
- }, []);
66
-
67
- return viewState;
68
- }