@peers-app/peers-ui 0.25.2 → 0.25.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 (180) hide show
  1. package/README.md +2 -1
  2. package/dist/components/markdown-editor/mention-node.d.ts +4 -0
  3. package/dist/components/markdown-editor/mention-node.js +4 -1
  4. package/dist/components/responsive-section-nav.d.ts +34 -0
  5. package/dist/components/responsive-section-nav.js +19 -0
  6. package/dist/components/responsive-section-nav.test.d.ts +1 -0
  7. package/dist/components/responsive-section-nav.test.js +45 -0
  8. package/dist/components/router.d.ts +2 -3
  9. package/dist/components/router.js +6 -9
  10. package/dist/globals-hash.test.d.ts +1 -0
  11. package/dist/globals-hash.test.js +16 -0
  12. package/dist/globals.d.ts +19 -0
  13. package/dist/globals.js +51 -12
  14. package/dist/hooks/use-host-contract.d.ts +18 -0
  15. package/dist/hooks/use-host-contract.js +53 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/peers-services-registration.d.ts +1 -5
  19. package/dist/peers-services-registration.js +6 -16
  20. package/dist/root-layout/root-layout-host.js +7 -4
  21. package/dist/root-layout/root-layout-host.test.js +3 -6
  22. package/dist/screens/account/account-screen.d.ts +1 -0
  23. package/dist/screens/account/account-screen.js +29 -53
  24. package/dist/screens/account/account-screen.test.d.ts +1 -0
  25. package/dist/screens/account/account-screen.test.js +47 -0
  26. package/dist/screens/contacts/contact-details.js +2 -23
  27. package/dist/screens/contacts/contact-list.d.ts +24 -1
  28. package/dist/screens/contacts/contact-list.js +128 -143
  29. package/dist/screens/contacts/contact-list.test.d.ts +1 -0
  30. package/dist/screens/contacts/contact-list.test.js +48 -0
  31. package/dist/screens/contacts/index.d.ts +0 -2
  32. package/dist/screens/contacts/index.js +0 -3
  33. package/dist/screens/device-pairing/device-pairing.js +17 -16
  34. package/dist/screens/device-pairing/device-pairing.test.js +4 -9
  35. package/dist/screens/device-pairing/identity-ipc.d.ts +11 -0
  36. package/dist/screens/device-pairing/identity-ipc.js +20 -0
  37. package/dist/screens/device-pairing/use-device-pairing-state.d.ts +2 -2
  38. package/dist/screens/device-pairing/use-device-pairing-state.js +7 -25
  39. package/dist/screens/devices/devices-list.d.ts +18 -0
  40. package/dist/screens/devices/devices-list.js +173 -0
  41. package/dist/screens/devices/devices-list.test.d.ts +1 -0
  42. package/dist/screens/devices/devices-list.test.js +53 -0
  43. package/dist/screens/devices/index.d.ts +1 -0
  44. package/dist/screens/devices/index.js +5 -0
  45. package/dist/screens/groups/group-details.d.ts +1 -0
  46. package/dist/screens/groups/group-details.js +4 -63
  47. package/dist/screens/groups/group-invite-panel.js +2 -2
  48. package/dist/screens/groups/group-list.d.ts +18 -1
  49. package/dist/screens/groups/group-list.js +107 -136
  50. package/dist/screens/groups/group-list.test.d.ts +1 -0
  51. package/dist/screens/groups/group-list.test.js +38 -0
  52. package/dist/screens/groups/group-members.d.ts +1 -0
  53. package/dist/screens/groups/group-members.js +2 -1
  54. package/dist/screens/groups/index.d.ts +0 -3
  55. package/dist/screens/groups/index.js +0 -4
  56. package/dist/screens/identity/identity-routes.d.ts +107 -0
  57. package/dist/screens/identity/identity-routes.js +245 -0
  58. package/dist/screens/identity/identity-routes.test.d.ts +1 -0
  59. package/dist/screens/identity/identity-routes.test.js +51 -0
  60. package/dist/screens/identity/identity-screen.d.ts +9 -0
  61. package/dist/screens/identity/identity-screen.js +143 -0
  62. package/dist/screens/identity/identity-screen.test.d.ts +1 -0
  63. package/dist/screens/identity/identity-screen.test.js +103 -0
  64. package/dist/screens/identity/index.d.ts +2 -0
  65. package/dist/screens/identity/index.js +6 -0
  66. package/dist/screens/identity/invite-contact-panel.d.ts +4 -0
  67. package/dist/screens/identity/invite-contact-panel.js +30 -0
  68. package/dist/screens/identity/profile-invite.d.ts +7 -0
  69. package/dist/screens/identity/profile-invite.js +62 -0
  70. package/dist/screens/invites/accept-invite.js +44 -2
  71. package/dist/screens/invites/accept-invite.test.d.ts +1 -0
  72. package/dist/screens/invites/accept-invite.test.js +103 -0
  73. package/dist/screens/invites/activity-view.d.ts +24 -0
  74. package/dist/screens/invites/activity-view.js +64 -0
  75. package/dist/screens/invites/activity-view.test.d.ts +1 -0
  76. package/dist/screens/invites/activity-view.test.js +108 -0
  77. package/dist/screens/invites/index.d.ts +1 -2
  78. package/dist/screens/invites/index.js +1 -3
  79. package/dist/screens/invites/invite-capture.d.ts +0 -2
  80. package/dist/screens/invites/invite-capture.js +1 -3
  81. package/dist/screens/invites/invite-classify.test.d.ts +1 -0
  82. package/dist/screens/invites/invite-classify.test.js +115 -0
  83. package/dist/screens/invites/invites-ipc.d.ts +1 -1
  84. package/dist/screens/invites/invites-ipc.js +5 -45
  85. package/dist/screens/invites/pending-invites-list.d.ts +35 -16
  86. package/dist/screens/invites/pending-invites-list.js +75 -30
  87. package/dist/screens/network-viewer/network-viewer-ipc.js +4 -59
  88. package/dist/screens/search/global-search.d.ts +1 -0
  89. package/dist/screens/search/global-search.js +2 -1
  90. package/dist/screens/settings/settings-page.js +25 -58
  91. package/dist/screens/setup-user.js +5 -5
  92. package/dist/screens/tools/tool-access-ipc.d.ts +2 -0
  93. package/dist/screens/tools/tool-access-ipc.js +8 -55
  94. package/dist/screens/welcome-modal.js +3 -2
  95. package/dist/system-apps/identity.app.d.ts +3 -0
  96. package/dist/system-apps/identity.app.js +10 -0
  97. package/dist/system-apps/index.d.ts +1 -4
  98. package/dist/system-apps/index.js +5 -17
  99. package/dist/tabs-layout/network-status-indicator.js +10 -3
  100. package/dist/tabs-layout/network-status-indicator.test.js +15 -0
  101. package/dist/tabs-layout/tabs-layout.js +3 -1
  102. package/dist/tabs-layout/tabs-state.d.ts +79 -1
  103. package/dist/tabs-layout/tabs-state.js +256 -79
  104. package/dist/tabs-layout/tabs-state.test.d.ts +1 -0
  105. package/dist/tabs-layout/tabs-state.test.js +110 -0
  106. package/package.json +3 -3
  107. package/src/components/markdown-editor/mention-node.ts +5 -1
  108. package/src/components/responsive-section-nav.test.tsx +88 -0
  109. package/src/components/responsive-section-nav.tsx +92 -0
  110. package/src/components/router.tsx +6 -9
  111. package/src/globals-hash.test.ts +19 -0
  112. package/src/globals.tsx +53 -13
  113. package/src/hooks/use-host-contract.ts +75 -0
  114. package/src/index.tsx +1 -0
  115. package/src/peers-services-registration.ts +6 -17
  116. package/src/root-layout/root-layout-host.test.tsx +4 -7
  117. package/src/root-layout/root-layout-host.tsx +9 -11
  118. package/src/screens/account/account-screen.test.tsx +61 -0
  119. package/src/screens/account/account-screen.tsx +116 -95
  120. package/src/screens/contacts/contact-details.tsx +4 -26
  121. package/src/screens/contacts/contact-list.test.tsx +59 -0
  122. package/src/screens/contacts/contact-list.tsx +246 -260
  123. package/src/screens/contacts/index.ts +0 -4
  124. package/src/screens/device-pairing/device-pairing.test.tsx +7 -9
  125. package/src/screens/device-pairing/device-pairing.tsx +19 -23
  126. package/src/screens/device-pairing/identity-ipc.ts +25 -0
  127. package/src/screens/device-pairing/use-device-pairing-state.ts +10 -33
  128. package/src/screens/devices/devices-list.test.tsx +68 -0
  129. package/src/screens/devices/devices-list.tsx +361 -0
  130. package/src/screens/devices/index.ts +1 -0
  131. package/src/screens/groups/group-details.tsx +8 -142
  132. package/src/screens/groups/group-invite-panel.tsx +2 -7
  133. package/src/screens/groups/group-list.test.tsx +44 -0
  134. package/src/screens/groups/group-list.tsx +227 -288
  135. package/src/screens/groups/group-members.tsx +3 -2
  136. package/src/screens/groups/index.ts +0 -5
  137. package/src/screens/identity/identity-routes.test.ts +70 -0
  138. package/src/screens/identity/identity-routes.ts +301 -0
  139. package/src/screens/identity/identity-screen.test.tsx +138 -0
  140. package/src/screens/identity/identity-screen.tsx +282 -0
  141. package/src/screens/identity/index.ts +2 -0
  142. package/src/screens/identity/invite-contact-panel.tsx +45 -0
  143. package/src/screens/identity/profile-invite.tsx +93 -0
  144. package/src/screens/invites/accept-invite.test.tsx +115 -0
  145. package/src/screens/invites/accept-invite.tsx +50 -3
  146. package/src/screens/invites/activity-view.test.tsx +127 -0
  147. package/src/screens/invites/activity-view.tsx +162 -0
  148. package/src/screens/invites/index.ts +1 -4
  149. package/src/screens/invites/invite-capture.ts +0 -3
  150. package/src/screens/invites/invite-classify.test.ts +129 -0
  151. package/src/screens/invites/invites-ipc.ts +6 -55
  152. package/src/screens/invites/pending-invites-list.tsx +94 -62
  153. package/src/screens/network-viewer/network-viewer-ipc.ts +8 -82
  154. package/src/screens/search/global-search.tsx +2 -1
  155. package/src/screens/settings/settings-page.tsx +53 -126
  156. package/src/screens/setup-user.tsx +5 -6
  157. package/src/screens/tools/tool-access-ipc.ts +13 -74
  158. package/src/screens/welcome-modal.tsx +3 -3
  159. package/src/system-apps/identity.app.ts +9 -0
  160. package/src/system-apps/index.ts +3 -12
  161. package/src/tabs-layout/network-status-indicator.test.tsx +22 -0
  162. package/src/tabs-layout/network-status-indicator.tsx +31 -0
  163. package/src/tabs-layout/tabs-layout.tsx +7 -1
  164. package/src/tabs-layout/tabs-state.test.ts +152 -0
  165. package/src/tabs-layout/tabs-state.ts +300 -83
  166. package/dist/screens/invites/invites-screen.d.ts +0 -9
  167. package/dist/screens/invites/invites-screen.js +0 -36
  168. package/dist/system-apps/account.app.d.ts +0 -2
  169. package/dist/system-apps/account.app.js +0 -8
  170. package/dist/system-apps/contacts.app.d.ts +0 -2
  171. package/dist/system-apps/contacts.app.js +0 -9
  172. package/dist/system-apps/groups.app.d.ts +0 -2
  173. package/dist/system-apps/groups.app.js +0 -9
  174. package/dist/system-apps/invites.app.d.ts +0 -2
  175. package/dist/system-apps/invites.app.js +0 -9
  176. package/src/screens/invites/invites-screen.tsx +0 -78
  177. package/src/system-apps/account.app.ts +0 -7
  178. package/src/system-apps/contacts.app.ts +0 -8
  179. package/src/system-apps/groups.app.ts +0 -8
  180. package/src/system-apps/invites.app.ts +0 -8
