@codemem/server 0.31.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.
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAGX,WAAW,EAIX,MAAM,eAAe,CAAC;AA8EvB,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAE3E,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AACtC,KAAK,iBAAiB,GAAG;IACxB,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;IACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAoBF,UAAU,wBAAwB;IACjC,cAAc,CAAC,EAAE;QAChB,OAAO,EAAE,0BAA0B,CAAC;QACpC,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,wBAAwB,EAAE,MAAM,CAAC;QACjC,4BAA4B,EAAE,MAAM,CAAC;KACrC,CAAC;CACF;AAw/CD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,GAAE,wBAA6B,8EA2W7F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,YAAY,EACtB,oBAAoB,CAAC,EAAE,MAAM,iBAAiB,GAAG,IAAI,8EAu/DrD"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAGX,WAAW,EAIX,MAAM,eAAe,CAAC;AA8EvB,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAE3E,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AACtC,KAAK,iBAAiB,GAAG;IACxB,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;IACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAoBF,UAAU,wBAAwB;IACjC,cAAc,CAAC,EAAE;QAChB,OAAO,EAAE,0BAA0B,CAAC;QACpC,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,wBAAwB,EAAE,MAAM,CAAC;QACjC,4BAA4B,EAAE,MAAM,CAAC;KACrC,CAAC;CACF;AA6mDD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,GAAE,wBAA6B,8EA2W7F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,YAAY,EACtB,oBAAoB,CAAC,EAAE,MAAM,iBAAiB,GAAG,IAAI,8EAwkErD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemem/server",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -19,7 +19,7 @@
19
19
  "drizzle-orm": "^0.45.2",
20
20
  "hono": "^4.12.16",
21
21
  "limiter": "^3.0.0",
22
- "@codemem/core": "^0.31.0"
22
+ "@codemem/core": "^0.31.1"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/better-sqlite3": "^7.6.13",
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;
@@ -3318,6 +3326,20 @@
3318
3326
  guardrail_warnings: payload.guardrail_warnings ?? mapping.guardrail_warnings
3319
3327
  };
