@dsh-workbench/core 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +11 -9
  2. package/bin/dsh-workbench +42 -4
  3. package/client.js +252 -36
  4. package/dist/client/client.body.js +252 -36
  5. package/dist/client/index.js +252 -36
  6. package/dist/host/index.js +1054 -737
  7. package/dist/types/app/client.d.ts +21 -15
  8. package/dist/types/app/layouts/rightbar/__tests__/market-order.test.d.ts +1 -0
  9. package/dist/types/app/layouts/rightbar/__tests__/market-running-display.test.d.ts +1 -0
  10. package/dist/types/app/layouts/rightbar/__tests__/market-workbench-act.test.d.ts +1 -0
  11. package/dist/types/app/layouts/rightbar/market-act.d.ts +2 -1
  12. package/dist/types/app/layouts/rightbar/market-rows.d.ts +3 -0
  13. package/dist/types/app/layouts/rightbar/market-settle-hold.d.ts +13 -0
  14. package/dist/types/app/layouts/rightbar/market-settle.d.ts +22 -0
  15. package/dist/types/app/layouts/rightbar/market-transient-state.d.ts +22 -0
  16. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +26 -1
  17. package/dist/types/modules/panels/__tests__/market-tools-harness.d.ts +3 -0
  18. package/dist/types/modules/panels/model/market-workbench-update.d.ts +19 -0
  19. package/dist/types/modules/panels/model/market.d.ts +4 -0
  20. package/dist/types/shared/api/bundle-install.d.ts +1 -0
  21. package/dist/types/shared/api/config-sync.d.ts +0 -2
  22. package/dist/types/shared/api/host/__tests__/post-channels.test.d.ts +1 -0
  23. package/dist/types/shared/api/host/__tests__/self-update.test.d.ts +1 -0
  24. package/dist/types/shared/api/host/bundle-manager.d.ts +1 -0
  25. package/dist/types/shared/api/host/bundle-routes.d.ts +1 -1
  26. package/dist/types/shared/api/host/config-sync.d.ts +2 -2
  27. package/dist/types/shared/api/host/post-channels.d.ts +18 -0
  28. package/dist/types/shared/api/host/self-update.d.ts +45 -0
  29. package/docs/HANDOFF.md +12 -5
  30. package/docs/OPERATIONS.md +35 -4
  31. package/package.json +4 -4
  32. package/scripts/seed-profile.sh +61 -1