@@ -2,64 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getToolAccessControlConsumer = getToolAccessControlConsumer;
4
4
  const peers_sdk_1 = require("@peers-app/peers-sdk");
5
- const HOST_TRANSPORT_TIMEOUT_MS = 10_000;
6
- const cachedByContext = new Map();
7
- const pendingByContext = new Map();
8
- function disposeCachedConsumers() {
9
- for (const cached of cachedByContext.values()) {
10
- cached.consumer.dispose();
11
- }
12
- cachedByContext.clear();
13
- }
14
- (0, peers_sdk_1.subscribeHostContractTransport)((transport) => {
15
- if ([...cachedByContext.values()].some((cached) => cached.transport !== transport)) {
16
- disposeCachedConsumers();
17
- pendingByContext.clear();
18
- }
19
- }, { emitCurrent: false });
20
- async function loadConsumer(transport, dataContextId) {
21
- cachedByContext.get(dataContextId)?.consumer.dispose();
22
- cachedByContext.delete(dataContextId);
23
- const consumer = (0, peers_sdk_1.createContractConsumer)((0, peers_sdk_1.getToolAccessControlContractDefinition)(), transport, {
24
- dataContextId,
25
- });
26
- try {
27
- await consumer.loadingPromise;
28
- if ((0, peers_sdk_1.getHostContractTransport)() !== transport) {
29
- consumer.dispose();
30
- pendingByContext.delete(dataContextId);
31
- return getToolAccessControlConsumer(dataContextId);
32
- }
33
- cachedByContext.set(dataContextId, { transport, consumer });
34
- return consumer;
35
- }
36
- catch (error) {
37
- consumer.dispose();
38
- throw error;
39
- }
40
- }
5
+ const consumers = new Map();
41
6
  /**
42
7
  * Return a typed local-host consumer for Tool Access Control in `dataContextId`.
8
+ *
9
+ * One consumer is cached per data context and rebuilt when the host transport changes.
43
10
  */
