@codemem/server 0.22.1 → 0.22.2
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.
- package/package.json +2 -2
- package/static/app.js +786 -348
- package/static/index.html +154 -6
package/static/app.js
CHANGED
|
@@ -148,6 +148,11 @@
|
|
|
148
148
|
lastSyncJoinRequests: [],
|
|
149
149
|
lastTeamInvite: null,
|
|
150
150
|
lastTeamJoin: null,
|
|
151
|
+
syncJoinFlowFeedback: null,
|
|
152
|
+
syncPeerFeedbackById: /* @__PURE__ */ new Map(),
|
|
153
|
+
syncPeersSectionFeedback: null,
|
|
154
|
+
syncJoinRequestsFeedback: null,
|
|
155
|
+
syncDiscoveredFeedback: null,
|
|
151
156
|
lastSyncAttempts: [],
|
|
152
157
|
lastSyncLegacyDevices: [],
|
|
153
158
|
lastSyncViewModel: null,
|
|
@@ -235,6 +240,19 @@
|
|
|
235
240
|
if (!resp.ok) throw new Error(`${url}: ${resp.status} ${resp.statusText}`);
|
|
236
241
|
return resp.json();
|
|
237
242
|
}
|
|
243
|
+
async function pingViewerReady(timeoutMs = 1200) {
|
|
244
|
+
const controller = new AbortController();
|
|
245
|
+
const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);
|
|
246
|
+
try {
|
|
247
|
+
const resp = await fetch("/api/stats", {
|
|
248
|
+
cache: "no-store",
|
|
249
|
+
signal: controller.signal
|
|
250
|
+
});
|
|
251
|
+
if (!resp.ok) throw new Error(`/api/stats: ${resp.status} ${resp.statusText}`);
|
|
252
|
+
} finally {
|
|
253
|
+
window.clearTimeout(timeoutId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
238
256
|
async function readJsonPayload(resp) {
|
|
239
257
|
const text = await resp.text();
|
|
240
258
|
try {
|
|
@@ -2511,18 +2529,18 @@
|
|
|
2511
2529
|
priority: 10,
|
|
2512
2530
|
title: `${device.name} needs repair`,
|
|
2513
2531
|
summary: device.summary,
|
|
2514
|
-
actionLabel: "
|
|
2532
|
+
actionLabel: "Open device",
|
|
2515
2533
|
deviceId: device.id
|
|
2516
2534
|
};
|
|
2517
2535
|
}
|
|
2518
2536
|
function createReviewItem(device) {
|
|
2519
2537
|
return {
|
|
2520
|
-
id: `review:${device.id}`,
|
|
2538
|
+
id: `review:${device.id}:${device.key || "default"}`,
|
|
2521
2539
|
kind: "review-team-device",
|
|
2522
2540
|
priority: 20,
|
|
2523
2541
|
title: `${device.name} is available to review`,
|
|
2524
2542
|
summary: device.summary,
|
|
2525
|
-
actionLabel: "
|
|
2543
|
+
actionLabel: "Open device",
|
|
2526
2544
|
deviceId: device.id
|
|
2527
2545
|
};
|
|
2528
2546
|
}
|
|
@@ -2533,7 +2551,7 @@
|
|
|
2533
2551
|
priority: 30,
|
|
2534
2552
|
title: `Name ${device.name}`,
|
|
2535
2553
|
summary: device.summary,
|
|
2536
|
-
actionLabel: "
|
|
2554
|
+
actionLabel: "Go to name field",
|
|
2537
2555
|
deviceId: device.id
|
|
2538
2556
|
};
|
|
2539
2557
|
}
|
|
@@ -2581,7 +2599,7 @@
|
|
|
2581
2599
|
priority: candidate.includesLocal ? 5 : 15,
|
|
2582
2600
|
title: `Possible duplicate person: ${candidate.displayName}`,
|
|
2583
2601
|
summary: candidate.includesLocal ? "At least one entry is marked as you. Confirm whether these records represent the same person." : "Multiple people share this name. Confirm whether they should stay separate or be combined.",
|
|
2584
|
-
actionLabel: "
|
|
2602
|
+
actionLabel: "Go to people",
|
|
2585
2603
|
actorIds: candidate.actorIds
|
|
2586
2604
|
});
|
|
2587
2605
|
});
|
|
@@ -2617,9 +2635,22 @@
|
|
|
2617
2635
|
}));
|
|
2618
2636
|
if (device.peer && trustSummary?.state === "trusted-by-you") attentionItems.push(createReviewItem({
|
|
2619
2637
|
id: device.deviceId,
|
|
2638
|
+
key: "other-device-trust",
|
|
2620
2639
|
name,
|
|
2621
2640
|
summary: "You accepted this device. Finish onboarding on the other device so it trusts this one too."
|
|
2622
2641
|
}));
|
|
2642
|
+
if (!device.peer && device.discovered?.stale) attentionItems.push(createReviewItem({
|
|
2643
|
+
id: device.deviceId,
|
|
2644
|
+
key: "stale-discovery",
|
|
2645
|
+
name,
|
|
2646
|
+
summary: "This device is no longer advertising fresh coordinator presence. Wait for it to check in again before connecting it here."
|
|
2647
|
+
}));
|
|
2648
|
+
if (!device.peer && Array.isArray(device.discovered?.groups) && device.discovered.groups.length > 1) attentionItems.push(createReviewItem({
|
|
2649
|
+
id: device.deviceId,
|
|
2650
|
+
key: "ambiguous-groups",
|
|
2651
|
+
name,
|
|
2652
|
+
summary: "This device appears in multiple coordinator groups. Review the team setup before approving it here."
|
|
2653
|
+
}));
|
|
2623
2654
|
if (device.peer && deviceNeedsFriendlyName({
|
|
2624
2655
|
localName: device.localName,
|
|
2625
2656
|
coordinatorName: device.coordinatorName,
|
|
@@ -3947,6 +3978,11 @@
|
|
|
3947
3978
|
placeholder: "Invite will appear here",
|
|
3948
3979
|
readOnly: true,
|
|
3949
3980
|
hidden: true
|
|
3981
|
+
}),
|
|
3982
|
+
/* @__PURE__ */ u("div", {
|
|
3983
|
+
className: "peer-meta",
|
|
3984
|
+
id: "syncInviteWarnings",
|
|
3985
|
+
hidden: true
|
|
3950
3986
|
})
|
|
3951
3987
|
]
|
|
3952
3988
|
});
|
|
@@ -4029,6 +4065,12 @@
|
|
|
4029
4065
|
function PairingText({ text }) {
|
|
4030
4066
|
return /* @__PURE__ */ u(k$2, { children: text });
|
|
4031
4067
|
}
|
|
4068
|
+
function SyncEmptyState({ title, detail }) {
|
|
4069
|
+
return /* @__PURE__ */ u("div", {
|
|
4070
|
+
class: "sync-empty-state",
|
|
4071
|
+
children: [/* @__PURE__ */ u("strong", { children: title }), /* @__PURE__ */ u("span", { children: detail })]
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4032
4074
|
function renderDiagnosticsGrid(mount, items) {
|
|
4033
4075
|
if (!items.length) {
|
|
4034
4076
|
clearSyncMount(mount);
|
|
@@ -4043,6 +4085,9 @@
|
|
|
4043
4085
|
}
|
|
4044
4086
|
renderIntoSyncMount(mount, /* @__PURE__ */ u(AttemptsList, { attempts }));
|
|
4045
4087
|
}
|
|
4088
|
+
function renderSyncEmptyState(mount, view) {
|
|
4089
|
+
renderIntoSyncMount(mount, /* @__PURE__ */ u(SyncEmptyState, { ...view }));
|
|
4090
|
+
}
|
|
4046
4091
|
function renderPairingView(payloadMount, hintMount, view) {
|
|
4047
4092
|
renderIntoSyncMount(payloadMount, /* @__PURE__ */ u(PairingText, { text: view.payloadText }));
|
|
4048
4093
|
if (hintMount) renderIntoSyncMount(hintMount, /* @__PURE__ */ u(PairingText, { text: view.hintText }));
|
|
@@ -4259,6 +4304,13 @@
|
|
|
4259
4304
|
//#region src/tabs/sync/diagnostics.ts
|
|
4260
4305
|
var SYNC_REDACT_MOUNT_ID = "syncRedactMount";
|
|
4261
4306
|
var SYNC_REDACT_LABEL_ID = "syncRedactLabel";
|
|
4307
|
+
function newestPeerPing(peers) {
|
|
4308
|
+
const timestamps = Object.values(peers || {}).map((peer) => {
|
|
4309
|
+
if (!peer || typeof peer !== "object") return "";
|
|
4310
|
+
return String(peer.last_ping_at || "").trim();
|
|
4311
|
+
}).filter(Boolean).sort();
|
|
4312
|
+
return timestamps.length ? timestamps[timestamps.length - 1] : null;
|
|
4313
|
+
}
|
|
4262
4314
|
var _renderSyncPeers = () => {};
|
|
4263
4315
|
function setRenderSyncPeers(fn) {
|
|
4264
4316
|
_renderSyncPeers = fn;
|
|
@@ -4295,6 +4347,31 @@
|
|
|
4295
4347
|
hintText: "Copy this command and run it on the other device. Use --include/--exclude to control which projects sync."
|
|
4296
4348
|
};
|
|
4297
4349
|
}
|
|
4350
|
+
function diagnosticsLoadingState() {
|
|
4351
|
+
return {
|
|
4352
|
+
title: "Diagnostics still loading.",
|
|
4353
|
+
detail: "Wait a moment for local sync status. If it stays blank, refresh the page or check whether sync is enabled on this device."
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
function diagnosticsUnavailableState() {
|
|
4357
|
+
return {
|
|
4358
|
+
title: "Diagnostics unavailable right now.",
|
|
4359
|
+
detail: "The viewer could not load sync status. Refresh this page, or check that the local codemem sync service is reachable before retrying."
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
function noAttemptsState() {
|
|
4363
|
+
const syncStatus = state.lastSyncStatus;
|
|
4364
|
+
return {
|
|
4365
|
+
title: "No recent sync attempts yet.",
|
|
4366
|
+
detail: syncStatus?.daemon_state === "disabled" || syncStatus?.enabled === false ? "Turn on sync in Settings → Device Sync first. Recent attempts will appear here after this device can actually run sync work." : "Trigger a sync pass or pair another device to generate activity here when you need low-level troubleshooting."
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
function unavailableAttemptsState() {
|
|
4370
|
+
return {
|
|
4371
|
+
title: "Recent attempts unavailable right now.",
|
|
4372
|
+
detail: "Attempt history could not be loaded because sync diagnostics failed. Refresh the page after local sync status is reachable again."
|
|
4373
|
+
};
|
|
4374
|
+
}
|
|
4298
4375
|
function renderSyncStatus() {
|
|
4299
4376
|
const syncStatusGrid = document.getElementById("syncStatusGrid");
|
|
4300
4377
|
const syncMeta = document.getElementById("syncMeta");
|
|
@@ -4303,16 +4380,16 @@
|
|
|
4303
4380
|
hideSkeleton("syncDiagSkeleton");
|
|
4304
4381
|
const status = state.lastSyncStatus;
|
|
4305
4382
|
if (!status) {
|
|
4306
|
-
|
|
4383
|
+
renderSyncEmptyState(syncStatusGrid, diagnosticsLoadingState());
|
|
4307
4384
|
renderActionList(syncActions, []);
|
|
4308
|
-
if (syncMeta) syncMeta.textContent = "Loading sync
|
|
4385
|
+
if (syncMeta) syncMeta.textContent = "Loading advanced sync diagnostics…";
|
|
4309
4386
|
return;
|
|
4310
4387
|
}
|
|
4311
4388
|
const peers = status.peers || {};
|
|
4312
4389
|
const pingPayload = status.ping || {};
|
|
4313
4390
|
const syncPayload = status.sync || {};
|
|
4314
4391
|
const lastSync = status.last_sync_at || status.last_sync_at_utc || null;
|
|
4315
|
-
const lastPing = pingPayload.last_ping_at || status.last_ping_at || null;
|
|
4392
|
+
const lastPing = pingPayload.last_ping_at || status.last_ping_at || newestPeerPing(peers) || null;
|
|
4316
4393
|
const syncError = status.last_sync_error || "";
|
|
4317
4394
|
const pingError = status.last_ping_error || "";
|
|
4318
4395
|
const pending = Number(status.pending || 0);
|
|
@@ -4328,8 +4405,8 @@
|
|
|
4328
4405
|
const peerCount = Object.keys(peers).length;
|
|
4329
4406
|
const syncNoPeers = !syncDisabled && peerCount === 0;
|
|
4330
4407
|
if (syncMeta) {
|
|
4331
|
-
const parts = syncDisabled ? ["
|
|
4332
|
-
`
|
|
4408
|
+
const parts = syncDisabled ? ["Advanced sync is off on this device", "Turn on sync in Settings → Device Sync when you want pairing payloads, peer status, and recent attempt details here"] : syncNoPeers ? ["Advanced sync is ready but idle", "Use Show pairing to connect another device, then this panel will start showing live peer status and recent attempts"] : [
|
|
4409
|
+
`Advanced state: ${daemonStateLabel}`,
|
|
4333
4410
|
`Peers: ${peerCount}`,
|
|
4334
4411
|
lastSync ? `Last sync: ${formatAgeShort(secondsSince(lastSync))} ago` : "Last sync: never"
|
|
4335
4412
|
];
|
|
@@ -4354,7 +4431,7 @@
|
|
|
4354
4431
|
},
|
|
4355
4432
|
{
|
|
4356
4433
|
label: "Last sync",
|
|
4357
|
-
value: "
|
|
4434
|
+
value: "Not running"
|
|
4358
4435
|
}
|
|
4359
4436
|
] : syncNoPeers ? [
|
|
4360
4437
|
{
|
|
@@ -4363,7 +4440,7 @@
|
|
|
4363
4440
|
},
|
|
4364
4441
|
{
|
|
4365
4442
|
label: "Mode",
|
|
4366
|
-
value: "
|
|
4443
|
+
value: "Ready to pair"
|
|
4367
4444
|
},
|
|
4368
4445
|
{
|
|
4369
4446
|
label: "Pending events",
|
|
@@ -4371,7 +4448,7 @@
|
|
|
4371
4448
|
},
|
|
4372
4449
|
{
|
|
4373
4450
|
label: "Last sync",
|
|
4374
|
-
value: "
|
|
4451
|
+
value: "Waiting for first peer"
|
|
4375
4452
|
}
|
|
4376
4453
|
] : [
|
|
4377
4454
|
{
|
|
@@ -4387,7 +4464,7 @@
|
|
|
4387
4464
|
value: lastSync ? `${formatAgeShort(secondsSince(lastSync))} ago` : "never"
|
|
4388
4465
|
},
|
|
4389
4466
|
{
|
|
4390
|
-
label: "Last ping",
|
|
4467
|
+
label: "Last peer ping",
|
|
4391
4468
|
value: lastPing ? `${formatAgeShort(secondsSince(lastPing))} ago` : "never"
|
|
4392
4469
|
},
|
|
4393
4470
|
{
|
|
@@ -4404,7 +4481,7 @@
|
|
|
4404
4481
|
value: `${syncPayload.seconds_since_last}s`
|
|
4405
4482
|
});
|
|
4406
4483
|
if (!syncDisabled && !syncNoPeers && pingPayload.seconds_since_last) items.push({
|
|
4407
|
-
label: "Since last ping",
|
|
4484
|
+
label: "Since last peer ping",
|
|
4408
4485
|
value: `${pingPayload.seconds_since_last}s`
|
|
4409
4486
|
});
|
|
4410
4487
|
if (!syncDisabled && retentionEnabled && retentionLastError) items.push({
|
|
@@ -4419,7 +4496,7 @@
|
|
|
4419
4496
|
command: "codemem sync start"
|
|
4420
4497
|
});
|
|
4421
4498
|
actions.push({
|
|
4422
|
-
label: "
|
|
4499
|
+
label: "Run one sync pass now.",
|
|
4423
4500
|
command: "codemem sync once"
|
|
4424
4501
|
});
|
|
4425
4502
|
} else if (daemonState === "needs_attention") actions.push({
|
|
@@ -4436,7 +4513,7 @@
|
|
|
4436
4513
|
command: "codemem sync restart && codemem sync once"
|
|
4437
4514
|
});
|
|
4438
4515
|
actions.push({
|
|
4439
|
-
label: "
|
|
4516
|
+
label: "Run doctor for the root cause.",
|
|
4440
4517
|
command: "codemem sync doctor"
|
|
4441
4518
|
});
|
|
4442
4519
|
} else if (!syncDisabled && !syncNoPeers && pending > 0) actions.push({
|
|
@@ -4450,7 +4527,7 @@
|
|
|
4450
4527
|
if (!syncAttempts) return;
|
|
4451
4528
|
const attempts = state.lastSyncAttempts;
|
|
4452
4529
|
if (!Array.isArray(attempts) || !attempts.length) {
|
|
4453
|
-
|
|
4530
|
+
renderSyncEmptyState(syncAttempts, noAttemptsState());
|
|
4454
4531
|
return;
|
|
4455
4532
|
}
|
|
4456
4533
|
renderAttemptsList(syncAttempts, attempts.slice(0, 5).map((attempt) => {
|
|
@@ -4462,6 +4539,16 @@
|
|
|
4462
4539
|
};
|
|
4463
4540
|
}));
|
|
4464
4541
|
}
|
|
4542
|
+
function renderSyncDiagnosticsUnavailable() {
|
|
4543
|
+
const syncStatusGrid = document.getElementById("syncStatusGrid");
|
|
4544
|
+
const syncAttempts = document.getElementById("syncAttempts");
|
|
4545
|
+
const syncMeta = document.getElementById("syncMeta");
|
|
4546
|
+
const syncActions = document.getElementById("syncActions");
|
|
4547
|
+
if (syncStatusGrid) renderSyncEmptyState(syncStatusGrid, diagnosticsUnavailableState());
|
|
4548
|
+
if (syncAttempts) renderSyncEmptyState(syncAttempts, unavailableAttemptsState());
|
|
4549
|
+
if (syncMeta) syncMeta.textContent = "Advanced diagnostics are unavailable right now. Refresh the page, or verify the local sync service before retrying.";
|
|
4550
|
+
renderActionList(syncActions, []);
|
|
4551
|
+
}
|
|
4465
4552
|
function renderPairingCollapsible() {
|
|
4466
4553
|
const mount = document.getElementById("syncPairingDisclosureMount");
|
|
4467
4554
|
const contentHost = document.getElementById("syncPairingPanelMount");
|
|
@@ -4575,7 +4662,10 @@
|
|
|
4575
4662
|
className: "sync-action",
|
|
4576
4663
|
children: [/* @__PURE__ */ u("div", {
|
|
4577
4664
|
className: "sync-action-text",
|
|
4578
|
-
children: "
|
|
4665
|
+
children: ["Add another teammate.", /* @__PURE__ */ u("span", {
|
|
4666
|
+
className: "sync-action-command",
|
|
4667
|
+
children: "Generate an invite when you are ready to bring another device into this team."
|
|
4668
|
+
})]
|
|
4579
4669
|
}), /* @__PURE__ */ u("button", {
|
|
4580
4670
|
type: "button",
|
|
4581
4671
|
className: "settings-button",
|
|
@@ -4593,9 +4683,9 @@
|
|
|
4593
4683
|
className: "sync-action",
|
|
4594
4684
|
children: [/* @__PURE__ */ u("div", {
|
|
4595
4685
|
className: "sync-action-text",
|
|
4596
|
-
children: ["
|
|
4686
|
+
children: ["Pair another device.", /* @__PURE__ */ u("span", {
|
|
4597
4687
|
className: "sync-action-command",
|
|
4598
|
-
children: "
|
|
4688
|
+
children: "Use the pairing command in Advanced diagnostics when you are ready to connect one."
|
|
4599
4689
|
})]
|
|
4600
4690
|
}), /* @__PURE__ */ u("button", {
|
|
4601
4691
|
type: "button",
|
|
@@ -4607,26 +4697,34 @@
|
|
|
4607
4697
|
}
|
|
4608
4698
|
function SyncInviteJoinPanels({ invitePanel, invitePanelOpen, inviteRestoreParent, joinPanel, joinRestoreParent, onToggleInvitePanel, pairedPeerCount, presenceStatus }) {
|
|
4609
4699
|
return /* @__PURE__ */ u(k$2, { children: [
|
|
4610
|
-
/* @__PURE__ */ u(
|
|
4700
|
+
presenceStatus === "not_enrolled" ? /* @__PURE__ */ u(k$2, { children: [
|
|
4701
|
+
joinPanel ? /* @__PURE__ */ u(ExistingElementSlot$1, {
|
|
4702
|
+
element: joinPanel,
|
|
4703
|
+
hidden: false,
|
|
4704
|
+
restoreParent: joinRestoreParent
|
|
4705
|
+
}) : null,
|
|
4706
|
+
/* @__PURE__ */ u("div", {
|
|
4707
|
+
className: "peer-meta",
|
|
4708
|
+
id: "syncJoinFeedback",
|
|
4709
|
+
hidden: true
|
|
4710
|
+
}),
|
|
4711
|
+
/* @__PURE__ */ u("div", {
|
|
4712
|
+
className: "sync-action",
|
|
4713
|
+
children: /* @__PURE__ */ u("div", {
|
|
4714
|
+
className: "sync-action-text",
|
|
4715
|
+
children: ["This device is not on the team yet.", /* @__PURE__ */ u("span", {
|
|
4716
|
+
className: "sync-action-command",
|
|
4717
|
+
children: "Import an invite or ask an admin to enroll it first."
|
|
4718
|
+
})]
|
|
4719
|
+
})
|
|
4720
|
+
})
|
|
4721
|
+
] }) : null,
|
|
4722
|
+
presenceStatus !== "not_enrolled" ? /* @__PURE__ */ u(InviteToggleRow, {
|
|
4611
4723
|
invitePanel,
|
|
4612
4724
|
invitePanelOpen,
|
|
4613
4725
|
inviteRestoreParent,
|
|
4614
4726
|
onToggle: onToggleInvitePanel
|
|
4615
|
-
}),
|
|
4616
|
-
presenceStatus === "not_enrolled" ? /* @__PURE__ */ u(k$2, { children: [joinPanel ? /* @__PURE__ */ u(ExistingElementSlot$1, {
|
|
4617
|
-
element: joinPanel,
|
|
4618
|
-
hidden: false,
|
|
4619
|
-
restoreParent: joinRestoreParent
|
|
4620
|
-
}) : null, /* @__PURE__ */ u("div", {
|
|
4621
|
-
className: "sync-action",
|
|
4622
|
-
children: /* @__PURE__ */ u("div", {
|
|
4623
|
-
className: "sync-action-text",
|
|
4624
|
-
children: ["This device is not connected to the team yet.", /* @__PURE__ */ u("span", {
|
|
4625
|
-
className: "sync-action-command",
|
|
4626
|
-
children: "Import a team invite or ask your admin to enroll this device"
|
|
4627
|
-
})]
|
|
4628
|
-
})
|
|
4629
|
-
})] }) : null,
|
|
4727
|
+
}) : null,
|
|
4630
4728
|
!pairedPeerCount && presenceStatus === "posted" ? /* @__PURE__ */ u(PairingCopyRow, {}) : null
|
|
4631
4729
|
] });
|
|
4632
4730
|
}
|
|
@@ -4671,7 +4769,30 @@
|
|
|
4671
4769
|
}, `${item.peerName}:${item.actorId}:${item.scopeLabel}`)) });
|
|
4672
4770
|
}
|
|
4673
4771
|
//#endregion
|
|
4772
|
+
//#region src/tabs/sync/components/sync-inline-feedback.tsx
|
|
4773
|
+
function SyncInlineFeedback({ feedback }) {
|
|
4774
|
+
if (!feedback?.message) return null;
|
|
4775
|
+
return /* @__PURE__ */ u("div", {
|
|
4776
|
+
className: `sync-inline-feedback ${feedback.tone}`,
|
|
4777
|
+
role: feedback.tone === "warning" ? "alert" : "status",
|
|
4778
|
+
"aria-live": feedback.tone === "warning" ? "assertive" : "polite",
|
|
4779
|
+
children: feedback.message
|
|
4780
|
+
});
|
|
4781
|
+
}
|
|
4782
|
+
//#endregion
|
|
4674
4783
|
//#region src/tabs/sync/components/team-sync-panel.tsx
|
|
4784
|
+
function SectionHeading({ count, label }) {
|
|
4785
|
+
return /* @__PURE__ */ u("div", {
|
|
4786
|
+
className: "sync-section-heading",
|
|
4787
|
+
children: [/* @__PURE__ */ u("div", {
|
|
4788
|
+
className: "sync-action-text sync-section-label",
|
|
4789
|
+
children: label
|
|
4790
|
+
}), count ? /* @__PURE__ */ u("span", {
|
|
4791
|
+
className: "badge actor-badge sync-section-count",
|
|
4792
|
+
children: count
|
|
4793
|
+
}) : null]
|
|
4794
|
+
});
|
|
4795
|
+
}
|
|
4675
4796
|
function AttentionRow({ item, onAction }) {
|
|
4676
4797
|
const [busy, setBusy] = d(false);
|
|
4677
4798
|
return /* @__PURE__ */ u("div", {
|
|
@@ -4700,228 +4821,256 @@
|
|
|
4700
4821
|
}
|
|
4701
4822
|
function PendingJoinRequestRow({ request, onApprove, onDeny }) {
|
|
4702
4823
|
const [busyAction, setBusyAction] = d(null);
|
|
4824
|
+
const [feedback, setFeedback] = d(null);
|
|
4703
4825
|
const [approveLabel, setApproveLabel] = d("Approve");
|
|
4704
4826
|
const [denyLabel, setDenyLabel] = d("Deny");
|
|
4705
4827
|
return /* @__PURE__ */ u("div", {
|
|
4706
4828
|
className: "actor-row",
|
|
4707
|
-
children: [
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
})
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
type: "button",
|
|
4720
|
-
className: "settings-button",
|
|
4721
|
-
disabled: busyAction !== null,
|
|
4722
|
-
onClick: async () => {
|
|
4723
|
-
setBusyAction("approve");
|
|
4724
|
-
setApproveLabel("Approving…");
|
|
4725
|
-
try {
|
|
4726
|
-
await onApprove(request);
|
|
4727
|
-
setApproveLabel("Approve");
|
|
4728
|
-
} catch {
|
|
4729
|
-
setApproveLabel("Retry");
|
|
4730
|
-
} finally {
|
|
4731
|
-
setBusyAction(null);
|
|
4732
|
-
}
|
|
4733
|
-
},
|
|
4734
|
-
children: approveLabel
|
|
4735
|
-
}), /* @__PURE__ */ u("button", {
|
|
4736
|
-
type: "button",
|
|
4737
|
-
className: "settings-button",
|
|
4738
|
-
disabled: busyAction !== null,
|
|
4739
|
-
onClick: async () => {
|
|
4740
|
-
setBusyAction("deny");
|
|
4741
|
-
setDenyLabel("Denying…");
|
|
4742
|
-
try {
|
|
4743
|
-
await onDeny(request);
|
|
4744
|
-
setDenyLabel("Deny");
|
|
4745
|
-
} catch {
|
|
4746
|
-
setDenyLabel("Retry deny");
|
|
4747
|
-
} finally {
|
|
4748
|
-
setBusyAction(null);
|
|
4749
|
-
}
|
|
4750
|
-
},
|
|
4751
|
-
children: denyLabel
|
|
4752
|
-
})]
|
|
4753
|
-
})]
|
|
4754
|
-
});
|
|
4755
|
-
}
|
|
4756
|
-
function DiscoveredDeviceRow({ row, onInspectConflict, onRemoveConflict, onReview }) {
|
|
4757
|
-
const [busy, setBusy] = d(null);
|
|
4758
|
-
const [reviewLabel, setReviewLabel] = d(row.actionLabel || "Review device");
|
|
4759
|
-
const [removeLabel, setRemoveLabel] = d("Remove broken device record");
|
|
4760
|
-
return /* @__PURE__ */ u("div", {
|
|
4761
|
-
className: "actor-row",
|
|
4762
|
-
"data-discovered-device-id": row.deviceId,
|
|
4763
|
-
children: [/* @__PURE__ */ u("div", {
|
|
4764
|
-
className: "actor-details",
|
|
4765
|
-
children: [/* @__PURE__ */ u("div", {
|
|
4766
|
-
className: "actor-title",
|
|
4767
|
-
children: [
|
|
4768
|
-
/* @__PURE__ */ u("strong", {
|
|
4769
|
-
title: row.displayTitle || void 0,
|
|
4770
|
-
children: row.displayName
|
|
4771
|
-
}),
|
|
4772
|
-
/* @__PURE__ */ u("span", {
|
|
4773
|
-
className: `badge actor-badge${row.availabilityLabel === "Offline" ? "" : " local"}`,
|
|
4774
|
-
children: row.availabilityLabel
|
|
4775
|
-
}),
|
|
4776
|
-
/* @__PURE__ */ u("span", {
|
|
4777
|
-
className: "badge actor-badge",
|
|
4778
|
-
children: row.connectionLabel
|
|
4779
|
-
}),
|
|
4780
|
-
row.approvalBadgeLabel ? /* @__PURE__ */ u("span", {
|
|
4781
|
-
className: "badge actor-badge",
|
|
4782
|
-
children: row.approvalBadgeLabel
|
|
4783
|
-
}) : null
|
|
4784
|
-
]
|
|
4785
|
-
}), /* @__PURE__ */ u("div", {
|
|
4786
|
-
className: "peer-meta",
|
|
4787
|
-
children: row.note
|
|
4788
|
-
})]
|
|
4789
|
-
}), /* @__PURE__ */ u("div", {
|
|
4790
|
-
className: "actor-actions",
|
|
4791
|
-
children: [
|
|
4792
|
-
row.mode === "accept" ? /* @__PURE__ */ u("button", {
|
|
4829
|
+
children: [
|
|
4830
|
+
/* @__PURE__ */ u("div", {
|
|
4831
|
+
className: "actor-details",
|
|
4832
|
+
children: /* @__PURE__ */ u("div", {
|
|
4833
|
+
className: "actor-title",
|
|
4834
|
+
title: request.requestId || void 0,
|
|
4835
|
+
children: request.displayName
|
|
4836
|
+
})
|
|
4837
|
+
}),
|
|
4838
|
+
/* @__PURE__ */ u("div", {
|
|
4839
|
+
className: "actor-actions",
|
|
4840
|
+
children: [/* @__PURE__ */ u("button", {
|
|
4793
4841
|
type: "button",
|
|
4794
4842
|
className: "settings-button",
|
|
4795
|
-
disabled:
|
|
4843
|
+
disabled: busyAction !== null,
|
|
4796
4844
|
onClick: async () => {
|
|
4797
|
-
|
|
4798
|
-
|
|
4845
|
+
setBusyAction("approve");
|
|
4846
|
+
setApproveLabel("Approving…");
|
|
4799
4847
|
try {
|
|
4800
|
-
await
|
|
4801
|
-
|
|
4848
|
+
setFeedback(await onApprove(request) || null);
|
|
4849
|
+
setApproveLabel("Approve");
|
|
4802
4850
|
} catch {
|
|
4803
|
-
|
|
4851
|
+
setApproveLabel("Retry");
|
|
4804
4852
|
} finally {
|
|
4805
|
-
|
|
4853
|
+
setBusyAction(null);
|
|
4806
4854
|
}
|
|
4807
4855
|
},
|
|
4808
|
-
children:
|
|
4809
|
-
}) : null,
|
|
4810
|
-
row.mode === "stale" || row.mode === "ambiguous" || row.mode === "scope-pending" ? /* @__PURE__ */ u("div", {
|
|
4811
|
-
className: "peer-meta",
|
|
4812
|
-
children: row.actionMessage
|
|
4813
|
-
}) : null,
|
|
4814
|
-
row.mode === "paired" && row.pairedMessage ? /* @__PURE__ */ u("div", {
|
|
4815
|
-
className: "peer-meta",
|
|
4816
|
-
children: row.pairedMessage
|
|
4817
|
-
}) : null,
|
|
4818
|
-
row.mode === "conflict" ? /* @__PURE__ */ u(k$2, { children: [/* @__PURE__ */ u("button", {
|
|
4819
|
-
type: "button",
|
|
4820
|
-
className: "settings-button",
|
|
4821
|
-
disabled: busy !== null,
|
|
4822
|
-
onClick: () => onInspectConflict(row),
|
|
4823
|
-
children: "Open device details"
|
|
4856
|
+
children: approveLabel
|
|
4824
4857
|
}), /* @__PURE__ */ u("button", {
|
|
4825
4858
|
type: "button",
|
|
4826
4859
|
className: "settings-button",
|
|
4827
|
-
disabled:
|
|
4860
|
+
disabled: busyAction !== null,
|
|
4828
4861
|
onClick: async () => {
|
|
4829
|
-
|
|
4830
|
-
|
|
4862
|
+
setBusyAction("deny");
|
|
4863
|
+
setDenyLabel("Denying…");
|
|
4831
4864
|
try {
|
|
4832
|
-
await
|
|
4833
|
-
|
|
4865
|
+
setFeedback(await onDeny(request) || null);
|
|
4866
|
+
setDenyLabel("Deny");
|
|
4834
4867
|
} catch {
|
|
4835
|
-
|
|
4868
|
+
setDenyLabel("Retry");
|
|
4836
4869
|
} finally {
|
|
4837
|
-
|
|
4870
|
+
setBusyAction(null);
|
|
4838
4871
|
}
|
|
4839
4872
|
},
|
|
4840
|
-
children:
|
|
4841
|
-
})]
|
|
4842
|
-
|
|
4843
|
-
|
|
4873
|
+
children: denyLabel
|
|
4874
|
+
})]
|
|
4875
|
+
}),
|
|
4876
|
+
/* @__PURE__ */ u(SyncInlineFeedback, { feedback })
|
|
4877
|
+
]
|
|
4878
|
+
});
|
|
4879
|
+
}
|
|
4880
|
+
function DiscoveredDeviceRow({ row, onInspectConflict, onRemoveConflict, onReview }) {
|
|
4881
|
+
const [busy, setBusy] = d(null);
|
|
4882
|
+
const [feedback, setFeedback] = d(null);
|
|
4883
|
+
const [reviewLabel, setReviewLabel] = d(row.actionLabel || "Review device");
|
|
4884
|
+
const [removeLabel, setRemoveLabel] = d("Remove broken device record");
|
|
4885
|
+
return /* @__PURE__ */ u("div", {
|
|
4886
|
+
className: "actor-row",
|
|
4887
|
+
"data-discovered-device-id": row.deviceId,
|
|
4888
|
+
children: [
|
|
4889
|
+
/* @__PURE__ */ u("div", {
|
|
4890
|
+
className: "actor-details",
|
|
4891
|
+
children: [/* @__PURE__ */ u("div", {
|
|
4892
|
+
className: "actor-title",
|
|
4893
|
+
children: [
|
|
4894
|
+
/* @__PURE__ */ u("strong", {
|
|
4895
|
+
title: row.displayTitle || void 0,
|
|
4896
|
+
children: row.displayName
|
|
4897
|
+
}),
|
|
4898
|
+
/* @__PURE__ */ u("span", {
|
|
4899
|
+
className: `badge actor-badge${row.availabilityLabel === "Offline" ? "" : " local"}`,
|
|
4900
|
+
children: row.availabilityLabel
|
|
4901
|
+
}),
|
|
4902
|
+
/* @__PURE__ */ u("span", {
|
|
4903
|
+
className: "badge actor-badge",
|
|
4904
|
+
children: row.connectionLabel
|
|
4905
|
+
}),
|
|
4906
|
+
row.approvalBadgeLabel ? /* @__PURE__ */ u("span", {
|
|
4907
|
+
className: "badge actor-badge",
|
|
4908
|
+
children: row.approvalBadgeLabel
|
|
4909
|
+
}) : null
|
|
4910
|
+
]
|
|
4911
|
+
}), /* @__PURE__ */ u("div", {
|
|
4912
|
+
className: "peer-meta",
|
|
4913
|
+
children: row.note
|
|
4914
|
+
})]
|
|
4915
|
+
}),
|
|
4916
|
+
/* @__PURE__ */ u("div", {
|
|
4917
|
+
className: "actor-actions",
|
|
4918
|
+
children: [
|
|
4919
|
+
row.mode === "accept" ? /* @__PURE__ */ u("button", {
|
|
4920
|
+
type: "button",
|
|
4921
|
+
className: "settings-button",
|
|
4922
|
+
disabled: busy !== null,
|
|
4923
|
+
onClick: async () => {
|
|
4924
|
+
setBusy("review");
|
|
4925
|
+
setReviewLabel("Reviewing…");
|
|
4926
|
+
try {
|
|
4927
|
+
setFeedback(await onReview(row) || null);
|
|
4928
|
+
setReviewLabel(row.actionLabel || "Review device");
|
|
4929
|
+
} catch {
|
|
4930
|
+
setReviewLabel("Retry");
|
|
4931
|
+
} finally {
|
|
4932
|
+
setBusy(null);
|
|
4933
|
+
}
|
|
4934
|
+
},
|
|
4935
|
+
children: reviewLabel
|
|
4936
|
+
}) : null,
|
|
4937
|
+
(row.mode === "stale" || row.mode === "ambiguous" || row.mode === "scope-pending") && row.actionMessage ? /* @__PURE__ */ u("div", {
|
|
4938
|
+
className: "peer-meta",
|
|
4939
|
+
children: row.actionMessage
|
|
4940
|
+
}) : null,
|
|
4941
|
+
row.mode === "paired" && row.pairedMessage ? /* @__PURE__ */ u("div", {
|
|
4942
|
+
className: "peer-meta",
|
|
4943
|
+
children: row.pairedMessage
|
|
4944
|
+
}) : null,
|
|
4945
|
+
row.mode === "conflict" ? /* @__PURE__ */ u(k$2, { children: [/* @__PURE__ */ u("button", {
|
|
4946
|
+
type: "button",
|
|
4947
|
+
className: "settings-button",
|
|
4948
|
+
disabled: busy !== null,
|
|
4949
|
+
onClick: () => onInspectConflict(row),
|
|
4950
|
+
children: "Open device details"
|
|
4951
|
+
}), /* @__PURE__ */ u("button", {
|
|
4952
|
+
type: "button",
|
|
4953
|
+
className: "settings-button",
|
|
4954
|
+
disabled: busy !== null,
|
|
4955
|
+
onClick: async () => {
|
|
4956
|
+
setBusy("remove");
|
|
4957
|
+
setRemoveLabel("Removing…");
|
|
4958
|
+
try {
|
|
4959
|
+
setFeedback(await onRemoveConflict(row) || null);
|
|
4960
|
+
setRemoveLabel("Remove broken device record");
|
|
4961
|
+
} catch {
|
|
4962
|
+
setRemoveLabel("Retry");
|
|
4963
|
+
} finally {
|
|
4964
|
+
setBusy(null);
|
|
4965
|
+
}
|
|
4966
|
+
},
|
|
4967
|
+
children: removeLabel
|
|
4968
|
+
})] }) : null
|
|
4969
|
+
]
|
|
4970
|
+
}),
|
|
4971
|
+
/* @__PURE__ */ u(SyncInlineFeedback, { feedback })
|
|
4972
|
+
]
|
|
4844
4973
|
});
|
|
4845
4974
|
}
|
|
4846
4975
|
function ActionContent(props) {
|
|
4847
4976
|
const hasAttentionItems = props.actionItems.length > 0;
|
|
4977
|
+
const hasOtherActionableWork = props.actionableCount > props.actionItems.length;
|
|
4848
4978
|
return /* @__PURE__ */ u(k$2, { children: [
|
|
4849
|
-
hasAttentionItems ? /* @__PURE__ */ u("
|
|
4850
|
-
|
|
4851
|
-
children: "Needs attention"
|
|
4852
|
-
}) : null,
|
|
4853
|
-
hasAttentionItems ? props.actionItems.slice(0, 4).map((item) => /* @__PURE__ */ u(AttentionRow, {
|
|
4979
|
+
hasAttentionItems || hasOtherActionableWork || props.presenceStatus !== "posted" ? /* @__PURE__ */ u(SectionHeading, { label: "Next steps" }) : null,
|
|
4980
|
+
hasAttentionItems ? props.actionItems.map((item) => /* @__PURE__ */ u(AttentionRow, {
|
|
4854
4981
|
item,
|
|
4855
4982
|
onAction: props.onAttentionAction
|
|
4856
4983
|
}, item.id)) : null,
|
|
4857
|
-
!hasAttentionItems && props.presenceStatus === "posted" ? /* @__PURE__ */ u("div", {
|
|
4984
|
+
!hasAttentionItems && !hasOtherActionableWork && props.presenceStatus === "posted" ? /* @__PURE__ */ u("div", {
|
|
4858
4985
|
className: "sync-action",
|
|
4859
4986
|
children: /* @__PURE__ */ u("div", {
|
|
4860
4987
|
className: "sync-action-text",
|
|
4861
|
-
children:
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4988
|
+
children: "Everything is healthy."
|
|
4989
|
+
})
|
|
4990
|
+
}) : null,
|
|
4991
|
+
!hasAttentionItems && hasOtherActionableWork && props.presenceStatus === "posted" ? /* @__PURE__ */ u("div", {
|
|
4992
|
+
className: "sync-action",
|
|
4993
|
+
children: /* @__PURE__ */ u("div", {
|
|
4994
|
+
className: "sync-action-text",
|
|
4995
|
+
children: "Review the team items below when you are ready."
|
|
4865
4996
|
})
|
|
4866
4997
|
}) : null,
|
|
4867
4998
|
!hasAttentionItems && props.presenceStatus === "not_enrolled" ? /* @__PURE__ */ u("div", {
|
|
4868
4999
|
className: "sync-action",
|
|
4869
5000
|
children: /* @__PURE__ */ u("div", {
|
|
4870
5001
|
className: "sync-action-text",
|
|
4871
|
-
children: ["This device
|
|
5002
|
+
children: ["This device needs team enrollment", /* @__PURE__ */ u("span", {
|
|
4872
5003
|
className: "sync-action-command",
|
|
4873
|
-
children: "Import an invite or ask
|
|
5004
|
+
children: "Import an invite or ask an admin to enroll it."
|
|
4874
5005
|
})]
|
|
4875
5006
|
})
|
|
4876
|
-
}) : null
|
|
4877
|
-
props.children
|
|
5007
|
+
}) : null
|
|
4878
5008
|
] });
|
|
4879
5009
|
}
|
|
5010
|
+
function TeamActionsContent({ children }) {
|
|
5011
|
+
if (!children) return null;
|
|
5012
|
+
return /* @__PURE__ */ u(k$2, { children: [/* @__PURE__ */ u(SectionHeading, { label: "Keep the team moving" }), children] });
|
|
5013
|
+
}
|
|
4880
5014
|
function TeamStatusPortal({ mount, statusSummary }) {
|
|
4881
5015
|
return $(/* @__PURE__ */ u("div", {
|
|
4882
5016
|
className: "sync-team-summary",
|
|
4883
|
-
children: [
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
5017
|
+
children: [
|
|
5018
|
+
/* @__PURE__ */ u("div", {
|
|
5019
|
+
className: "sync-team-status-row",
|
|
5020
|
+
children: [/* @__PURE__ */ u("span", {
|
|
5021
|
+
className: "sync-team-status-label",
|
|
5022
|
+
children: "Team status"
|
|
5023
|
+
}), /* @__PURE__ */ u("span", {
|
|
5024
|
+
className: statusSummary.badgeClassName,
|
|
5025
|
+
children: statusSummary.presenceLabel
|
|
5026
|
+
})]
|
|
5027
|
+
}),
|
|
5028
|
+
statusSummary.headline ? /* @__PURE__ */ u("div", {
|
|
5029
|
+
className: "sync-team-headline",
|
|
5030
|
+
children: statusSummary.headline
|
|
5031
|
+
}) : null,
|
|
5032
|
+
/* @__PURE__ */ u("div", {
|
|
5033
|
+
className: "sync-team-metrics sync-team-metrics-secondary",
|
|
5034
|
+
children: statusSummary.metricsText
|
|
5035
|
+
})
|
|
5036
|
+
]
|
|
4896
5037
|
}), mount);
|
|
4897
5038
|
}
|
|
4898
5039
|
function DiscoveredPortal({ mount, rows, onInspectConflict, onRemoveConflict, onReview }) {
|
|
4899
|
-
if (!mount) return null;
|
|
4900
|
-
return $(/* @__PURE__ */ u(k$2, { children:
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
5040
|
+
if (!mount || !rows.length && !state.syncDiscoveredFeedback) return null;
|
|
5041
|
+
return $(/* @__PURE__ */ u(k$2, { children: [
|
|
5042
|
+
/* @__PURE__ */ u(SectionHeading, {
|
|
5043
|
+
count: rows.length || void 0,
|
|
5044
|
+
label: "Devices seen on team"
|
|
5045
|
+
}),
|
|
5046
|
+
/* @__PURE__ */ u(SyncInlineFeedback, { feedback: state.syncDiscoveredFeedback }),
|
|
5047
|
+
rows.map((row) => /* @__PURE__ */ u(DiscoveredDeviceRow, {
|
|
5048
|
+
row,
|
|
5049
|
+
onInspectConflict,
|
|
5050
|
+
onRemoveConflict,
|
|
5051
|
+
onReview
|
|
5052
|
+
}, row.deviceId))
|
|
5053
|
+
] }), mount);
|
|
4906
5054
|
}
|
|
4907
5055
|
function PendingRequestsPortal({ mount, requests, onApprove, onDeny }) {
|
|
4908
|
-
if (!mount || !requests.length) return null;
|
|
4909
|
-
return $(/* @__PURE__ */ u(k$2, { children: [
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
requests
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
5056
|
+
if (!mount || !requests.length && !state.syncJoinRequestsFeedback) return null;
|
|
5057
|
+
return $(/* @__PURE__ */ u(k$2, { children: [
|
|
5058
|
+
/* @__PURE__ */ u(SectionHeading, {
|
|
5059
|
+
count: requests.length || void 0,
|
|
5060
|
+
label: "Pending join requests"
|
|
5061
|
+
}),
|
|
5062
|
+
/* @__PURE__ */ u(SyncInlineFeedback, { feedback: state.syncJoinRequestsFeedback }),
|
|
5063
|
+
requests.map((request) => /* @__PURE__ */ u(PendingJoinRequestRow, {
|
|
5064
|
+
request,
|
|
5065
|
+
onApprove,
|
|
5066
|
+
onDeny
|
|
5067
|
+
}, request.requestId))
|
|
5068
|
+
] }), mount);
|
|
4921
5069
|
}
|
|
4922
5070
|
function TeamSyncPanel(props) {
|
|
4923
5071
|
return /* @__PURE__ */ u(k$2, { children: [
|
|
4924
5072
|
/* @__PURE__ */ u(ActionContent, { ...props }),
|
|
5073
|
+
/* @__PURE__ */ u(TeamActionsContent, { children: props.children }),
|
|
4925
5074
|
/* @__PURE__ */ u(TeamStatusPortal, {
|
|
4926
5075
|
mount: props.listMount,
|
|
4927
5076
|
statusSummary: props.statusSummary
|
|
@@ -9869,14 +10018,43 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
9869
10018
|
}
|
|
9870
10019
|
function setInviteOutputVisibility() {
|
|
9871
10020
|
const syncInviteOutput = document.getElementById("syncInviteOutput");
|
|
10021
|
+
const syncInviteWarnings = document.getElementById("syncInviteWarnings");
|
|
9872
10022
|
if (!syncInviteOutput) return;
|
|
9873
10023
|
const encoded = String(state.lastTeamInvite?.encoded || "").trim();
|
|
9874
10024
|
syncInviteOutput.value = encoded;
|
|
9875
10025
|
syncInviteOutput.hidden = !encoded;
|
|
10026
|
+
if (syncInviteWarnings) {
|
|
10027
|
+
const warnings = Array.isArray(state.lastTeamInvite?.warnings) ? state.lastTeamInvite.warnings : [];
|
|
10028
|
+
syncInviteWarnings.textContent = warnings.join(" · ");
|
|
10029
|
+
syncInviteWarnings.hidden = warnings.length === 0;
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
function setJoinFeedbackVisibility() {
|
|
10033
|
+
const syncJoinFeedback = document.getElementById("syncJoinFeedback");
|
|
10034
|
+
if (!syncJoinFeedback) return;
|
|
10035
|
+
const feedback = state.syncJoinFlowFeedback;
|
|
10036
|
+
syncJoinFeedback.textContent = feedback?.message || "";
|
|
10037
|
+
syncJoinFeedback.hidden = !feedback?.message;
|
|
10038
|
+
syncJoinFeedback.setAttribute("role", feedback?.tone === "warning" ? "alert" : "status");
|
|
10039
|
+
syncJoinFeedback.setAttribute("aria-live", feedback?.tone === "warning" ? "assertive" : "polite");
|
|
10040
|
+
syncJoinFeedback.className = `peer-meta${feedback ? ` ${feedback.tone === "warning" ? "sync-inline-feedback warning" : "sync-inline-feedback success"}` : ""}`;
|
|
9876
10041
|
}
|
|
9877
10042
|
function clearContent(node) {
|
|
9878
10043
|
if (node) node.textContent = "";
|
|
9879
10044
|
}
|
|
10045
|
+
function pulseAttentionTarget(target) {
|
|
10046
|
+
if (!(target instanceof HTMLElement)) return;
|
|
10047
|
+
target.classList.remove("sync-attention-target");
|
|
10048
|
+
target.offsetWidth;
|
|
10049
|
+
target.classList.add("sync-attention-target");
|
|
10050
|
+
window.setTimeout(() => target.classList.remove("sync-attention-target"), 900);
|
|
10051
|
+
}
|
|
10052
|
+
function prefersReducedMotion() {
|
|
10053
|
+
return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
10054
|
+
}
|
|
10055
|
+
function syncScrollBehavior() {
|
|
10056
|
+
return prefersReducedMotion() ? "auto" : "smooth";
|
|
10057
|
+
}
|
|
9880
10058
|
function renderInvitePolicySelect() {
|
|
9881
10059
|
const mount = document.getElementById("syncInvitePolicyMount");
|
|
9882
10060
|
if (!mount) return;
|
|
@@ -9944,11 +10122,13 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
9944
10122
|
const meta = document.getElementById("syncTeamMeta");
|
|
9945
10123
|
const setupPanel = document.getElementById("syncSetupPanel");
|
|
9946
10124
|
const list = document.getElementById("syncTeamStatus");
|
|
10125
|
+
const listHeading = list?.previousElementSibling;
|
|
9947
10126
|
const actions = document.getElementById("syncTeamActions");
|
|
9948
10127
|
if (!meta || !setupPanel || !list || !actions) return;
|
|
9949
10128
|
renderAdminSetupDisclosure();
|
|
9950
10129
|
renderInvitePolicySelect();
|
|
9951
10130
|
setInviteOutputVisibility();
|
|
10131
|
+
setJoinFeedbackVisibility();
|
|
9952
10132
|
const invitePanel = document.getElementById("syncInvitePanel");
|
|
9953
10133
|
const inviteRestoreParent = document.getElementById("syncAdminSection");
|
|
9954
10134
|
const joinPanel = document.getElementById("syncJoinPanel");
|
|
@@ -9973,10 +10153,13 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
9973
10153
|
const focusAttentionTarget = (item) => {
|
|
9974
10154
|
if (item.kind === "possible-duplicate-person") {
|
|
9975
10155
|
const actorList = document.getElementById("syncActorsList");
|
|
9976
|
-
if (actorList instanceof HTMLElement)
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
10156
|
+
if (actorList instanceof HTMLElement) {
|
|
10157
|
+
actorList.scrollIntoView({
|
|
10158
|
+
block: "center",
|
|
10159
|
+
behavior: syncScrollBehavior()
|
|
10160
|
+
});
|
|
10161
|
+
pulseAttentionTarget(actorList);
|
|
10162
|
+
}
|
|
9980
10163
|
return;
|
|
9981
10164
|
}
|
|
9982
10165
|
const deviceId = String(item.deviceId || "").trim();
|
|
@@ -9986,10 +10169,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
9986
10169
|
if (renameInput instanceof HTMLInputElement) {
|
|
9987
10170
|
renameInput.scrollIntoView({
|
|
9988
10171
|
block: "center",
|
|
9989
|
-
behavior:
|
|
10172
|
+
behavior: syncScrollBehavior()
|
|
9990
10173
|
});
|
|
9991
10174
|
renameInput.focus();
|
|
9992
10175
|
renameInput.select();
|
|
10176
|
+
pulseAttentionTarget(renameInput);
|
|
9993
10177
|
return;
|
|
9994
10178
|
}
|
|
9995
10179
|
}
|
|
@@ -9997,21 +10181,25 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
9997
10181
|
if (peerCard instanceof HTMLElement) {
|
|
9998
10182
|
peerCard.scrollIntoView({
|
|
9999
10183
|
block: "center",
|
|
10000
|
-
behavior:
|
|
10184
|
+
behavior: syncScrollBehavior()
|
|
10001
10185
|
});
|
|
10186
|
+
pulseAttentionTarget(peerCard);
|
|
10002
10187
|
return;
|
|
10003
10188
|
}
|
|
10004
10189
|
const discoveredRow = document.querySelector(`[data-discovered-device-id="${CSS.escape(deviceId)}"]`);
|
|
10005
|
-
if (discoveredRow instanceof HTMLElement)
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10190
|
+
if (discoveredRow instanceof HTMLElement) {
|
|
10191
|
+
discoveredRow.scrollIntoView({
|
|
10192
|
+
block: "center",
|
|
10193
|
+
behavior: syncScrollBehavior()
|
|
10194
|
+
});
|
|
10195
|
+
pulseAttentionTarget(discoveredRow);
|
|
10196
|
+
}
|
|
10009
10197
|
};
|
|
10010
10198
|
const reviewDuplicatePeople = async (item) => {
|
|
10011
10199
|
const actorIds = Array.isArray(item.actorIds) ? item.actorIds.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
10012
10200
|
const actors = (Array.isArray(state.lastSyncActors) ? state.lastSyncActors : []).filter((actor) => actorIds.includes(String(actor?.actor_id || "").trim()));
|
|
10013
10201
|
if (actors.length < 2) {
|
|
10014
|
-
showGlobalNotice("This
|
|
10202
|
+
showGlobalNotice("This duplicate review is outdated. Refresh the card and review the remaining people entries.", "warning");
|
|
10015
10203
|
return;
|
|
10016
10204
|
}
|
|
10017
10205
|
const result = await openDuplicatePersonDialog({
|
|
@@ -10033,7 +10221,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10033
10221
|
const primary = actors.find((actor) => String(actor?.actor_id || "") === result.primaryActorId);
|
|
10034
10222
|
const secondary = actors.find((actor) => String(actor?.actor_id || "") === result.secondaryActorId);
|
|
10035
10223
|
if (!primary?.actor_id || !secondary?.actor_id) {
|
|
10036
|
-
showGlobalNotice("Could not determine which people to combine.", "warning");
|
|
10224
|
+
showGlobalNotice("Could not determine which people to combine. Refresh People & devices and try the review again.", "warning");
|
|
10037
10225
|
return;
|
|
10038
10226
|
}
|
|
10039
10227
|
try {
|
|
@@ -10060,7 +10248,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10060
10248
|
});
|
|
10061
10249
|
};
|
|
10062
10250
|
const configured = Boolean(coordinator && coordinator.configured);
|
|
10063
|
-
meta.textContent = configured ? `
|
|
10251
|
+
meta.textContent = configured ? `Team: ${(coordinator.groups || []).join(", ") || "none"}` : "Start by joining an existing team or creating one, then connect people and devices.";
|
|
10252
|
+
meta.title = configured ? String(coordinator.coordinator_url || "").trim() : "";
|
|
10064
10253
|
if (!configured) {
|
|
10065
10254
|
teardownTeamSyncRender(actions, [
|
|
10066
10255
|
list,
|
|
@@ -10069,6 +10258,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10069
10258
|
]);
|
|
10070
10259
|
setupPanel.hidden = false;
|
|
10071
10260
|
list.hidden = true;
|
|
10261
|
+
if (listHeading) listHeading.hidden = true;
|
|
10072
10262
|
actions.hidden = true;
|
|
10073
10263
|
if (joinRequests) joinRequests.hidden = true;
|
|
10074
10264
|
if (discoveredPanel) discoveredPanel.hidden = true;
|
|
@@ -10076,18 +10266,17 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10076
10266
|
}
|
|
10077
10267
|
setupPanel.hidden = true;
|
|
10078
10268
|
list.hidden = false;
|
|
10269
|
+
if (listHeading) listHeading.hidden = false;
|
|
10079
10270
|
actions.hidden = false;
|
|
10080
10271
|
const presenceStatus = String(coordinator.presence_status || "");
|
|
10081
|
-
const presenceLabel = presenceStatus === "posted" ? "Connected" : presenceStatus === "not_enrolled" ? "
|
|
10082
|
-
const metricParts = [`Connected devices: ${Number(syncView.summary?.connectedDeviceCount || 0)}`, `Seen on team: ${Number(syncView.summary?.seenOnTeamCount || 0)}`];
|
|
10083
|
-
if (Number(syncView.summary?.offlineTeamDeviceCount || 0) > 0) metricParts.push(`Offline on team: ${Number(syncView.summary?.offlineTeamDeviceCount || 0)}`);
|
|
10084
|
-
const statusSummary = {
|
|
10085
|
-
badgeClassName: `pill ${presenceStatus === "posted" ? "pill-success" : presenceStatus === "not_enrolled" ? "pill-warning" : "pill-error"}`,
|
|
10086
|
-
metricsText: metricParts.join(" · "),
|
|
10087
|
-
presenceLabel
|
|
10088
|
-
};
|
|
10272
|
+
const presenceLabel = presenceStatus === "posted" ? "Connected" : presenceStatus === "not_enrolled" ? "Needs enrollment" : "Connection error";
|
|
10089
10273
|
const localPeers = Array.isArray(state.lastSyncPeers) ? state.lastSyncPeers : [];
|
|
10090
10274
|
const attentionItems = Array.isArray(syncView.attentionItems) ? syncView.attentionItems : [];
|
|
10275
|
+
const connectedCount = Number(syncView.summary?.connectedDeviceCount || 0);
|
|
10276
|
+
const seenOnTeamCount = Number(syncView.summary?.seenOnTeamCount || 0);
|
|
10277
|
+
const offlineTeamDeviceCount = Number(syncView.summary?.offlineTeamDeviceCount || 0);
|
|
10278
|
+
const metricParts = [`Connected ${connectedCount}`, `Team ${seenOnTeamCount}`];
|
|
10279
|
+
if (offlineTeamDeviceCount > 0) metricParts.push(`Offline ${offlineTeamDeviceCount}`);
|
|
10091
10280
|
const discoveredRows = (Array.isArray(coordinator.discovered_devices) ? coordinator.discovered_devices : []).map((device) => {
|
|
10092
10281
|
const deviceId = String(device.device_id || "").trim();
|
|
10093
10282
|
const displayName = resolveFriendlyDeviceName({
|
|
@@ -10107,33 +10296,28 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10107
10296
|
const canAccept = Boolean(deviceId) && Boolean(fingerprint) && !pairedPeer && !device.stale && !hasAmbiguousCoordinatorGroup;
|
|
10108
10297
|
const addresses = Array.isArray(device.addresses) ? device.addresses : [];
|
|
10109
10298
|
const noteParts = [addresses.length ? addresses.map((address) => isSyncRedactionEnabled() ? redactAddress(String(address || "")) : String(address || "")).filter(Boolean).join(" · ") : "No fresh addresses"];
|
|
10110
|
-
if (displayTitle) noteParts.push(`device id: ${deviceId}`);
|
|
10299
|
+
if (!addresses.length && displayTitle) noteParts.push(`device id: ${deviceId}`);
|
|
10111
10300
|
let actionMessage = null;
|
|
10112
10301
|
let mode = canAccept ? "accept" : "none";
|
|
10113
10302
|
let pairedMessage = null;
|
|
10114
|
-
if (hasConflict)
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
} else if (hasAmbiguousCoordinatorGroup) {
|
|
10118
|
-
noteParts.push("this device appears in multiple coordinator groups; review team setup before approving it here");
|
|
10119
|
-
actionMessage = "This device is visible through multiple coordinator groups. Fix that team setup first, then approve it here.";
|
|
10303
|
+
if (hasConflict) mode = "conflict";
|
|
10304
|
+
else if (hasAmbiguousCoordinatorGroup) {
|
|
10305
|
+
actionMessage = "This device appears in multiple coordinator groups. Review team setup first or ask an admin to clean up the duplicate enrollment before approving it here.";
|
|
10120
10306
|
mode = "ambiguous";
|
|
10121
10307
|
} else if (pairedPeer && isPeerScopeReviewPending(deviceId)) {
|
|
10122
|
-
|
|
10123
|
-
actionMessage = `Review this device's scope in People & devices next.`;
|
|
10308
|
+
actionMessage = `Finish this device's scope review in People & devices before you sync it.`;
|
|
10124
10309
|
mode = "scope-pending";
|
|
10125
10310
|
} else if (pairedPeer?.last_error) {
|
|
10126
|
-
noteParts.push(`
|
|
10311
|
+
noteParts.push(`error: ${String(pairedPeer.last_error)}`);
|
|
10127
10312
|
mode = "paired";
|
|
10128
10313
|
} else if (pairedPeer?.status?.peer_state) {
|
|
10129
|
-
noteParts.push(`
|
|
10314
|
+
noteParts.push(`status: ${String(pairedPeer.status.peer_state)}`);
|
|
10130
10315
|
mode = "paired";
|
|
10131
10316
|
} else if (!pairedPeer && device.stale) {
|
|
10132
|
-
actionMessage = "Wait for a fresh coordinator presence update.";
|
|
10317
|
+
actionMessage = "Wait for a fresh coordinator presence update, then review this device again here.";
|
|
10133
10318
|
mode = "stale";
|
|
10134
10319
|
} else if (pairedPeer) mode = "paired";
|
|
10135
|
-
if (approvalSummary.description)
|
|
10136
|
-
if (mode === "paired") pairedMessage = approvalSummary.state === "waiting-for-other-device" ? approvalSummary.description || "Waiting on the other device." : String(pairedPeer?.last_error || "").toLowerCase().includes("401") && String(pairedPeer?.last_error || "").toLowerCase().includes("unauthorized") ? "Waiting for the other device to trust this one before sync can work." : "Manage this device in People & devices.";
|
|
10320
|
+
if (mode === "paired") pairedMessage = approvalSummary.state === "waiting-for-other-device" ? approvalSummary.description || "Waiting on the other device." : String(pairedPeer?.last_error || "").toLowerCase().includes("401") && String(pairedPeer?.last_error || "").toLowerCase().includes("unauthorized") ? "Waiting for the other device to trust this one before sync can work." : null;
|
|
10137
10321
|
return {
|
|
10138
10322
|
actionMessage,
|
|
10139
10323
|
actionLabel: approvalSummary.actionLabel || "Review device",
|
|
@@ -10153,9 +10337,19 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10153
10337
|
displayName: String(request.display_name || request.device_id || "Pending device"),
|
|
10154
10338
|
requestId: String(request.request_id || "")
|
|
10155
10339
|
}));
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10340
|
+
const discoveredActionableCount = discoveredRows.filter((row) => row.mode === "accept" || row.mode === "scope-pending").length;
|
|
10341
|
+
const actionableCount = attentionItems.length + pendingJoinRequests.length + discoveredActionableCount;
|
|
10342
|
+
const teamLabel = (coordinator.groups || []).join(", ") || "none";
|
|
10343
|
+
const statusSummary = {
|
|
10344
|
+
badgeClassName: `pill ${presenceStatus === "posted" ? "pill-success" : presenceStatus === "not_enrolled" ? "pill-warning" : "pill-error"}`,
|
|
10345
|
+
headline: presenceStatus === "posted" ? actionableCount > 0 ? `${actionableCount} item${actionableCount === 1 ? "" : "s"} need attention` : "Everything is healthy" : presenceStatus === "not_enrolled" ? "This device is not enrolled in the team yet" : "Sync needs attention",
|
|
10346
|
+
metricsText: metricParts.join(" · "),
|
|
10347
|
+
presenceLabel
|
|
10348
|
+
};
|
|
10349
|
+
meta.textContent = presenceStatus === "posted" ? actionableCount > 0 ? `Team: ${teamLabel}. Start with the next step below, then scan the current team status.` : `Team: ${teamLabel}. Team status and device details are below.` : presenceStatus === "not_enrolled" ? `Team: ${teamLabel}. Enroll this device first, then return here to review the rest of the team.` : `Team: ${teamLabel}. Fix the current sync issue first, then use the rest of this card to verify the team state.`;
|
|
10350
|
+
if (discoveredPanel) discoveredPanel.hidden = discoveredRows.length === 0 && !state.syncDiscoveredFeedback;
|
|
10351
|
+
if (discoveredMeta) discoveredMeta.textContent = discoveredRows.length ? "Review anything here that still needs trust, repair, or approval." : "";
|
|
10352
|
+
if (joinRequests) joinRequests.hidden = pendingJoinRequests.length === 0 && !state.syncJoinRequestsFeedback;
|
|
10159
10353
|
teardownTeamSyncRender(actions, [
|
|
10160
10354
|
list,
|
|
10161
10355
|
joinRequests,
|
|
@@ -10166,6 +10360,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10166
10360
|
actions.appendChild(actionMount);
|
|
10167
10361
|
renderIntoSyncMount(actionMount, _$1(TeamSyncPanel, {
|
|
10168
10362
|
actionItems: attentionItems,
|
|
10363
|
+
actionableCount,
|
|
10169
10364
|
children: _$1(SyncInviteJoinPanels, {
|
|
10170
10365
|
invitePanel,
|
|
10171
10366
|
invitePanelOpen: teamInvitePanelOpen,
|
|
@@ -10187,11 +10382,18 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10187
10382
|
onApproveJoinRequest: async (request) => {
|
|
10188
10383
|
try {
|
|
10189
10384
|
await reviewJoinRequest(request.requestId, "approve");
|
|
10190
|
-
|
|
10385
|
+
const feedback = {
|
|
10386
|
+
message: `Approved ${request.displayName}. They can now sync with the team.`,
|
|
10387
|
+
tone: "success"
|
|
10388
|
+
};
|
|
10389
|
+
state.syncJoinRequestsFeedback = feedback;
|
|
10191
10390
|
await _loadSyncData$1();
|
|
10391
|
+
return feedback;
|
|
10192
10392
|
} catch (error) {
|
|
10193
|
-
|
|
10194
|
-
|
|
10393
|
+
return {
|
|
10394
|
+
message: friendlyError(error, "Failed to approve join request. Keep it pending and try again after the coordinator refreshes."),
|
|
10395
|
+
tone: "warning"
|
|
10396
|
+
};
|
|
10195
10397
|
}
|
|
10196
10398
|
},
|
|
10197
10399
|
onAttentionAction: async (item) => {
|
|
@@ -10208,14 +10410,21 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10208
10410
|
confirmLabel: "Deny request",
|
|
10209
10411
|
cancelLabel: "Keep request pending",
|
|
10210
10412
|
tone: "danger"
|
|
10211
|
-
})) return;
|
|
10413
|
+
})) return null;
|
|
10212
10414
|
try {
|
|
10213
10415
|
await reviewJoinRequest(request.requestId, "deny");
|
|
10214
|
-
|
|
10416
|
+
const feedback = {
|
|
10417
|
+
message: `Denied join request from ${request.displayName}.`,
|
|
10418
|
+
tone: "success"
|
|
10419
|
+
};
|
|
10420
|
+
state.syncJoinRequestsFeedback = feedback;
|
|
10215
10421
|
await _loadSyncData$1();
|
|
10422
|
+
return feedback;
|
|
10216
10423
|
} catch (error) {
|
|
10217
|
-
|
|
10218
|
-
|
|
10424
|
+
return {
|
|
10425
|
+
message: friendlyError(error, "Failed to deny join request. Leave it pending for now, then retry after the coordinator refreshes."),
|
|
10426
|
+
tone: "warning"
|
|
10427
|
+
};
|
|
10219
10428
|
}
|
|
10220
10429
|
},
|
|
10221
10430
|
onInspectConflict: (row) => {
|
|
@@ -10223,7 +10432,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10223
10432
|
if (peerCard instanceof HTMLElement) {
|
|
10224
10433
|
peerCard.scrollIntoView({
|
|
10225
10434
|
block: "center",
|
|
10226
|
-
behavior:
|
|
10435
|
+
behavior: syncScrollBehavior()
|
|
10227
10436
|
});
|
|
10228
10437
|
showGlobalNotice(`Opened the conflicting local device record for ${row.displayName}.`, "warning");
|
|
10229
10438
|
return;
|
|
@@ -10237,20 +10446,27 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10237
10446
|
confirmLabel: "Remove device record",
|
|
10238
10447
|
cancelLabel: "Keep device record",
|
|
10239
10448
|
tone: "danger"
|
|
10240
|
-
})) return;
|
|
10449
|
+
})) return null;
|
|
10241
10450
|
try {
|
|
10242
10451
|
await deletePeer(row.deviceId);
|
|
10243
|
-
|
|
10452
|
+
const feedback = {
|
|
10453
|
+
message: `Removed the broken local device record for ${row.displayName}. If it is still available, review it again from Next steps or Devices seen on team.`,
|
|
10454
|
+
tone: "success"
|
|
10455
|
+
};
|
|
10456
|
+
state.syncDiscoveredFeedback = feedback;
|
|
10244
10457
|
await _loadSyncData$1();
|
|
10458
|
+
return feedback;
|
|
10245
10459
|
} catch (error) {
|
|
10246
|
-
|
|
10247
|
-
|
|
10460
|
+
return {
|
|
10461
|
+
message: friendlyError(error, "Failed to remove the broken local device record. The old local record is still present in People & devices."),
|
|
10462
|
+
tone: "warning"
|
|
10463
|
+
};
|
|
10248
10464
|
}
|
|
10249
10465
|
},
|
|
10250
10466
|
onReviewDiscoveredDevice: async (row) => {
|
|
10251
10467
|
try {
|
|
10252
10468
|
const reviewedName = await reviewDiscoveredDeviceName(row.displayName);
|
|
10253
|
-
if (!reviewedName) return;
|
|
10469
|
+
if (!reviewedName) return null;
|
|
10254
10470
|
const result = await acceptDiscoveredPeer(row.deviceId, row.fingerprint);
|
|
10255
10471
|
const optimisticName = String(result?.name || row.displayName || "").trim() || row.displayName;
|
|
10256
10472
|
state.pendingAcceptedSyncPeers = [...Array.isArray(state.pendingAcceptedSyncPeers) ? state.pendingAcceptedSyncPeers.filter((peer) => String(peer?.peer_device_id || "").trim() !== row.deviceId) : [], {
|
|
@@ -10263,23 +10479,44 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10263
10479
|
last_error: "Waiting for the other device to approve this one."
|
|
10264
10480
|
}];
|
|
10265
10481
|
requestPeerScopeReview(row.deviceId);
|
|
10266
|
-
|
|
10482
|
+
let feedback = {
|
|
10483
|
+
message: row.approvalBadgeLabel === "Needs your approval" ? `Approved ${row.displayName} on this device. Two-way trust should be ready once both devices refresh.` : `Step 1 complete on this device for ${row.displayName}. Finish onboarding on the other device so both sides trust each other for sync.`,
|
|
10484
|
+
tone: "success"
|
|
10485
|
+
};
|
|
10267
10486
|
try {
|
|
10268
10487
|
if (reviewedName.trim() !== optimisticName.trim()) {
|
|
10269
10488
|
await renamePeer(row.deviceId, reviewedName.trim());
|
|
10270
|
-
|
|
10489
|
+
state.pendingAcceptedSyncPeers = state.pendingAcceptedSyncPeers.map((peer) => String(peer?.peer_device_id || "").trim() === row.deviceId ? {
|
|
10490
|
+
...peer,
|
|
10491
|
+
name: reviewedName.trim()
|
|
10492
|
+
} : peer);
|
|
10493
|
+
feedback = {
|
|
10494
|
+
message: `Connected ${reviewedName.trim()} and saved its name.`,
|
|
10495
|
+
tone: "success"
|
|
10496
|
+
};
|
|
10271
10497
|
}
|
|
10272
10498
|
} catch (error) {
|
|
10273
|
-
|
|
10499
|
+
feedback = {
|
|
10500
|
+
message: friendlyError(error, "Device connected, but naming did not finish."),
|
|
10501
|
+
tone: "warning"
|
|
10502
|
+
};
|
|
10274
10503
|
}
|
|
10504
|
+
state.syncDiscoveredFeedback = feedback;
|
|
10275
10505
|
try {
|
|
10276
10506
|
await _loadSyncData$1();
|
|
10277
10507
|
} catch (error) {
|
|
10278
|
-
|
|
10508
|
+
feedback = {
|
|
10509
|
+
message: friendlyError(error, "Device connected, but the screen did not refresh yet. Refresh this page before trying the next step."),
|
|
10510
|
+
tone: "warning"
|
|
10511
|
+
};
|
|
10512
|
+
state.syncDiscoveredFeedback = feedback;
|
|
10279
10513
|
}
|
|
10514
|
+
return feedback;
|
|
10280
10515
|
} catch (error) {
|
|
10281
|
-
|
|
10282
|
-
|
|
10516
|
+
return {
|
|
10517
|
+
message: friendlyError(error, "Failed to review this device. Wait for a fresh presence update and try again."),
|
|
10518
|
+
tone: "warning"
|
|
10519
|
+
};
|
|
10283
10520
|
}
|
|
10284
10521
|
},
|
|
10285
10522
|
pendingJoinRequests,
|
|
@@ -10316,17 +10553,23 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10316
10553
|
ttl_hours: ttlValue || 24
|
|
10317
10554
|
});
|
|
10318
10555
|
state.lastTeamInvite = result;
|
|
10556
|
+
setInviteOutputVisibility();
|
|
10319
10557
|
syncInviteOutput.value = String(result.encoded || "");
|
|
10320
10558
|
syncInviteOutput.hidden = false;
|
|
10321
10559
|
syncInviteOutput.focus();
|
|
10322
10560
|
syncInviteOutput.select();
|
|
10323
|
-
|
|
10324
|
-
(
|
|
10561
|
+
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
|
|
10562
|
+
showGlobalNotice(warnings.length ? `Invite created. Copy it above and review ${warnings.length === 1 ? "1 warning" : `${warnings.length} warnings`}.` : "Invite created. Copy the text above and share it with your teammate.", warnings.length ? "warning" : "success");
|
|
10325
10563
|
} catch (error) {
|
|
10326
|
-
showGlobalNotice(friendlyError(error, "Failed to create invite."), "warning");
|
|
10327
|
-
|
|
10564
|
+
showGlobalNotice(friendlyError(error, "Failed to create invite. Check the team name, invite lifetime, and coordinator reachability, then try again."), "warning");
|
|
10565
|
+
syncCreateInviteButton.textContent = "Retry";
|
|
10328
10566
|
syncCreateInviteButton.disabled = false;
|
|
10329
|
-
|
|
10567
|
+
return;
|
|
10568
|
+
} finally {
|
|
10569
|
+
if (syncCreateInviteButton.disabled) {
|
|
10570
|
+
syncCreateInviteButton.disabled = false;
|
|
10571
|
+
syncCreateInviteButton.textContent = "Create invite";
|
|
10572
|
+
}
|
|
10330
10573
|
}
|
|
10331
10574
|
});
|
|
10332
10575
|
syncJoinButton?.addEventListener("click", async () => {
|
|
@@ -10342,14 +10585,37 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10342
10585
|
try {
|
|
10343
10586
|
const result = await importCoordinatorInvite(inviteValue);
|
|
10344
10587
|
state.lastTeamJoin = result;
|
|
10345
|
-
|
|
10588
|
+
let feedback = {
|
|
10589
|
+
message: result.status === "pending" ? "Join request sent. Waiting for admin approval." : "Joined the team.",
|
|
10590
|
+
tone: "success"
|
|
10591
|
+
};
|
|
10592
|
+
state.syncJoinFlowFeedback = feedback;
|
|
10593
|
+
setJoinFeedbackVisibility();
|
|
10346
10594
|
syncJoinInvite.value = "";
|
|
10347
|
-
|
|
10595
|
+
try {
|
|
10596
|
+
await loadSyncData();
|
|
10597
|
+
} catch (error) {
|
|
10598
|
+
feedback = {
|
|
10599
|
+
message: friendlyError(error, "Joined the team, but this view has not refreshed yet."),
|
|
10600
|
+
tone: "warning"
|
|
10601
|
+
};
|
|
10602
|
+
state.syncJoinFlowFeedback = feedback;
|
|
10603
|
+
setJoinFeedbackVisibility();
|
|
10604
|
+
}
|
|
10348
10605
|
} catch (error) {
|
|
10349
|
-
|
|
10350
|
-
|
|
10606
|
+
state.syncJoinFlowFeedback = {
|
|
10607
|
+
message: friendlyError(error, "Failed to import invite. Check that the invite is complete, current, and meant for this team, then try again."),
|
|
10608
|
+
tone: "warning"
|
|
10609
|
+
};
|
|
10610
|
+
setJoinFeedbackVisibility();
|
|
10611
|
+
syncJoinButton.textContent = "Retry";
|
|
10351
10612
|
syncJoinButton.disabled = false;
|
|
10352
|
-
|
|
10613
|
+
return;
|
|
10614
|
+
} finally {
|
|
10615
|
+
if (syncJoinButton.disabled) {
|
|
10616
|
+
syncJoinButton.disabled = false;
|
|
10617
|
+
syncJoinButton.textContent = "Join team";
|
|
10618
|
+
}
|
|
10353
10619
|
}
|
|
10354
10620
|
});
|
|
10355
10621
|
syncNowButton?.addEventListener("click", async () => {
|
|
@@ -10360,7 +10626,10 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10360
10626
|
const summary = summarizeSyncRunResult(await triggerSync());
|
|
10361
10627
|
showGlobalNotice(summary.message, summary.warning ? "warning" : void 0);
|
|
10362
10628
|
} catch (error) {
|
|
10363
|
-
showGlobalNotice(friendlyError(error, "Failed to start sync."), "warning");
|
|
10629
|
+
showGlobalNotice(friendlyError(error, "Failed to start sync. Retry once, then run codemem sync doctor if the problem keeps coming back."), "warning");
|
|
10630
|
+
syncNowButton.textContent = "Retry";
|
|
10631
|
+
syncNowButton.disabled = false;
|
|
10632
|
+
return;
|
|
10364
10633
|
}
|
|
10365
10634
|
syncNowButton.disabled = false;
|
|
10366
10635
|
syncNowButton.textContent = "Sync now";
|
|
@@ -10448,12 +10717,17 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10448
10717
|
className: "actor-title",
|
|
10449
10718
|
children: [/* @__PURE__ */ u("strong", { children: actorDisplayLabel(actor) }), /* @__PURE__ */ u("span", {
|
|
10450
10719
|
className: `badge actor-badge${actor.is_local ? " local" : ""}`,
|
|
10720
|
+
title: actor.is_local ? localActorNote(hiddenLocalDuplicateCount) : void 0,
|
|
10451
10721
|
children: actor.is_local ? "You" : `${count} device${count === 1 ? "" : "s"}`
|
|
10452
10722
|
})]
|
|
10453
|
-
}), /* @__PURE__ */ u("div", {
|
|
10723
|
+
}), !actor.is_local ? /* @__PURE__ */ u("div", {
|
|
10454
10724
|
className: "peer-meta",
|
|
10455
|
-
children:
|
|
10456
|
-
|
|
10725
|
+
children: [
|
|
10726
|
+
count,
|
|
10727
|
+
" assigned device",
|
|
10728
|
+
count === 1 ? "" : "s"
|
|
10729
|
+
]
|
|
10730
|
+
}) : null]
|
|
10457
10731
|
}), /* @__PURE__ */ u("div", {
|
|
10458
10732
|
className: "actor-actions",
|
|
10459
10733
|
children: actor.is_local ? /* @__PURE__ */ u("div", {
|
|
@@ -10509,7 +10783,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10509
10783
|
function SyncActorsList({ actors, hiddenLocalDuplicateCount, onRename, onMerge }) {
|
|
10510
10784
|
if (!actors.length) return /* @__PURE__ */ u("div", {
|
|
10511
10785
|
className: "sync-empty-state",
|
|
10512
|
-
children: "No people yet. Create
|
|
10786
|
+
children: [/* @__PURE__ */ u("strong", { children: "No people yet." }), /* @__PURE__ */ u("span", { children: "Create a named person here, then assign each device below to keep sync ownership readable." })]
|
|
10513
10787
|
});
|
|
10514
10788
|
return /* @__PURE__ */ u(k$2, { children: actors.map((actor) => {
|
|
10515
10789
|
const actorId = String(actor.actor_id || actor.display_name || actor.is_local || "");
|
|
@@ -10595,6 +10869,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10595
10869
|
const cardRef = A$1(null);
|
|
10596
10870
|
const [scopeHost, setScopeHost] = d(null);
|
|
10597
10871
|
const [renameValue, setRenameValue] = d(displayName);
|
|
10872
|
+
const [feedback, setFeedback] = d(() => state.syncPeerFeedbackById.get(peerId) ?? null);
|
|
10598
10873
|
const [renameBusy, setRenameBusy] = d(false);
|
|
10599
10874
|
const [renameLabel, setRenameLabel] = d("Save name");
|
|
10600
10875
|
const [syncBusy, setSyncBusy] = d(false);
|
|
@@ -10627,6 +10902,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10627
10902
|
const excludeEditor = T$1(() => createChipEditor(excludeList, "Add excluded project", "No exclusions"), [peerId, excludeList.join("|")]);
|
|
10628
10903
|
y(() => {
|
|
10629
10904
|
setRenameValue(displayName);
|
|
10905
|
+
setFeedback(state.syncPeerFeedbackById.get(peerId) ?? null);
|
|
10630
10906
|
setRenameBusy(false);
|
|
10631
10907
|
setRenameLabel("Save name");
|
|
10632
10908
|
setSyncBusy(false);
|
|
@@ -10650,28 +10926,33 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10650
10926
|
if (!scopeReviewRequested || !cardRef.current) return;
|
|
10651
10927
|
queueMicrotask(() => cardRef.current?.scrollIntoView({
|
|
10652
10928
|
block: "center",
|
|
10653
|
-
behavior: "smooth"
|
|
10929
|
+
behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth"
|
|
10654
10930
|
}));
|
|
10655
10931
|
}, [scopeReviewRequested]);
|
|
10656
10932
|
async function rename() {
|
|
10657
10933
|
if (!peerId) return;
|
|
10658
10934
|
const nextName = renameValue.trim();
|
|
10659
10935
|
if (!nextName) {
|
|
10660
|
-
|
|
10936
|
+
const warning = {
|
|
10937
|
+
message: "Enter a friendly name for this device.",
|
|
10938
|
+
tone: "warning"
|
|
10939
|
+
};
|
|
10940
|
+
setFeedback(warning);
|
|
10941
|
+
state.syncPeerFeedbackById.set(peerId, warning);
|
|
10661
10942
|
document.querySelector(`[data-device-name-input="${CSS.escape(peerId)}"]`)?.focus();
|
|
10662
10943
|
return;
|
|
10663
10944
|
}
|
|
10664
10945
|
setRenameBusy(true);
|
|
10665
10946
|
setRenameLabel("Saving…");
|
|
10666
|
-
let ok = false;
|
|
10667
10947
|
try {
|
|
10668
|
-
await onRename(peerId, nextName);
|
|
10669
|
-
|
|
10948
|
+
const nextFeedback = await onRename(peerId, nextName);
|
|
10949
|
+
setFeedback(nextFeedback);
|
|
10950
|
+
state.syncPeerFeedbackById.set(peerId, nextFeedback);
|
|
10951
|
+
setRenameLabel("Save name");
|
|
10670
10952
|
} catch {
|
|
10671
|
-
setRenameLabel("Retry
|
|
10953
|
+
setRenameLabel("Retry");
|
|
10672
10954
|
} finally {
|
|
10673
10955
|
setRenameBusy(false);
|
|
10674
|
-
if (ok) setRenameLabel("Save name");
|
|
10675
10956
|
}
|
|
10676
10957
|
}
|
|
10677
10958
|
async function sync() {
|
|
@@ -10686,7 +10967,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10686
10967
|
}
|
|
10687
10968
|
setSyncBusy(true);
|
|
10688
10969
|
try {
|
|
10689
|
-
await onSync(peer, primaryAddress);
|
|
10970
|
+
const nextFeedback = await onSync(peer, primaryAddress);
|
|
10971
|
+
setFeedback(nextFeedback);
|
|
10972
|
+
if (nextFeedback) state.syncPeerFeedbackById.set(peerId, nextFeedback);
|
|
10690
10973
|
} finally {
|
|
10691
10974
|
setSyncBusy(false);
|
|
10692
10975
|
}
|
|
@@ -10716,46 +10999,46 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10716
10999
|
async function savePerson() {
|
|
10717
11000
|
if (!peerId) return;
|
|
10718
11001
|
setApplyActorBusy(true);
|
|
10719
|
-
setApplyActorLabel("
|
|
10720
|
-
let ok = false;
|
|
11002
|
+
setApplyActorLabel("Saving…");
|
|
10721
11003
|
try {
|
|
10722
|
-
await onAssignActor(peerId, selectedActorId || null);
|
|
10723
|
-
|
|
11004
|
+
const nextFeedback = await onAssignActor(peerId, selectedActorId || null);
|
|
11005
|
+
setFeedback(nextFeedback);
|
|
11006
|
+
state.syncPeerFeedbackById.set(peerId, nextFeedback);
|
|
11007
|
+
setApplyActorLabel("Save person");
|
|
10724
11008
|
} catch {
|
|
10725
11009
|
setApplyActorLabel("Retry");
|
|
10726
11010
|
} finally {
|
|
10727
11011
|
setApplyActorBusy(false);
|
|
10728
|
-
if (ok) setApplyActorLabel("Save person");
|
|
10729
11012
|
}
|
|
10730
11013
|
}
|
|
10731
11014
|
async function saveScope() {
|
|
10732
11015
|
if (!peerId) return;
|
|
10733
11016
|
setSaveScopeBusy(true);
|
|
10734
11017
|
setSaveScopeLabel("Saving…");
|
|
10735
|
-
let ok = false;
|
|
10736
11018
|
try {
|
|
10737
|
-
await onSaveScope(peerId, includeEditor.values(), excludeEditor.values());
|
|
10738
|
-
|
|
11019
|
+
const nextFeedback = await onSaveScope(peerId, includeEditor.values(), excludeEditor.values());
|
|
11020
|
+
setFeedback(nextFeedback);
|
|
11021
|
+
state.syncPeerFeedbackById.set(peerId, nextFeedback);
|
|
11022
|
+
setSaveScopeLabel("Save scope");
|
|
10739
11023
|
} catch {
|
|
10740
|
-
setSaveScopeLabel("Retry
|
|
11024
|
+
setSaveScopeLabel("Retry");
|
|
10741
11025
|
} finally {
|
|
10742
11026
|
setSaveScopeBusy(false);
|
|
10743
|
-
if (ok) setSaveScopeLabel("Save scope");
|
|
10744
11027
|
}
|
|
10745
11028
|
}
|
|
10746
11029
|
async function resetScope() {
|
|
10747
11030
|
if (!peerId) return;
|
|
10748
11031
|
setResetScopeBusy(true);
|
|
10749
11032
|
setResetScopeLabel("Resetting…");
|
|
10750
|
-
let ok = false;
|
|
10751
11033
|
try {
|
|
10752
|
-
await onResetScope(peerId);
|
|
10753
|
-
|
|
11034
|
+
const nextFeedback = await onResetScope(peerId);
|
|
11035
|
+
setFeedback(nextFeedback);
|
|
11036
|
+
state.syncPeerFeedbackById.set(peerId, nextFeedback);
|
|
11037
|
+
setResetScopeLabel("Reset to global scope");
|
|
10754
11038
|
} catch {
|
|
10755
|
-
setResetScopeLabel("Retry
|
|
11039
|
+
setResetScopeLabel("Retry");
|
|
10756
11040
|
} finally {
|
|
10757
11041
|
setResetScopeBusy(false);
|
|
10758
|
-
if (ok) setResetScopeLabel("Reset to global scope");
|
|
10759
11042
|
}
|
|
10760
11043
|
}
|
|
10761
11044
|
return /* @__PURE__ */ u("div", {
|
|
@@ -10849,10 +11132,10 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10849
11132
|
children: [
|
|
10850
11133
|
scopeReviewRequested ? /* @__PURE__ */ u("div", {
|
|
10851
11134
|
className: "peer-meta",
|
|
10852
|
-
children: "Review this device's
|
|
11135
|
+
children: "Review this device's sharing scope now."
|
|
10853
11136
|
}) : pendingScopeReview ? /* @__PURE__ */ u("div", {
|
|
10854
11137
|
className: "peer-meta",
|
|
10855
|
-
children: "Scope review still pending.
|
|
11138
|
+
children: "Scope review still pending."
|
|
10856
11139
|
}) : null,
|
|
10857
11140
|
/* @__PURE__ */ u("div", {
|
|
10858
11141
|
className: "peer-scope-summary",
|
|
@@ -10862,10 +11145,6 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10862
11145
|
className: "peer-meta",
|
|
10863
11146
|
children: peer.actor_display_name ? `Assigned to ${peer.claimed_local_actor ? "You" : String(peer.actor_display_name)}` : "Unassigned person"
|
|
10864
11147
|
}),
|
|
10865
|
-
/* @__PURE__ */ u("div", {
|
|
10866
|
-
className: "peer-meta",
|
|
10867
|
-
children: trustSummary.description
|
|
10868
|
-
}),
|
|
10869
11148
|
/* @__PURE__ */ u("div", {
|
|
10870
11149
|
className: "peer-actor-row",
|
|
10871
11150
|
children: [/* @__PURE__ */ u("div", {
|
|
@@ -10889,6 +11168,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10889
11168
|
children: applyActorLabel
|
|
10890
11169
|
})]
|
|
10891
11170
|
}),
|
|
11171
|
+
/* @__PURE__ */ u(SyncInlineFeedback, { feedback }),
|
|
10892
11172
|
/* @__PURE__ */ u("div", { ref: setScopeHost })
|
|
10893
11173
|
]
|
|
10894
11174
|
})
|
|
@@ -10896,17 +11176,20 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10896
11176
|
});
|
|
10897
11177
|
}
|
|
10898
11178
|
function SyncPeersList(props) {
|
|
10899
|
-
|
|
11179
|
+
const sectionFeedback = state.syncPeersSectionFeedback;
|
|
11180
|
+
const syncStatus = state.lastSyncStatus;
|
|
11181
|
+
const syncDisabled = syncStatus?.daemon_state === "disabled" || syncStatus?.enabled === false;
|
|
11182
|
+
if (!props.peers.length) return /* @__PURE__ */ u(k$2, { children: [/* @__PURE__ */ u(SyncInlineFeedback, { feedback: sectionFeedback }), /* @__PURE__ */ u("div", {
|
|
10900
11183
|
className: "sync-empty-state",
|
|
10901
|
-
children: "No devices
|
|
10902
|
-
});
|
|
10903
|
-
return /* @__PURE__ */ u(k$2, { children: props.peers.map((peer) => {
|
|
11184
|
+
children: [/* @__PURE__ */ u("strong", { children: "No paired devices yet." }), /* @__PURE__ */ u("span", { children: syncDisabled ? "Turn on sync in Settings → Device Sync first, then use Show pairing in Advanced diagnostics to connect another device." : "Use the Show pairing control in Advanced diagnostics, run the command on the other device, then come back here to name and assign it." })]
|
|
11185
|
+
})] });
|
|
11186
|
+
return /* @__PURE__ */ u(k$2, { children: [/* @__PURE__ */ u(SyncInlineFeedback, { feedback: sectionFeedback }), props.peers.map((peer) => {
|
|
10904
11187
|
const peerId = String(peer.peer_device_id || peer.name || "unknown-peer");
|
|
10905
11188
|
return /* @__PURE__ */ u(SyncPeerCard, {
|
|
10906
11189
|
peer,
|
|
10907
11190
|
...props
|
|
10908
11191
|
}, peerId);
|
|
10909
|
-
}) });
|
|
11192
|
+
})] });
|
|
10910
11193
|
}
|
|
10911
11194
|
function renderSyncPeersList(mount, props) {
|
|
10912
11195
|
renderIntoSyncMount(mount, /* @__PURE__ */ u(SyncPeersList, { ...props }));
|
|
@@ -10962,6 +11245,12 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10962
11245
|
//#region src/tabs/sync/people.ts
|
|
10963
11246
|
var _loadSyncData = async () => {};
|
|
10964
11247
|
var legacyDeviceValue = "";
|
|
11248
|
+
function setPeopleCreateControlsDisabled(disabled) {
|
|
11249
|
+
const createButton = document.getElementById("syncActorCreateButton");
|
|
11250
|
+
const createInput = document.getElementById("syncActorCreateInput");
|
|
11251
|
+
if (createButton) createButton.disabled = disabled;
|
|
11252
|
+
if (createInput) createInput.disabled = disabled;
|
|
11253
|
+
}
|
|
10965
11254
|
function setLoadSyncData(fn) {
|
|
10966
11255
|
_loadSyncData = fn;
|
|
10967
11256
|
}
|
|
@@ -10970,6 +11259,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10970
11259
|
const actorMeta = document.getElementById("syncActorsMeta");
|
|
10971
11260
|
if (!actorList) return;
|
|
10972
11261
|
hideSkeleton("syncActorsSkeleton");
|
|
11262
|
+
setPeopleCreateControlsDisabled(false);
|
|
10973
11263
|
const actorVisibility = deriveVisiblePeopleActors({
|
|
10974
11264
|
actors: state.lastSyncActors,
|
|
10975
11265
|
peers: state.lastSyncPeers,
|
|
@@ -10977,7 +11267,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10977
11267
|
});
|
|
10978
11268
|
const actors = actorVisibility.visibleActors;
|
|
10979
11269
|
if (actorMeta) {
|
|
10980
|
-
actorMeta.textContent = actors.length ? "
|
|
11270
|
+
actorMeta.textContent = actors.length ? "Manage people here, then assign devices below." : "No named people yet. Create a person here, then assign devices below so sync ownership is easier to review.";
|
|
10981
11271
|
if (actorVisibility.hiddenLocalDuplicateCount > 0) actorMeta.textContent += ` ${actorVisibility.hiddenLocalDuplicateCount} unresolved duplicate ${actorVisibility.hiddenLocalDuplicateCount === 1 ? "entry is" : "entries are"} hidden here until reviewed in Needs attention.`;
|
|
10982
11272
|
}
|
|
10983
11273
|
renderSyncActorsList(actorList, {
|
|
@@ -10999,6 +11289,16 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
10999
11289
|
}
|
|
11000
11290
|
});
|
|
11001
11291
|
}
|
|
11292
|
+
function renderSyncActorsUnavailable() {
|
|
11293
|
+
const actorList = document.getElementById("syncActorsList");
|
|
11294
|
+
const actorMeta = document.getElementById("syncActorsMeta");
|
|
11295
|
+
setPeopleCreateControlsDisabled(true);
|
|
11296
|
+
if (actorMeta) actorMeta.textContent = "People controls are temporarily unavailable. Refresh this page to retry, but device status and sync health are still available below.";
|
|
11297
|
+
if (actorList) renderSyncEmptyState(actorList, {
|
|
11298
|
+
title: "People unavailable right now.",
|
|
11299
|
+
detail: "Refresh this page to reload named people once the people endpoint is responding again."
|
|
11300
|
+
});
|
|
11301
|
+
}
|
|
11002
11302
|
function renderSyncPeers() {
|
|
11003
11303
|
const syncPeers = document.getElementById("syncPeers");
|
|
11004
11304
|
if (!syncPeers) return;
|
|
@@ -11009,75 +11309,133 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
11009
11309
|
onRename: async (peerId, nextName) => {
|
|
11010
11310
|
try {
|
|
11011
11311
|
await renamePeer(peerId, nextName);
|
|
11012
|
-
showGlobalNotice("Device name saved.");
|
|
11013
11312
|
await _loadSyncData();
|
|
11313
|
+
return {
|
|
11314
|
+
message: "Device name saved.",
|
|
11315
|
+
tone: "success"
|
|
11316
|
+
};
|
|
11014
11317
|
} catch (error) {
|
|
11015
|
-
|
|
11016
|
-
|
|
11318
|
+
return {
|
|
11319
|
+
message: friendlyError(error, "Failed to save device name."),
|
|
11320
|
+
tone: "warning"
|
|
11321
|
+
};
|
|
11017
11322
|
}
|
|
11018
11323
|
},
|
|
11019
11324
|
onSync: async (peer, address) => {
|
|
11020
11325
|
try {
|
|
11021
11326
|
const summary = summarizeSyncRunResult(await triggerSync(address));
|
|
11022
11327
|
const peerId = String(peer?.peer_device_id || "");
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11328
|
+
let feedback;
|
|
11329
|
+
if (!summary.ok) feedback = {
|
|
11330
|
+
message: summary.message,
|
|
11331
|
+
tone: "warning"
|
|
11332
|
+
};
|
|
11333
|
+
else if (peerId && isPeerScopeReviewPending(peerId)) feedback = {
|
|
11334
|
+
message: `Triggered sync for ${peer?.name || (peerId ? peerId.slice(0, 8) : "unknown")}. Review scope in this card if you want tighter sharing rules.`,
|
|
11335
|
+
tone: "warning"
|
|
11336
|
+
};
|
|
11337
|
+
else feedback = {
|
|
11338
|
+
message: summary.message,
|
|
11339
|
+
tone: "success"
|
|
11340
|
+
};
|
|
11341
|
+
try {
|
|
11342
|
+
await _loadSyncData();
|
|
11343
|
+
} catch {
|
|
11344
|
+
feedback = {
|
|
11345
|
+
message: "Sync started, but this view has not refreshed yet. Refresh the page or use Sync now again before retrying.",
|
|
11346
|
+
tone: "warning"
|
|
11347
|
+
};
|
|
11348
|
+
}
|
|
11349
|
+
return feedback;
|
|
11026
11350
|
} catch (error) {
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
await _loadSyncData();
|
|
11032
|
-
} catch {
|
|
11033
|
-
showGlobalNotice("Sync started, but the local status view did not refresh yet.", "warning");
|
|
11351
|
+
return {
|
|
11352
|
+
message: friendlyError(error, "Failed to trigger sync."),
|
|
11353
|
+
tone: "warning"
|
|
11354
|
+
};
|
|
11034
11355
|
}
|
|
11035
|
-
return null;
|
|
11036
11356
|
},
|
|
11037
11357
|
onRemove: async (peerId, label) => {
|
|
11038
11358
|
try {
|
|
11039
11359
|
await deletePeer(peerId);
|
|
11040
|
-
|
|
11360
|
+
const feedback = {
|
|
11361
|
+
message: `Removed peer ${label}.`,
|
|
11362
|
+
tone: "success"
|
|
11363
|
+
};
|
|
11364
|
+
state.syncPeerFeedbackById.delete(peerId);
|
|
11365
|
+
state.syncPeersSectionFeedback = feedback;
|
|
11041
11366
|
await _loadSyncData();
|
|
11367
|
+
return feedback;
|
|
11042
11368
|
} catch (error) {
|
|
11043
|
-
|
|
11044
|
-
|
|
11369
|
+
return {
|
|
11370
|
+
message: friendlyError(error, "Failed to remove peer. The local peer entry is still here."),
|
|
11371
|
+
tone: "warning"
|
|
11372
|
+
};
|
|
11045
11373
|
}
|
|
11046
11374
|
},
|
|
11047
11375
|
onAssignActor: async (peerId, actorId) => {
|
|
11048
11376
|
try {
|
|
11049
11377
|
await assignPeerActor(peerId, actorId);
|
|
11050
|
-
showGlobalNotice(actorId ? "Device person updated." : "Device person cleared.");
|
|
11051
11378
|
await _loadSyncData();
|
|
11379
|
+
return {
|
|
11380
|
+
message: actorId ? "Device person updated." : "Device person cleared.",
|
|
11381
|
+
tone: "success"
|
|
11382
|
+
};
|
|
11052
11383
|
} catch (error) {
|
|
11053
|
-
|
|
11054
|
-
|
|
11384
|
+
return {
|
|
11385
|
+
message: friendlyError(error, "Failed to update device person. The current assignment is unchanged."),
|
|
11386
|
+
tone: "warning"
|
|
11387
|
+
};
|
|
11055
11388
|
}
|
|
11056
11389
|
},
|
|
11057
11390
|
onSaveScope: async (peerId, include, exclude) => {
|
|
11058
11391
|
try {
|
|
11059
11392
|
await updatePeerScope(peerId, include, exclude);
|
|
11060
11393
|
clearPeerScopeReview(peerId);
|
|
11061
|
-
showGlobalNotice("Device sync scope saved.");
|
|
11062
11394
|
await _loadSyncData();
|
|
11395
|
+
return {
|
|
11396
|
+
message: "Device sync scope saved.",
|
|
11397
|
+
tone: "success"
|
|
11398
|
+
};
|
|
11063
11399
|
} catch (error) {
|
|
11064
|
-
|
|
11065
|
-
|
|
11400
|
+
return {
|
|
11401
|
+
message: friendlyError(error, "Failed to save device scope. The current sharing rules are still active."),
|
|
11402
|
+
tone: "warning"
|
|
11403
|
+
};
|
|
11066
11404
|
}
|
|
11067
11405
|
},
|
|
11068
11406
|
onResetScope: async (peerId) => {
|
|
11069
11407
|
try {
|
|
11070
11408
|
await updatePeerScope(peerId, null, null, true);
|
|
11071
11409
|
clearPeerScopeReview(peerId);
|
|
11072
|
-
showGlobalNotice("Device sync scope reset to global defaults.");
|
|
11073
11410
|
await _loadSyncData();
|
|
11411
|
+
return {
|
|
11412
|
+
message: "Device sync scope reset to global defaults.",
|
|
11413
|
+
tone: "success"
|
|
11414
|
+
};
|
|
11074
11415
|
} catch (error) {
|
|
11075
|
-
|
|
11076
|
-
|
|
11416
|
+
return {
|
|
11417
|
+
message: friendlyError(error, "Failed to reset device scope. The current sharing rules are still active."),
|
|
11418
|
+
tone: "warning"
|
|
11419
|
+
};
|
|
11077
11420
|
}
|
|
11078
11421
|
}
|
|
11079
11422
|
});
|
|
11080
11423
|
}
|
|
11424
|
+
function renderSyncPeopleUnavailable() {
|
|
11425
|
+
const actorList = document.getElementById("syncActorsList");
|
|
11426
|
+
const actorMeta = document.getElementById("syncActorsMeta");
|
|
11427
|
+
const syncPeers = document.getElementById("syncPeers");
|
|
11428
|
+
setPeopleCreateControlsDisabled(true);
|
|
11429
|
+
if (actorMeta) actorMeta.textContent = "People and device details are unavailable right now. Refresh this page to retry once local sync status is reachable again.";
|
|
11430
|
+
if (actorList) renderSyncEmptyState(actorList, {
|
|
11431
|
+
title: "People unavailable right now.",
|
|
11432
|
+
detail: "Refresh this page to reload named people once the local sync status endpoint is responding again."
|
|
11433
|
+
});
|
|
11434
|
+
if (syncPeers) renderSyncEmptyState(syncPeers, {
|
|
11435
|
+
title: "Devices unavailable right now.",
|
|
11436
|
+
detail: "Refresh this page to reload paired devices. When sync is reachable again, you can rename, assign, or pair devices here."
|
|
11437
|
+
});
|
|
11438
|
+
}
|
|
11081
11439
|
function renderLegacyDeviceClaims() {
|
|
11082
11440
|
const panel = document.getElementById("syncLegacyClaims");
|
|
11083
11441
|
const mount = document.getElementById("syncLegacyDeviceSelectMount");
|
|
@@ -11184,6 +11542,18 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
11184
11542
|
join_requests: []
|
|
11185
11543
|
};
|
|
11186
11544
|
}
|
|
11545
|
+
function hideStaleSyncSecondarySections() {
|
|
11546
|
+
const sharingReview = document.getElementById("syncSharingReview");
|
|
11547
|
+
const sharingReviewList = document.getElementById("syncSharingReviewList");
|
|
11548
|
+
const sharingReviewMeta = document.getElementById("syncSharingReviewMeta");
|
|
11549
|
+
const legacyClaims = document.getElementById("syncLegacyClaims");
|
|
11550
|
+
const legacyClaimsMeta = document.getElementById("syncLegacyClaimsMeta");
|
|
11551
|
+
if (sharingReview) sharingReview.hidden = true;
|
|
11552
|
+
if (sharingReviewList) sharingReviewList.textContent = "";
|
|
11553
|
+
if (sharingReviewMeta) sharingReviewMeta.textContent = "";
|
|
11554
|
+
if (legacyClaims) legacyClaims.hidden = true;
|
|
11555
|
+
if (legacyClaimsMeta) legacyClaimsMeta.textContent = "";
|
|
11556
|
+
}
|
|
11187
11557
|
async function loadSyncData() {
|
|
11188
11558
|
const requestId = ++latestSyncLoadRequestId;
|
|
11189
11559
|
try {
|
|
@@ -11222,7 +11592,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
11222
11592
|
const statusPayload = payload.status && typeof payload.status === "object" ? payload.status : null;
|
|
11223
11593
|
if (statusPayload) state.lastSyncStatus = statusPayload;
|
|
11224
11594
|
if (Array.isArray(actorsPayload?.items)) state.lastSyncActors = actorsPayload.items;
|
|
11225
|
-
else
|
|
11595
|
+
else state.lastSyncActors = [];
|
|
11226
11596
|
const payloadPeers = Array.isArray(payload.peers) ? payload.peers : [];
|
|
11227
11597
|
const realPeerIds = new Set(payloadPeers.map((peer) => String(peer?.peer_device_id || "").trim()).filter(Boolean));
|
|
11228
11598
|
const pendingPeers = Array.isArray(state.pendingAcceptedSyncPeers) ? state.pendingAcceptedSyncPeers.filter((peer) => {
|
|
@@ -11251,18 +11621,17 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
11251
11621
|
renderLegacyDeviceClaims();
|
|
11252
11622
|
renderSyncAttempts();
|
|
11253
11623
|
renderHealthOverview();
|
|
11254
|
-
if (actorLoadError)
|
|
11255
|
-
const actorMeta = document.getElementById("syncActorsMeta");
|
|
11256
|
-
if (actorMeta) actorMeta.textContent = "People controls are temporarily unavailable. Device status and sync health still loaded.";
|
|
11257
|
-
}
|
|
11624
|
+
if (actorLoadError) renderSyncActorsUnavailable();
|
|
11258
11625
|
} catch {
|
|
11259
11626
|
if (requestId !== latestSyncLoadRequestId) return;
|
|
11627
|
+
lastSyncHash = "";
|
|
11260
11628
|
hideSkeleton("syncTeamSkeleton");
|
|
11261
11629
|
hideSkeleton("syncActorsSkeleton");
|
|
11262
11630
|
hideSkeleton("syncPeersSkeleton");
|
|
11263
11631
|
hideSkeleton("syncDiagSkeleton");
|
|
11264
|
-
|
|
11265
|
-
|
|
11632
|
+
hideStaleSyncSecondarySections();
|
|
11633
|
+
renderSyncPeopleUnavailable();
|
|
11634
|
+
renderSyncDiagnosticsUnavailable();
|
|
11266
11635
|
}
|
|
11267
11636
|
}
|
|
11268
11637
|
async function loadPairingData() {
|
|
@@ -11270,6 +11639,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
11270
11639
|
state.pairingPayloadRaw = await loadPairing() || null;
|
|
11271
11640
|
renderPairing();
|
|
11272
11641
|
} catch {
|
|
11642
|
+
state.pairingPayloadRaw = null;
|
|
11273
11643
|
renderPairing();
|
|
11274
11644
|
}
|
|
11275
11645
|
}
|
|
@@ -12924,10 +13294,60 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
12924
13294
|
try {
|
|
12925
13295
|
const runtime = await loadRuntimeInfo();
|
|
12926
13296
|
if (!runtime?.version) return;
|
|
12927
|
-
setRuntimeLabel(runtime.version, "
|
|
13297
|
+
setRuntimeLabel(runtime.version, "692076f");
|
|
12928
13298
|
} catch {}
|
|
12929
13299
|
}
|
|
12930
13300
|
var lastAnnouncedRefreshState = null;
|
|
13301
|
+
var RECONNECT_POLL_MS = 1500;
|
|
13302
|
+
var reconnectTimer = null;
|
|
13303
|
+
var reconnecting = false;
|
|
13304
|
+
function setReconnectOverlay(open, detail) {
|
|
13305
|
+
const overlay = $$2("viewerReconnectOverlay");
|
|
13306
|
+
const detailEl = $$2("viewerReconnectDetail");
|
|
13307
|
+
if (!overlay || !detailEl) return;
|
|
13308
|
+
overlay.hidden = !open;
|
|
13309
|
+
detailEl.textContent = detail || "Trying again automatically while the viewer comes back.";
|
|
13310
|
+
}
|
|
13311
|
+
async function isViewerReady() {
|
|
13312
|
+
try {
|
|
13313
|
+
await pingViewerReady();
|
|
13314
|
+
return true;
|
|
13315
|
+
} catch {
|
|
13316
|
+
return false;
|
|
13317
|
+
}
|
|
13318
|
+
}
|
|
13319
|
+
function stopReconnectLoop() {
|
|
13320
|
+
if (reconnectTimer) {
|
|
13321
|
+
clearTimeout(reconnectTimer);
|
|
13322
|
+
reconnectTimer = null;
|
|
13323
|
+
}
|
|
13324
|
+
reconnecting = false;
|
|
13325
|
+
setReconnectOverlay(false);
|
|
13326
|
+
}
|
|
13327
|
+
function canResumeRefresh() {
|
|
13328
|
+
return document.visibilityState !== "hidden" && !isSettingsOpen();
|
|
13329
|
+
}
|
|
13330
|
+
function scheduleReconnectLoop() {
|
|
13331
|
+
if (reconnecting) return;
|
|
13332
|
+
reconnecting = true;
|
|
13333
|
+
stopPolling();
|
|
13334
|
+
setRefreshStatus("error", "(reconnecting)");
|
|
13335
|
+
setReconnectOverlay(true, "The viewer server is restarting or temporarily unavailable. Trying again automatically…");
|
|
13336
|
+
const tick = async () => {
|
|
13337
|
+
if (await isViewerReady()) {
|
|
13338
|
+
stopReconnectLoop();
|
|
13339
|
+
if (canResumeRefresh()) {
|
|
13340
|
+
setRefreshStatus("refreshing");
|
|
13341
|
+
startPolling();
|
|
13342
|
+
doRefresh();
|
|
13343
|
+
} else setRefreshStatus("paused", document.visibilityState === "hidden" ? "(tab hidden)" : "(settings open)");
|
|
13344
|
+
return;
|
|
13345
|
+
}
|
|
13346
|
+
setReconnectOverlay(true, "Still reconnecting… the viewer will recover automatically as soon as the server responds.");
|
|
13347
|
+
reconnectTimer = setTimeout(tick, RECONNECT_POLL_MS);
|
|
13348
|
+
};
|
|
13349
|
+
reconnectTimer = setTimeout(tick, RECONNECT_POLL_MS);
|
|
13350
|
+
}
|
|
12931
13351
|
function setRefreshStatus(rs, detail) {
|
|
12932
13352
|
state.refreshState = rs;
|
|
12933
13353
|
const el = $$2("refreshStatus");
|
|
@@ -12947,6 +13367,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
12947
13367
|
announce("Auto refresh paused.");
|
|
12948
13368
|
return;
|
|
12949
13369
|
}
|
|
13370
|
+
if (rs === "error" && detail === "(reconnecting)") {
|
|
13371
|
+
el.textContent = "reconnecting…";
|
|
13372
|
+
announce("Viewer reconnecting.");
|
|
13373
|
+
return;
|
|
13374
|
+
}
|
|
12950
13375
|
if (rs === "error") {
|
|
12951
13376
|
el.textContent = "refresh failed";
|
|
12952
13377
|
announce("Refresh failed.");
|
|
@@ -12970,7 +13395,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
12970
13395
|
if (document.visibilityState === "hidden") {
|
|
12971
13396
|
stopPolling();
|
|
12972
13397
|
setRefreshStatus("paused", "(tab hidden)");
|
|
12973
|
-
} else if (!isSettingsOpen()) {
|
|
13398
|
+
} else if (!isSettingsOpen() && !reconnecting) {
|
|
12974
13399
|
startPolling();
|
|
12975
13400
|
refresh();
|
|
12976
13401
|
}
|
|
@@ -13026,10 +13451,12 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
13026
13451
|
});
|
|
13027
13452
|
var refreshDebounceTimer = null;
|
|
13028
13453
|
async function refresh() {
|
|
13454
|
+
if (reconnecting) return;
|
|
13029
13455
|
if (refreshDebounceTimer) clearTimeout(refreshDebounceTimer);
|
|
13030
13456
|
refreshDebounceTimer = setTimeout(() => doRefresh(), 80);
|
|
13031
13457
|
}
|
|
13032
13458
|
async function doRefresh() {
|
|
13459
|
+
if (reconnecting) return;
|
|
13033
13460
|
if (state.refreshInFlight) {
|
|
13034
13461
|
state.refreshQueued = true;
|
|
13035
13462
|
return;
|
|
@@ -13044,10 +13471,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
13044
13471
|
await Promise.all(promises);
|
|
13045
13472
|
setRefreshStatus("idle");
|
|
13046
13473
|
} catch {
|
|
13047
|
-
|
|
13474
|
+
if (!await isViewerReady()) scheduleReconnectLoop();
|
|
13475
|
+
else setRefreshStatus("error");
|
|
13048
13476
|
} finally {
|
|
13049
13477
|
state.refreshInFlight = false;
|
|
13050
|
-
if (state.refreshQueued) {
|
|
13478
|
+
if (state.refreshQueued && !reconnecting) {
|
|
13051
13479
|
state.refreshQueued = false;
|
|
13052
13480
|
doRefresh();
|
|
13053
13481
|
}
|
|
@@ -13061,6 +13489,16 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
13061
13489
|
initSyncTab(() => refresh());
|
|
13062
13490
|
initSettings(stopPolling, startPolling, () => refresh());
|
|
13063
13491
|
loadProjects();
|
|
13492
|
+
$$2("viewerReconnectRetry")?.addEventListener("click", async () => {
|
|
13493
|
+
setReconnectOverlay(true, "Checking whether the viewer server is back…");
|
|
13494
|
+
if (!await isViewerReady()) {
|
|
13495
|
+
scheduleReconnectLoop();
|
|
13496
|
+
return;
|
|
13497
|
+
}
|
|
13498
|
+
stopReconnectLoop();
|
|
13499
|
+
startPolling();
|
|
13500
|
+
doRefresh();
|
|
13501
|
+
});
|
|
13064
13502
|
loadRuntimeLabel();
|
|
13065
13503
|
refresh();
|
|
13066
13504
|
startPolling();
|