3320
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
+ }
3321
3343
  async function deleteSharingDomainProjectMapping(id) {
3322
3344
  const resp = await fetch(`/api/sync/sharing-domains/project-mappings/${encodeURIComponent(id)}`, { method: "DELETE" });
3323
3345
  const { text, payload } = await readJsonPayload(resp);
@@ -3335,6 +3357,20 @@
3335
3357
  if (!payload?.workspace_identity) throw new Error("response missing project reassignment");
3336
3358
  return payload;
3337
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
+ }
3338
3374
  async function reassignLegacySharedReviewGroup$1(input) {
3339
3375
  const resp = await fetch("/api/sync/legacy-shared-review/reassign", {
3340
3376
  method: "POST",
@@ -13508,7 +13544,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
13508
13544
  }) : null, _$3(Tooltip, {
13509
13545
  label: formatDate(createdAtRaw),
13510
13546
  side: "left"
13511
- }, _$3("div", { className: "small feed-age" }, relative)), Boolean(item.owned_by_self) && memoryId > 0 ? _$3(FeedItemMenu, {
13547
+ }, _$3("div", { className: "small feed-age" }, relative)), item.owned_by_self && memoryId > 0 ? _$3(FeedItemMenu, {
13512
13548
  assignProjectDisabled: movingProject,
13513
13549
  disabled: deletingMemory,
13514
13550
  onAssignProject: () => void moveProject(),
@@ -13523,7 +13559,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
13523
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, {
13524
13560
  key: `${String(tag)}-${index}`,
13525
13561
  tag
13526
- }))) : null, Boolean(item.owned_by_self) && memoryId > 0 ? _$3("div", { className: "feed-visibility-controls" }, _$3("select", {
13562
+ }))) : null, item.owned_by_self && memoryId > 0 ? _$3("div", { className: "feed-visibility-controls" }, _$3("select", {
13527
13563
  "aria-label": `Visibility for ${String(item.title || "memory")}`,
13528
13564
  className: "feed-visibility-select",
13529
13565
  disabled: savingVisibility,
@@ -14399,11 +14435,13 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14399
14435
  ];
14400
14436
  var refreshProjects = null;
14401
14437
  var currentOffset = 0;
14402
- var lastLimit = 25;
14438
+ var lastLimit = 250;
14403
14439
  var scopes = [];
14404
14440
  var openProjectDetails = /* @__PURE__ */ new Set();
14441
+ var openProjectClusters = /* @__PURE__ */ new Set();
14405
14442
  var draftDomainSelections = /* @__PURE__ */ new Map();
14406
14443
  var pendingConfirmations = /* @__PURE__ */ new Map();
14444
+ var pendingForgetConfirmations = /* @__PURE__ */ new Map();
14407
14445
  function el(id) {
14408
14446
  return document.getElementById(id);
14409
14447
  }
@@ -14429,6 +14467,14 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14429
14467
  if (project.cwd) return project.cwd;
14430
14468
  return project.workspace_identity;
14431
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
+ }
14432
14478
  function scopeLabel(scopeId) {
14433
14479
  if (!scopeId) return "—";
14434
14480
  const scope = scopes.find((item) => item.scope_id === scopeId);
@@ -14473,6 +14519,22 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14473
14519
  showGlobalNotice(error instanceof Error ? error.message : "Unable to update project Sharing domain.", "warning");
14474
14520
  }
14475
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
+ }
14476
14538
  async function removeProjectMapping(project) {
14477
14539
  if (project.mapping_id == null) return;
14478
14540
  try {
@@ -14485,6 +14547,30 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14485
14547
  showGlobalNotice(error instanceof Error ? error.message : "Unable to remove project Sharing domain mapping.", "warning");
14486
14548
  }
14487
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
+ }
14488
14574
  async function reassignProject(project) {
14489
14575
  if (project.identity_source === "unmapped") return;
14490
14576
  const currentProject = String(project.project || project.display_project || "").trim();
@@ -14583,7 +14669,13 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14583
14669
  changeProject.disabled = project.session_count === 0;
14584
14670
  if (changeProject.disabled) changeProject.title = "No sessions are available to reassign for this saved mapping.";
14585
14671
  changeProject.addEventListener("click", () => void reassignProject(project));
14586
- actions.append(label, select, save, keepLocal, remove, changeProject);
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);
14587
14679
  const pending = pendingConfirmations.get(project.workspace_identity);
14588
14680
  if (pending) {
14589
14681
  const warningBox = document.createElement("div");
@@ -14623,6 +14715,33 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14623
14715
  warningBox.append(title, intro, list, confirm, cancel);
14624
14716
  actions.appendChild(warningBox);
14625
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
+ }
14626
14745
  return actions;
14627
14746
  }