44
11
  async function getToolAccessControlConsumer(dataContextId) {
45
- const transport = (0, peers_sdk_1.getHostContractTransport)() ??
46
- (await (0, peers_sdk_1.waitForHostContractTransport)({ timeoutMs: HOST_TRANSPORT_TIMEOUT_MS }));
47
- const cached = cachedByContext.get(dataContextId);
48
- if (cached?.transport === transport) {
49
- return cached.consumer;
50
- }
51
- const pending = pendingByContext.get(dataContextId);
52
- if (pending) {
53
- return pending;
54
- }
55
- const loadPromise = loadConsumer(transport, dataContextId);
56
- pendingByContext.set(dataContextId, loadPromise);
57
- try {
58
- return await loadPromise;
59
- }
60
- finally {
61
- if (pendingByContext.get(dataContextId) === loadPromise) {
62
- pendingByContext.delete(dataContextId);
63
- }
12
+ let handle = consumers.get(dataContextId);
13
+ if (!handle) {
14
+ handle = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getToolAccessControlContractDefinition)(), { dataContextId: async () => dataContextId });
15
+ consumers.set(dataContextId, handle);
64
16
  }
17
+ return handle.get();
65
18
  }
@@ -6,10 +6,11 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
6
6
  const react_1 = require("react");
