@codemem/server 0.30.0 → 0.31.1
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +367 -27
- package/dist/index.js.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +1329 -56
- package/static/index.html +295 -1
package/static/app.js
CHANGED
|
@@ -3268,6 +3268,14 @@
|
|
|
3268
3268
|
if (!resp.ok) throw new Error(detail || payloadError(payload) || text || "request failed");
|
|
3269
3269
|
return payload;
|
|
3270
3270
|
}
|
|
3271
|
+
var ProjectForgetConfirmationError = class extends Error {
|
|
3272
|
+
preview;
|
|
3273
|
+
constructor(preview) {
|
|
3274
|
+
super("Project forget confirmation required");
|
|
3275
|
+
this.name = "ProjectForgetConfirmationError";
|
|
3276
|
+
this.preview = preview;
|
|
3277
|
+
}
|
|
3278
|
+
};
|
|
3271
3279
|
var SharingDomainGuardrailConfirmationError = class extends Error {
|
|
3272
3280
|
requiredGuardrails;
|
|
3273
3281
|
requiredGuardrailTokens;
|
|
@@ -3280,9 +3288,26 @@
|
|
|
3280
3288
|
this.guardrailWarnings = input.guardrail_warnings ?? [];
|
|
3281
3289
|
}
|
|
3282
3290
|
};
|
|
3291
|
+
var LegacySharedReviewConfirmationError = class extends Error {
|
|
3292
|
+
preview;
|
|
3293
|
+
constructor(preview) {
|
|
3294
|
+
super("Legacy shared review confirmation required");
|
|
3295
|
+
this.name = "LegacySharedReviewConfirmationError";
|
|
3296
|
+
this.preview = preview;
|
|
3297
|
+
}
|
|
3298
|
+
};
|
|
3283
3299
|
async function loadSharingDomainSettings() {
|
|
3284
3300
|
return fetchJson("/api/sync/sharing-domains/settings");
|
|
3285
3301
|
}
|
|
3302
|
+
async function loadProjectScopeInventory(input = {}) {
|
|
3303
|
+
const params = new URLSearchParams();
|
|
3304
|
+
for (const [key, value] of Object.entries(input)) {
|
|
3305
|
+
if (value == null || value === "") continue;
|
|
3306
|
+
params.set(key, String(value));
|
|
3307
|
+
}
|
|
3308
|
+
const query = params.toString();
|
|
3309
|
+
return fetchJson(`/api/sync/projects${query ? `?${query}` : ""}`);
|
|
3310
|
+
}
|
|
3286
3311
|
async function saveSharingDomainProjectMapping(input) {
|
|
3287
3312
|
const resp = await fetch("/api/sync/sharing-domains/project-mappings", {
|
|
3288
3313
|
method: "PUT",
|
|
@@ -3301,12 +3326,65 @@
|
|
|
3301
3326
|
guardrail_warnings: payload.guardrail_warnings ?? mapping.guardrail_warnings
|
|
3302
3327
|
};
|
|
3303
3328
|
}
|
|
3329
|
+
async function saveSharingDomainProjectMappings(input) {
|
|
3330
|
+
const resp = await fetch("/api/sync/sharing-domains/project-mappings/bulk", {
|
|
3331
|
+
method: "PUT",
|
|
3332
|
+
headers: { "Content-Type": "application/json" },
|
|
3333
|
+
body: JSON.stringify(input)
|
|
3334
|
+
});
|
|
3335
|
+
const { text, payload } = await readJsonPayload(resp);
|
|
3336
|
+
if (!resp.ok) {
|
|
3337
|
+
if (payload?.error === "guardrail_confirmation_required") throw new SharingDomainGuardrailConfirmationError(payload);
|
|
3338
|
+
throw new Error(payloadError(payload) || text || "request failed");
|
|
3339
|
+
}
|
|
3340
|
+
if (!Array.isArray(payload?.mappings)) throw new Error("response missing mappings");
|
|
3341
|
+
return payload.mappings;
|
|
3342
|
+
}
|
|
3304
3343
|
async function deleteSharingDomainProjectMapping(id) {
|
|
3305
3344
|
const resp = await fetch(`/api/sync/sharing-domains/project-mappings/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
3306
3345
|
const { text, payload } = await readJsonPayload(resp);
|
|
3307
3346
|
if (!resp.ok) throw new Error(payloadError(payload) || text || "request failed");
|
|
3308
3347
|
return Boolean(payload?.deleted);
|
|
3309
3348
|
}
|
|
3349
|
+
async function reassignProjectInventoryProject(input) {
|
|
3350
|
+
const resp = await fetch("/api/sync/projects/reassign-project", {
|
|
3351
|
+
method: "POST",
|
|
3352
|
+
headers: { "Content-Type": "application/json" },
|
|
3353
|
+
body: JSON.stringify(input)
|
|
3354
|
+
});
|
|
3355
|
+
const { text, payload } = await readJsonPayload(resp);
|
|
3356
|
+
if (!resp.ok) throw new Error(payloadError(payload) || text || "request failed");
|
|
3357
|
+
if (!payload?.workspace_identity) throw new Error("response missing project reassignment");
|
|
3358
|
+
return payload;
|
|
3359
|
+
}
|
|
3360
|
+
async function forgetProjectInventoryMemories(input) {
|
|
3361
|
+
const resp = await fetch("/api/sync/projects/forget", {
|
|
3362
|
+
method: "POST",
|
|
3363
|
+
headers: { "Content-Type": "application/json" },
|
|
3364
|
+
body: JSON.stringify(input)
|
|
3365
|
+
});
|
|
3366
|
+
const { text, payload } = await readJsonPayload(resp);
|
|
3367
|
+
if (!resp.ok) {
|
|
3368
|
+
if (payload?.error === "project_forget_confirmation_required" && payload.preview) throw new ProjectForgetConfirmationError(payload.preview);
|
|
3369
|
+
throw new Error(payloadError(payload) || text || "request failed");
|
|
3370
|
+
}
|
|
3371
|
+
if (!payload?.workspace_identity) throw new Error("response missing project forget result");
|
|
3372
|
+
return payload;
|
|
3373
|
+
}
|
|
3374
|
+
async function reassignLegacySharedReviewGroup$1(input) {
|
|
3375
|
+
const resp = await fetch("/api/sync/legacy-shared-review/reassign", {
|
|
3376
|
+
method: "POST",
|
|
3377
|
+
headers: { "Content-Type": "application/json" },
|
|
3378
|
+
body: JSON.stringify(input)
|
|
3379
|
+
});
|
|
3380
|
+
const { text, payload } = await readJsonPayload(resp);
|
|
3381
|
+
if (!resp.ok) {
|
|
3382
|
+
if (payload?.error === "legacy_review_confirmation_required" && payload.preview) throw new LegacySharedReviewConfirmationError(payload.preview);
|
|
3383
|
+
throw new Error(payloadError(payload) || text || "request failed");
|
|
3384
|
+
}
|
|
3385
|
+
if (!payload?.workspace_identity) throw new Error("response missing legacy review reassignment");
|
|
3386
|
+
return payload;
|
|
3387
|
+
}
|
|
3310
3388
|
async function loadCoordinatorGroupPreferences(groupId) {
|
|
3311
3389
|
const resp = await fetch(`/api/coordinator/admin/groups/${encodeURIComponent(groupId)}/preferences`);
|
|
3312
3390
|
const { text, payload } = await readJsonPayload(resp);
|
|
@@ -3397,7 +3475,7 @@
|
|
|
3397
3475
|
}
|
|
3398
3476
|
//#endregion
|
|
3399
3477
|
//#region src/lib/dom.ts
|
|
3400
|
-
function el(tag, className, text) {
|
|
3478
|
+
function el$1(tag, className, text) {
|
|
3401
3479
|
const node = document.createElement(tag);
|
|
3402
3480
|
if (className) node.className = className;
|
|
3403
3481
|
if (text !== void 0 && text !== null) node.textContent = String(text);
|
|
@@ -3445,8 +3523,9 @@
|
|
|
3445
3523
|
//#region src/lib/state.ts
|
|
3446
3524
|
var ALL_TAB_IDS = [
|
|
3447
3525
|
"feed",
|
|
3448
|
-
"
|
|
3526
|
+
"projects",
|
|
3449
3527
|
"sync",
|
|
3528
|
+
"health",
|
|
3450
3529
|
"coordinator-admin"
|
|
3451
3530
|
];
|
|
3452
3531
|
var TAB_KEY = "codemem-tab";
|
|
@@ -13465,7 +13544,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
13465
13544
|
}) : null, _$3(Tooltip, {
|
|
13466
13545
|
label: formatDate(createdAtRaw),
|
|
13467
13546
|
side: "left"
|
|
13468
|
-
}, _$3("div", { className: "small feed-age" }, relative)),
|
|
13547
|
+
}, _$3("div", { className: "small feed-age" }, relative)), item.owned_by_self && memoryId > 0 ? _$3(FeedItemMenu, {
|
|
13469
13548
|
assignProjectDisabled: movingProject,
|
|
13470
13549
|
disabled: deletingMemory,
|
|
13471
13550
|
onAssignProject: () => void moveProject(),
|
|
@@ -13480,7 +13559,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
13480
13559
|
})), _$3("div", { className: "feed-meta" }, metaText || "No tags, files, or provenance details attached."), bodyContent, _$3("div", { className: "feed-footer" }, _$3("div", { className: "feed-footer-left" }, tags.length ? _$3("div", { className: "feed-tags" }, tags.map((tag, index) => _$3(TagChip, {
|
|
13481
13560
|
key: `${String(tag)}-${index}`,
|
|
13482
13561
|
tag
|
|
13483
|
-
}))) : null,
|
|
13562
|
+
}))) : null, item.owned_by_self && memoryId > 0 ? _$3("div", { className: "feed-visibility-controls" }, _$3("select", {
|
|
13484
13563
|
"aria-label": `Visibility for ${String(item.title || "memory")}`,
|
|
13485
13564
|
className: "feed-visibility-select",
|
|
13486
13565
|
disabled: savingVisibility,
|
|
@@ -14344,6 +14423,552 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
14344
14423
|
if (state.activeTab === "feed" && previousActorId !== nextActorId) updateFeedView(true);
|
|
14345
14424
|
}
|
|
14346
14425
|
//#endregion
|
|
14426
|
+
//#region src/tabs/projects.ts
|
|
14427
|
+
var STATUS_OPTIONS = [
|
|
14428
|
+
["", "All projects"],
|
|
14429
|
+
["needs_attention", "Needs review"],
|
|
14430
|
+
["suggested", "Has suggestion"],
|
|
14431
|
+
["local_only", "Stays on this device"],
|
|
14432
|
+
["explicitly_mapped", "Already assigned"],
|
|
14433
|
+
["legacy_review", "Older shared data"],
|
|
14434
|
+
["unmapped", "Missing project identity"]
|
|
14435
|
+
];
|
|
14436
|
+
var refreshProjects = null;
|
|
14437
|
+
var currentOffset = 0;
|
|
14438
|
+
var lastLimit = 250;
|
|
14439
|
+
var scopes = [];
|
|
14440
|
+
var openProjectDetails = /* @__PURE__ */ new Set();
|
|
14441
|
+
var openProjectClusters = /* @__PURE__ */ new Set();
|
|
14442
|
+
var draftDomainSelections = /* @__PURE__ */ new Map();
|
|
14443
|
+
var pendingConfirmations = /* @__PURE__ */ new Map();
|
|
14444
|
+
var pendingForgetConfirmations = /* @__PURE__ */ new Map();
|
|
14445
|
+
function el(id) {
|
|
14446
|
+
return document.getElementById(id);
|
|
14447
|
+
}
|
|
14448
|
+
function formatStatus(status) {
|
|
14449
|
+
return STATUS_OPTIONS.find(([value]) => value === status)?.[1] ?? status.replaceAll("_", " ");
|
|
14450
|
+
}
|
|
14451
|
+
function formatResolution(reason) {
|
|
14452
|
+
switch (reason) {
|
|
14453
|
+
case "exact_mapping": return "assigned to a Sharing domain";
|
|
14454
|
+
case "pattern_mapping": return "assigned by matching rule";
|
|
14455
|
+
case "explicit_override": return "manually assigned";
|
|
14456
|
+
default: return "stays on this device";
|
|
14457
|
+
}
|
|
14458
|
+
}
|
|
14459
|
+
function formatLatest(value) {
|
|
14460
|
+
if (!value) return "No recent sessions";
|
|
14461
|
+
const date = new Date(value);
|
|
14462
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
14463
|
+
return date.toLocaleString();
|
|
14464
|
+
}
|
|
14465
|
+
function strongestSignal(project) {
|
|
14466
|
+
if (project.git_remote) return project.git_remote;
|
|
14467
|
+
if (project.cwd) return project.cwd;
|
|
14468
|
+
return project.workspace_identity;
|
|
14469
|
+
}
|
|
14470
|
+
function projectClusterKey(project) {
|
|
14471
|
+
if (project.git_remote) return `git:${project.git_remote}`;
|
|
14472
|
+
if (project.project) return `project:${project.project}`;
|
|
14473
|
+
return `identity:${project.workspace_identity}`;
|
|
14474
|
+
}
|
|
14475
|
+
function projectClusterLabel(project) {
|
|
14476
|
+
return project.project || project.display_project || strongestSignal(project);
|
|
14477
|
+
}
|
|
14478
|
+
function scopeLabel(scopeId) {
|
|
14479
|
+
if (!scopeId) return "—";
|
|
14480
|
+
const scope = scopes.find((item) => item.scope_id === scopeId);
|
|
14481
|
+
return scope?.label ? `${scope.label} (${scope.scope_id})` : scopeId;
|
|
14482
|
+
}
|
|
14483
|
+
function assignableScopes() {
|
|
14484
|
+
return scopes.filter((scope) => scope.scope_id !== "legacy-shared-review");
|
|
14485
|
+
}
|
|
14486
|
+
function guardrailHeading(warning) {
|
|
14487
|
+
switch (warning.code) {
|
|
14488
|
+
case "unknown_project_local_only": return "Current behavior";
|
|
14489
|
+
case "basename_collision_review": return "Name collision";
|
|
14490
|
+
case "scope_reassignment_old_copies": return "Previous copies";
|
|
14491
|
+
case "broad_org_domain_pattern":
|
|
14492
|
+
case "home_directory_org_domain_pattern": return "Broad mapping";
|
|
14493
|
+
default: return "Review item";
|
|
14494
|
+
}
|
|
14495
|
+
}
|
|
14496
|
+
async function saveProjectMapping(project, scopeId, confirmedGuardrailTokens = []) {
|
|
14497
|
+
try {
|
|
14498
|
+
await saveSharingDomainProjectMapping({
|
|
14499
|
+
...project.mapping_id && project.resolution_reason === "exact_mapping" ? { id: project.mapping_id } : {},
|
|
14500
|
+
...confirmedGuardrailTokens.length > 0 ? { confirmed_guardrail_tokens: confirmedGuardrailTokens } : {},
|
|
14501
|
+
project_pattern: project.display_project,
|
|
14502
|
+
scope_id: scopeId,
|
|
14503
|
+
workspace_identity: project.workspace_identity
|
|
14504
|
+
});
|
|
14505
|
+
pendingConfirmations.delete(project.workspace_identity);
|
|
14506
|
+
draftDomainSelections.delete(project.workspace_identity);
|
|
14507
|
+
showGlobalNotice("Project Sharing domain updated. Device access grants are unchanged.");
|
|
14508
|
+
refreshProjects?.();
|
|
14509
|
+
} catch (error) {
|
|
14510
|
+
if (error instanceof SharingDomainGuardrailConfirmationError) {
|
|
14511
|
+
pendingConfirmations.set(project.workspace_identity, {
|
|
14512
|
+
requiredGuardrailTokens: error.requiredGuardrailTokens,
|
|
14513
|
+
scopeId,
|
|
14514
|
+
warnings: error.guardrailWarnings
|
|
14515
|
+
});
|
|
14516
|
+
refreshProjects?.();
|
|
14517
|
+
return;
|
|
14518
|
+
}
|
|
14519
|
+
showGlobalNotice(error instanceof Error ? error.message : "Unable to update project Sharing domain.", "warning");
|
|
14520
|
+
}
|
|
14521
|
+
}
|
|
14522
|
+
async function saveProjectClusterMapping(projects, scopeId) {
|
|
14523
|
+
const assignable = projects.filter((project) => project.identity_source !== "unmapped");
|
|
14524
|
+
if (assignable.length === 0) return;
|
|
14525
|
+
try {
|
|
14526
|
+
await saveSharingDomainProjectMappings({ mappings: assignable.map((project) => ({
|
|
14527
|
+
...project.mapping_id && project.resolution_reason === "exact_mapping" ? { id: project.mapping_id } : {},
|
|
14528
|
+
project_pattern: project.display_project,
|
|
14529
|
+
scope_id: scopeId,
|
|
14530
|
+
workspace_identity: project.workspace_identity
|
|
14531
|
+
})) });
|
|
14532
|
+
showGlobalNotice(`Updated ${assignable.length} project identit${assignable.length === 1 ? "y" : "ies"}. Device access grants are unchanged.`);
|
|
14533
|
+
refreshProjects?.();
|
|
14534
|
+
} catch (error) {
|
|
14535
|
+
showGlobalNotice(error instanceof SharingDomainGuardrailConfirmationError ? "One or more identities in this group need review before bulk assignment. Expand the group and save those identities directly." : error instanceof Error ? error.message : "Unable to update project Sharing domains.", "warning");
|
|
14536
|
+
}
|
|
14537
|
+
}
|
|
14538
|
+
async function removeProjectMapping(project) {
|
|
14539
|
+
if (project.mapping_id == null) return;
|
|
14540
|
+
try {
|
|
14541
|
+
await deleteSharingDomainProjectMapping(project.mapping_id);
|
|
14542
|
+
pendingConfirmations.delete(project.workspace_identity);
|
|
14543
|
+
draftDomainSelections.delete(project.workspace_identity);
|
|
14544
|
+
showGlobalNotice("Project Sharing domain mapping removed. The next fallback now applies.");
|
|
14545
|
+
refreshProjects?.();
|
|
14546
|
+
} catch (error) {
|
|
14547
|
+
showGlobalNotice(error instanceof Error ? error.message : "Unable to remove project Sharing domain mapping.", "warning");
|
|
14548
|
+
}
|
|
14549
|
+
}
|
|
14550
|
+
async function forgetProjectMemories(project, confirmed = false) {
|
|
14551
|
+
try {
|
|
14552
|
+
const pending = pendingForgetConfirmations.get(project.workspace_identity);
|
|
14553
|
+
const result = await forgetProjectInventoryMemories({
|
|
14554
|
+
...confirmed && pending ? { confirmation_token: pending.confirmationToken } : {},
|
|
14555
|
+
confirmed,
|
|
14556
|
+
workspace_identity: project.workspace_identity
|
|
14557
|
+
});
|
|
14558
|
+
pendingForgetConfirmations.delete(project.workspace_identity);
|
|
14559
|
+
showGlobalNotice(`Forgot ${result.forgotten_memory_count.toLocaleString()} local memor${result.forgotten_memory_count === 1 ? "y" : "ies"}. ${result.peer_owned_memory_count.toLocaleString()} peer-owned memor${result.peer_owned_memory_count === 1 ? "y was" : "ies were"} left unchanged.`);
|
|
14560
|
+
refreshProjects?.();
|
|
14561
|
+
} catch (error) {
|
|
14562
|
+
if (error instanceof ProjectForgetConfirmationError) {
|
|
14563
|
+
pendingForgetConfirmations.set(project.workspace_identity, {
|
|
14564
|
+
confirmationToken: error.preview.confirmation_token,
|
|
14565
|
+
localOwnedMemoryCount: error.preview.local_owned_memory_count,
|
|
14566
|
+
peerOwnedMemoryCount: error.preview.peer_owned_memory_count
|
|
14567
|
+
});
|
|
14568
|
+
refreshProjects?.();
|
|
14569
|
+
return;
|
|
14570
|
+
}
|
|
14571
|
+
showGlobalNotice(error instanceof Error ? error.message : "Unable to forget project memories.", "warning");
|
|
14572
|
+
}
|
|
14573
|
+
}
|
|
14574
|
+
async function reassignProject(project) {
|
|
14575
|
+
if (project.identity_source === "unmapped") return;
|
|
14576
|
+
const currentProject = String(project.project || project.display_project || "").trim();
|
|
14577
|
+
let suggestions = [];
|
|
14578
|
+
try {
|
|
14579
|
+
suggestions = (await loadProjects$1()).filter((name) => name && name !== currentProject);
|
|
14580
|
+
} catch {}
|
|
14581
|
+
const nextProject = await openSyncInputDialog({
|
|
14582
|
+
cancelLabel: "Cancel",
|
|
14583
|
+
confirmLabel: "Change project",
|
|
14584
|
+
description: `This will update ${project.session_count} session${project.session_count === 1 ? "" : "s"} and ${project.memory_count ?? 0} memor${project.memory_count === 1 ? "y" : "ies"} by changing the stored project. Sharing-domain assignment stays unchanged.`,
|
|
14585
|
+
initialValue: currentProject,
|
|
14586
|
+
placeholder: "Project name",
|
|
14587
|
+
suggestions,
|
|
14588
|
+
title: "Change project",
|
|
14589
|
+
validate: (value) => {
|
|
14590
|
+
const trimmed = value.trim();
|
|
14591
|
+
if (!trimmed) return "Enter a project name.";
|
|
14592
|
+
if (trimmed === currentProject) return "Already assigned to this project.";
|
|
14593
|
+
return null;
|
|
14594
|
+
}
|
|
14595
|
+
});
|
|
14596
|
+
if (nextProject == null) return;
|
|
14597
|
+
try {
|
|
14598
|
+
const result = await reassignProjectInventoryProject({
|
|
14599
|
+
project: nextProject.trim(),
|
|
14600
|
+
workspace_identity: project.workspace_identity
|
|
14601
|
+
});
|
|
14602
|
+
showGlobalNotice(`Changed project to ${result.project} for ${result.moved_session_count} session${result.moved_session_count === 1 ? "" : "s"}.`);
|
|
14603
|
+
refreshProjects?.();
|
|
14604
|
+
} catch (error) {
|
|
14605
|
+
showGlobalNotice(error instanceof Error ? error.message : "Unable to change project.", "warning");
|
|
14606
|
+
}
|
|
14607
|
+
}
|
|
14608
|
+
function renderProjectActions(project) {
|
|
14609
|
+
const actions = document.createElement("div");
|
|
14610
|
+
actions.className = "project-inventory-actions";
|
|
14611
|
+
if (project.identity_source === "unmapped") {
|
|
14612
|
+
const note = document.createElement("div");
|
|
14613
|
+
note.className = "settings-note";
|
|
14614
|
+
note.textContent = "This project is missing a stable path, git remote, or workspace id. It stays Local only until it has a stable identity.";
|
|
14615
|
+
actions.appendChild(note);
|
|
14616
|
+
return actions;
|
|
14617
|
+
}
|
|
14618
|
+
const label = document.createElement("label");
|
|
14619
|
+
label.className = "sr-only";
|
|
14620
|
+
const selectId = `project-domain-${project.workspace_identity.replace(/[^a-z0-9_-]/gi, "-")}`;
|
|
14621
|
+
label.htmlFor = selectId;
|
|
14622
|
+
label.textContent = `Sharing domain for ${project.display_project}`;
|
|
14623
|
+
const select = document.createElement("select");
|
|
14624
|
+
select.id = selectId;
|
|
14625
|
+
select.className = "project-domain-select";
|
|
14626
|
+
const currentAssignable = assignableScopes().some((scope) => scope.scope_id === project.resolved_scope_id);
|
|
14627
|
+
if (!currentAssignable && project.resolved_scope_id) {
|
|
14628
|
+
const current = document.createElement("option");
|
|
14629
|
+
current.value = project.resolved_scope_id;
|
|
14630
|
+
current.textContent = `${scopeLabel(project.resolved_scope_id)} — not assignable`;
|
|
14631
|
+
current.disabled = true;
|
|
14632
|
+
select.appendChild(current);
|
|
14633
|
+
}
|
|
14634
|
+
for (const scope of assignableScopes()) {
|
|
14635
|
+
const option = document.createElement("option");
|
|
14636
|
+
option.value = scope.scope_id;
|
|
14637
|
+
option.textContent = scope.label ? `${scope.label} · ${scope.scope_id}` : scope.scope_id;
|
|
14638
|
+
select.appendChild(option);
|
|
14639
|
+
}
|
|
14640
|
+
select.value = draftDomainSelections.get(project.workspace_identity) ?? project.suggested_scope_id ?? project.resolved_scope_id;
|
|
14641
|
+
const save = document.createElement("button");
|
|
14642
|
+
save.className = "settings-button";
|
|
14643
|
+
save.type = "button";
|
|
14644
|
+
save.textContent = project.suggested_scope_id && select.value === project.suggested_scope_id ? "Confirm suggestion" : "Save domain";
|
|
14645
|
+
save.disabled = select.value === project.resolved_scope_id && !currentAssignable;
|
|
14646
|
+
save.addEventListener("click", () => void saveProjectMapping(project, select.value));
|
|
14647
|
+
select.addEventListener("change", () => {
|
|
14648
|
+
draftDomainSelections.set(project.workspace_identity, select.value);
|
|
14649
|
+
pendingConfirmations.delete(project.workspace_identity);
|
|
14650
|
+
save.textContent = "Save domain";
|
|
14651
|
+
save.disabled = false;
|
|
14652
|
+
refreshProjects?.();
|
|
14653
|
+
});
|
|
14654
|
+
const keepLocal = document.createElement("button");
|
|
14655
|
+
keepLocal.className = "settings-button";
|
|
14656
|
+
keepLocal.type = "button";
|
|
14657
|
+
keepLocal.textContent = "Keep local-only";
|
|
14658
|
+
keepLocal.addEventListener("click", () => void saveProjectMapping(project, "local-default"));
|
|
14659
|
+
const remove = document.createElement("button");
|
|
14660
|
+
remove.className = "settings-button";
|
|
14661
|
+
remove.type = "button";
|
|
14662
|
+
remove.textContent = "Remove mapping";
|
|
14663
|
+
remove.disabled = project.mapping_id == null || project.resolution_reason !== "exact_mapping";
|
|
14664
|
+
remove.addEventListener("click", () => void removeProjectMapping(project));
|
|
14665
|
+
const changeProject = document.createElement("button");
|
|
14666
|
+
changeProject.className = "settings-button";
|
|
14667
|
+
changeProject.type = "button";
|
|
14668
|
+
changeProject.textContent = "Change project…";
|
|
14669
|
+
changeProject.disabled = project.session_count === 0;
|
|
14670
|
+
if (changeProject.disabled) changeProject.title = "No sessions are available to reassign for this saved mapping.";
|
|
14671
|
+
changeProject.addEventListener("click", () => void reassignProject(project));
|
|
14672
|
+
const forget = document.createElement("button");
|
|
14673
|
+
forget.className = "settings-button danger";
|
|
14674
|
+
forget.type = "button";
|
|
14675
|
+
forget.textContent = "Forget local memories…";
|
|
14676
|
+
forget.disabled = (project.memory_count ?? 0) === 0;
|
|
14677
|
+
forget.addEventListener("click", () => void forgetProjectMemories(project));
|
|
14678
|
+
actions.append(label, select, save, keepLocal, remove, changeProject, forget);
|
|
14679
|
+
const pending = pendingConfirmations.get(project.workspace_identity);
|
|
14680
|
+
if (pending) {
|
|
14681
|
+
const warningBox = document.createElement("div");
|
|
14682
|
+
warningBox.className = "settings-note project-guardrail-confirmation";
|
|
14683
|
+
warningBox.setAttribute("role", "alert");
|
|
14684
|
+
const title = document.createElement("strong");
|
|
14685
|
+
title.textContent = "Confirmation required before saving this Sharing domain.";
|
|
14686
|
+
const intro = document.createElement("p");
|
|
14687
|
+
intro.textContent = "Codemem can save this change after you acknowledge the checks below. Verify the workspace details, then confirm to complete the save.";
|
|
14688
|
+
const list = document.createElement("ul");
|
|
14689
|
+
for (const warning of pending.warnings) {
|
|
14690
|
+
const item = document.createElement("li");
|
|
14691
|
+
const itemTitle = document.createElement("strong");
|
|
14692
|
+
itemTitle.textContent = `${guardrailHeading(warning)}: `;
|
|
14693
|
+
const message = document.createElement("span");
|
|
14694
|
+
message.textContent = warning.message;
|
|
14695
|
+
item.append(itemTitle, message);
|
|
14696
|
+
list.appendChild(item);
|
|
14697
|
+
}
|
|
14698
|
+
const confirm = document.createElement("button");
|
|
14699
|
+
confirm.className = "settings-button";
|
|
14700
|
+
confirm.type = "button";
|
|
14701
|
+
confirm.textContent = "I understand, save domain";
|
|
14702
|
+
confirm.addEventListener("click", () => {
|
|
14703
|
+
const currentPending = pendingConfirmations.get(project.workspace_identity);
|
|
14704
|
+
if (!currentPending || currentPending.scopeId !== select.value) return;
|
|
14705
|
+
saveProjectMapping(project, currentPending.scopeId, currentPending.requiredGuardrailTokens);
|
|
14706
|
+
});
|
|
14707
|
+
const cancel = document.createElement("button");
|
|
14708
|
+
cancel.className = "settings-button";
|
|
14709
|
+
cancel.type = "button";
|
|
14710
|
+
cancel.textContent = "Cancel";
|
|
14711
|
+
cancel.addEventListener("click", () => {
|
|
14712
|
+
pendingConfirmations.delete(project.workspace_identity);
|
|
14713
|
+
refreshProjects?.();
|
|
14714
|
+
});
|
|
14715
|
+
warningBox.append(title, intro, list, confirm, cancel);
|
|
14716
|
+
actions.appendChild(warningBox);
|
|
14717
|
+
}
|
|
14718
|
+
const pendingForget = pendingForgetConfirmations.get(project.workspace_identity);
|
|
14719
|
+
if (pendingForget) {
|
|
14720
|
+
const warningBox = document.createElement("div");
|
|
14721
|
+
warningBox.className = "settings-note project-guardrail-confirmation";
|
|
14722
|
+
warningBox.setAttribute("role", "alert");
|
|
14723
|
+
const title = document.createElement("strong");
|
|
14724
|
+
title.textContent = "Confirm project memory cleanup.";
|
|
14725
|
+
const intro = document.createElement("p");
|
|
14726
|
+
intro.textContent = `${pendingForget.localOwnedMemoryCount.toLocaleString()} locally owned memor${pendingForget.localOwnedMemoryCount === 1 ? "y" : "ies"} will be forgotten. ${pendingForget.peerOwnedMemoryCount.toLocaleString()} peer-owned memor${pendingForget.peerOwnedMemoryCount === 1 ? "y" : "ies"} will be left unchanged.`;
|
|
14727
|
+
const detail = document.createElement("p");
|
|
14728
|
+
detail.textContent = "Use this only to clean up wrongly attributed local project inventory; it forgets actual local memories on this device.";
|
|
14729
|
+
const confirm = document.createElement("button");
|
|
14730
|
+
confirm.className = "settings-button danger";
|
|
14731
|
+
confirm.type = "button";
|
|
14732
|
+
confirm.textContent = "I understand, forget local memories";
|
|
14733
|
+
confirm.addEventListener("click", () => void forgetProjectMemories(project, true));
|
|
14734
|
+
const cancel = document.createElement("button");
|
|
14735
|
+
cancel.className = "settings-button";
|
|
14736
|
+
cancel.type = "button";
|
|
14737
|
+
cancel.textContent = "Cancel";
|
|
14738
|
+
cancel.addEventListener("click", () => {
|
|
14739
|
+
pendingForgetConfirmations.delete(project.workspace_identity);
|
|
14740
|
+
refreshProjects?.();
|
|
14741
|
+
});
|
|
14742
|
+
warningBox.append(title, intro, detail, confirm, cancel);
|
|
14743
|
+
actions.appendChild(warningBox);
|
|
14744
|
+
}
|
|
14745
|
+
return actions;
|
|
14746
|
+
}
|
|
14747
|
+
function renderProjectRow(project) {
|
|
14748
|
+
const row = document.createElement("article");
|
|
14749
|
+
row.className = "project-inventory-row";
|
|
14750
|
+
const header = document.createElement("div");
|
|
14751
|
+
header.className = "project-inventory-row-header";
|
|
14752
|
+
const title = document.createElement("div");
|
|
14753
|
+
title.className = "project-inventory-title";
|
|
14754
|
+
title.textContent = project.display_project;
|
|
14755
|
+
header.appendChild(title);
|
|
14756
|
+
const domain = document.createElement("div");
|
|
14757
|
+
domain.className = "project-inventory-domain";
|
|
14758
|
+
domain.textContent = scopeLabel(project.resolved_scope_id);
|
|
14759
|
+
header.appendChild(domain);
|
|
14760
|
+
row.appendChild(header);
|
|
14761
|
+
const meta = document.createElement("div");
|
|
14762
|
+
meta.className = "project-inventory-meta";
|
|
14763
|
+
meta.textContent = `${formatResolution(project.resolution_reason)} · ${project.identity_source} · ${formatLatest(project.latest_session_at)}`;
|
|
14764
|
+
row.appendChild(meta);
|
|
14765
|
+
const signal = document.createElement("div");
|
|
14766
|
+
signal.className = "project-inventory-signal mono";
|
|
14767
|
+
signal.textContent = strongestSignal(project);
|
|
14768
|
+
row.appendChild(signal);
|
|
14769
|
+
if (project.suggested_scope_id && project.suggested_scope_id !== project.resolved_scope_id) {
|
|
14770
|
+
const suggestion = document.createElement("div");
|
|
14771
|
+
suggestion.className = "settings-note project-suggestion-note";
|
|
14772
|
+
suggestion.textContent = project.suggestion_reason ? `Suggestion: ${project.suggestion_reason}` : `Suggestion: map this project to ${scopeLabel(project.suggested_scope_id)}.`;
|
|
14773
|
+
row.appendChild(suggestion);
|
|
14774
|
+
}
|
|
14775
|
+
const warnings = project.guardrail_warnings.filter((warning) => warning.severity === "warning");
|
|
14776
|
+
if (warnings.length > 0) {
|
|
14777
|
+
const warningBox = document.createElement("div");
|
|
14778
|
+
warningBox.className = "settings-note project-attention-note";
|
|
14779
|
+
warningBox.textContent = `Needs attention: ${warnings.map((warning) => warning.message).join(" ")}`;
|
|
14780
|
+
row.appendChild(warningBox);
|
|
14781
|
+
}
|
|
14782
|
+
if (project.statuses.length > 0) {
|
|
14783
|
+
const badges = document.createElement("div");
|
|
14784
|
+
badges.className = "project-inventory-badges";
|
|
14785
|
+
for (const status of project.statuses) {
|
|
14786
|
+
const badge = document.createElement("span");
|
|
14787
|
+
badge.className = `project-status-badge ${status}`;
|
|
14788
|
+
badge.textContent = formatStatus(status);
|
|
14789
|
+
badges.appendChild(badge);
|
|
14790
|
+
}
|
|
14791
|
+
row.appendChild(badges);
|
|
14792
|
+
}
|
|
14793
|
+
const detail = document.createElement("details");
|
|
14794
|
+
detail.className = "project-inventory-details";
|
|
14795
|
+
detail.open = openProjectDetails.has(project.workspace_identity);
|
|
14796
|
+
detail.addEventListener("toggle", () => {
|
|
14797
|
+
if (detail.open) openProjectDetails.add(project.workspace_identity);
|
|
14798
|
+
else openProjectDetails.delete(project.workspace_identity);
|
|
14799
|
+
});
|
|
14800
|
+
const summary = document.createElement("summary");
|
|
14801
|
+
summary.textContent = "Identity and mapping details";
|
|
14802
|
+
detail.appendChild(summary);
|
|
14803
|
+
const list = document.createElement("dl");
|
|
14804
|
+
list.className = "project-detail-grid";
|
|
14805
|
+
const fields = [
|
|
14806
|
+
["Workspace identity", project.workspace_identity],
|
|
14807
|
+
["Project", project.project],
|
|
14808
|
+
["CWD", project.cwd],
|
|
14809
|
+
["Git remote", project.git_remote],
|
|
14810
|
+
["Git branch", project.git_branch],
|
|
14811
|
+
["Suggested domain", project.suggested_scope_id],
|
|
14812
|
+
["Suggestion reason", project.suggestion_reason],
|
|
14813
|
+
["Sessions", project.session_count],
|
|
14814
|
+
["Memories", project.memory_count ?? "count unavailable"]
|
|
14815
|
+
];
|
|
14816
|
+
for (const [label, value] of fields) {
|
|
14817
|
+
const dt = document.createElement("dt");
|
|
14818
|
+
dt.textContent = label;
|
|
14819
|
+
const dd = document.createElement("dd");
|
|
14820
|
+
dd.textContent = value == null || value === "" ? "—" : String(value);
|
|
14821
|
+
list.append(dt, dd);
|
|
14822
|
+
}
|
|
14823
|
+
detail.appendChild(list);
|
|
14824
|
+
detail.appendChild(renderProjectActions(project));
|
|
14825
|
+
row.appendChild(detail);
|
|
14826
|
+
return row;
|
|
14827
|
+
}
|
|
14828
|
+
function clusterDomainLabel(projects) {
|
|
14829
|
+
const uniqueScopes = [...new Set(projects.map((project) => project.resolved_scope_id))];
|
|
14830
|
+
return uniqueScopes.length === 1 ? scopeLabel(uniqueScopes[0]) : "Mixed Sharing domains";
|
|
14831
|
+
}
|
|
14832
|
+
function renderProjectCluster(projects) {
|
|
14833
|
+
if (projects.length === 1) return renderProjectRow(projects[0]);
|
|
14834
|
+
const clusterKey = projectClusterKey(projects[0]);
|
|
14835
|
+
const row = document.createElement("article");
|
|
14836
|
+
row.className = "project-inventory-row project-inventory-cluster";
|
|
14837
|
+
const header = document.createElement("div");
|
|
14838
|
+
header.className = "project-inventory-row-header";
|
|
14839
|
+
const title = document.createElement("div");
|
|
14840
|
+
title.className = "project-inventory-title";
|
|
14841
|
+
title.textContent = projectClusterLabel(projects[0]);
|
|
14842
|
+
header.appendChild(title);
|
|
14843
|
+
const domain = document.createElement("div");
|
|
14844
|
+
domain.className = "project-inventory-domain";
|
|
14845
|
+
domain.textContent = clusterDomainLabel(projects);
|
|
14846
|
+
header.appendChild(domain);
|
|
14847
|
+
row.appendChild(header);
|
|
14848
|
+
const memoryCount = projects.reduce((total, project) => total + (project.memory_count ?? 0), 0);
|
|
14849
|
+
const sessionCount = projects.reduce((total, project) => total + project.session_count, 0);
|
|
14850
|
+
const meta = document.createElement("div");
|
|
14851
|
+
meta.className = "project-inventory-meta";
|
|
14852
|
+
meta.textContent = `${projects.length} identities · ${sessionCount.toLocaleString()} sessions · ${memoryCount.toLocaleString()} memories`;
|
|
14853
|
+
row.appendChild(meta);
|
|
14854
|
+
const actions = document.createElement("div");
|
|
14855
|
+
actions.className = "project-inventory-actions";
|
|
14856
|
+
const hasGuardrailWarnings = projects.some((project) => project.guardrail_warnings.length > 0);
|
|
14857
|
+
const suggestedScopes = new Set(projects.map((project) => project.suggested_scope_id).filter((scopeId) => Boolean(scopeId)));
|
|
14858
|
+
const resolvedScopes = new Set(projects.map((project) => project.resolved_scope_id));
|
|
14859
|
+
const select = document.createElement("select");
|
|
14860
|
+
select.className = "project-domain-select";
|
|
14861
|
+
select.setAttribute("aria-label", `Sharing domain for ${projectClusterLabel(projects[0])} group`);
|
|
14862
|
+
const placeholder = document.createElement("option");
|
|
14863
|
+
placeholder.value = "";
|
|
14864
|
+
placeholder.textContent = "Choose domain…";
|
|
14865
|
+
select.appendChild(placeholder);
|
|
14866
|
+
for (const scope of assignableScopes()) {
|
|
14867
|
+
const option = document.createElement("option");
|
|
14868
|
+
option.value = scope.scope_id;
|
|
14869
|
+
option.textContent = scope.label ? `${scope.label} · ${scope.scope_id}` : scope.scope_id;
|
|
14870
|
+
select.appendChild(option);
|
|
14871
|
+
}
|
|
14872
|
+
select.value = "";
|
|
14873
|
+
const save = document.createElement("button");
|
|
14874
|
+
save.className = "settings-button";
|
|
14875
|
+
save.type = "button";
|
|
14876
|
+
save.textContent = `Save domain for ${projects.length} identities`;
|
|
14877
|
+
save.disabled = true;
|
|
14878
|
+
save.addEventListener("click", () => void saveProjectClusterMapping(projects, select.value));
|
|
14879
|
+
select.addEventListener("change", () => {
|
|
14880
|
+
save.disabled = !select.value || hasGuardrailWarnings;
|
|
14881
|
+
});
|
|
14882
|
+
actions.append(select, save);
|
|
14883
|
+
if (suggestedScopes.size > 1 || resolvedScopes.size > 1 || hasGuardrailWarnings) {
|
|
14884
|
+
const note = document.createElement("div");
|
|
14885
|
+
note.className = "settings-note project-attention-note";
|
|
14886
|
+
note.textContent = hasGuardrailWarnings ? "One or more identities in this group need individual review before bulk assignment." : "This group has mixed suggestions or current domains. Choose a domain explicitly before bulk assignment.";
|
|
14887
|
+
actions.appendChild(note);
|
|
14888
|
+
}
|
|
14889
|
+
row.appendChild(actions);
|
|
14890
|
+
const details = document.createElement("details");
|
|
14891
|
+
details.className = "project-inventory-details";
|
|
14892
|
+
details.open = openProjectClusters.has(clusterKey);
|
|
14893
|
+
details.addEventListener("toggle", () => {
|
|
14894
|
+
if (details.open) openProjectClusters.add(clusterKey);
|
|
14895
|
+
else openProjectClusters.delete(clusterKey);
|
|
14896
|
+
});
|
|
14897
|
+
const summary = document.createElement("summary");
|
|
14898
|
+
summary.textContent = "Show identities in this project";
|
|
14899
|
+
details.appendChild(summary);
|
|
14900
|
+
for (const project of projects) details.appendChild(renderProjectRow(project));
|
|
14901
|
+
row.appendChild(details);
|
|
14902
|
+
return row;
|
|
14903
|
+
}
|
|
14904
|
+
function projectClusters(projects) {
|
|
14905
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
14906
|
+
for (const project of projects) {
|
|
14907
|
+
const key = projectClusterKey(project);
|
|
14908
|
+
byKey.set(key, [...byKey.get(key) ?? [], project]);
|
|
14909
|
+
}
|
|
14910
|
+
return [...byKey.values()];
|
|
14911
|
+
}
|
|
14912
|
+
function renderEmpty(message) {
|
|
14913
|
+
const list = el("projectsInventoryList");
|
|
14914
|
+
if (!list) return;
|
|
14915
|
+
list.textContent = "";
|
|
14916
|
+
const empty = document.createElement("div");
|
|
14917
|
+
empty.className = "settings-note";
|
|
14918
|
+
empty.textContent = message;
|
|
14919
|
+
list.appendChild(empty);
|
|
14920
|
+
}
|
|
14921
|
+
async function loadProjectsData() {
|
|
14922
|
+
const meta = el("projectsInventoryMeta");
|
|
14923
|
+
const list = el("projectsInventoryList");
|
|
14924
|
+
if (!meta || !list) return;
|
|
14925
|
+
meta.textContent = "Loading project inventory…";
|
|
14926
|
+
try {
|
|
14927
|
+
const [result, settings] = await Promise.all([loadProjectScopeInventory({
|
|
14928
|
+
limit: lastLimit,
|
|
14929
|
+
offset: currentOffset,
|
|
14930
|
+
q: el("projectsSearch")?.value.trim() || void 0,
|
|
14931
|
+
status: el("projectsStatusFilter")?.value || void 0
|
|
14932
|
+
}), loadSharingDomainSettings()]);
|
|
14933
|
+
scopes = settings.scopes;
|
|
14934
|
+
list.textContent = "";
|
|
14935
|
+
if (result.projects.length === 0) renderEmpty("No projects match those filters.");
|
|
14936
|
+
else for (const cluster of projectClusters(result.projects)) list.appendChild(renderProjectCluster(cluster));
|
|
14937
|
+
meta.textContent = result.total === 0 ? "0 project identities found" : `${result.total} project identit${result.total === 1 ? "y" : "ies"} found · showing ${result.offset + 1}-${Math.min(result.offset + result.projects.length, result.total)}`;
|
|
14938
|
+
const prev = el("projectsPrevPage");
|
|
14939
|
+
const next = el("projectsNextPage");
|
|
14940
|
+
if (prev) prev.disabled = result.offset === 0;
|
|
14941
|
+
if (next) next.disabled = !result.has_more;
|
|
14942
|
+
} catch (error) {
|
|
14943
|
+
meta.textContent = "Project inventory failed to load.";
|
|
14944
|
+
renderEmpty(error instanceof Error ? error.message : "Unable to load project inventory.");
|
|
14945
|
+
}
|
|
14946
|
+
}
|
|
14947
|
+
function initProjectsTab(refresh) {
|
|
14948
|
+
refreshProjects = refresh;
|
|
14949
|
+
const status = el("projectsStatusFilter");
|
|
14950
|
+
if (status && status.options.length === 0) for (const [value, label] of STATUS_OPTIONS) {
|
|
14951
|
+
const option = document.createElement("option");
|
|
14952
|
+
option.value = value;
|
|
14953
|
+
option.textContent = label;
|
|
14954
|
+
status.appendChild(option);
|
|
14955
|
+
}
|
|
14956
|
+
const requestRefresh = () => {
|
|
14957
|
+
currentOffset = 0;
|
|
14958
|
+
refreshProjects?.();
|
|
14959
|
+
};
|
|
14960
|
+
el("projectsSearch")?.addEventListener("input", requestRefresh);
|
|
14961
|
+
status?.addEventListener("change", requestRefresh);
|
|
14962
|
+
el("projectsPrevPage")?.addEventListener("click", () => {
|
|
14963
|
+
currentOffset = Math.max(0, currentOffset - lastLimit);
|
|
14964
|
+
refreshProjects?.();
|
|
14965
|
+
});
|
|
14966
|
+
el("projectsNextPage")?.addEventListener("click", () => {
|
|
14967
|
+
currentOffset += lastLimit;
|
|
14968
|
+
refreshProjects?.();
|
|
14969
|
+
});
|
|
14970
|
+
}
|
|
14971
|
+
//#endregion
|
|
14347
14972
|
//#region src/tabs/settings/data/parse.ts
|
|
14348
14973
|
function parseCommandArgv(raw, options) {
|
|
14349
14974
|
const text = raw.trim();
|
|
@@ -15964,7 +16589,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
15964
16589
|
return scopes.find((scope) => scope.scope_id === scopeId)?.label || scopeId;
|
|
15965
16590
|
}
|
|
15966
16591
|
function resetDrafts(settings) {
|
|
15967
|
-
return Object.fromEntries(settings.projects.map((project) => [project.workspace_identity, project.resolved_scope_id]));
|
|
16592
|
+
return Object.fromEntries(settings.projects.map((project) => [project.workspace_identity, project.suggested_scope_id ?? project.resolved_scope_id]));
|
|
15968
16593
|
}
|
|
15969
16594
|
function canAssignProject(project) {
|
|
15970
16595
|
return project.identity_source !== "unmapped";
|
|
@@ -15980,6 +16605,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
15980
16605
|
function visibleProjectWarnings(project) {
|
|
15981
16606
|
return (project.guardrail_warnings ?? []).filter((warning) => warning.severity === "warning" || warning.code === "unknown_project_local_only");
|
|
15982
16607
|
}
|
|
16608
|
+
function isUsingSuggestion(project, currentValue) {
|
|
16609
|
+
return Boolean(project.suggested_scope_id && currentValue === project.suggested_scope_id && project.suggested_scope_id !== project.resolved_scope_id);
|
|
16610
|
+
}
|
|
15983
16611
|
function settingsMappingWarnings(settings) {
|
|
15984
16612
|
return settings.mappings.flatMap((mapping) => mapping.guardrail_warnings ?? []);
|
|
15985
16613
|
}
|
|
@@ -16015,7 +16643,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
16015
16643
|
y$2(() => {
|
|
16016
16644
|
reload();
|
|
16017
16645
|
}, []);
|
|
16018
|
-
const scopeOptions = T$1(() => (settings?.scopes ?? []).map((scope) => ({
|
|
16646
|
+
const scopeOptions = T$1(() => (settings?.scopes ?? []).filter((scope) => scope.scope_id !== "legacy-shared-review").map((scope) => ({
|
|
16019
16647
|
value: scope.scope_id,
|
|
16020
16648
|
label: domainLabel(scope)
|
|
16021
16649
|
})), [settings?.scopes]);
|
|
@@ -16070,20 +16698,32 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
16070
16698
|
setSavingKey(null);
|
|
16071
16699
|
}
|
|
16072
16700
|
};
|
|
16701
|
+
const openProjects = () => {
|
|
16702
|
+
window.location.hash = "projects";
|
|
16703
|
+
};
|
|
16073
16704
|
return /* @__PURE__ */ u("div", {
|
|
16074
16705
|
className: "settings-group",
|
|
16075
16706
|
children: [
|
|
16076
16707
|
/* @__PURE__ */ u("h3", {
|
|
16077
16708
|
className: "settings-group-title",
|
|
16078
|
-
children: "Sharing
|
|
16709
|
+
children: "Advanced Sharing-domain mappings"
|
|
16079
16710
|
}),
|
|
16080
16711
|
/* @__PURE__ */ u("div", {
|
|
16081
16712
|
className: "small",
|
|
16082
|
-
children: "
|
|
16713
|
+
children: "Use Projects for day-to-day project/domain review. This fallback panel exposes the same mapping controls for troubleshooting and recovery."
|
|
16083
16714
|
}),
|
|
16084
16715
|
/* @__PURE__ */ u("div", {
|
|
16085
16716
|
className: "small",
|
|
16086
|
-
children: "
|
|
16717
|
+
children: "Saving a mapping changes local scope resolution for future writes; it does not grant any peer or coordinator member access by itself."
|
|
16718
|
+
}),
|
|
16719
|
+
/* @__PURE__ */ u("div", {
|
|
16720
|
+
className: "section-actions",
|
|
16721
|
+
children: /* @__PURE__ */ u("button", {
|
|
16722
|
+
className: "settings-button",
|
|
16723
|
+
onClick: openProjects,
|
|
16724
|
+
type: "button",
|
|
16725
|
+
children: "Open Projects review"
|
|
16726
|
+
})
|
|
16087
16727
|
}),
|
|
16088
16728
|
settings ? /* @__PURE__ */ u(GuardrailMessages, {
|
|
16089
16729
|
label: "Sharing domain mapping warnings",
|
|
@@ -16109,6 +16749,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
16109
16749
|
const unchanged = currentValue === project.resolved_scope_id;
|
|
16110
16750
|
const assignable = canAssignProject(project);
|
|
16111
16751
|
const currentScopeName = scopeName(settings.scopes, project.resolved_scope_id);
|
|
16752
|
+
const usingSuggestion = isUsingSuggestion(project, currentValue);
|
|
16753
|
+
const suggestedScopeName = project.suggested_scope_id ? scopeName(settings.scopes, project.suggested_scope_id) : null;
|
|
16112
16754
|
const pendingConfirmation = confirmation?.workspaceIdentity === project.workspace_identity ? confirmation : null;
|
|
16113
16755
|
const canRemoveProjectMapping = assignable && project.mapping_id != null && project.resolution_reason === "exact_mapping";
|
|
16114
16756
|
return /* @__PURE__ */ u("div", {
|
|
@@ -16150,6 +16792,15 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
16150
16792
|
resolutionLabel(project.resolution_reason)
|
|
16151
16793
|
]
|
|
16152
16794
|
}),
|
|
16795
|
+
project.suggested_scope_id && project.suggestion_reason ? /* @__PURE__ */ u("div", {
|
|
16796
|
+
className: "settings-note",
|
|
16797
|
+
role: "status",
|
|
16798
|
+
children: [
|
|
16799
|
+
/* @__PURE__ */ u("strong", { children: ["Suggested mapping: ", suggestedScopeName] }),
|
|
16800
|
+
/* @__PURE__ */ u("div", { children: project.suggestion_reason }),
|
|
16801
|
+
/* @__PURE__ */ u("div", { children: "Folders, paths, and git remotes are hints only. Saving this mapping requires your confirmation and does not grant any peer access." })
|
|
16802
|
+
]
|
|
16803
|
+
}) : null,
|
|
16153
16804
|
/* @__PURE__ */ u(GuardrailMessages, { warnings: visibleProjectWarnings(project) }),
|
|
16154
16805
|
!assignable ? /* @__PURE__ */ u("div", {
|
|
16155
16806
|
className: "settings-note",
|
|
@@ -16186,7 +16837,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
16186
16837
|
disabled: !assignable || saving || unchanged,
|
|
16187
16838
|
onClick: () => void saveProject(project),
|
|
16188
16839
|
type: "button",
|
|
16189
|
-
children: saving ? "Saving…" : "Save Sharing domain"
|
|
16840
|
+
children: saving ? "Saving…" : usingSuggestion ? "Confirm suggested mapping" : "Save Sharing domain"
|
|
16190
16841
|
}), /* @__PURE__ */ u("button", {
|
|
16191
16842
|
className: "settings-button",
|
|
16192
16843
|
disabled: saving || !canRemoveProjectMapping,
|
|
@@ -17346,6 +17997,90 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
17346
17997
|
emptyMessage: "No Sharing-domain grants exist for this device yet. Project filters below cannot send data by themselves."
|
|
17347
17998
|
};
|
|
17348
17999
|
}
|
|
18000
|
+
var PERSONAL_SCOPE_WORDS = [
|
|
18001
|
+
"personal",
|
|
18002
|
+
"private",
|
|
18003
|
+
"home",
|
|
18004
|
+
"mine",
|
|
18005
|
+
"me"
|
|
18006
|
+
];
|
|
18007
|
+
var OSS_SCOPE_WORDS = [
|
|
18008
|
+
"oss",
|
|
18009
|
+
"open source",
|
|
18010
|
+
"opensource",
|
|
18011
|
+
"community",
|
|
18012
|
+
"public"
|
|
18013
|
+
];
|
|
18014
|
+
var WORK_LIKE_SCOPE_WORDS = ["work", "client"];
|
|
18015
|
+
var LOCAL_SCOPE_WORDS = [
|
|
18016
|
+
"local",
|
|
18017
|
+
"legacy",
|
|
18018
|
+
"review"
|
|
18019
|
+
];
|
|
18020
|
+
function scopeSearchText(scope) {
|
|
18021
|
+
return [
|
|
18022
|
+
scope.scope_id,
|
|
18023
|
+
scope.label,
|
|
18024
|
+
scope.kind,
|
|
18025
|
+
scope.authority_type
|
|
18026
|
+
].map((value) => cleanText(value) ?? "").join(" ").toLowerCase();
|
|
18027
|
+
}
|
|
18028
|
+
function scopeIdentitySearchText(scope) {
|
|
18029
|
+
return [
|
|
18030
|
+
scope.scope_id,
|
|
18031
|
+
scope.label,
|
|
18032
|
+
scope.kind
|
|
18033
|
+
].map((value) => cleanText(value) ?? "").join(" ").toLowerCase();
|
|
18034
|
+
}
|
|
18035
|
+
function escapeRegex(value) {
|
|
18036
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18037
|
+
}
|
|
18038
|
+
function hasScopeWord(scope, words) {
|
|
18039
|
+
const text = scopeSearchText(scope);
|
|
18040
|
+
return words.some((word) => {
|
|
18041
|
+
const escaped = escapeRegex(word.toLowerCase());
|
|
18042
|
+
return new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`).test(text);
|
|
18043
|
+
});
|
|
18044
|
+
}
|
|
18045
|
+
function isPersonalOrOssScope(scope) {
|
|
18046
|
+
return hasScopeWord(scope, PERSONAL_SCOPE_WORDS) || hasScopeWord(scope, OSS_SCOPE_WORDS);
|
|
18047
|
+
}
|
|
18048
|
+
function isLocalOrLegacyScope(scope) {
|
|
18049
|
+
const text = scopeIdentitySearchText(scope);
|
|
18050
|
+
return LOCAL_SCOPE_WORDS.some((word) => {
|
|
18051
|
+
const escaped = escapeRegex(word.toLowerCase());
|
|
18052
|
+
return new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`).test(text);
|
|
18053
|
+
});
|
|
18054
|
+
}
|
|
18055
|
+
function isWorkLikeScope(scope) {
|
|
18056
|
+
return !isPersonalOrOssScope(scope) && (!isLocalOrLegacyScope(scope) || hasScopeWord(scope, WORK_LIKE_SCOPE_WORDS));
|
|
18057
|
+
}
|
|
18058
|
+
function derivePeerGrantRoleMismatchView(peer) {
|
|
18059
|
+
const scopes = Array.isArray(peer.authorized_scopes) ? peer.authorized_scopes : [];
|
|
18060
|
+
if (!Boolean(cleanText(peer.discovered_via_group_id) || cleanText(peer.discovered_via_coordinator_id)) || scopes.length === 0) return {
|
|
18061
|
+
badgeLabel: null,
|
|
18062
|
+
detail: "",
|
|
18063
|
+
isVisible: false,
|
|
18064
|
+
message: "",
|
|
18065
|
+
title: ""
|
|
18066
|
+
};
|
|
18067
|
+
const hasPersonalOrOssGrant = scopes.some(isPersonalOrOssScope);
|
|
18068
|
+
const hasWorkLikeGrant = scopes.some(isWorkLikeScope);
|
|
18069
|
+
if (!hasPersonalOrOssGrant || hasWorkLikeGrant) return {
|
|
18070
|
+
badgeLabel: null,
|
|
18071
|
+
detail: "",
|
|
18072
|
+
isVisible: false,
|
|
18073
|
+
message: "",
|
|
18074
|
+
title: ""
|
|
18075
|
+
};
|
|
18076
|
+
return {
|
|
18077
|
+
badgeLabel: "Review domain fit",
|
|
18078
|
+
detail: "Coordinator/group context helps find the device, and project filters only narrow already-authorized domains. Neither one grants a missing work/client domain.",
|
|
18079
|
+
isVisible: true,
|
|
18080
|
+
message: "This coordinator-discovered device has personal or OSS Sharing-domain grants, but no separate work/client-like domain grant. If this device is meant for work/client sync, add that Sharing domain explicitly before treating sync as validated.",
|
|
18081
|
+
title: "Check this device's Sharing-domain role"
|
|
18082
|
+
};
|
|
18083
|
+
}
|
|
17349
18084
|
function derivePeerProjectNarrowingView(scope) {
|
|
17350
18085
|
const effectiveInclude = cleanList(scope?.effective_include);
|
|
17351
18086
|
const effectiveExclude = cleanList(scope?.effective_exclude);
|
|
@@ -17714,23 +18449,23 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
17714
18449
|
let query = "";
|
|
17715
18450
|
let activeIndex = 0;
|
|
17716
18451
|
let popoverOpen = false;
|
|
17717
|
-
const container = el("div", "project-scope-picker");
|
|
17718
|
-
const selectedList = el("ul", "project-scope-picker-selected");
|
|
17719
|
-
const trigger = el("button", "project-scope-picker-trigger settings-button");
|
|
18452
|
+
const container = el$1("div", "project-scope-picker");
|
|
18453
|
+
const selectedList = el$1("ul", "project-scope-picker-selected");
|
|
18454
|
+
const trigger = el$1("button", "project-scope-picker-trigger settings-button");
|
|
17720
18455
|
trigger.type = "button";
|
|
17721
|
-
const triggerLabel = el("span", null, placeholder || "Add project");
|
|
17722
|
-
const triggerPlus = el("span", null, "+");
|
|
18456
|
+
const triggerLabel = el$1("span", null, placeholder || "Add project");
|
|
18457
|
+
const triggerPlus = el$1("span", null, "+");
|
|
17723
18458
|
triggerPlus.setAttribute("aria-hidden", "true");
|
|
17724
18459
|
trigger.append(triggerPlus, triggerLabel);
|
|
17725
18460
|
trigger.setAttribute("aria-haspopup", "listbox");
|
|
17726
18461
|
trigger.setAttribute("aria-expanded", "false");
|
|
17727
|
-
const popover = el("div", "project-scope-picker-popover project-scope-picker-popover--inline");
|
|
18462
|
+
const popover = el$1("div", "project-scope-picker-popover project-scope-picker-popover--inline");
|
|
17728
18463
|
popover.hidden = true;
|
|
17729
|
-
const search = el("input", "project-scope-picker-search");
|
|
18464
|
+
const search = el$1("input", "project-scope-picker-search");
|
|
17730
18465
|
search.type = "text";
|
|
17731
18466
|
search.placeholder = "Search or create…";
|
|
17732
18467
|
search.setAttribute("aria-label", "Search projects");
|
|
17733
|
-
const results = el("div", "project-scope-picker-results");
|
|
18468
|
+
const results = el$1("div", "project-scope-picker-results");
|
|
17734
18469
|
results.setAttribute("role", "listbox");
|
|
17735
18470
|
popover.append(search, results);
|
|
17736
18471
|
const knownPool = () => {
|
|
@@ -17783,14 +18518,14 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
17783
18518
|
const renderSelected = () => {
|
|
17784
18519
|
selectedList.textContent = "";
|
|
17785
18520
|
if (!values.length) {
|
|
17786
|
-
const note = el("li", "project-scope-picker-selected-empty", emptyLabel);
|
|
18521
|
+
const note = el$1("li", "project-scope-picker-selected-empty", emptyLabel);
|
|
17787
18522
|
selectedList.appendChild(note);
|
|
17788
18523
|
return;
|
|
17789
18524
|
}
|
|
17790
18525
|
values.forEach((value) => {
|
|
17791
|
-
const chip = el("li", "project-scope-picker-selected-chip");
|
|
17792
|
-
const label = el("span", null, value);
|
|
17793
|
-
const remove = el("button", "project-scope-picker-selected-remove", "×");
|
|
18526
|
+
const chip = el$1("li", "project-scope-picker-selected-chip");
|
|
18527
|
+
const label = el$1("span", null, value);
|
|
18528
|
+
const remove = el$1("button", "project-scope-picker-selected-remove", "×");
|
|
17794
18529
|
remove.type = "button";
|
|
17795
18530
|
remove.setAttribute("aria-label", `Remove ${value}`);
|
|
17796
18531
|
remove.addEventListener("click", () => {
|
|
@@ -17809,19 +18544,19 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
17809
18544
|
const rowCount = rows.length + (withCreate ? 1 : 0);
|
|
17810
18545
|
if (activeIndex >= rowCount) activeIndex = Math.max(0, rowCount - 1);
|
|
17811
18546
|
if (!rows.length && !withCreate) {
|
|
17812
|
-
const empty = el("div", "project-scope-picker-empty-row", "No projects yet. Type a name to create one.");
|
|
18547
|
+
const empty = el$1("div", "project-scope-picker-empty-row", "No projects yet. Type a name to create one.");
|
|
17813
18548
|
results.appendChild(empty);
|
|
17814
18549
|
return;
|
|
17815
18550
|
}
|
|
17816
18551
|
rows.forEach((project, index) => {
|
|
17817
18552
|
const selected = values.includes(project);
|
|
17818
|
-
const row = el("button", index === activeIndex ? "project-scope-picker-row project-scope-picker-row--active" : "project-scope-picker-row");
|
|
18553
|
+
const row = el$1("button", index === activeIndex ? "project-scope-picker-row project-scope-picker-row--active" : "project-scope-picker-row");
|
|
17819
18554
|
row.type = "button";
|
|
17820
18555
|
row.setAttribute("role", "option");
|
|
17821
18556
|
row.setAttribute("aria-selected", selected ? "true" : "false");
|
|
17822
|
-
const check = el("span", "project-scope-picker-row-check", selected ? "✓" : "");
|
|
18557
|
+
const check = el$1("span", "project-scope-picker-row-check", selected ? "✓" : "");
|
|
17823
18558
|
check.setAttribute("aria-hidden", "true");
|
|
17824
|
-
const labelEl = el("span", "project-scope-picker-row-label", project);
|
|
18559
|
+
const labelEl = el$1("span", "project-scope-picker-row-label", project);
|
|
17825
18560
|
row.append(check, labelEl);
|
|
17826
18561
|
row.addEventListener("click", () => {
|
|
17827
18562
|
activeIndex = index;
|
|
@@ -17835,12 +18570,12 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
17835
18570
|
});
|
|
17836
18571
|
if (withCreate) {
|
|
17837
18572
|
const index = rows.length;
|
|
17838
|
-
const row = el("button", index === activeIndex ? "project-scope-picker-row project-scope-picker-row--create project-scope-picker-row--active" : "project-scope-picker-row project-scope-picker-row--create");
|
|
18573
|
+
const row = el$1("button", index === activeIndex ? "project-scope-picker-row project-scope-picker-row--create project-scope-picker-row--active" : "project-scope-picker-row project-scope-picker-row--create");
|
|
17839
18574
|
row.type = "button";
|
|
17840
18575
|
row.setAttribute("role", "option");
|
|
17841
|
-
const check = el("span", "project-scope-picker-row-check", "+");
|
|
18576
|
+
const check = el$1("span", "project-scope-picker-row-check", "+");
|
|
17842
18577
|
check.setAttribute("aria-hidden", "true");
|
|
17843
|
-
const labelEl = el("span", "project-scope-picker-row-label", `Create "${query.trim()}"`);
|
|
18578
|
+
const labelEl = el$1("span", "project-scope-picker-row-label", `Create "${query.trim()}"`);
|
|
17844
18579
|
row.append(check, labelEl);
|
|
17845
18580
|
row.addEventListener("click", () => createFromQuery());
|
|
17846
18581
|
row.addEventListener("mouseenter", () => {
|
|
@@ -17920,11 +18655,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
17920
18655
|
}
|
|
17921
18656
|
container.hidden = false;
|
|
17922
18657
|
actions.slice(0, 2).forEach((item) => {
|
|
17923
|
-
const row = el("div", "sync-action");
|
|
17924
|
-
const textWrap = el("div", "sync-action-text");
|
|
18658
|
+
const row = el$1("div", "sync-action");
|
|
18659
|
+
const textWrap = el$1("div", "sync-action-text");
|
|
17925
18660
|
textWrap.textContent = item.label;
|
|
17926
|
-
textWrap.appendChild(el("span", "sync-action-command", item.command));
|
|
17927
|
-
const btn = el("button", "settings-button sync-action-copy", "Copy");
|
|
18661
|
+
textWrap.appendChild(el$1("span", "sync-action-command", item.command));
|
|
18662
|
+
const btn = el$1("button", "settings-button sync-action-copy", "Copy");
|
|
17928
18663
|
btn.addEventListener("click", () => copyToClipboard(item.command, btn));
|
|
17929
18664
|
row.append(textWrap, btn);
|
|
17930
18665
|
container.appendChild(row);
|
|
@@ -18645,6 +19380,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
18645
19380
|
const directionHint = DIRECTION_GLYPH[derivePeerDirection(peer)];
|
|
18646
19381
|
const peerStatus = peer.status || {};
|
|
18647
19382
|
const authorizedDomains = derivePeerAuthorizedDomainsView(peer);
|
|
19383
|
+
const grantRoleMismatch = derivePeerGrantRoleMismatchView(peer);
|
|
18648
19384
|
const scopeRejections = derivePeerScopeRejectionsView(peer);
|
|
18649
19385
|
const scope = peer.project_scope || {};
|
|
18650
19386
|
const projectNarrowing = derivePeerProjectNarrowingView(scope);
|
|
@@ -18900,6 +19636,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
18900
19636
|
title: "Inbound ops rejected by the sharing-domain scope gate. Expand for reason codes.",
|
|
18901
19637
|
children: scopeRejections.badgeLabel
|
|
18902
19638
|
}) : null,
|
|
19639
|
+
grantRoleMismatch.badgeLabel ? /* @__PURE__ */ u("span", {
|
|
19640
|
+
className: "badge badge-offline",
|
|
19641
|
+
title: "Review whether explicit Sharing-domain grants match this device's intended role.",
|
|
19642
|
+
children: grantRoleMismatch.badgeLabel
|
|
19643
|
+
}) : null,
|
|
18903
19644
|
peer.discovered_via_group_id ? /* @__PURE__ */ u("span", {
|
|
18904
19645
|
className: "badge actor-badge",
|
|
18905
19646
|
title: `Discovered through coordinator group ${peer.discovered_via_group_id}`,
|
|
@@ -19008,6 +19749,15 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
19008
19749
|
children: domain.label
|
|
19009
19750
|
}, domain.scopeId))
|
|
19010
19751
|
}) : null,
|
|
19752
|
+
grantRoleMismatch.isVisible ? /* @__PURE__ */ u("div", {
|
|
19753
|
+
className: "settings-note",
|
|
19754
|
+
role: "status",
|
|
19755
|
+
children: [
|
|
19756
|
+
/* @__PURE__ */ u("strong", { children: grantRoleMismatch.title }),
|
|
19757
|
+
/* @__PURE__ */ u("div", { children: grantRoleMismatch.message }),
|
|
19758
|
+
/* @__PURE__ */ u("div", { children: grantRoleMismatch.detail })
|
|
19759
|
+
]
|
|
19760
|
+
}) : null,
|
|
19011
19761
|
/* @__PURE__ */ u("div", {
|
|
19012
19762
|
className: "peer-meta",
|
|
19013
19763
|
children: projectNarrowing.note
|
|
@@ -20497,6 +21247,32 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20497
21247
|
}
|
|
20498
21248
|
//#endregion
|
|
20499
21249
|
//#region src/tabs/sync/components/sync-sharing-review.tsx
|
|
21250
|
+
function formatIdentitySource(source) {
|
|
21251
|
+
switch (source) {
|
|
21252
|
+
case "cwd": return "Matched by folder";
|
|
21253
|
+
case "git_remote": return "Matched by git remote";
|
|
21254
|
+
case "workspace_id": return "Matched by workspace";
|
|
21255
|
+
case "project": return "Matched by project name";
|
|
21256
|
+
case "unmapped": return "Missing project identity";
|
|
21257
|
+
default: return `Matched by ${source.replace(/_/g, " ")}`;
|
|
21258
|
+
}
|
|
21259
|
+
}
|
|
21260
|
+
function needsProjectCleanup(group) {
|
|
21261
|
+
const label = group.displayProject.toLowerCase();
|
|
21262
|
+
return label.includes("fatal:") || label.includes("not a git repository") || label === "unknown project" || group.identitySource === "unmapped";
|
|
21263
|
+
}
|
|
21264
|
+
function canReassignLegacyGroup(group) {
|
|
21265
|
+
return (group.reassignableMemoryCount ?? group.memoryCount) > 0;
|
|
21266
|
+
}
|
|
21267
|
+
function groupSearchText(group) {
|
|
21268
|
+
return [
|
|
21269
|
+
group.displayProject,
|
|
21270
|
+
group.identitySource,
|
|
21271
|
+
group.suggestedScopeId ?? "",
|
|
21272
|
+
group.suggestionReason ?? "",
|
|
21273
|
+
group.workspaceIdentity
|
|
21274
|
+
].join(" ").toLowerCase();
|
|
21275
|
+
}
|
|
20500
21276
|
function SharingReviewRow({ item, onReview }) {
|
|
20501
21277
|
return /* @__PURE__ */ u("div", {
|
|
20502
21278
|
className: "actor-row",
|
|
@@ -20530,31 +21306,354 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20530
21306
|
})]
|
|
20531
21307
|
});
|
|
20532
21308
|
}
|
|
20533
|
-
function LegacySharedReviewRow({ item }) {
|
|
21309
|
+
function LegacySharedReviewRow({ item, onReassign, onReview }) {
|
|
21310
|
+
const groups = item.groups ?? [];
|
|
21311
|
+
const targetScopes = item.targetScopes ?? [];
|
|
21312
|
+
const initialSelections = Object.fromEntries(groups.map((group) => [group.workspaceIdentity, group.suggestedScopeId ?? ""]));
|
|
21313
|
+
const [pending, setPending] = d$2({});
|
|
21314
|
+
const [errors, setErrors] = d$2({});
|
|
21315
|
+
const [selectedScopes, setSelectedScopes] = d$2(initialSelections);
|
|
21316
|
+
const [selectedGroups, setSelectedGroups] = d$2({});
|
|
21317
|
+
const [filter, setFilter] = d$2("all");
|
|
21318
|
+
const [query, setQuery] = d$2("");
|
|
21319
|
+
const [busyIdentity, setBusyIdentity] = d$2(null);
|
|
21320
|
+
const shownGroupCount = groups.length;
|
|
21321
|
+
const groupCount = item.totalGroupCount ?? shownGroupCount;
|
|
21322
|
+
const cleanupCount = groups.filter(needsProjectCleanup).length;
|
|
21323
|
+
const suggestedCount = groups.filter((group) => group.suggestedScopeId && !needsProjectCleanup(group) && canReassignLegacyGroup(group)).length;
|
|
21324
|
+
const selectedReassignableCount = groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group)).length;
|
|
21325
|
+
const selectedCount = groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group) && Boolean(selectedScopes[group.workspaceIdentity])).length;
|
|
21326
|
+
const visibleGroups = groups.filter((group) => {
|
|
21327
|
+
const cleanup = needsProjectCleanup(group);
|
|
21328
|
+
if (filter === "suggested" && (!group.suggestedScopeId || cleanup)) return false;
|
|
21329
|
+
if (filter === "cleanup" && !cleanup) return false;
|
|
21330
|
+
if (filter === "no-suggestion" && (group.suggestedScopeId || cleanup)) return false;
|
|
21331
|
+
if (filter === "selected" && !selectedGroups[group.workspaceIdentity]) return false;
|
|
21332
|
+
return !query.trim() || groupSearchText(group).includes(query.trim().toLowerCase());
|
|
21333
|
+
});
|
|
21334
|
+
async function applyGroup(group) {
|
|
21335
|
+
const selectedScopeId = selectedScopes[group.workspaceIdentity] || group.suggestedScopeId || "";
|
|
21336
|
+
if (!selectedScopeId || !onReassign || busyIdentity) return;
|
|
21337
|
+
setBusyIdentity(group.workspaceIdentity);
|
|
21338
|
+
setErrors((current) => {
|
|
21339
|
+
const next = { ...current };
|
|
21340
|
+
delete next[group.workspaceIdentity];
|
|
21341
|
+
return next;
|
|
21342
|
+
});
|
|
21343
|
+
try {
|
|
21344
|
+
const preview = await onReassign(group, selectedScopeId, Boolean(pending[group.workspaceIdentity]), pending[group.workspaceIdentity]?.confirmation_token);
|
|
21345
|
+
if (preview) setPending((current) => ({
|
|
21346
|
+
...current,
|
|
21347
|
+
[group.workspaceIdentity]: preview
|
|
21348
|
+
}));
|
|
21349
|
+
else setPending((current) => {
|
|
21350
|
+
const next = { ...current };
|
|
21351
|
+
delete next[group.workspaceIdentity];
|
|
21352
|
+
return next;
|
|
21353
|
+
});
|
|
21354
|
+
} catch (error) {
|
|
21355
|
+
setErrors((current) => ({
|
|
21356
|
+
...current,
|
|
21357
|
+
[group.workspaceIdentity]: error instanceof Error ? error.message : "Unable to reassign legacy review memories."
|
|
21358
|
+
}));
|
|
21359
|
+
} finally {
|
|
21360
|
+
setBusyIdentity(null);
|
|
21361
|
+
}
|
|
21362
|
+
}
|
|
21363
|
+
function updateSelectedScope(group, scopeId) {
|
|
21364
|
+
setSelectedScopes((current) => ({
|
|
21365
|
+
...current,
|
|
21366
|
+
[group.workspaceIdentity]: scopeId
|
|
21367
|
+
}));
|
|
21368
|
+
setPending((current) => {
|
|
21369
|
+
const next = { ...current };
|
|
21370
|
+
delete next[group.workspaceIdentity];
|
|
21371
|
+
return next;
|
|
21372
|
+
});
|
|
21373
|
+
setErrors((current) => {
|
|
21374
|
+
const next = { ...current };
|
|
21375
|
+
delete next[group.workspaceIdentity];
|
|
21376
|
+
return next;
|
|
21377
|
+
});
|
|
21378
|
+
}
|
|
21379
|
+
function toggleGroup(group, checked) {
|
|
21380
|
+
setSelectedGroups((current) => ({
|
|
21381
|
+
...current,
|
|
21382
|
+
[group.workspaceIdentity]: checked
|
|
21383
|
+
}));
|
|
21384
|
+
}
|
|
21385
|
+
function setVisibleSelected(checked) {
|
|
21386
|
+
setSelectedGroups((current) => ({
|
|
21387
|
+
...current,
|
|
21388
|
+
...Object.fromEntries(visibleGroups.filter(canReassignLegacyGroup).map((group) => [group.workspaceIdentity, checked]))
|
|
21389
|
+
}));
|
|
21390
|
+
}
|
|
21391
|
+
function selectSuggestedGroups() {
|
|
21392
|
+
setSelectedGroups((current) => ({
|
|
21393
|
+
...current,
|
|
21394
|
+
...Object.fromEntries(groups.filter((group) => group.suggestedScopeId && !needsProjectCleanup(group) && canReassignLegacyGroup(group)).map((group) => [group.workspaceIdentity, true]))
|
|
21395
|
+
}));
|
|
21396
|
+
}
|
|
21397
|
+
async function applySelectedGroups() {
|
|
21398
|
+
if (!onReassign || busyIdentity || selectedCount === 0) return;
|
|
21399
|
+
const selected = groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group) && Boolean(selectedScopes[group.workspaceIdentity]));
|
|
21400
|
+
if (selected.every((group) => pending[group.workspaceIdentity])) return;
|
|
21401
|
+
setBusyIdentity("bulk");
|
|
21402
|
+
try {
|
|
21403
|
+
for (const group of selected) {
|
|
21404
|
+
if (pending[group.workspaceIdentity]) continue;
|
|
21405
|
+
const selectedScopeId = selectedScopes[group.workspaceIdentity] || group.suggestedScopeId || "";
|
|
21406
|
+
if (!selectedScopeId) continue;
|
|
21407
|
+
try {
|
|
21408
|
+
const preview = await onReassign(group, selectedScopeId, false);
|
|
21409
|
+
setErrors((current) => {
|
|
21410
|
+
const next = { ...current };
|
|
21411
|
+
delete next[group.workspaceIdentity];
|
|
21412
|
+
return next;
|
|
21413
|
+
});
|
|
21414
|
+
if (preview) setPending((current) => ({
|
|
21415
|
+
...current,
|
|
21416
|
+
[group.workspaceIdentity]: preview
|
|
21417
|
+
}));
|
|
21418
|
+
} catch (error) {
|
|
21419
|
+
setErrors((current) => ({
|
|
21420
|
+
...current,
|
|
21421
|
+
[group.workspaceIdentity]: error instanceof Error ? error.message : "Unable to reassign legacy review memories."
|
|
21422
|
+
}));
|
|
21423
|
+
}
|
|
21424
|
+
}
|
|
21425
|
+
} finally {
|
|
21426
|
+
setBusyIdentity(null);
|
|
21427
|
+
}
|
|
21428
|
+
}
|
|
20534
21429
|
return /* @__PURE__ */ u("div", {
|
|
20535
|
-
className: "
|
|
20536
|
-
children:
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
21430
|
+
className: "legacy-review-card",
|
|
21431
|
+
children: [
|
|
21432
|
+
/* @__PURE__ */ u("div", {
|
|
21433
|
+
className: "legacy-review-header",
|
|
21434
|
+
children: [/* @__PURE__ */ u("div", { children: [
|
|
21435
|
+
/* @__PURE__ */ u("div", {
|
|
21436
|
+
className: "actor-title",
|
|
21437
|
+
children: [/* @__PURE__ */ u("strong", { children: "Legacy shared review" }), /* @__PURE__ */ u("span", {
|
|
21438
|
+
className: "badge badge-offline",
|
|
21439
|
+
children: "Needs project review"
|
|
21440
|
+
})]
|
|
21441
|
+
}),
|
|
21442
|
+
/* @__PURE__ */ u("div", {
|
|
21443
|
+
className: "peer-meta legacy-review-summary",
|
|
21444
|
+
children: [
|
|
21445
|
+
groupCount.toLocaleString(),
|
|
21446
|
+
" older",
|
|
21447
|
+
" ",
|
|
21448
|
+
groupCount === 1 ? "project needs" : "projects need",
|
|
21449
|
+
" a Sharing domain. They contain",
|
|
21450
|
+
" ",
|
|
21451
|
+
item.memoryCount.toLocaleString(),
|
|
21452
|
+
" older shared memories total; review the projects, not individual memories.",
|
|
21453
|
+
shownGroupCount < groupCount ? ` Showing ${shownGroupCount.toLocaleString()} projects; open Projects to review the rest.` : ""
|
|
21454
|
+
]
|
|
21455
|
+
}),
|
|
21456
|
+
/* @__PURE__ */ u("div", {
|
|
21457
|
+
className: "peer-meta legacy-review-warning",
|
|
21458
|
+
children: "Nothing moves automatically. Reassignment changes future sync authorization; it does not erase copies peers already received."
|
|
21459
|
+
})
|
|
21460
|
+
] }), /* @__PURE__ */ u("div", {
|
|
21461
|
+
className: "actor-actions",
|
|
21462
|
+
children: /* @__PURE__ */ u("button", {
|
|
21463
|
+
type: "button",
|
|
21464
|
+
className: "settings-button",
|
|
21465
|
+
onClick: onReview,
|
|
21466
|
+
children: "Manage all projects"
|
|
21467
|
+
})
|
|
20543
21468
|
})]
|
|
20544
|
-
}),
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
|
|
21469
|
+
}),
|
|
21470
|
+
/* @__PURE__ */ u("div", {
|
|
21471
|
+
className: "legacy-review-toolbar",
|
|
21472
|
+
children: [/* @__PURE__ */ u("div", {
|
|
21473
|
+
className: "legacy-review-filter-row",
|
|
21474
|
+
children: [
|
|
21475
|
+
["all", `Showing ${shownGroupCount.toLocaleString()}`],
|
|
21476
|
+
["suggested", `Suggested ${suggestedCount.toLocaleString()}`],
|
|
21477
|
+
["cleanup", `Needs cleanup ${cleanupCount.toLocaleString()}`],
|
|
21478
|
+
["no-suggestion", "No suggestion"],
|
|
21479
|
+
["selected", `Selected ${selectedCount.toLocaleString()}`]
|
|
21480
|
+
].map(([value, label]) => /* @__PURE__ */ u("button", {
|
|
21481
|
+
className: filter === value ? "settings-button legacy-filter active" : "settings-button legacy-filter",
|
|
21482
|
+
onClick: () => setFilter(value),
|
|
21483
|
+
type: "button",
|
|
21484
|
+
children: label
|
|
21485
|
+
}, value))
|
|
21486
|
+
}), /* @__PURE__ */ u("div", {
|
|
21487
|
+
className: "legacy-review-bulk-row",
|
|
21488
|
+
children: [
|
|
21489
|
+
/* @__PURE__ */ u("input", {
|
|
21490
|
+
"aria-label": "Search legacy project groups",
|
|
21491
|
+
className: "peer-scope-input legacy-review-search",
|
|
21492
|
+
onInput: (event) => setQuery(event.currentTarget.value),
|
|
21493
|
+
placeholder: "Search project, signal, or destination…",
|
|
21494
|
+
value: query
|
|
21495
|
+
}),
|
|
21496
|
+
/* @__PURE__ */ u("button", {
|
|
21497
|
+
className: "settings-button",
|
|
21498
|
+
onClick: () => setVisibleSelected(true),
|
|
21499
|
+
type: "button",
|
|
21500
|
+
children: "Select visible"
|
|
21501
|
+
}),
|
|
21502
|
+
/* @__PURE__ */ u("button", {
|
|
21503
|
+
className: "settings-button",
|
|
21504
|
+
onClick: selectSuggestedGroups,
|
|
21505
|
+
type: "button",
|
|
21506
|
+
children: "Select suggested"
|
|
21507
|
+
}),
|
|
21508
|
+
/* @__PURE__ */ u("button", {
|
|
21509
|
+
className: "settings-button",
|
|
21510
|
+
onClick: () => setVisibleSelected(false),
|
|
21511
|
+
type: "button",
|
|
21512
|
+
children: "Clear visible"
|
|
21513
|
+
}),
|
|
21514
|
+
onReassign && targetScopes.length > 0 ? /* @__PURE__ */ u("button", {
|
|
21515
|
+
className: "settings-save",
|
|
21516
|
+
disabled: busyIdentity != null || selectedCount === 0 || groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group) && Boolean(selectedScopes[group.workspaceIdentity])).every((group) => pending[group.workspaceIdentity]),
|
|
21517
|
+
onClick: () => void applySelectedGroups(),
|
|
21518
|
+
type: "button",
|
|
21519
|
+
children: busyIdentity === "bulk" ? "Previewing…" : `Preview ${selectedCount.toLocaleString()} selected`
|
|
21520
|
+
}) : onReassign && selectedReassignableCount > 0 ? /* @__PURE__ */ u("span", {
|
|
21521
|
+
className: "legacy-review-cleanup-note",
|
|
21522
|
+
children: "Add or join a Sharing domain before bulk reassignment."
|
|
21523
|
+
}) : null,
|
|
21524
|
+
selectedCount > 0 && groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group) && Boolean(selectedScopes[group.workspaceIdentity])).every((group) => pending[group.workspaceIdentity]) ? /* @__PURE__ */ u("span", {
|
|
21525
|
+
className: "legacy-review-cleanup-note",
|
|
21526
|
+
children: "Bulk actions only preview. Confirm each project individually to avoid partial reassignment."
|
|
21527
|
+
}) : null
|
|
21528
|
+
]
|
|
21529
|
+
})]
|
|
21530
|
+
}),
|
|
21531
|
+
groups.length > 0 ? /* @__PURE__ */ u("ul", {
|
|
21532
|
+
className: "legacy-review-groups",
|
|
21533
|
+
"aria-label": "Legacy shared review project groups",
|
|
21534
|
+
children: visibleGroups.map((group) => /* @__PURE__ */ u("li", {
|
|
21535
|
+
className: "legacy-review-group",
|
|
21536
|
+
children: [
|
|
21537
|
+
/* @__PURE__ */ u("div", {
|
|
21538
|
+
className: "legacy-review-group-main",
|
|
21539
|
+
children: [/* @__PURE__ */ u("div", {
|
|
21540
|
+
className: "legacy-review-title-wrap",
|
|
21541
|
+
children: [/* @__PURE__ */ u("input", {
|
|
21542
|
+
"aria-label": `Select ${group.displayProject}`,
|
|
21543
|
+
checked: Boolean(selectedGroups[group.workspaceIdentity]),
|
|
21544
|
+
className: "cm-checkbox",
|
|
21545
|
+
disabled: !canReassignLegacyGroup(group),
|
|
21546
|
+
onChange: (event) => toggleGroup(group, event.currentTarget.checked),
|
|
21547
|
+
type: "checkbox"
|
|
21548
|
+
}), /* @__PURE__ */ u("div", { children: [
|
|
21549
|
+
/* @__PURE__ */ u("div", {
|
|
21550
|
+
className: "legacy-review-group-title",
|
|
21551
|
+
children: group.displayProject
|
|
21552
|
+
}),
|
|
21553
|
+
/* @__PURE__ */ u("div", {
|
|
21554
|
+
className: "legacy-review-group-meta",
|
|
21555
|
+
children: [formatIdentitySource(group.identitySource), group.suggestedScopeId ? ` · suggested ${group.suggestedScopeId}` : ""]
|
|
21556
|
+
}),
|
|
21557
|
+
!canReassignLegacyGroup(group) ? /* @__PURE__ */ u("div", {
|
|
21558
|
+
className: "legacy-review-cleanup-note",
|
|
21559
|
+
children: "Peer-owned only. These older memories were received from another device, so this device cannot reassign them to a Sharing domain."
|
|
21560
|
+
}) : group.peerOwnedMemoryCount ? /* @__PURE__ */ u("div", {
|
|
21561
|
+
className: "legacy-review-cleanup-note",
|
|
21562
|
+
children: [
|
|
21563
|
+
(group.reassignableMemoryCount ?? group.memoryCount).toLocaleString(),
|
|
21564
|
+
" ",
|
|
21565
|
+
"local memor",
|
|
21566
|
+
(group.reassignableMemoryCount ?? group.memoryCount) === 1 ? "y" : "ies",
|
|
21567
|
+
" ",
|
|
21568
|
+
"can be reassigned; ",
|
|
21569
|
+
group.peerOwnedMemoryCount.toLocaleString(),
|
|
21570
|
+
" peer-owned memor",
|
|
21571
|
+
group.peerOwnedMemoryCount === 1 ? "y" : "ies",
|
|
21572
|
+
" will stay in review."
|
|
21573
|
+
]
|
|
21574
|
+
}) : null,
|
|
21575
|
+
needsProjectCleanup(group) ? /* @__PURE__ */ u("div", {
|
|
21576
|
+
className: "legacy-review-cleanup-note",
|
|
21577
|
+
children: "Unclear project identity. Inspect or correct the project before trusting a domain."
|
|
21578
|
+
}) : null
|
|
21579
|
+
] })]
|
|
21580
|
+
}), /* @__PURE__ */ u("span", {
|
|
21581
|
+
className: "legacy-review-count",
|
|
21582
|
+
children: [
|
|
21583
|
+
group.memoryCount.toLocaleString(),
|
|
21584
|
+
" memor",
|
|
21585
|
+
group.memoryCount === 1 ? "y" : "ies"
|
|
21586
|
+
]
|
|
21587
|
+
})]
|
|
21588
|
+
}),
|
|
21589
|
+
group.suggestionReason ? /* @__PURE__ */ u("div", {
|
|
21590
|
+
className: "legacy-review-reason",
|
|
21591
|
+
children: group.suggestionReason
|
|
21592
|
+
}) : null,
|
|
21593
|
+
errors[group.workspaceIdentity] ? /* @__PURE__ */ u("div", {
|
|
21594
|
+
className: "legacy-review-confirmation",
|
|
21595
|
+
role: "alert",
|
|
21596
|
+
children: errors[group.workspaceIdentity]
|
|
21597
|
+
}) : null,
|
|
21598
|
+
pending[group.workspaceIdentity] ? /* @__PURE__ */ u("div", {
|
|
21599
|
+
className: "legacy-review-confirmation",
|
|
21600
|
+
role: "alert",
|
|
21601
|
+
children: [
|
|
21602
|
+
pending[group.workspaceIdentity].warning,
|
|
21603
|
+
" This will reassign",
|
|
21604
|
+
" ",
|
|
21605
|
+
pending[group.workspaceIdentity].reassignable_memory_count.toLocaleString(),
|
|
21606
|
+
" of",
|
|
21607
|
+
" ",
|
|
21608
|
+
pending[group.workspaceIdentity].memory_count.toLocaleString(),
|
|
21609
|
+
" memories",
|
|
21610
|
+
pending[group.workspaceIdentity].skipped_memory_count ? `; ${pending[group.workspaceIdentity].skipped_memory_count.toLocaleString()} peer-owned copies will be left unchanged` : "",
|
|
21611
|
+
"."
|
|
21612
|
+
]
|
|
21613
|
+
}) : null,
|
|
21614
|
+
targetScopes.length > 0 && onReassign && canReassignLegacyGroup(group) ? /* @__PURE__ */ u("label", {
|
|
21615
|
+
className: "legacy-review-target",
|
|
21616
|
+
children: [/* @__PURE__ */ u("span", { children: "Destination Sharing domain" }), /* @__PURE__ */ u("select", {
|
|
21617
|
+
className: "peer-scope-input",
|
|
21618
|
+
disabled: busyIdentity != null,
|
|
21619
|
+
onChange: (event) => updateSelectedScope(group, event.currentTarget.value),
|
|
21620
|
+
value: selectedScopes[group.workspaceIdentity] || "",
|
|
21621
|
+
children: [/* @__PURE__ */ u("option", {
|
|
21622
|
+
value: "",
|
|
21623
|
+
children: "Choose domain…"
|
|
21624
|
+
}), targetScopes.map((scope) => /* @__PURE__ */ u("option", {
|
|
21625
|
+
value: scope.scopeId,
|
|
21626
|
+
children: [
|
|
21627
|
+
scope.label,
|
|
21628
|
+
" · ",
|
|
21629
|
+
scope.authorityType,
|
|
21630
|
+
scope.scopeId === group.suggestedScopeId ? " · suggested" : ""
|
|
21631
|
+
]
|
|
21632
|
+
}, scope.scopeId))]
|
|
21633
|
+
})]
|
|
21634
|
+
}) : null,
|
|
21635
|
+
targetScopes.length > 0 && onReassign && canReassignLegacyGroup(group) ? /* @__PURE__ */ u("div", {
|
|
21636
|
+
className: "legacy-review-actions",
|
|
21637
|
+
children: /* @__PURE__ */ u("button", {
|
|
21638
|
+
type: "button",
|
|
21639
|
+
className: "settings-button",
|
|
21640
|
+
disabled: busyIdentity != null || !selectedScopes[group.workspaceIdentity],
|
|
21641
|
+
onClick: () => void applyGroup(group),
|
|
21642
|
+
children: busyIdentity === group.workspaceIdentity ? pending[group.workspaceIdentity] ? "Reassigning…" : "Previewing…" : pending[group.workspaceIdentity] ? "I understand, reassign memories" : "Preview reassignment"
|
|
21643
|
+
})
|
|
21644
|
+
}) : null
|
|
21645
|
+
]
|
|
21646
|
+
}, group.workspaceIdentity))
|
|
21647
|
+
}) : null
|
|
21648
|
+
]
|
|
20554
21649
|
});
|
|
20555
21650
|
}
|
|
20556
|
-
function SyncSharingReview({ items, legacyReview, onReview }) {
|
|
20557
|
-
return /* @__PURE__ */ u(k$3, { children: [legacyReview ? /* @__PURE__ */ u(LegacySharedReviewRow, {
|
|
21651
|
+
function SyncSharingReview({ items, legacyReview, onLegacyReassign, onLegacyReview, onReview }) {
|
|
21652
|
+
return /* @__PURE__ */ u(k$3, { children: [legacyReview ? /* @__PURE__ */ u(LegacySharedReviewRow, {
|
|
21653
|
+
item: legacyReview,
|
|
21654
|
+
onReassign: onLegacyReassign,
|
|
21655
|
+
onReview: onLegacyReview ?? onReview
|
|
21656
|
+
}) : null, items.map((item) => /* @__PURE__ */ u(SharingReviewRow, {
|
|
20558
21657
|
item,
|
|
20559
21658
|
onReview
|
|
20560
21659
|
}, `${item.peerName}:${item.actorId}:${item.scopeLabel}`))] });
|
|
@@ -20566,6 +21665,28 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20566
21665
|
state.feedQuery = "";
|
|
20567
21666
|
window.location.hash = "feed";
|
|
20568
21667
|
}
|
|
21668
|
+
function openProjectsReview() {
|
|
21669
|
+
window.location.hash = "projects";
|
|
21670
|
+
}
|
|
21671
|
+
async function reassignLegacySharedReviewGroup(group, scopeId, confirmedOldCopies, confirmationToken) {
|
|
21672
|
+
try {
|
|
21673
|
+
const result = await reassignLegacySharedReviewGroup$1({
|
|
21674
|
+
...confirmationToken ? { confirmation_token: confirmationToken } : {},
|
|
21675
|
+
confirmed_old_copies: confirmedOldCopies,
|
|
21676
|
+
scope_id: scopeId,
|
|
21677
|
+
workspace_identity: group.workspaceIdentity
|
|
21678
|
+
});
|
|
21679
|
+
state.lastSyncLegacySharedReview = result.legacy_shared_review ?? null;
|
|
21680
|
+
showGlobalNotice(`Reassigned ${result.reassigned_memory_count.toLocaleString()} legacy review memor${result.reassigned_memory_count === 1 ? "y" : "ies"} to ${result.target_scope_label}.`);
|
|
21681
|
+
renderSyncSharingReview();
|
|
21682
|
+
return null;
|
|
21683
|
+
} catch (error) {
|
|
21684
|
+
if (error instanceof LegacySharedReviewConfirmationError) return error.preview;
|
|
21685
|
+
const message = error instanceof Error ? error.message : "Unable to reassign legacy review memories.";
|
|
21686
|
+
showGlobalNotice(message, "warning");
|
|
21687
|
+
throw new Error(message);
|
|
21688
|
+
}
|
|
21689
|
+
}
|
|
20569
21690
|
function renderSyncSharingReview() {
|
|
20570
21691
|
const panel = document.getElementById("syncSharingReview");
|
|
20571
21692
|
const meta = document.getElementById("syncSharingReviewMeta");
|
|
@@ -20576,8 +21697,31 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20576
21697
|
const legacyRaw = state.lastSyncLegacySharedReview;
|
|
20577
21698
|
const legacyCount = Math.max(0, Number(legacyRaw?.memory_count ?? 0));
|
|
20578
21699
|
const legacyReview = legacyRaw?.has_data ? {
|
|
21700
|
+
groups: Array.isArray(legacyRaw.groups) ? legacyRaw.groups.map((group) => {
|
|
21701
|
+
const raw = group;
|
|
21702
|
+
return {
|
|
21703
|
+
displayProject: String(raw.display_project || raw.project || "Unknown project"),
|
|
21704
|
+
identitySource: String(raw.identity_source || "unknown"),
|
|
21705
|
+
lastUpdatedAt: typeof raw.last_updated_at === "string" ? raw.last_updated_at : null,
|
|
21706
|
+
memoryCount: Number(raw.memory_count || 0),
|
|
21707
|
+
peerOwnedMemoryCount: Number(raw.peer_owned_memory_count || 0),
|
|
21708
|
+
reassignableMemoryCount: Number(raw.reassignable_memory_count || 0),
|
|
21709
|
+
suggestedScopeId: typeof raw.suggested_scope_id === "string" ? raw.suggested_scope_id : null,
|
|
21710
|
+
suggestionReason: typeof raw.suggestion_reason === "string" ? raw.suggestion_reason : null,
|
|
21711
|
+
workspaceIdentity: String(raw.workspace_identity || raw.display_project || "unknown")
|
|
21712
|
+
};
|
|
21713
|
+
}) : [],
|
|
20579
21714
|
memoryCount: legacyCount,
|
|
20580
|
-
scopeId: String(legacyRaw.scope_id || "legacy-shared-review")
|
|
21715
|
+
scopeId: String(legacyRaw.scope_id || "legacy-shared-review"),
|
|
21716
|
+
totalGroupCount: Number(legacyRaw.total_group_count || legacyRaw.groups?.length || 0),
|
|
21717
|
+
targetScopes: Array.isArray(legacyRaw.target_scopes) ? legacyRaw.target_scopes.map((scope) => {
|
|
21718
|
+
const raw = scope;
|
|
21719
|
+
return {
|
|
21720
|
+
authorityType: String(raw.authority_type || "unknown"),
|
|
21721
|
+
label: String(raw.label || raw.scope_id || "Unknown domain"),
|
|
21722
|
+
scopeId: String(raw.scope_id || "")
|
|
21723
|
+
};
|
|
21724
|
+
}).filter((scope) => scope.scopeId) : []
|
|
20581
21725
|
} : null;
|
|
20582
21726
|
if (!items.length && !legacyReview) {
|
|
20583
21727
|
clearSyncMount(list);
|
|
@@ -20598,6 +21742,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20598
21742
|
shareableCount: Number(item.shareable_count || 0)
|
|
20599
21743
|
})),
|
|
20600
21744
|
legacyReview,
|
|
21745
|
+
onLegacyReassign: reassignLegacySharedReviewGroup,
|
|
21746
|
+
onLegacyReview: openProjectsReview,
|
|
20601
21747
|
onReview: openFeedSharingReview
|
|
20602
21748
|
}));
|
|
20603
21749
|
}
|
|
@@ -20783,13 +21929,121 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20783
21929
|
try {
|
|
20784
21930
|
const runtime = await loadRuntimeInfo();
|
|
20785
21931
|
if (!runtime?.version) return;
|
|
20786
|
-
setRuntimeLabel(runtime.version, "
|
|
21932
|
+
setRuntimeLabel(runtime.version, "3400309");
|
|
20787
21933
|
} catch {}
|
|
20788
21934
|
}
|
|
20789
21935
|
var lastAnnouncedRefreshState = null;
|
|
20790
21936
|
var RECONNECT_POLL_MS = 1500;
|
|
21937
|
+
var LEGACY_UPGRADE_NOTICE_DISMISSED_KEY = "codemem-legacy-upgrade-notice-dismissed";
|
|
20791
21938
|
var reconnectTimer = null;
|
|
20792
21939
|
var reconnecting = false;
|
|
21940
|
+
var legacyUpgradeNoticeShown = false;
|
|
21941
|
+
var legacyUpgradeNoticePreviousFocus = null;
|
|
21942
|
+
function readNonNegativeCount(value, fallback = 0) {
|
|
21943
|
+
const count = Number(value);
|
|
21944
|
+
return Number.isFinite(count) ? Math.max(0, count) : fallback;
|
|
21945
|
+
}
|
|
21946
|
+
function readLegacyUpgradeReviewSummary(payload) {
|
|
21947
|
+
if (!payload || typeof payload !== "object") return null;
|
|
21948
|
+
const review = payload.legacy_shared_review ?? payload;
|
|
21949
|
+
if (!review || typeof review !== "object") return null;
|
|
21950
|
+
const raw = review;
|
|
21951
|
+
if (raw.has_data !== true) return null;
|
|
21952
|
+
const groups = Array.isArray(raw.groups) ? raw.groups : [];
|
|
21953
|
+
const groupCount = readNonNegativeCount(raw.total_group_count, groups.length);
|
|
21954
|
+
const memoryCount = readNonNegativeCount(raw.memory_count);
|
|
21955
|
+
if (groupCount <= 0 || memoryCount <= 0) return null;
|
|
21956
|
+
return {
|
|
21957
|
+
groupCount,
|
|
21958
|
+
memoryCount
|
|
21959
|
+
};
|
|
21960
|
+
}
|
|
21961
|
+
function isLegacyUpgradeNoticeDismissed() {
|
|
21962
|
+
try {
|
|
21963
|
+
return localStorage.getItem(LEGACY_UPGRADE_NOTICE_DISMISSED_KEY) === "1";
|
|
21964
|
+
} catch {
|
|
21965
|
+
return false;
|
|
21966
|
+
}
|
|
21967
|
+
}
|
|
21968
|
+
function dismissLegacyUpgradeNoticeIfRequested() {
|
|
21969
|
+
if (!document.getElementById("legacyUpgradeDontShow")?.checked) return;
|
|
21970
|
+
try {
|
|
21971
|
+
localStorage.setItem(LEGACY_UPGRADE_NOTICE_DISMISSED_KEY, "1");
|
|
21972
|
+
} catch {}
|
|
21973
|
+
}
|
|
21974
|
+
function legacyUpgradeFocusableElements() {
|
|
21975
|
+
const modal = $("legacyUpgradeModal");
|
|
21976
|
+
if (!modal || modal.hidden) return [];
|
|
21977
|
+
return Array.from(modal.querySelectorAll("a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])")).filter((element) => !element.hidden && element.offsetParent !== null);
|
|
21978
|
+
}
|
|
21979
|
+
function focusLegacyUpgradeModal() {
|
|
21980
|
+
const focusable = legacyUpgradeFocusableElements();
|
|
21981
|
+
const primary = $("legacyUpgradeReviewGroups");
|
|
21982
|
+
(primary && focusable.includes(primary) ? primary : focusable[0])?.focus();
|
|
21983
|
+
}
|
|
21984
|
+
function handleLegacyUpgradeModalKeydown(event) {
|
|
21985
|
+
const modal = $("legacyUpgradeModal");
|
|
21986
|
+
if (!modal || modal.hidden) return;
|
|
21987
|
+
if (event.key === "Escape") {
|
|
21988
|
+
event.preventDefault();
|
|
21989
|
+
dismissLegacyUpgradeNoticeIfRequested();
|
|
21990
|
+
setLegacyUpgradeNotice(false);
|
|
21991
|
+
return;
|
|
21992
|
+
}
|
|
21993
|
+
if (event.key !== "Tab") return;
|
|
21994
|
+
const focusable = legacyUpgradeFocusableElements();
|
|
21995
|
+
if (focusable.length === 0) return;
|
|
21996
|
+
const first = focusable[0];
|
|
21997
|
+
const last = focusable[focusable.length - 1];
|
|
21998
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
21999
|
+
event.preventDefault();
|
|
22000
|
+
last.focus();
|
|
22001
|
+
return;
|
|
22002
|
+
}
|
|
22003
|
+
if (!event.shiftKey && document.activeElement === last) {
|
|
22004
|
+
event.preventDefault();
|
|
22005
|
+
first.focus();
|
|
22006
|
+
}
|
|
22007
|
+
}
|
|
22008
|
+
function setLegacyUpgradeBackgroundInert(inert) {
|
|
22009
|
+
for (const element of document.querySelectorAll("header, .tab-bar, .tab-panel, #toastRoot, #viewerReconnectOverlay, #settingsDialogMount")) {
|
|
22010
|
+
if (element.id === "viewerReconnectOverlay" && element.hidden) continue;
|
|
22011
|
+
element.inert = inert;
|
|
22012
|
+
if (inert) element.setAttribute("aria-hidden", "true");
|
|
22013
|
+
else element.removeAttribute("aria-hidden");
|
|
22014
|
+
}
|
|
22015
|
+
}
|
|
22016
|
+
function setLegacyUpgradeNotice(open, summary) {
|
|
22017
|
+
const overlay = $("legacyUpgradeModalBackdrop");
|
|
22018
|
+
const modal = $("legacyUpgradeModal");
|
|
22019
|
+
const summaryEl = $("legacyUpgradeSummary");
|
|
22020
|
+
if (!overlay || !modal || !summaryEl) return;
|
|
22021
|
+
if (summary) summaryEl.textContent = `${summary.groupCount.toLocaleString()} older ${summary.groupCount === 1 ? "project needs" : "projects need"} a Sharing domain. They contain ${summary.memoryCount.toLocaleString()} older shared memories total; you will review the projects, not individual memories.`;
|
|
22022
|
+
overlay.hidden = !open;
|
|
22023
|
+
modal.hidden = !open;
|
|
22024
|
+
if (open) {
|
|
22025
|
+
legacyUpgradeNoticePreviousFocus = document.activeElement;
|
|
22026
|
+
document.addEventListener("keydown", handleLegacyUpgradeModalKeydown);
|
|
22027
|
+
focusLegacyUpgradeModal();
|
|
22028
|
+
setLegacyUpgradeBackgroundInert(true);
|
|
22029
|
+
return;
|
|
22030
|
+
}
|
|
22031
|
+
setLegacyUpgradeBackgroundInert(false);
|
|
22032
|
+
document.removeEventListener("keydown", handleLegacyUpgradeModalKeydown);
|
|
22033
|
+
if (legacyUpgradeNoticePreviousFocus && document.contains(legacyUpgradeNoticePreviousFocus)) legacyUpgradeNoticePreviousFocus.focus();
|
|
22034
|
+
legacyUpgradeNoticePreviousFocus = null;
|
|
22035
|
+
}
|
|
22036
|
+
function maybeShowLegacyUpgradeNotice(summary) {
|
|
22037
|
+
if (!summary || legacyUpgradeNoticeShown || isLegacyUpgradeNoticeDismissed()) return;
|
|
22038
|
+
legacyUpgradeNoticeShown = true;
|
|
22039
|
+
setLegacyUpgradeNotice(true, summary);
|
|
22040
|
+
}
|
|
22041
|
+
async function checkLegacyUpgradeNotice() {
|
|
22042
|
+
if (legacyUpgradeNoticeShown || isLegacyUpgradeNoticeDismissed()) return;
|
|
22043
|
+
try {
|
|
22044
|
+
maybeShowLegacyUpgradeNotice(readLegacyUpgradeReviewSummary(await loadSyncStatus(false, "", { includeJoinRequests: false })));
|
|
22045
|
+
} catch {}
|
|
22046
|
+
}
|
|
20793
22047
|
function setReconnectOverlay(open, detail) {
|
|
20794
22048
|
const overlay = $("viewerReconnectOverlay");
|
|
20795
22049
|
const detailEl = $("viewerReconnectDetail");
|
|
@@ -20980,10 +22234,12 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
20980
22234
|
state.lastCoordinatorAdminStatus = null;
|
|
20981
22235
|
}));
|
|
20982
22236
|
if (refreshTab === "feed") promises.push(loadFeedData());
|
|
22237
|
+
if (refreshTab === "projects") promises.push(loadProjectsData());
|
|
20983
22238
|
if (refreshTab === "sync" || refreshTab === "health") promises.push(loadSyncData());
|
|
20984
22239
|
if (refreshTab === "coordinator-admin") promises.push(loadCoordinatorAdminData());
|
|
20985
22240
|
if (state.syncPairingOpen) promises.push(loadPairingData());
|
|
20986
22241
|
await Promise.all(promises);
|
|
22242
|
+
maybeShowLegacyUpgradeNotice(readLegacyUpgradeReviewSummary(state.lastSyncLegacySharedReview));
|
|
20987
22243
|
const nextTab = resolveAccessibleTab(state.activeTab, state.lastCoordinatorAdminStatus);
|
|
20988
22244
|
if (nextTab !== state.activeTab) setActiveTab(nextTab);
|
|
20989
22245
|
renderTabs(state.activeTab);
|
|
@@ -21006,6 +22262,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
21006
22262
|
setTheme(getTheme());
|
|
21007
22263
|
initTabs();
|
|
21008
22264
|
initFeedTab();
|
|
22265
|
+
initProjectsTab(() => refresh());
|
|
21009
22266
|
initSyncTab(() => refresh());
|
|
21010
22267
|
initCoordinatorAdminTab();
|
|
21011
22268
|
initSettings(stopPolling, startPolling, () => refresh());
|
|
@@ -21028,7 +22285,23 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
|
|
|
21028
22285
|
} catch {}
|
|
21029
22286
|
if (!reconnecting) setReconnectOverlay(false);
|
|
21030
22287
|
});
|
|
22288
|
+
$("legacyUpgradeReviewGroups")?.addEventListener("click", () => {
|
|
22289
|
+
dismissLegacyUpgradeNoticeIfRequested();
|
|
22290
|
+
setLegacyUpgradeNotice(false);
|
|
22291
|
+
window.location.hash = "sync";
|
|
22292
|
+
setTimeout(() => document.getElementById("syncSharingReview")?.scrollIntoView({ block: "start" }), 120);
|
|
22293
|
+
});
|
|
22294
|
+
$("legacyUpgradeReviewProjects")?.addEventListener("click", () => {
|
|
22295
|
+
dismissLegacyUpgradeNoticeIfRequested();
|
|
22296
|
+
setLegacyUpgradeNotice(false);
|
|
22297
|
+
window.location.hash = "projects";
|
|
22298
|
+
});
|
|
22299
|
+
$("legacyUpgradeNotNow")?.addEventListener("click", () => {
|
|
22300
|
+
dismissLegacyUpgradeNoticeIfRequested();
|
|
22301
|
+
setLegacyUpgradeNotice(false);
|
|
22302
|
+
});
|
|
21031
22303
|
loadRuntimeLabel();
|
|
22304
|
+
checkLegacyUpgradeNotice();
|
|
21032
22305
|
refresh();
|
|
21033
22306
|
startPolling();
|
|
21034
22307
|
//#endregion
|