14628
14747
  function renderProjectRow(project) {
@@ -14706,6 +14825,90 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14706
14825
  row.appendChild(detail);
14707
14826
  return row;
14708
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
+ }
14709
14912
  function renderEmpty(message) {
14710
14913
  const list = el("projectsInventoryList");
14711
14914
  if (!list) return;
@@ -14730,8 +14933,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
14730
14933
  scopes = settings.scopes;
14731
14934
  list.textContent = "";
14732
14935
  if (result.projects.length === 0) renderEmpty("No projects match those filters.");
14733
- else for (const project of result.projects) list.appendChild(renderProjectRow(project));
14734
- meta.textContent = result.total === 0 ? "0 projects found" : `${result.total} project${result.total === 1 ? "" : "s"} found · showing ${result.offset + 1}-${Math.min(result.offset + result.projects.length, result.total)}`;
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)}`;
14735
14938
  const prev = el("projectsPrevPage");
14736
14939
  const next = el("projectsNextPage");
14737
14940
  if (prev) prev.disabled = result.offset === 0;
@@ -21058,6 +21261,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21058
21261
  const label = group.displayProject.toLowerCase();
21059
21262
  return label.includes("fatal:") || label.includes("not a git repository") || label === "unknown project" || group.identitySource === "unmapped";
21060
21263
  }
21264
+ function canReassignLegacyGroup(group) {
21265
+ return (group.reassignableMemoryCount ?? group.memoryCount) > 0;
21266
+ }
21061
21267
  function groupSearchText(group) {
21062
21268
  return [
21063
21269
  group.displayProject,
@@ -21103,9 +21309,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21103
21309
  function LegacySharedReviewRow({ item, onReassign, onReview }) {
21104
21310
  const groups = item.groups ?? [];
21105
21311
  const targetScopes = item.targetScopes ?? [];
21106
- const firstTargetScopeId = targetScopes[0]?.scopeId ?? null;
21107
- const initialSelections = Object.fromEntries(groups.map((group) => [group.workspaceIdentity, group.suggestedScopeId ?? firstTargetScopeId ?? ""]));
21312
+ const initialSelections = Object.fromEntries(groups.map((group) => [group.workspaceIdentity, group.suggestedScopeId ?? ""]));
21108
21313
  const [pending, setPending] = d$2({});
21314
+ const [errors, setErrors] = d$2({});
21109
21315
  const [selectedScopes, setSelectedScopes] = d$2(initialSelections);
21110
21316
  const [selectedGroups, setSelectedGroups] = d$2({});
21111
21317
  const [filter, setFilter] = d$2("all");
@@ -21114,8 +21320,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21114
21320
  const shownGroupCount = groups.length;
21115
21321
  const groupCount = item.totalGroupCount ?? shownGroupCount;
21116
21322
  const cleanupCount = groups.filter(needsProjectCleanup).length;
21117
- const suggestedCount = groups.filter((group) => group.suggestedScopeId && !needsProjectCleanup(group)).length;
21118
- const selectedCount = groups.filter((group) => selectedGroups[group.workspaceIdentity]).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;
21119
21326
  const visibleGroups = groups.filter((group) => {
21120
21327
  const cleanup = needsProjectCleanup(group);
21121
21328
  if (filter === "suggested" && (!group.suggestedScopeId || cleanup)) return false;
@@ -21128,8 +21335,13 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21128
21335
  const selectedScopeId = selectedScopes[group.workspaceIdentity] || group.suggestedScopeId || "";
21129
21336
  if (!selectedScopeId || !onReassign || busyIdentity) return;
21130
21337
  setBusyIdentity(group.workspaceIdentity);
21338
+ setErrors((current) => {
21339
+ const next = { ...current };
21340
+ delete next[group.workspaceIdentity];
21341
+ return next;
21342
+ });
21131
21343
  try {
21132
- const preview = await onReassign(group, selectedScopeId, Boolean(pending[group.workspaceIdentity]));
21344
+ const preview = await onReassign(group, selectedScopeId, Boolean(pending[group.workspaceIdentity]), pending[group.workspaceIdentity]?.confirmation_token);
21133
21345
  if (preview) setPending((current) => ({
21134
21346
  ...current,
21135
21347
  [group.workspaceIdentity]: preview
@@ -21139,6 +21351,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21139
21351
  delete next[group.workspaceIdentity];
21140
21352
  return next;
21141
21353
  });
21354
+ } catch (error) {
21355
+ setErrors((current) => ({
21356
+ ...current,
21357
+ [group.workspaceIdentity]: error instanceof Error ? error.message : "Unable to reassign legacy review memories."
21358
+ }));
21142
21359
  } finally {
21143
21360
  setBusyIdentity(null);
21144
21361
  }
@@ -21153,6 +21370,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21153
21370
  delete next[group.workspaceIdentity];
21154
21371
  return next;
21155
21372
  });
21373
+ setErrors((current) => {
21374
+ const next = { ...current };
21375
+ delete next[group.workspaceIdentity];
21376
+ return next;
21377
+ });
21156
21378
  }
21157
21379
  function toggleGroup(group, checked) {
21158
21380
  setSelectedGroups((current) => ({
@@ -21163,38 +21385,40 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21163
21385
  function setVisibleSelected(checked) {
21164
21386
  setSelectedGroups((current) => ({
21165
21387
  ...current,
21166
- ...Object.fromEntries(visibleGroups.map((group) => [group.workspaceIdentity, checked]))
21388
+ ...Object.fromEntries(visibleGroups.filter(canReassignLegacyGroup).map((group) => [group.workspaceIdentity, checked]))
21167
21389
  }));
21168
21390
  }
21169
21391
  function selectSuggestedGroups() {
21170
21392
  setSelectedGroups((current) => ({
21171
21393
  ...current,
21172
- ...Object.fromEntries(groups.filter((group) => group.suggestedScopeId && !needsProjectCleanup(group)).map((group) => [group.workspaceIdentity, true]))
21394
+ ...Object.fromEntries(groups.filter((group) => group.suggestedScopeId && !needsProjectCleanup(group) && canReassignLegacyGroup(group)).map((group) => [group.workspaceIdentity, true]))
21173
21395
  }));
21174
21396
  }
21175
21397
  async function applySelectedGroups() {
21176
21398
  if (!onReassign || busyIdentity || selectedCount === 0) return;
21177
- const selected = groups.filter((group) => selectedGroups[group.workspaceIdentity]);
21178
- const allPreviewed = selected.every((group) => pending[group.workspaceIdentity]);
21399
+ const selected = groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group) && Boolean(selectedScopes[group.workspaceIdentity]));
21400
+ if (selected.every((group) => pending[group.workspaceIdentity])) return;
21179
21401
  setBusyIdentity("bulk");
21180
21402
  try {
21181
21403
  for (const group of selected) {
21404
+ if (pending[group.workspaceIdentity]) continue;
21182
21405
  const selectedScopeId = selectedScopes[group.workspaceIdentity] || group.suggestedScopeId || "";
21183
21406
  if (!selectedScopeId) continue;
21184
- const preview = await onReassign(group, selectedScopeId, allPreviewed);
21185
- if (preview) setPending((current) => ({
21186
- ...current,
21187
- [group.workspaceIdentity]: preview
21188
- }));
21189
- else if (allPreviewed) {
21190
- setPending((current) => {
21407
+ try {
21408
+ const preview = await onReassign(group, selectedScopeId, false);
21409
+ setErrors((current) => {
21191
21410
  const next = { ...current };
21192
21411
  delete next[group.workspaceIdentity];
21193
21412
  return next;
21194
21413
  });
21195
- setSelectedGroups((current) => ({
21414
+ if (preview) setPending((current) => ({
21415
+ ...current,
21416
+ [group.workspaceIdentity]: preview
21417
+ }));
21418
+ } catch (error) {
21419
+ setErrors((current) => ({
21196
21420
  ...current,
21197
- [group.workspaceIdentity]: false
21421
+ [group.workspaceIdentity]: error instanceof Error ? error.message : "Unable to reassign legacy review memories."
21198
21422
  }));
21199
21423
  }
21200
21424
  }
@@ -21226,7 +21450,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21226
21450
  " ",
21227
21451
  item.memoryCount.toLocaleString(),
21228
21452
  " older shared memories total; review the projects, not individual memories.",
21229
- shownGroupCount < groupCount ? ` Showing the first ${shownGroupCount.toLocaleString()} projects by memory count.` : ""
21453
+ shownGroupCount < groupCount ? ` Showing ${shownGroupCount.toLocaleString()} projects; open Projects to review the rest.` : ""
21230
21454
  ]
21231
21455
  }),
21232
21456
  /* @__PURE__ */ u("div", {
@@ -21289,13 +21513,17 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21289
21513
  }),
21290
21514
  onReassign && targetScopes.length > 0 ? /* @__PURE__ */ u("button", {
21291
21515
  className: "settings-save",
21292
- disabled: busyIdentity != null || selectedCount === 0,
21516
+ disabled: busyIdentity != null || selectedCount === 0 || groups.filter((group) => selectedGroups[group.workspaceIdentity] && canReassignLegacyGroup(group) && Boolean(selectedScopes[group.workspaceIdentity])).every((group) => pending[group.workspaceIdentity]),
21293
21517
  onClick: () => void applySelectedGroups(),
21294
21518
  type: "button",
21295
- children: busyIdentity === "bulk" ? "Working…" : selectedCount > 0 && groups.filter((group) => selectedGroups[group.workspaceIdentity]).every((group) => pending[group.workspaceIdentity]) ? `Apply ${selectedCount.toLocaleString()} selected` : `Preview ${selectedCount.toLocaleString()} selected`
21296
- }) : onReassign && selectedCount > 0 ? /* @__PURE__ */ u("span", {
21519
+ children: busyIdentity === "bulk" ? "Previewing…" : `Preview ${selectedCount.toLocaleString()} selected`
21520
+ }) : onReassign && selectedReassignableCount > 0 ? /* @__PURE__ */ u("span", {
21297
21521
  className: "legacy-review-cleanup-note",
21298
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."
21299
21527
  }) : null
21300
21528
  ]
21301
21529
  })]
@@ -21314,6 +21542,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21314
21542
  "aria-label": `Select ${group.displayProject}`,
21315
21543
  checked: Boolean(selectedGroups[group.workspaceIdentity]),
21316
21544
  className: "cm-checkbox",
21545
+ disabled: !canReassignLegacyGroup(group),
21317
21546
  onChange: (event) => toggleGroup(group, event.currentTarget.checked),
21318
21547
  type: "checkbox"
21319
21548
  }), /* @__PURE__ */ u("div", { children: [
@@ -21325,6 +21554,24 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21325
21554
  className: "legacy-review-group-meta",
21326
21555
  children: [formatIdentitySource(group.identitySource), group.suggestedScopeId ? ` · suggested ${group.suggestedScopeId}` : ""]
21327
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,
21328
21575
  needsProjectCleanup(group) ? /* @__PURE__ */ u("div", {
21329
21576
  className: "legacy-review-cleanup-note",
21330
21577
  children: "Unclear project identity. Inspect or correct the project before trusting a domain."
@@ -21343,6 +21590,11 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21343
21590
  className: "legacy-review-reason",
21344
21591
  children: group.suggestionReason
21345
21592
  }) : null,
21593
+ errors[group.workspaceIdentity] ? /* @__PURE__ */ u("div", {
21594
+ className: "legacy-review-confirmation",
21595
+ role: "alert",
21596
+ children: errors[group.workspaceIdentity]
21597
+ }) : null,
21346
21598
  pending[group.workspaceIdentity] ? /* @__PURE__ */ u("div", {
21347
21599
  className: "legacy-review-confirmation",
21348
21600
  role: "alert",
@@ -21359,14 +21611,17 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21359
21611
  "."
21360
21612
  ]
21361
21613
  }) : null,
21362
- targetScopes.length > 0 && onReassign ? /* @__PURE__ */ u("label", {
21614
+ targetScopes.length > 0 && onReassign && canReassignLegacyGroup(group) ? /* @__PURE__ */ u("label", {
21363
21615
  className: "legacy-review-target",
21364
21616
  children: [/* @__PURE__ */ u("span", { children: "Destination Sharing domain" }), /* @__PURE__ */ u("select", {
21365
21617
  className: "peer-scope-input",
21366
21618
  disabled: busyIdentity != null,
21367
21619
  onChange: (event) => updateSelectedScope(group, event.currentTarget.value),
21368
- value: selectedScopes[group.workspaceIdentity] || group.suggestedScopeId || "",
21369
- children: targetScopes.map((scope) => /* @__PURE__ */ u("option", {
21620
+ value: selectedScopes[group.workspaceIdentity] || "",
21621
+ children: [/* @__PURE__ */ u("option", {
21622
+ value: "",
21623
+ children: "Choose domain…"
21624
+ }), targetScopes.map((scope) => /* @__PURE__ */ u("option", {
21370
21625
  value: scope.scopeId,
21371
21626
  children: [
21372
21627
  scope.label,
@@ -21374,17 +21629,17 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21374
21629
  scope.authorityType,
21375
21630
  scope.scopeId === group.suggestedScopeId ? " · suggested" : ""
21376
21631
  ]
21377
- }, scope.scopeId))
21632
+ }, scope.scopeId))]
21378
21633
  })]
21379
21634
  }) : null,
21380
- targetScopes.length > 0 && onReassign ? /* @__PURE__ */ u("div", {
21635
+ targetScopes.length > 0 && onReassign && canReassignLegacyGroup(group) ? /* @__PURE__ */ u("div", {
21381
21636
  className: "legacy-review-actions",
21382
21637
  children: /* @__PURE__ */ u("button", {
21383
21638
  type: "button",
21384
21639
  className: "settings-button",
21385
- disabled: busyIdentity != null,
21640
+ disabled: busyIdentity != null || !selectedScopes[group.workspaceIdentity],
21386
21641
  onClick: () => void applyGroup(group),
21387
- children: busyIdentity === group.workspaceIdentity ? "Reassigning…" : pending[group.workspaceIdentity] ? "I understand, reassign memories" : "Preview reassignment"
21642
+ children: busyIdentity === group.workspaceIdentity ? pending[group.workspaceIdentity] ? "Reassigning…" : "Previewing…" : pending[group.workspaceIdentity] ? "I understand, reassign memories" : "Preview reassignment"
21388
21643
  })
21389
21644
  }) : null
21390
21645
  ]
@@ -21413,9 +21668,10 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21413
21668
  function openProjectsReview() {
21414
21669
  window.location.hash = "projects";
21415
21670
  }
21416
- async function reassignLegacySharedReviewGroup(group, scopeId, confirmedOldCopies) {
21671
+ async function reassignLegacySharedReviewGroup(group, scopeId, confirmedOldCopies, confirmationToken) {
21417
21672
  try {
21418
21673
  const result = await reassignLegacySharedReviewGroup$1({
21674
+ ...confirmationToken ? { confirmation_token: confirmationToken } : {},
21419
21675
  confirmed_old_copies: confirmedOldCopies,
21420
21676
  scope_id: scopeId,
21421
21677
  workspace_identity: group.workspaceIdentity
@@ -21426,8 +21682,9 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21426
21682
  return null;
21427
21683
  } catch (error) {
21428
21684
  if (error instanceof LegacySharedReviewConfirmationError) return error.preview;
21429
- showGlobalNotice(error instanceof Error ? error.message : "Unable to reassign legacy review memories.", "warning");
21430
- return null;
21685
+ const message = error instanceof Error ? error.message : "Unable to reassign legacy review memories.";
21686
+ showGlobalNotice(message, "warning");
21687
+ throw new Error(message);
21431
21688
  }
21432
21689
  }
21433
21690
  function renderSyncSharingReview() {
@@ -21447,6 +21704,8 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21447
21704
  identitySource: String(raw.identity_source || "unknown"),
21448
21705
  lastUpdatedAt: typeof raw.last_updated_at === "string" ? raw.last_updated_at : null,
21449
21706
  memoryCount: Number(raw.memory_count || 0),
21707
+ peerOwnedMemoryCount: Number(raw.peer_owned_memory_count || 0),
21708
+ reassignableMemoryCount: Number(raw.reassignable_memory_count || 0),
21450
21709
  suggestedScopeId: typeof raw.suggested_scope_id === "string" ? raw.suggested_scope_id : null,
21451
21710
  suggestionReason: typeof raw.suggestion_reason === "string" ? raw.suggestion_reason : null,
21452
21711
  workspaceIdentity: String(raw.workspace_identity || raw.display_project || "unknown")
@@ -21670,7 +21929,7 @@ For more information, see https://radix-ui.com/primitives/docs/components/${titl
21670
21929
  try {
21671
21930
  const runtime = await loadRuntimeInfo();
21672
21931
  if (!runtime?.version) return;
21673
- setRuntimeLabel(runtime.version, "2ee815b");
21932
+ setRuntimeLabel(runtime.version, "3400309");
21674
21933
  } catch {}
21675
21934
  }
21676
21935
  var lastAnnouncedRefreshState = null;