7
7
  const hooks_1 = require("../hooks");
8
8
  const peers_services_registration_1 = require("../peers-services-registration");
9
+ const identity_ipc_1 = require("./device-pairing/identity-ipc");
9
10
  async function tryRegisterPeersServicesAfterWelcome() {
10
11
  try {
11
- const token = await peers_sdk_1.rpcServerCalls.registerWithPeersServices();
12
- (0, peers_services_registration_1.persistPeersServicesRegistration)(token);
12
+ const { token } = await (await (0, identity_ipc_1.identityTools)()).registerWithPeersServices();
13
+ await (0, peers_services_registration_1.persistPeersServicesRegistration)(token);
13
14
  }
14
15
  catch (err) {
15
16
  console.warn("Peers services registration after welcome modal:", err);
@@ -0,0 +1,3 @@
1
+ import type { IAppNav } from "@peers-app/peers-sdk";
2
+ /** Identity system app: activity, people, groups, devices, and account. */
3
+ export declare const identityApp: IAppNav;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.identityApp = void 0;
4
+ /** Identity system app: activity, people, groups, devices, and account. */
5
+ exports.identityApp = {
6
+ name: "Identity",
7
+ displayName: "Identity",
8
+ iconClassName: "bi-person-badge",
9
+ navigationPath: "identity",
10
+ };
@@ -1,12 +1,9 @@
1
1
  import type { IAppNav } from "@peers-app/peers-sdk";
2
2
  import type { IPackageWithNavs } from "../ui-router/routes-loader";
3
- export { accountApp } from "./account.app";
4
3
  export { assistantsApp } from "./assistants.app";
5
4
  export { consoleLogsApp } from "./console-logs.app";
6
- export { contactsApp } from "./contacts.app";
7
5
  export { dataExplorerApp } from "./data-explorer.app";
8
- export { groupsApp } from "./groups.app";
9
- export { invitesApp } from "./invites.app";
6
+ export { identityApp } from "./identity.app";
10
7
  export { mobileSettingsApp } from "./mobile-settings.app";
11
8
  export { networkViewerApp } from "./network-viewer.app";
12
9
  export { packagesApp } from "./packages.app";
@@ -1,20 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.systemPackage = exports.systemApps = exports.workflowsApp = exports.variablesApp = exports.typesApp = exports.toolsApp = exports.threadsApp = exports.settingsApp = exports.searchApp = exports.packagesApp = exports.networkViewerApp = exports.mobileSettingsApp = exports.invitesApp = exports.groupsApp = exports.dataExplorerApp = exports.contactsApp = exports.consoleLogsApp = exports.assistantsApp = exports.accountApp = void 0;
4
- var account_app_1 = require("./account.app");
5
- Object.defineProperty(exports, "accountApp", { enumerable: true, get: function () { return account_app_1.accountApp; } });
3
+ exports.systemPackage = exports.systemApps = exports.workflowsApp = exports.variablesApp = exports.typesApp = exports.toolsApp = exports.threadsApp = exports.settingsApp = exports.searchApp = exports.packagesApp = exports.networkViewerApp = exports.mobileSettingsApp = exports.identityApp = exports.dataExplorerApp = exports.consoleLogsApp = exports.assistantsApp = void 0;
6
4
  var assistants_app_1 = require("./assistants.app");
7
5
  Object.defineProperty(exports, "assistantsApp", { enumerable: true, get: function () { return assistants_app_1.assistantsApp; } });
8
6
  var console_logs_app_1 = require("./console-logs.app");
9
7
  Object.defineProperty(exports, "consoleLogsApp", { enumerable: true, get: function () { return console_logs_app_1.consoleLogsApp; } });
10
- var contacts_app_1 = require("./contacts.app");
11
- Object.defineProperty(exports, "contactsApp", { enumerable: true, get: function () { return contacts_app_1.contactsApp; } });
12
8
  var data_explorer_app_1 = require("./data-explorer.app");
13
9
  Object.defineProperty(exports, "dataExplorerApp", { enumerable: true, get: function () { return data_explorer_app_1.dataExplorerApp; } });
14
- var groups_app_1 = require("./groups.app");
15
- Object.defineProperty(exports, "groupsApp", { enumerable: true, get: function () { return groups_app_1.groupsApp; } });
16
- var invites_app_1 = require("./invites.app");
17
- Object.defineProperty(exports, "invitesApp", { enumerable: true, get: function () { return invites_app_1.invitesApp; } });
10
+ var identity_app_1 = require("./identity.app");
11
+ Object.defineProperty(exports, "identityApp", { enumerable: true, get: function () { return identity_app_1.identityApp; } });
18
12
  var mobile_settings_app_1 = require("./mobile-settings.app");
19
13
  Object.defineProperty(exports, "mobileSettingsApp", { enumerable: true, get: function () { return mobile_settings_app_1.mobileSettingsApp; } });
20
14
  var network_viewer_app_1 = require("./network-viewer.app");
@@ -36,13 +30,10 @@ var variables_app_1 = require("./variables.app");
36
30
  Object.defineProperty(exports, "variablesApp", { enumerable: true, get: function () { return variables_app_1.variablesApp; } });
37
31
  var workflows_app_1 = require("./workflows.app");
38
32
  Object.defineProperty(exports, "workflowsApp", { enumerable: true, get: function () { return workflows_app_1.workflowsApp; } });
39
- const account_app_2 = require("./account.app");
40
33
  const assistants_app_2 = require("./assistants.app");
41
34
  const console_logs_app_2 = require("./console-logs.app");
42
- const contacts_app_2 = require("./contacts.app");
43
35
  const data_explorer_app_2 = require("./data-explorer.app");
44
- const groups_app_2 = require("./groups.app");
45
- const invites_app_2 = require("./invites.app");
36
+ const identity_app_2 = require("./identity.app");
46
37
  const mobile_settings_app_2 = require("./mobile-settings.app");
47
38
  const network_viewer_app_2 = require("./network-viewer.app");
48
39
  const packages_app_2 = require("./packages.app");
@@ -74,11 +65,8 @@ exports.systemApps = [
74
65
  workflows_app_2.workflowsApp,
75
66
  assistants_app_2.assistantsApp,
76
67
  packages_app_2.packagesApp,
77
- contacts_app_2.contactsApp,
78
- groups_app_2.groupsApp,
79
- invites_app_2.invitesApp,
68
+ identity_app_2.identityApp,
80
69
  // User & Settings Apps
81
- account_app_2.accountApp,
82
70
  settings_app_2.settingsApp,
83
71
  // Mobile Settings (only in React Native)
84
72
  ...(isReactNative() ? [mobile_settings_app_2.mobileSettingsApp] : []),
@@ -12,6 +12,7 @@ exports.TabsStripIndicators = TabsStripIndicators;
12
12
  const jsx_runtime_1 = require("react/jsx-runtime");
13
13
  const moment_1 = __importDefault(require("moment"));
14
14
  const react_1 = require("react");
15
+ const identity_routes_1 = require("../screens/identity/identity-routes");
15
16
  const network_viewer_ipc_1 = require("../screens/network-viewer/network-viewer-ipc");
16
17
  const tabs_state_1 = require("./tabs-state");
17
18
  const use_network_status_1 = require("./use-network-status");
@@ -189,8 +190,8 @@ function ConnectedDevicesList({ loadState }) {
189
190
  }
190
191
  return ((0, jsx_runtime_1.jsx)("ul", { className: "small mb-0 ps-0", style: { maxHeight: "180px", overflowY: "auto" }, children: loadState.connections.map((connection) => ((0, jsx_runtime_1.jsx)(ConnectedDeviceRow, { connection: connection }, connection.deviceId))) }));
191
192
  }
192
- function NetworkStatusDetails({ detailsId, status, unavailable, connectionsLoadState, onOpenViewer, }) {
193
- return ((0, jsx_runtime_1.jsx)("div", { id: detailsId, role: "dialog", "aria-label": "Network status details", className: "card shadow position-absolute end-0 mt-1", style: { top: "100%", width: "320px", zIndex: 1100 }, children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body p-3", children: [(0, jsx_runtime_1.jsx)("h6", { className: "card-title mb-2", children: "Network status" }), unavailable || !status ? ((0, jsx_runtime_1.jsx)("p", { className: "small text-muted mb-3", children: "Network diagnostics are temporarily unavailable. The local host may still be starting or reconnecting." })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("dl", { className: "row small mb-2", children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Connections" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.connectionCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Syncing contexts" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.syncingContextCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Sync errors" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.errorCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Groups connected" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.connectedContextCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Groups offline" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.disconnectedContextCount }), status.lastSuccessfulSyncAt != null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Last sync" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end text-truncate", title: new Date(status.lastSuccessfulSyncAt).toLocaleString(), children: formatRelativeSyncTime(status.lastSuccessfulSyncAt) })] }))] }), status.errors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-danger py-2 px-2 small mb-2", children: [status.errors[0].message, status.errors.length > 1 && ` (+${status.errors.length - 1} more)`] })), (0, jsx_runtime_1.jsxs)("div", { className: "border-top pt-2 mt-1 mb-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "small text-muted mb-1", children: "Devices" }), (0, jsx_runtime_1.jsx)(ConnectedDevicesList, { loadState: connectionsLoadState })] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-primary w-100", onClick: onOpenViewer, children: "Open Network Viewer" })] }) }));
193
+ function NetworkStatusDetails({ detailsId, status, unavailable, connectionsLoadState, onUseInvite, onOpenIdentity, onOpenViewer, }) {
194
+ return ((0, jsx_runtime_1.jsx)("div", { id: detailsId, role: "dialog", "aria-label": "Network status details", className: "card shadow position-absolute end-0 mt-1", style: { top: "100%", width: "320px", zIndex: 1100 }, children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2 mb-3", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-primary flex-fill", onClick: onUseInvite, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-qr-code me-1", "aria-hidden": "true" }), "Use Invite"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-primary flex-fill", onClick: onOpenIdentity, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-person-badge me-1", "aria-hidden": "true" }), "Identity"] })] }), (0, jsx_runtime_1.jsx)("h6", { className: "card-title mb-2", children: "Network status" }), unavailable || !status ? ((0, jsx_runtime_1.jsx)("p", { className: "small text-muted mb-3", children: "Network diagnostics are temporarily unavailable. The local host may still be starting or reconnecting." })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("dl", { className: "row small mb-2", children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Connections" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.connectionCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Syncing contexts" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.syncingContextCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Sync errors" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.errorCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Groups connected" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.connectedContextCount }), (0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Groups offline" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end", children: status.disconnectedContextCount }), status.lastSuccessfulSyncAt != null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-7", children: "Last sync" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-5 text-end text-truncate", title: new Date(status.lastSuccessfulSyncAt).toLocaleString(), children: formatRelativeSyncTime(status.lastSuccessfulSyncAt) })] }))] }), status.errors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "alert alert-danger py-2 px-2 small mb-2", children: [status.errors[0].message, status.errors.length > 1 && ` (+${status.errors.length - 1} more)`] })), (0, jsx_runtime_1.jsxs)("div", { className: "border-top pt-2 mt-1 mb-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "small text-muted mb-1", children: "Devices" }), (0, jsx_runtime_1.jsx)(ConnectedDevicesList, { loadState: connectionsLoadState })] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-primary w-100", onClick: onOpenViewer, children: "Open Network Viewer" })] }) }));
194
195
  }