@@ -1381,6 +1381,7 @@ var en = {
1381
1381
  "panels.market.lead": "Installed on this machine, shared by every project.",
1382
1382
  "panels.market.install": "Install",
1383
1383
  "panels.market.update": "Update",
1384
+ "panels.market.swapSlow": "Not swapped yet — maybe a slow or failed download. Press Update again; if it stays like this, restart dsh.",
1384
1385
  "panels.market.uninstall": "Uninstall",
1385
1386
  "panels.market.uninstallLabel": "Uninstall {name}",
1386
1387
  "panels.market.installing": "Installing…",
@@ -1632,7 +1633,7 @@ var en = {
1632
1633
  "panel.conversation": "Conversation",
1633
1634
  "panels.label": "Global panels",
1634
1635
  "configSync.nav": "Config sync",
1635
- "configSync.lead": "Bring over the rows another profile configures explicitly — pick any profile as the source. A restart applies them.",
1636
+ "configSync.lead": "Bring over the rows another profile configures explicitly — pick any profile as the source.",
1636
1637
  "configSync.source": "Source",
1637
1638
  "configSync.sourceBroken": "its config is unreadable",
1638
1639
  "configSync.sourceRows": "{name}: {count} rows are new or different for the workbench",
@@ -1656,7 +1657,7 @@ var en = {
1656
1657
  "configSync.apply": "Sync {count} selected",
1657
1658
  "configSync.applyNone": "Pick some rows first",
1658
1659
  "configSync.applying": "Writing…",
1659
- "configSync.applied": "Written to the workbench config · restart to apply",
1660
+ "configSync.applied": "Written to the workbench config · applied",
1660
1661
  "configSync.appliedIds": "Written this time: {ids}",
1661
1662
  "configSync.skippedIds": "Left alone (already here): {ids}",
1662
1663
  "configSync.nothing": "Nothing to sync",
@@ -1775,6 +1776,7 @@ var zh = {
1775
1776
  "panels.market.lead": "装在这台机器上,所有项目共用。",
1776
1777
  "panels.market.install": "安装",
1777
1778
  "panels.market.update": "更新",
1779
+ "panels.market.swapSlow": "还没换上:可能网慢或没装上。再点一次「更新」;一直这样就把 dsh 重启一遍。",
1778
1780
  "panels.market.uninstall": "卸载",
1779
1781
  "panels.market.uninstallLabel": "卸载 {name}",
1780
1782
  "panels.market.installing": "正在安装…",
@@ -2026,7 +2028,7 @@ var zh = {
2026
2028
  "panel.conversation": "对话",
2027
2029
  "panels.label": "全局面板",
2028
2030
  "configSync.nav": "配置同步",
2029
- "configSync.lead": "把另一个 profile 里显式配过的行搬过来 —— 来源随你选。搬完要重启工作台才生效。",
2031
+ "configSync.lead": "把另一个 profile 里显式配过的行搬过来 —— 来源随你选。",
2030
2032
  "configSync.source": "来源",
2031
2033
  "configSync.sourceBroken": "读不到它的配置",
2032
2034
  "configSync.sourceRows": "{name} 这份里你有 {count} 行可搬",
@@ -2050,7 +2052,7 @@ var zh = {
2050
2052
  "configSync.apply": "同步选中的 {count} 行",
2051
2053
  "configSync.applyNone": "先勾几行",
2052
2054
  "configSync.applying": "正在写入…",
2053
- "configSync.applied": "已写入工作台配置 · 重启工作台后生效",
2055
+ "configSync.applied": "已写入工作台配置 · 已生效",
2054
2056
  "configSync.appliedIds": "这次写进去:{ids}",
2055
2057
  "configSync.skippedIds": "这几行工作台自己已有,没动:{ids}",
2056
2058
  "configSync.nothing": "没有需要同步的内容",
@@ -2880,6 +2882,38 @@ function holdBack(installed, roster, active, previous) {
2880
2882
  const fresh = previous === null ? [] : pending.filter((unit) => !previous.has(unit));
2881
2883
  return pending.filter((unit) => active.has(unit) || fresh.includes(unit));
2882
2884
  }
2885
+ var WORKBENCH_SWAP_MS = 18e4;
2886
+ function swapLanded(until, installed) {
2887
+ return until !== void 0 && installed === until;
2888
+ }
2889
+ function swapUntil(action, application, latest) {
2890
+ if (action !== "update-workbench" || application !== "restart-required") return void 0;
2891
+ return latest;
2892
+ }
2893
+ var READ_BACK_ACTIONS = /* @__PURE__ */ new Set(["update", "update-bundle"]);
2894
+ function awaitsReadBack(action, application, version) {
2895
+ return application === "applied" && version !== void 0 && READ_BACK_ACTIONS.has(action);
2896
+ }
2897
+ function readBackDone(input) {
2898
+ if (input.action === "update") return !input.outdated;
2899
+ if (input.entry === void 0 || input.entry.local === true || input.entry.latest === void 0) return true;
2900
+ if (input.entry.latest === input.entry.installed) return true;
2901
+ if (input.clicked === void 0) return true;
2902
+ return input.entry.installed !== input.clicked;
2903
+ }
2904
+ function settleLanded(input) {
2905
+ if (input.until !== void 0) return swapLanded(input.until, input.installed);
2906
+ if (input.readBack !== true) return true;
2907
+ return readBackDone({
2908
+ action: input.action,
2909
+ entry: input.entry,
2910
+ clicked: input.clicked,
2911
+ outdated: input.outdated
2912
+ });
2913
+ }
2914
+ function holdWindowMs(until) {
2915
+ return until === void 0 ? MODULE_SETTLE_MS : WORKBENCH_SWAP_MS;
2916
+ }
2883
2917
  //#endregion
2884
2918
  //#region src/app/layouts/rightbar/market-installed-mock.ts
2885
2919
  function installedMocks(input) {
@@ -4012,6 +4046,7 @@ function workbenchRow(version) {
4012
4046
  key: "panels.market.workbenchLocal",
4013
4047
  params: {}
4014
4048
  } : void 0;
4049
+ const stale = !local && version.running !== void 0 && version.running !== version.installed;
4015
4050
  return {
4016
4051
  id: WORKBENCH_ROW_ID,
4017
4052
  titleKey: WORKBENCH_TITLE_KEY,
@@ -4021,6 +4056,7 @@ function workbenchRow(version) {
4021
4056
  canUpdate: update !== void 0,
4022
4057
  canUninstall: false,
4023
4058
  ...note === void 0 ? {} : { note },
4059
+ ...stale ? { hint: "panels.market.moduleRestartHint" } : {},
4024
4060
  version: {
4025
4061
  installed: version.installed,
4026
4062
  ...latest === void 0 ? {} : { latest }
@@ -4083,6 +4119,10 @@ function mergeTitles(known, manifests) {
4083
4119
  }
4084
4120
  return next ?? known;
4085
4121
  }
4122
+ function installedIdSet(installed) {
4123
+ if (installed === void 0) return null;
4124
+ return new Set(installed.map((item) => item.unit));
4125
+ }
4086
4126
  function installedRows(modules, manifests, listed, known, defaults = /* @__PURE__ */ new Set(), changed = {}) {
4087
4127
  const titles = new Map(manifests.map((manifest) => [manifest.id, manifest.card?.titleKey]));
4088
4128
  const rows = [];
@@ -4145,6 +4185,9 @@ function isOutdated(capability) {
4145
4185
  if (capability === void 0) return false;
4146
4186
  return capability.status === "missing-commands";
4147
4187
  }
4188
+ function capabilityOutdated(capabilities, unit) {
4189
+ return isOutdated(capabilities?.[unit]);
4190
+ }
4148
4191
  function envNameOf(source) {
4149
4192
  if (source === void 0) return "";
4150
4193
  if (source.kind === "local") return source.env;
@@ -4206,6 +4249,12 @@ function marketHintVisible(manifests, capabilities, modules, workbench) {
4206
4249
  const self = workbenchRow(workbench);
4207
4250
  return marketHasUpdates([...marketRows(manifests, capabilities, marketUpdatesOf(modules)), ...self === void 0 ? [] : [self]], sharedRows(manifests, capabilities));
4208
4251
  }
4252
+ function marketOrder(rows, titleOf) {
4253
+ return [...rows].sort((left, right) => {
4254
+ const byTitle = titleOf(left).localeCompare(titleOf(right), "zh-Hans-CN");
4255
+ return byTitle === 0 ? left.id.localeCompare(right.id) : byTitle;
4256
+ });
4257
+ }
4209
4258
  function marketSections(rows) {
4210
4259
  const installed = rows.filter((row) => isOnMachine(row.state));
4211
4260
  const rest = rows.filter((row) => !isOnMachine(row.state));
@@ -4343,6 +4392,74 @@ function useVisibleBusy(active, floorMs) {
4343
4392
  return busyView(active, visible);
4344
4393
  }
4345
4394
  //#endregion
4395
+ //#region src/app/layouts/rightbar/market-settle-hold.ts
4396
+ function useSettleHold(input) {
4397
+ const { pending, landed, t, onChanged, setPending, setFailed } = input;
4398
+ const until = pending?.until;
4399
+ const id = pending?.id ?? "";
4400
+ const watching = pending !== null && (pending.until !== void 0 || pending.readBack === true);
4401
+ const sinceRef = useRef(0);
4402
+ useEffect(() => {
4403
+ if (!watching || !landed) return;
4404
+ setPending(null);
4405
+ }, [
4406
+ watching,
4407
+ landed,
4408
+ setPending
4409
+ ]);
4410
+ useEffect(() => {
4411
+ if (!watching) {
4412
+ sinceRef.current = 0;
4413
+ return;
4414
+ }
4415
+ if (sinceRef.current === 0) sinceRef.current = Date.now();
4416
+ const deadline = sinceRef.current + holdWindowMs(until);
4417
+ const tick = window.setInterval(() => {
4418
+ onChanged();
4419
+ if (Date.now() < deadline) return;
4420
+ window.clearInterval(tick);
4421
+ setPending(null);
4422
+ setFailed({
4423
+ id,
4424
+ message: t("panels.market.swapSlow")
4425
+ });
4426
+ }, 800);
4427
+ return () => window.clearInterval(tick);
4428
+ }, [
4429
+ watching,
4430
+ landed,
4431
+ until,
4432
+ id,
4433
+ onChanged,
4434
+ setPending,
4435
+ setFailed,
4436
+ t
4437
+ ]);
4438
+ }
4439
+ //#endregion
4440
+ //#region src/app/layouts/rightbar/market-transient-state.ts
4441
+ function useMarketTransient(input) {
4442
+ const { mock, onPending } = input;
4443
+ const [pending, setPending] = useState(mock?.pending ?? null);
4444
+ const [confirming, setConfirming] = useState(mock?.confirming ?? null);
4445
+ const [failed, setFailed] = useState(mock?.failure ?? null);
4446
+ const [notice, setNotice] = useState(mock?.notice ?? null);
4447
+ useEffect(() => {
4448
+ onPending?.(pending !== null);
4449
+ return () => onPending?.(false);
4450
+ }, [pending, onPending]);
4451
+ return {
4452
+ pending,
4453
+ setPending,
4454
+ confirming,
4455
+ setConfirming,
4456
+ failed,
4457
+ setFailed,
4458
+ notice,
4459
+ setNotice
4460
+ };
4461
+ }
4462
+ //#endregion
4346
4463
  //#region src/app/layouts/rightbar/market-plugin-rows.ts
4347
4464
  function installedPluginKept(value) {
4348
4465
  if (typeof value !== "object" || value === null) return void 0;
@@ -4505,6 +4622,80 @@ var ACTION_LABEL_KEY = {
4505
4622
  "uninstall-plugin": "panels.market.uninstall",
4506
4623
  "update-workbench": "panels.market.update"
4507
4624
  };
4625
+ function holdOf(pending, application) {
4626
+ if (pending === null || pending.row === void 0) return void 0;
4627
+ if (application !== "applied") return void 0;
4628
+ return {
4629
+ id: pending.id,
4630
+ row: pending.row
4631
+ };
4632
+ }
4633
+ function pendingLanded(pending, updates, capabilities, installed) {
4634
+ if (pending === null) return true;
4635
+ return settleLanded({
4636
+ action: pending.action,
4637
+ until: pending.until,
4638
+ readBack: pending.readBack,
4639
+ installed,
4640
+ entry: updates[pending.id],
4641
+ clicked: pending.version,
4642
+ outdated: capabilityOutdated(capabilities, pending.id)
4643
+ });
4644
+ }
4645
+ function holdExpired(held, backInRoster, elapsedMs) {
4646
+ if (held === void 0) return true;
4647
+ return backInRoster || elapsedMs >= 15e3;
4648
+ }
4649
+ function heldIdOf(pending, held) {
4650
+ return pending?.id ?? held?.id ?? null;
4651
+ }
4652
+ function pinnedRowOf(pending, held) {
4653
+ return pending?.row ?? held?.row;
4654
+ }
4655
+ function withPinnedRow(rows, pinned) {
4656
+ if (pinned === void 0) return [...rows];
4657
+ if (!rows.some((row) => row.id === pinned.id)) return [...rows, pinned];
4658
+ return rows.map((row) => row.id === pinned.id ? pinned : row);
4659
+ }
4660
+ function marketFootVisible(shown, installed) {
4661
+ return shown.length > 0 || installed.length > 0;
4662
+ }
4663
+ function useMarketTitles(manifests, mocked) {
4664
+ const [titles, setTitles] = useState(/* @__PURE__ */ new Map());
4665
+ useEffect(() => {
4666
+ if (!mocked) setTitles((known) => mergeTitles(known, manifests));
4667
+ }, [manifests, mocked]);
4668
+ return titles;
4669
+ }
4670
+ function useHeldRow(pending, backInRoster) {
4671
+ const [held, setHeld] = useState(void 0);
4672
+ const heldAtRef = useRef(0);
4673
+ const hold = useCallback((next) => {
4674
+ if (next !== void 0) heldAtRef.current = Date.now();
4675
+ setHeld(next);
4676
+ }, []);
4677
+ const back = held === void 0 ? false : backInRoster(held.id);
4678
+ useEffect(() => {
4679
+ if (held === void 0) return;
4680
+ if (holdExpired(held, back, Date.now() - heldAtRef.current)) {
4681
+ setHeld(void 0);
4682
+ return;
4683
+ }
4684
+ const timer = setTimeout(() => setHeld(void 0), MODULE_SETTLE_MS);
4685
+ return () => clearTimeout(timer);
4686
+ }, [held, back]);
4687
+ useEffect(() => {
4688
+ if (pending !== null) setHeld(void 0);
4689
+ }, [pending]);
4690
+ return {
4691
+ held,
4692
+ hold
4693
+ };
4694
+ }
4695
+ function removingIdOf(pending, failed) {
4696
+ if (pending === null || pending.action !== "remove-bundle") return null;
4697
+ return failed?.id === pending.id ? null : pending.id;
4698
+ }
4508
4699
  function pendingRowOf(input) {
4509
4700
  const { pending, removing } = input;
4510
4701
  if (removing === null || pending === null) return void 0;
@@ -4901,18 +5092,21 @@ function changeFailure(change, t) {
4901
5092
  }
4902
5093
  //#endregion
4903
5094
  //#region src/app/layouts/rightbar/market-act.ts
5095
+ var STEADY_ACTIONS = /* @__PURE__ */ new Set(["update", "update-bundle"]);
4904
5096
  function marketActOf(options) {
4905
5097
  const { t, mocked, rowOf, seats, send, onChanged } = options;
4906
5098
  return async (id, action) => {
4907
5099
  if (mocked) return;
4908
5100
  const row = rowOf(id);
4909
- seats.setPending({
5101
+ const next = {
4910
5102
  id,
4911
5103
  action,
4912
5104
  ...row?.titleKey === void 0 ? {} : { titleKey: row.titleKey },
4913
5105
  ...row?.pkg === void 0 ? {} : { pkg: row.pkg },
4914
- ...row?.version?.installed === void 0 ? {} : { version: row.version.installed }
4915
- });
5106
+ ...row?.version?.installed === void 0 ? {} : { version: row.version.installed },
5107
+ ...row === void 0 || !STEADY_ACTIONS.has(action) ? {} : { row }
5108
+ };
5109
+ seats.setPending(next);
4916
5110
  seats.setConfirming(null);
4917
5111
  seats.setFailed(null);
4918
5112
  if (action !== "uninstall-plugin") seats.setNotice(null);
@@ -4926,8 +5120,9 @@ function marketActOf(options) {
4926
5120
  action,
4927
5121
  unit: id
4928
5122
  });
4929
- seats.setPending(null);
4930
5123
  if (!outcome.ok) {
5124
+ seats.setPending(null);
5125
+ seats.setHeld(void 0);
4931
5126
  onChanged();
4932
5127
  seats.setFailed({
4933
5128
  id,
@@ -4935,8 +5130,17 @@ function marketActOf(options) {
4935
5130
  });
4936
5131
  return;
4937
5132
  }
5133
+ const change = changeOf(outcome.value);
5134
+ const until = swapUntil(action, change?.application, row?.version?.latest);
5135
+ const readBack = awaitsReadBack(action, change?.application, next.version);
5136
+ seats.setPending(until === void 0 && !readBack ? null : {
5137
+ ...next,
5138
+ ...until === void 0 ? {} : { until },
5139
+ ...readBack ? { readBack: true } : {}
5140
+ });
5141
+ seats.setHeld(readBack ? void 0 : holdOf(next, change?.application));
4938
5142
  if (action === "uninstall-plugin") seats.setNotice(installedPluginNotice(outcome.value, t));
4939
- const reason = changeFailure(changeOf(outcome.value), t);
5143
+ const reason = changeFailure(change, t);
4940
5144
  onChanged();
4941
5145
  if (reason !== void 0) seats.setFailed({
4942
5146
  id,
@@ -4979,6 +5183,7 @@ function ConfirmUninstall({ t, name, onConfirm, onCancel }) {
4979
5183
  }
4980
5184
  function MarketBody({ t, self, shown, pluginList, installedList, shared, pending, failed, notice, act, onConfirming, setDefault, boardBusy }) {
4981
5185
  const failureOf = (id) => failed?.id === id ? failed.message : null;
5186
+ const foot = marketFootVisible(shown, installedList);
4982
5187
  return /* @__PURE__ */ h(Fragment, null, notice === null ? null : /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketNote }, notice), /* @__PURE__ */ h(MarketSection, {
4983
5188
  t,
4984
5189
  rows: self === void 0 ? [] : [self],
@@ -4992,7 +5197,7 @@ function MarketBody({ t, self, shown, pluginList, installedList, shared, pending
4992
5197
  }), self === void 0 ? null : /* @__PURE__ */ h("div", {
4993
5198
  className: MarketPage_module_default.marketSelfDivider,
4994
5199
  "aria-hidden": "true"
4995
- }), marketSections([...shown, ...installedList]).map((section) => /* @__PURE__ */ h(MarketSection, {
5200
+ }), marketSections(marketOrder([...shown, ...installedList], (row) => marketRowTitle(row, t))).map((section) => /* @__PURE__ */ h(MarketSection, {
4996
5201
  key: section.id,
4997
5202
  t,
4998
5203
  titleKey: section.titleKey,
@@ -5017,10 +5222,10 @@ function MarketBody({ t, self, shown, pluginList, installedList, shared, pending
5017
5222
  act,
5018
5223
  confirmActions: CONFIRM_UNINSTALL,
5019
5224
  onConfirming
5020
- }), /* @__PURE__ */ h(PanelFoot, {
5225
+ }), foot ? /* @__PURE__ */ h(PanelFoot, {
5021
5226
  size: "md",
5022
5227
  text: t("panels.market.foot")
5023
- }), /* @__PURE__ */ h(SharedSection, {
5228
+ }) : null, /* @__PURE__ */ h(SharedSection, {
5024
5229
  t,
5025
5230
  rows: shared,
5026
5231
  pending,
@@ -5029,22 +5234,19 @@ function MarketBody({ t, self, shown, pluginList, installedList, shared, pending
5029
5234
  }));
5030
5235
  }
5031
5236
  function MarketView({ t, manifests, installable, capabilities, freshness, onChanged, onPending, updates, installed, installedRead, workbench, plugins, settling, mock, defaults = [], changed = {} }) {
5032
- const [titles, setTitles] = useState(/* @__PURE__ */ new Map());
5033
- useEffect(() => {
5034
- if (mock === void 0) setTitles((known) => mergeTitles(known, manifests));
5035
- }, [manifests, mock]);
5237
+ const titles = useMarketTitles(manifests, mock !== void 0);
5036
5238
  const catalog = mock?.manifests ?? manifests;
5037
5239
  const marketable = mock === void 0 ? marketCatalog(manifests, installable) : catalog;
5038
5240
  const effective = mock === void 0 ? capabilities : mock.capabilities;
5039
- const source = liveManifests(marketable, effective, mock === void 0 && installed !== void 0 ? new Set(installed.map((item) => item.unit)) : null);
5241
+ const source = liveManifests(marketable, effective, installedIdSet(mock === void 0 ? installed : void 0));
5040
5242
  const byDefault = new Set(defaults);
5041
- const rows = marketRows(source, effective, mock?.updates ?? updates ?? {}, byDefault, changed);
5243
+ const updateMap = mock?.updates ?? updates ?? {};
5244
+ const rows = marketRows(source, effective, updateMap, byDefault, changed);
5042
5245
  const shared = sharedRows(source, effective);
5043
- const [pending, setPending] = useState(mock?.pending ?? null);
5044
- useEffect(() => {
5045
- onPending?.(pending !== null);
5046
- return () => onPending?.(false);
5047
- }, [pending, onPending]);
5246
+ const { pending, setPending, confirming, setConfirming, failed, setFailed, notice, setNotice } = useMarketTransient({
5247
+ mock,
5248
+ onPending
5249
+ });
5048
5250
  const provision = useHostWrite({
5049
5251
  channel: "provision",
5050
5252
  networkText: t("panels.failed")
@@ -5072,19 +5274,19 @@ function MarketView({ t, manifests, installable, capabilities, freshness, onChan
5072
5274
  setNotice(t("panels.failed"));
5073
5275
  }
5074
5276
  };
5075
- const [confirming, setConfirming] = useState(mock?.confirming ?? null);
5076
- const [failed, setFailed] = useState(mock?.failure ?? null);
5077
- const [notice, setNotice] = useState(mock?.notice ?? null);
5078
- const removing = pending !== null && pending.action === "remove-bundle" && failed?.id !== pending.id ? pending.id : null;
5079
- const installedList = installedRows((mock?.installed ?? installed ?? []).filter((module) => !(settling?.has(module.unit) ?? false) && module.unit !== removing), catalog, new Set(rows.map((row) => row.id)), titles, byDefault, changed);
5277
+ const removing = removingIdOf(pending, failed);
5278
+ const steady = useHeldRow(pending, (id) => catalog.some((manifest) => manifest.id === id));
5279
+ const pinned = pinnedRowOf(pending, steady.held);
5280
+ const installedList = installedRows((mock?.installed ?? installed ?? []).filter((module) => module.unit !== pinned?.id && (!(settling?.has(module.unit) ?? false) || module.unit === heldIdOf(pending, steady.held)) && module.unit !== removing), catalog, new Set(rows.map((row) => row.id)), titles, byDefault, changed);
5080
5281
  const pendingRow = pendingRowOf({
5081
5282
  pending,
5082
5283
  removing,
5083
5284
  rows,
5084
5285
  titles
5085
5286
  });
5086
- const shown = pendingRow === void 0 ? rows : [...rows, pendingRow];
5087
- const self = workbenchRow(mock === void 0 ? workbench : mock.workbench);
5287
+ const selfRow = workbenchRow(mock === void 0 ? workbench : mock.workbench);
5288
+ const shown = withPinnedRow(pendingRow === void 0 ? rows : [...rows, pendingRow], pinned);
5289
+ const self = selfRow;
5088
5290
  const pluginList = installedPluginRows(mock?.plugins ?? plugins ?? [], byDefault, changed);
5089
5291
  const rowById = new Map([
5090
5292
  ...shown,
@@ -5093,19 +5295,29 @@ function MarketView({ t, manifests, installable, capabilities, freshness, onChan
5093
5295
  ...pluginList
5094
5296
  ].map((row) => [row.id, row]));
5095
5297
  const confirmingRow = confirming === null ? void 0 : rowById.get(confirming);
5298
+ const seats = {
5299
+ setPending,
5300
+ setHeld: steady.hold,
5301
+ setConfirming,
5302
+ setFailed,
5303
+ setNotice
5304
+ };
5096
5305
  const act = marketActOf({
5097
5306
  t,
5098
5307
  mocked: mock !== void 0,
5099
5308
  rowOf: (id) => rowById.get(id),
5100
- seats: {
5101
- setPending,
5102
- setConfirming,
5103
- setFailed,
5104
- setNotice
5105
- },
5309
+ seats,
5106
5310
  send: (body) => provision.send(body),
5107
5311
  onChanged
5108
5312
  });
5313
+ useSettleHold({
5314
+ pending,
5315
+ landed: pendingLanded(pending, updateMap, effective, workbench?.installed),
5316
+ t,
5317
+ onChanged,
5318
+ setPending,
5319
+ setFailed
5320
+ });
5109
5321
  const settled = marketSettled(freshness, installedRead);
5110
5322
  const nothing = shown.length === 0 && self === void 0 && installedList.length === 0 && pluginList.length === 0;
5111
5323
  if (!settled || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ h(MarketChecking, { t });
@@ -6864,6 +7076,10 @@ function ConfigSyncSection({ t }) {
6864
7076
  }) : null, rows.length > 0 ? /* @__PURE__ */ h("div", { className: ConfigSyncSection_module_default.foot }, /* @__PURE__ */ h(_deepseek_ai_dsh_client_ui_primitives.Button, {
6865
7077
  variant: "primary",
6866
7078
  size: "md",
7079
+ icon: /* @__PURE__ */ h(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {
7080
+ size: 16,
7081
+ "aria-hidden": "true"
7082
+ }),
6867
7083
  disabled: chosen.length === 0 || busy.busy,
6868
7084
  onClick: apply
6869
7085
  }, chosen.length === 0 ? t("configSync.applyNone") : busy.busy ? t("configSync.applying") : t("configSync.apply", { count: chosen.length }))) : null) : null));