195
196
  /** Accessible reactive network status button and details popover. */
196
197
  function NetworkStatusIndicator({ compact = false }) {
@@ -305,7 +306,13 @@ function NetworkStatusIndicator({ compact = false }) {
305
306
  fontWeight: 700,
306
307
  right: "-0.28rem",
307
308
  bottom: "-0.32rem",
308
- }, "aria-hidden": "true", children: presentation.disconnectedGroupsOverlay.count }))] }), !compact && ((0, jsx_runtime_1.jsxs)("span", { className: "small", children: [viewState.status?.connectionCount ?? 0, (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: " connections" })] }))] }), open && ((0, jsx_runtime_1.jsx)(NetworkStatusDetails, { detailsId: detailsId, status: viewState.status, unavailable: viewState.availability !== "ready", connectionsLoadState: connectionsLoadState, onOpenViewer: () => {
309
+ }, "aria-hidden": "true", children: presentation.disconnectedGroupsOverlay.count }))] }), !compact && ((0, jsx_runtime_1.jsxs)("span", { className: "small", children: [viewState.status?.connectionCount ?? 0, (0, jsx_runtime_1.jsx)("span", { className: "visually-hidden", children: " connections" })] }))] }), open && ((0, jsx_runtime_1.jsx)(NetworkStatusDetails, { detailsId: detailsId, status: viewState.status, unavailable: viewState.availability !== "ready", connectionsLoadState: connectionsLoadState, onUseInvite: () => {
310
+ setOpen(false);
311
+ (0, tabs_state_1.goToTabPath)(identity_routes_1.IDENTITY_ACCEPT_PATH);
312
+ }, onOpenIdentity: () => {
313
+ setOpen(false);
314
+ (0, tabs_state_1.goToTabPath)(identity_routes_1.IDENTITY_ACCOUNT_PATH);
315
+ }, onOpenViewer: () => {
309
316
  setOpen(false);
310
317
  (0, tabs_state_1.goToTabPath)("network-viewer");
311
318
  } }))] }));
@@ -7,6 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const peers_sdk_1 = require("@peers-app/peers-sdk");
8
8
  const react_1 = require("@testing-library/react");
9
9
  const moment_1 = __importDefault(require("moment"));
10
+ const identity_routes_1 = require("../screens/identity/identity-routes");
10
11
  const network_status_indicator_1 = require("./network-status-indicator");
11
12
  const goToTabPath = jest.fn();
12
13
  const getConnections = jest.fn();
@@ -424,6 +425,20 @@ describe("NetworkStatusIndicator", () => {
424
425
  expect(view.getByRole("dialog")).toHaveTextContent("Couldn't load devices");
425
426
  });
426
427
  });
428
+ it("offers shortcuts to use an invite and open the Identity account", () => {
429
+ mockViewState = { availability: "ready", status: status({ state: "synced" }) };
430
+ const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(network_status_indicator_1.NetworkStatusIndicator, {}));
431
+ react_1.fireEvent.click(view.getByRole("button", { name: /Network synced/ }));
432
+ expect(view.getByRole("button", { name: "Use Invite" }).querySelector(".bi-qr-code")).toBeTruthy();
433
+ expect(view.getByRole("button", { name: "Identity" }).querySelector(".bi-person-badge")).toBeTruthy();
434
+ react_1.fireEvent.click(view.getByRole("button", { name: "Use Invite" }));
435
+ expect(goToTabPath).toHaveBeenCalledWith(identity_routes_1.IDENTITY_ACCEPT_PATH);
436
+ expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
437
+ react_1.fireEvent.click(view.getByRole("button", { name: /Network synced/ }));
438
+ react_1.fireEvent.click(view.getByRole("button", { name: "Identity" }));
439
+ expect(goToTabPath).toHaveBeenCalledWith(identity_routes_1.IDENTITY_ACCOUNT_PATH);
440
+ expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
441
+ });
427
442
  it("updates on rerender and opens details before navigating to Network Viewer", async () => {
428
443
  const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(network_status_indicator_1.NetworkStatusIndicator, {}));
429
444
  expect(view.getByRole("button", { name: /loading/ })).toBeTruthy();
@@ -44,6 +44,7 @@ const group_switcher_1 = require("../components/group-switcher");
44
44
  const router_1 = require("../components/router");
45
45
  const globals_1 = require("../globals");
46
46
  const hooks_1 = require("../hooks");
47
+ const identity_routes_1 = require("../screens/identity/identity-routes");
47
48
  const color_mode_dropdown_1 = require("../screens/settings/color-mode-dropdown");
48
49
  const system_apps_1 = require("../system-apps");
49
50
  const routes_loader_1 = require("../ui-router/routes-loader");
@@ -281,7 +282,8 @@ const TabContentRenderer = react_1.default.memo(({ tab, isMobile }) => {
281
282
  // System apps use the main Router (which reads from mainContentPath)
282
283
  // Package apps use UIRouter with explicit path
283
284
  const isSystemApp = tab.packageId === "system-apps";
284
- return ((0, jsx_runtime_1.jsx)("div", { className: `h-100 overflow-auto ${isMobile ? "p-2" : "p-3"}`, children: isSystemApp ? ((0, jsx_runtime_1.jsx)(router_1.Router, { path: tab.path })) : ((0, jsx_runtime_1.jsx)(ui_loader_1.UIRouter, { path: tab.path, uiCategory: "screen", props: {} })) }));
285
+ const identityApp = isSystemApp && (0, identity_routes_1.isIdentityPath)(tab.path);
286
+ return ((0, jsx_runtime_1.jsx)("div", { className: `h-100 ${identityApp ? "overflow-hidden" : "overflow-auto"} ${identityApp ? "" : isMobile ? "p-2" : "p-3"}`, children: isSystemApp ? ((0, jsx_runtime_1.jsx)(router_1.Router, { path: tab.path })) : ((0, jsx_runtime_1.jsx)(ui_loader_1.UIRouter, { path: tab.path, uiCategory: "screen", props: {} })) }));
285
287
  }, (prevProps, nextProps) => {
286
288
  // Only re-render if the tab or isMobile changed
287
289
  return (prevProps.tab.tabId === nextProps.tab.tabId &&
@@ -1,5 +1,6 @@
1
1
  import { type IAppNav, type Observable } from "@peers-app/peers-sdk";
2
2
  import { type IPackageWithNavs } from "../ui-router/routes-loader";
3
+ /** One top-level app tab. */
3
4
  export interface TabState {
4
5
  tabId: string;
5
6
  packageId?: string;
@@ -7,24 +8,92 @@ export interface TabState {
7
8
  title: string;
8
9
  iconClassName?: string;
9
10
  }
11
+ /** The app launcher tab. It is always present and cannot be closed. */
10
12
  export declare const launcherApp: TabState;
13
+ /** Open tabs. Writes through to device storage after the initial restore. */
11
14
  export declare const activeTabs: Observable<TabState[]> & {
12
15
  loadingPromise: Promise<void>;
13
16
  };
17
+ /** Id of the tab whose path is showing. */
14
18
  export declare const activeTabId: Observable<string> & {
15
19
  loadingPromise: Promise<void>;
16
20
  };
21
+ /** App roots recently opened from the launcher, newest first. */
17
22
  export declare const recentlyUsedApps: Observable<string[]> & {
18
23
  loadingPromise: Promise<void>;
19
24
  };
25
+ /** Tab ids whose bodies have been mounted at least once. */
20
26
  export declare const initializedTabs: Set<string>;
27
+ /** Options for {@link navigateTo}. */
28
+ export interface NavigateOptions {
29
+ /**
30
+ * When the requested path is an app's root and that app is already open,
31
+ * activate the existing tab without resetting its section.
32
+ */
33
+ preserveSectionOnAppRoot?: boolean;
34
+ }
35
+ /**
36
+ * Open a path in the tab that owns its app.
37
+ *
38
+ * Legacy identity aliases are rewritten first. A route owned by the current
39
+ * app replaces that tab's path. A route owned by another open app activates
40
+ * that app's tab. Otherwise a new tab is opened.
41
+ *
42
+ * @param rawPath - Requested route, with or without a query string.
43
+ * @param options - App-root behavior. Launcher opens preserve the current section.
44
+ */
45
+ export declare function navigateTo(rawPath: string, options?: NavigateOptions): void;
46
+ /**
47
+ * Open an app or path from the launcher.
48
+ *
49
+ * Choosing an app that is already open keeps the section the user was on.
50
+ *
51
+ * @param path - App root or deep link.
52
+ */
21
53
  export declare function goToTabPath(path: string): void;
22
- export declare const handleMainPathChanged: (oldPath: string, newPath: string, setNewMainPath: (path: string) => void) => void;
54
+ /**
55
+ * Apply a {@link mainContentPath} write to the open tabs.
56
+ *
57
+ * @param _oldPath - Previous route. Retained for existing call sites.
58
+ * @param newPath - Requested route.
59
+ * @param _setNewMainPath - Direct path setter. Navigation updates the path itself.
60
+ */
61
+ export declare const handleMainPathChanged: (_oldPath: string, newPath: string, _setNewMainPath: (path: string) => void) => void;
62
+ /**
63
+ * Rewrite stored tabs onto canonical routes.
64
+ *
65
+ * Contacts, Groups, and Devices tabs from before the Identity app become one
66
+ * Identity tab. The active tab is kept when it is one of those duplicates.
67
+ *
68
+ * @param tabs - Persisted tabs.
69
+ * @param activeId - Persisted active tab id.
70
+ */
71
+ export declare function restoreNavigationState(tabs: TabState[], activeId: string): {
72
+ tabs: TabState[];
73
+ activeId: string;
74
+ };
75
+ /**
76
+ * Map a stored recent-app path onto the current app root.
77
+ *
78
+ * @param path - Persisted recent path, possibly a legacy alias.
79
+ */
80
+ export declare function migrateRecentPath(path: string): string;
23
81
  type AppInfo = {
24
82
  navItem: IAppNav;
25
83
  package: IPackageWithNavs;
26
84
  };
85
+ /**
86
+ * Resolve the app that owns a route.
87
+ *
88
+ * @param path - Route, with or without a query string.
89
+ * @returns The matching app, the launcher for an empty path, or `undefined`.
90
+ */
27
91
  export declare function determineAppFromPath(path: string): AppInfo | undefined;
92
+ /**
93
+ * Set the active tab's title.
94
+ *
95
+ * @param newTitle - Title shown on the tab strip.
96
+ */
28
97
  export declare const updateActiveTabTitle: (newTitle: string) => void;
29
98
  /**
30
99
  * Enable or disable single-tab mode.
@@ -36,8 +105,17 @@ export declare const updateActiveTabTitle: (newTitle: string) => void;
36
105
  * and disable it on unmount.
37
106
  */
38
107
  export declare const setSingleTabMode: (enabled: boolean) => void;
108
+ /**
109
+ * Open a tab, or activate an existing tab with the same package and path.
110
+ *
111
+ * @param tab - Tab contents. `tabId` is assigned here.
112
+ * @param forceNew - When true, always append a tab instead of reusing one.
113
+ */
39
114
  export declare const openNewTab: (tab: Omit<TabState, "tabId">, forceNew?: boolean) => void;
115
+ /** Close the active tab. The launcher tab stays open. */
40
116
  export declare const closeCurrentTab: () => void;
117
+ /** Activate the next open tab, wrapping around. */
41
118
  export declare const switchToNextTab: () => void;
119
+ /** Activate the previous open tab, wrapping around. */
42
120
  export declare const switchToPreviousTab: () => void;
43
121
  export {};