@dsh-workbench/shared 1.0.0 → 1.1.0

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,3 +1,4 @@
1
+ import type { UnitTitle } from '../unit.js';
1
2
  export declare const BUNDLE_REGISTRIES_ROUTE = "bundle-registries";
2
3
  export declare const BUNDLE_INSPECT_ROUTE = "bundle-inspect";
3
4
  export declare const BUNDLE_REGISTRY_DEFAULT = "default";
@@ -114,11 +115,14 @@ export interface BundleRemovePayload {
114
115
  export interface BundleCancellationPayload {
115
116
  readonly cancellation: BundleCancellation | null;
116
117
  }
118
+ export type ModuleUpdateBlock = 'needs-workbench' | 'incompatible';
117
119
  export interface ModuleVersion {
118
120
  readonly unit: string;
119
121
  readonly pkg: string;
120
122
  readonly installed: string;
123
+ readonly title?: UnitTitle;
121
124
  readonly latest?: string;
125
+ readonly latestBlocked?: ModuleUpdateBlock;
122
126
  readonly local?: boolean;
123
127
  readonly refused?: readonly string[];
124
128
  }
@@ -128,6 +132,11 @@ export interface WorkbenchVersion {
128
132
  readonly latest?: string;
129
133
  readonly running?: string;
130
134
  readonly local?: boolean;
135
+ readonly followUp?: {
136
+ readonly id: string;
137
+ readonly kind: 'data-migration';
138
+ readonly hard: boolean;
139
+ };
131
140
  }
132
141
  export interface ModuleVersionsPayload {
133
142
  readonly modules: readonly ModuleVersion[];
@@ -4,6 +4,7 @@ export declare const MARKET_DISCOVER_ROUTE = "panels/market-discover";
4
4
  export declare const MARKET_DOC_ROUTE = "panels/market-doc";
5
5
  export declare const MARKET_PUBLISH_PREVIEW_ROUTE = "panels/market-publish-preview";
6
6
  export declare const MARKET_SOURCES_RESOURCE = "market-sources";
7
+ export declare const MARKET_SOURCES_ROUTE = "panels/market-sources";
7
8
  export declare const MARKET_WORKBENCH_LABEL = "workbench";
8
9
  export type MarketSourceKind = 'http' | 'git';
9
10
  export type MarketSourcePolicy = 'artifacts-in-repo' | 'catalog-only';
@@ -4,4 +4,5 @@ export const MARKET_DISCOVER_ROUTE = 'panels/market-discover';
4
4
  export const MARKET_DOC_ROUTE = 'panels/market-doc';
5
5
  export const MARKET_PUBLISH_PREVIEW_ROUTE = 'panels/market-publish-preview';
6
6
  export const MARKET_SOURCES_RESOURCE = 'market-sources';
7
+ export const MARKET_SOURCES_ROUTE = 'panels/market-sources';
7
8
  export const MARKET_WORKBENCH_LABEL = 'workbench';
@@ -1,18 +1,17 @@
1
- import type { UnitHostNeeds, UnitManifest } from '../contract/unit.js';
1
+ import type { UnitHostNeeds, UnitManifest, UnitTitle } from '../contract/unit.js';
2
2
  import { type Need } from './needs.js';
3
3
  /**
4
4
  * 声明协议当前版本;模块清单的 dsh.workbench.apiVersion 要写这个整数。
5
5
  */
6
- export declare const WORKBENCH_API_VERSION = 2;
6
+ export declare const WORKBENCH_API_VERSION = 3;
7
7
  /**
8
8
  * 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
9
9
  */
10
10
  export declare const SUPPORTED_API_VERSIONS: readonly number[];
11
11
  /**
12
- * 清单里那半张卡片声明:文案键加排序位;卡片默认显不显示不在这里(那是运行期面板配置的事)。
12
+ * 清单里那半张卡片声明:只剩排序位;卡片默认显不显示不在这里(那是运行期面板配置的事)。
13
13
  */
14
14
  export interface UnitCardDeclaration {
15
- readonly titleKey: string;
16
15
  readonly order: number;
17
16
  }
18
17
  /**
@@ -24,11 +23,12 @@ export interface UnitToolDeclaration {
24
23
  readonly key?: string;
25
24
  }
26
25
  /**
27
- * 模块清单里 dsh.workbench 那一段的完整形状(协议版本、单元 id、卡片、工具、宿主与环境需要)。
26
+ * 模块清单里 dsh.workbench 那一段的完整形状(协议版本、单元 id、名字、卡片、工具、宿主与环境需要)。
28
27
  */
29
28
  export interface UnitDeclaration {
30
29
  readonly apiVersion: number;
31
30
  readonly unit: string;
31
+ readonly title: UnitTitle;
32
32
  readonly card: UnitCardDeclaration | null;
33
33
  readonly tools: readonly UnitToolDeclaration[];
34
34
  readonly host: UnitHostNeeds;
@@ -42,6 +42,12 @@ export declare const DECLARATION_KEYS: readonly string[];
42
42
  * 协议要求必写的键;needs 不在其中(没有环境需要就不写)。
43
43
  */
44
44
  export declare const DECLARATION_REQUIRED_KEYS: readonly string[];
45
+ /**
46
+ * 读一份声明里的协议版本;不是对象或 apiVersion 不是整数时给 undefined。
47
+ * @param raw 清单里 dsh.workbench 那段原始值。
48
+ * @returns 声明声明的协议版本,读不出来时 undefined。
49
+ */
50
+ export declare function declarationApiVersionOf(raw: unknown): number | undefined;
45
51
  /**
46
52
  * 查清单里 dsh.workbench 那一段的形状,回一串给人看的问题(中文,带字段路径)。
47
53
  * @param subject 报错时用的主语,一般是包名。
@@ -1,8 +1,9 @@
1
+ import { unitTitleKey } from '../contract/unit.js';
1
2
  import { needsProblemsOf } from './needs.js';
2
3
  /**
3
4
  * 声明协议当前版本;模块清单的 dsh.workbench.apiVersion 要写这个整数。
4
5
  */
5
- export const WORKBENCH_API_VERSION = 2;
6
+ export const WORKBENCH_API_VERSION = 3;
6
7
  /**
7
8
  * 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
8
9
  */
@@ -13,6 +14,7 @@ export const SUPPORTED_API_VERSIONS = [WORKBENCH_API_VERSION];
13
14
  export const DECLARATION_KEYS = [
14
15
  'apiVersion',
15
16
  'unit',
17
+ 'title',
16
18
  'card',
17
19
  'tools',
18
20
  'host',
@@ -24,11 +26,13 @@ export const DECLARATION_KEYS = [
24
26
  export const DECLARATION_REQUIRED_KEYS = [
25
27
  'apiVersion',
26
28
  'unit',
29
+ 'title',
27
30
  'card',
28
31
  'tools',
29
32
  'host',
30
33
  ];
31
- const CARD_KEYS = ['titleKey', 'order'];
34
+ const CARD_KEYS = ['order'];
35
+ const TITLE_KEYS = ['zh', 'en'];
32
36
  const TOOL_KEYS = ['name', 'writes', 'key'];
33
37
  const TOOL_KEY_SHAPE = /^[a-z][a-zA-Z0-9]*$/;
34
38
  const HOST_KEYS = ['inject', 'optional'];
@@ -38,6 +42,17 @@ function text(value) {
38
42
  function isRecord(value) {
39
43
  return typeof value === 'object' && value !== null && !Array.isArray(value);
40
44
  }
45
+ /**
46
+ * 读一份声明里的协议版本;不是对象或 apiVersion 不是整数时给 undefined。
47
+ * @param raw 清单里 dsh.workbench 那段原始值。
48
+ * @returns 声明声明的协议版本,读不出来时 undefined。
49
+ */
50
+ export function declarationApiVersionOf(raw) {
51
+ if (!isRecord(raw))
52
+ return undefined;
53
+ const version = raw.apiVersion;
54
+ return Number.isInteger(version) ? version : undefined;
55
+ }
41
56
  function outsideKeys(value, allowed) {
42
57
  return Object.keys(value).filter((key) => !allowed.includes(key));
43
58
  }
@@ -66,10 +81,9 @@ export function declarationProblemsOf(subject, raw) {
66
81
  ];
67
82
  }
68
83
  if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version)) {
69
- const hint = version === 1
70
- ? ` —— v2 把 \`env\` 的三张表(command / plugins / libs)换成了 \`needs\`:` +
71
- '把外部需要逐条写成 `{ kind, name, purpose, ... }`(kind 是 bin / extension / lib / env),' +
72
- '再把 apiVersion 改成 2'
84
+ const hint = version === 2
85
+ ? ' —— v3 把卡片标题挪成了顶层 `title`(`{ "zh": "…", "en": "…" }` 字面量):' +
86
+ '把 `card.titleKey` 删掉、在顶层写 title,再把 apiVersion 改成 3'
73
87
  : '';
74
88
  return [
75
89
  `${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ` +
@@ -88,6 +102,23 @@ export function declarationProblemsOf(subject, raw) {
88
102
  (key === 'card' ? '(这个模块没有卡片就写 `"card": null`)' : ''));
89
103
  }
90
104
  }
105
+ const title = raw.title;
106
+ if (title !== undefined) {
107
+ if (!isRecord(title)) {
108
+ problems.push(`${subject}: dsh.workbench.title 要是对象(中英各一份),现在是 ${JSON.stringify(title)}`);
109
+ }
110
+ else {
111
+ const extra = outsideKeys(title, TITLE_KEYS);
112
+ if (extra.length > 0) {
113
+ problems.push(`${subject}: dsh.workbench.title 里有不认的键 ${listOf(extra)} —— title 只有 ${listOf(TITLE_KEYS)}`);
114
+ }
115
+ for (const language of TITLE_KEYS) {
116
+ if (text(title[language]) === '') {
117
+ problems.push(`${subject}: dsh.workbench.title.${language} 要是非空字符串(市场和面板里显示的名字)`);
118
+ }
119
+ }
120
+ }
121
+ }
91
122
  const card = raw.card;
92
123
  if (card !== undefined && card !== null) {
93
124
  if (!isRecord(card)) {
@@ -97,10 +128,7 @@ export function declarationProblemsOf(subject, raw) {
97
128
  const extra = outsideKeys(card, CARD_KEYS);
98
129
  if (extra.length > 0) {
99
130
  problems.push(`${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}` +
100
- `(卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`);
101
- }
102
- if (text(card.titleKey) === '') {
103
- problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
131
+ `(名字写在顶层的 title 里;卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`);
104
132
  }
105
133
  if (typeof card.order !== 'number' || !Number.isFinite(card.order)) {
106
134
  problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
@@ -193,15 +221,17 @@ export function readDeclaration(subject, raw) {
193
221
  if (needsRead.errors.length > 0)
194
222
  return { problems: needsRead.errors, warnings: needsRead.warnings };
195
223
  const card = declaration.card;
224
+ const id = text(declaration.unit);
196
225
  return {
197
226
  declaration,
198
227
  problems: [],
199
228
  manifest: {
200
- id: text(declaration.unit),
229
+ id,
230
+ title: { zh: text(declaration.title.zh), en: text(declaration.title.en) },
201
231
  card: card === null
202
232
  ? null
203
233
  : {
204
- titleKey: text(card.titleKey),
234
+ titleKey: unitTitleKey(id),
205
235
  order: card.order,
206
236
  },
207
237
  tools: declaration.tools.map((tool) => ({
@@ -1,4 +1,17 @@
1
1
  import type { ExtensionNeed, LibNeed, Need } from './needs.js';
2
+ /**
3
+ * 模块的人类可读名字:中英各一份,写在 package.json 的 dsh.workbench.title 里。
4
+ */
5
+ export interface UnitTitle {
6
+ readonly zh: string;
7
+ readonly en: string;
8
+ }
9
+ /**
10
+ * 外壳替模块名合成的词表键:模块自己不必写名字键,名字直接从清单里的 title 注册进来。
11
+ * @param unit 单元 id。
12
+ * @returns 该单元名字在词表里的键。
13
+ */
14
+ export declare function unitTitleKey(unit: string): string;
2
15
  /**
3
16
  * 一张卡片在清单里的静态声明(文案键加排序位);默认显不显示由运行期面板配置定。
4
17
  */
@@ -28,10 +41,11 @@ export interface PluginRelease {
28
41
  readonly latest?: string;
29
42
  }
30
43
  /**
31
- * 装配期用的模块清单:单元 id、卡片、工具、宿主服务与环境需要,比声明少一层协议包装。
44
+ * 装配期用的模块清单:单元 id、名字、卡片、工具、宿主服务与环境需要,比声明少一层协议包装。
32
45
  */
33
46
  export interface UnitManifest {
34
47
  readonly id: string;
48
+ readonly title?: UnitTitle;
35
49
  readonly card: UnitCard | null;
36
50
  readonly tools: readonly UnitTool[];
37
51
  readonly host: UnitHostNeeds;
@@ -1,3 +1,11 @@
1
+ /**
2
+ * 外壳替模块名合成的词表键:模块自己不必写名字键,名字直接从清单里的 title 注册进来。
3
+ * @param unit 单元 id。
4
+ * @returns 该单元名字在词表里的键。
5
+ */
6
+ export function unitTitleKey(unit) {
7
+ return `dsh-workbench.unit.${unit}.title`;
8
+ }
1
9
  /**
2
10
  * 模块声明的全部环境需要(没写就是空表)。
3
11
  * @param manifest 模块清单。
@@ -2,7 +2,7 @@ import type { NeedState } from '../contract/unit-capability.js';
2
2
  /**
3
3
  * 供给动作的那几档(装 / 升 / 卸,模块、共用依赖与插件各一套);写口按它分派。
4
4
  */
5
- export type ProvisionAction = 'install' | 'update' | 'uninstall' | 'update-dependency' | 'install-bundle' | 'install-version' | 'update-bundle' | 'remove-bundle' | 'cancel-install' | 'update-plugin' | 'uninstall-plugin' | 'update-workbench';
5
+ export type ProvisionAction = 'install' | 'update' | 'uninstall' | 'update-dependency' | 'install-bundle' | 'install-version' | 'update-bundle' | 'remove-bundle' | 'cancel-install' | 'update-plugin' | 'uninstall-plugin' | 'update-workbench' | 'migrate-workbench' | 'dismiss-followup';
6
6
  /**
7
7
  * 宿主对「这个单元现在能不能用」的那份读数:状态汇总加逐条 need 的细节。
8
8
  */
@@ -1,6 +1,6 @@
1
1
  import type { ProvisionAction } from '../contract/workbench-contract.js';
2
2
  export type { ProvisionAction };
3
- export declare const PROVISION_ACTIONS: readonly ["install", "update", "uninstall", "install-bundle", "install-version", "update-bundle", "remove-bundle", "cancel-install", "update-dependency", "update-plugin", "uninstall-plugin", "update-workbench"];
3
+ export declare const PROVISION_ACTIONS: readonly ["install", "update", "uninstall", "install-bundle", "install-version", "update-bundle", "remove-bundle", "cancel-install", "update-dependency", "update-plugin", "uninstall-plugin", "update-workbench", "migrate-workbench", "dismiss-followup"];
4
4
  export declare const DEPENDENCY_ACTIONS: readonly ["update-dependency"];
5
5
  export declare function isDependencyAction(action: ProvisionAction): action is (typeof DEPENDENCY_ACTIONS)[number];
6
6
  export type ProvisionTarget = 'unit' | 'id' | 'spec' | 'name' | 'requestId';
@@ -11,6 +11,8 @@ export const PROVISION_ACTIONS = [
11
11
  'update-plugin',
12
12
  'uninstall-plugin',
13
13
  'update-workbench',
14
+ 'migrate-workbench',
15
+ 'dismiss-followup',
14
16
  ];
15
17
  export const DEPENDENCY_ACTIONS = ['update-dependency'];
16
18
  export function isDependencyAction(action) {
@@ -27,8 +29,10 @@ export function provisionTargetOf(action) {
27
29
  return 'name';
28
30
  if (action === 'update-plugin' || action === 'uninstall-plugin')
29
31
  return 'name';
30
- if (action === 'update-workbench')
32
+ if (action === 'update-workbench' || action === 'migrate-workbench')
31
33
  return 'name';
34
+ if (action === 'dismiss-followup')
35
+ return 'id';
32
36
  if (action === 'cancel-install')
33
37
  return 'requestId';
34
38
  return 'unit';
@@ -58,8 +62,10 @@ function requestOf(action, value) {
58
62
  return { action, name: value };
59
63
  if (action === 'update-plugin' || action === 'uninstall-plugin')
60
64
  return { action, name: value };
61
- if (action === 'update-workbench')
65
+ if (action === 'update-workbench' || action === 'migrate-workbench')
62
66
  return { action, name: value };
67
+ if (action === 'dismiss-followup')
68
+ return { action, id: value };
63
69
  if (action === 'cancel-install')
64
70
  return { action, requestId: value };
65
71
  return { action, unit: value };
@@ -3,6 +3,9 @@ export interface SupplyDeclaration {
3
3
  readonly id: string;
4
4
  readonly needs?: readonly Need[];
5
5
  }
6
+ export interface SupplyIndexOptions {
7
+ readonly env?: Readonly<Record<string, string | undefined>>;
8
+ }
6
9
  export interface SupplyEntry {
7
10
  readonly key: string;
8
11
  readonly kind: 'extension' | 'lib';
@@ -31,4 +34,4 @@ export interface SupplyIndex {
31
34
  export declare function npmSourceOf(sources: readonly SupplySource[] | undefined): Extract<SupplySource, {
32
35
  kind: 'npm';
33
36
  }> | undefined;
34
- export declare function buildSupplyIndex(declarations: readonly SupplyDeclaration[]): SupplyIndex;
37
+ export declare function buildSupplyIndex(declarations: readonly SupplyDeclaration[], options?: SupplyIndexOptions): SupplyIndex;
@@ -1,3 +1,4 @@
1
+ import { npmRegistryOf } from './plugin-source.js';
1
2
  export function npmSourceOf(sources) {
2
3
  return sources?.find((source) => source.kind === 'npm');
3
4
  }
@@ -29,18 +30,22 @@ function packageOf(need) {
29
30
  function keyOf(kind, pkg) {
30
31
  return `${kind}:${pkg}`;
31
32
  }
32
- function sourceCompatible(left, right) {
33
+ function registryAddressOf(source, env) {
34
+ const resolved = npmRegistryOf(source, env);
35
+ return resolved.ok ? resolved.value : undefined;
36
+ }
37
+ function sourceCompatible(left, right, env) {
33
38
  if (left.kind !== right.kind)
34
39
  return false;
35
40
  if (left.kind === 'npm' && right.kind === 'npm') {
36
41
  if (text(left.package) !== '' && text(right.package) !== '' && text(left.package) !== text(right.package)) {
37
42
  return false;
38
43
  }
39
- if (text(left.registryEnv) !== text(right.registryEnv))
40
- return false;
41
- const l = text(left.registryDefault);
42
- const r = text(right.registryDefault);
43
- return l === '' || r === '' || l === r;
44
+ const l = registryAddressOf(left, env);
45
+ const r = registryAddressOf(right, env);
46
+ if (l !== undefined && r !== undefined)
47
+ return l === r;
48
+ return text(left.registryEnv) === text(right.registryEnv);
44
49
  }
45
50
  if (left.kind === 'git' && right.kind === 'git')
46
51
  return text(left.url) === text(right.url);
@@ -49,12 +54,12 @@ function sourceCompatible(left, right) {
49
54
  }
50
55
  return false;
51
56
  }
52
- function sourcesCompatible(left, right) {
57
+ function sourcesCompatible(left, right, env) {
53
58
  if (left.length !== right.length)
54
59
  return false;
55
60
  return left.every((source, index) => {
56
61
  const other = right[index];
57
- return other !== undefined && sourceCompatible(source, other);
62
+ return other !== undefined && sourceCompatible(source, other, env);
58
63
  });
59
64
  }
60
65
  function mergedSource(left, right) {
@@ -69,13 +74,13 @@ function mergedSource(left, right) {
69
74
  }
70
75
  return left;
71
76
  }
72
- function mergeSources(entry, sources) {
77
+ function mergeSources(entry, sources, env) {
73
78
  if (!entry.declared) {
74
79
  entry.declared = true;
75
80
  entry.sources = [...sources];
76
81
  return true;
77
82
  }
78
- if (!sourcesCompatible(entry.sources, sources)) {
83
+ if (!sourcesCompatible(entry.sources, sources, env)) {
79
84
  entry.conflicted = true;
80
85
  return false;
81
86
  }
@@ -100,7 +105,8 @@ function addDeclarer(entry, unit, commands) {
100
105
  if (!entry.commands.includes(command))
101
106
  entry.commands.push(command);
102
107
  }
103
- export function buildSupplyIndex(declarations) {
108
+ export function buildSupplyIndex(declarations, options = {}) {
109
+ const env = options.env ?? {};
104
110
  const entries = new Map();
105
111
  const byName = new Map();
106
112
  const conflicts = [];
@@ -138,7 +144,7 @@ export function buildSupplyIndex(declarations) {
138
144
  else {
139
145
  addDeclarer(entry, declaration.id, []);
140
146
  }
141
- const same = mergeSources(entry, need.sources ?? []);
147
+ const same = mergeSources(entry, need.sources ?? [], env);
142
148
  if (!same) {
143
149
  conflicts.push({
144
150
  kind: 'source-mismatch',
package/dist/i18n/en.js CHANGED
@@ -129,14 +129,14 @@ export const en = {
129
129
  'panels.market.update': 'Update',
130
130
  'panels.market.swapSlow': 'Not swapped yet — maybe a slow or failed download. Press Update again; if it stays like this, restart dsh.',
131
131
  'panels.market.removeSlow': 'Not removed yet — maybe a slow network or an uninstall that did not finish. Check again in a moment; if it stays like this, restart dsh.',
132
- 'panels.market.updateNoChange': 'Not updated: you were on {from}, and after the run the disk still has that version; the newest on the registry is {latest}.',
132
+ 'panels.market.updateNoChange': 'Update failed: still {from}. The newest on the registry is {latest} — press Update again.',
133
133
  'panels.market.updateUnknownVersion': '(unknown)',
134
- 'panels.market.updateGone': 'Not updated: after the run the module cannot be read from disk.',
135
- 'panels.market.updateRestored': 'Not updated: the new version was missing from disk, so {version} was put back.',
134
+ 'panels.market.updateGone': 'Update failed: the module is gone after the update.',
135
+ 'panels.market.updateRestored': 'Update failed: the new version could not be found after the update, so {version} was put back.',
136
136
  'panels.market.updateBehind': 'Installed {installed}, but the newest on the registry is {latest} — not the latest; you can update once more.',
137
- 'panels.market.selfUpdateFailed': 'Self-update failed: {reason}',
137
+ 'panels.market.selfUpdateFailed': 'Workbench update failed: {reason}',
138
138
  'panels.market.selfUpdateUnknown': 'the outside process left no reason (see ~/.dsh/workbench/self-update.log)',
139
- 'panels.market.selfUpdateRolledBack': 'Not swapped; rolled back to {version}.',
139
+ 'panels.market.selfUpdateRolledBack': 'Workbench update failed; rolled back to {version}.',
140
140
  'panels.market.uninstall': 'Uninstall',
141
141
  'panels.market.uninstallLabel': 'Uninstall {name}',
142
142
  'panels.market.installing': 'Installing…',
@@ -158,9 +158,27 @@ export const en = {
158
158
  'panels.market.empty': 'No modules available',
159
159
  'panels.market.sectionPlugins': 'Installed opencli plugins',
160
160
  'panels.market.moduleOutdated': 'update available',
161
+ 'panels.market.updateNeedsWorkbench': 'this version needs a newer workbench first',
162
+ 'panels.market.updateIncompatible': 'the new version does not match this workbench, so it cannot be installed now',
161
163
  'panels.market.moduleRefused': 'This module does not match this workbench, so it cannot be used for now.',
162
164
  'panels.market.moduleRestartHint': 'restart dsh once to bring it up',
163
165
  'panels.market.pendingRestart': 'restart required',
166
+ 'panels.market.pendingFollowUp': 'old data to migrate',
167
+ 'panels.market.migrationHint': 'old sessions and projects are not merged yet',
168
+ 'panels.market.migrateAction': 'Migrate old data',
169
+ 'panels.market.migrating': 'Migrating…',
170
+ 'panels.market.migrateConfirmTitle': 'Migrate old data?',
171
+ 'panels.market.migrateConfirmDetail': 'Quit dsh web / Desktop first. Only missing data is added; nothing is overwritten.',
172
+ 'panels.market.migrateConfirmAck': "I've quit dsh web / Desktop",
173
+ 'panels.market.migrateConfirmYes': 'Start migration',
174
+ 'panels.market.migrateDone': 'Migration complete: {added} projects added; restart the workbench and dsh web to see them.',
175
+ 'panels.market.dismissFollowUp': "Don't remind me",
176
+ 'panels.market.dismissing': 'Dismissing…',
177
+ 'panels.market.dismissFollowUpTitle': 'Stop reminding about this?',
178
+ 'panels.market.dismissFollowUpDetail': "The reminder will be hidden from the market, but old data will not be migrated or deleted. Old sessions and projects stay in the old directory and won't appear in the new version; you can still run dsh-workbench migrate later.",
179
+ 'panels.market.dismissFollowUpAck': "I understand the old data won't appear automatically",
180
+ 'panels.market.dismissFollowUpYes': "Don't remind me",
181
+ 'panels.market.dismissed': 'No longer reminded; old data stays in the old directory and can still be migrated with dsh-workbench migrate.',
164
182
  'panels.market.workbenchName': 'Workbench',
165
183
  'panels.market.pluginBroken': 'This plugin install is broken: it cannot be identified. Uninstall it first, then ask the agent to reinstall it by package name.',
166
184
  'panels.market.restartNeeded': 'The new version is installed — restart once to pick it up.',
@@ -244,7 +262,7 @@ export const en = {
244
262
  'panels.market.add.exampleTitle': 'Examples',
245
263
  'panels.market.add.exampleFill': 'Fill in an example',
246
264
  'panels.market.add.registryLabel': 'Source',
247
- 'panels.market.add.registryProfile': "This profile's registry",
265
+ 'panels.market.add.registryProfile': "This workbench's registry",
248
266
  'panels.market.add.registryPnpm': "pnpm's own registry",
249
267
  'panels.market.add.check': 'Check',
250
268
  'panels.market.add.checking': 'Checking…',
@@ -265,12 +283,16 @@ export const en = {
265
283
  'panels.market.tabs': 'The two faces of the module market',
266
284
  'panels.tab.discover': 'Discover',
267
285
  'panels.discover.loading': 'Reading the index',
286
+ 'panels.discover.slowHint': 'Slow to load: finish any authorization prompt, or switch sources.',
268
287
  'panels.discover.lead': 'Browse modules from an index source; install and go.',
269
288
  'panels.discover.addSource': 'Add',
270
289
  'panels.discover.addingSource': 'Adding…',
271
290
  'panels.discover.addFirstSource': 'Add index source',
272
291
  'panels.discover.removeSource': 'Remove',
273
292
  'panels.discover.removeSourceNamed': 'Remove {url}',
293
+ 'panels.discover.useSource': 'Use',
294
+ 'panels.discover.useSourceNamed': 'Use {label}',
295
+ 'panels.discover.sourceCurrent': 'In use',
274
296
  'panels.discover.refresh': 'Refresh',
275
297
  'panels.discover.search': 'Search modules',
276
298
  'panels.discover.all': 'All',
@@ -282,8 +304,8 @@ export const en = {
282
304
  'panels.discover.sourcePlaceholder': 'https://example.com/index.json or git@git.example.com:wb/market.git',
283
305
  'panels.discover.sourceRef': 'Branch',
284
306
  'panels.discover.sourcePath': 'Index file',
285
- 'panels.discover.sourceLocal': 'Local clone (optional)',
286
- 'panels.discover.sourceLocalHint': 'Leave empty to clone into ~/.dsh/workbench/market-index/.',
307
+ 'panels.discover.sourceLocal': 'Local copy folder (optional)',
308
+ 'panels.discover.sourceLocalHint': 'Leave empty to keep it in ~/.dsh/workbench/market-index/.',
287
309
  'panels.discover.save': 'Save and refresh',
288
310
  'panels.discover.cancel': 'Cancel',
289
311
  'panels.discover.retry': 'Retry',
@@ -296,9 +318,9 @@ export const en = {
296
318
  'panels.discover.missingIndex': 'This repository has no {path} yet.',
297
319
  'panels.discover.initTitle': 'Initialize this repository as a module market index?',
298
320
  'panels.discover.initCta': 'Initialize market index',
299
- 'panels.discover.initWhat': 'It will write market.json and README.md on {branch}, commit once, and push. Modules you publish from this workbench will commit their artifacts under packages/<unit>/. The repository stays owned by its owner.',
321
+ 'panels.discover.initWhat': 'It will write market.json and README.md on {branch}, commit once, and push. Modules you publish from this workbench will commit their build files under packages/<unit>/. The repository stays owned by its owner.',
300
322
  'panels.discover.initBranch': 'Branch',
301
- 'panels.discover.initBranchHint': 'The index and artifacts go to this branch; an empty repository gets it created.',
323
+ 'panels.discover.initBranchHint': 'The index and build files go to this branch; an empty repository gets it created.',
302
324
  'panels.discover.initBranchBad': 'Use only letters, digits, dots, underscores, slashes, or dashes in a branch name.',
303
325
  'panels.discover.initOk': 'Initialize',
304
326
  'panels.discover.initBusy': 'Initializing…',
@@ -402,11 +424,11 @@ export const en = {
402
424
  'panels.market.add.doneApplied': 'Installed and live.',
403
425
  'panels.market.add.doneRestart': 'Installed — restart once to pick it up.',
404
426
  'panels.market.add.restartWhy': 'Why this needs a restart: {reason}',
405
- 'panels.market.add.restartWhy.no-hmr': 'this configuration only applies changes at startup (hot reload is off)',
427
+ 'panels.market.add.restartWhy.no-hmr': 'this configuration is only read at startup, so a restart is needed',
406
428
  'panels.market.add.restartWhy.package-moved': 'an installed package now resolves to another directory, or the change replaced something dsh itself provides',
407
429
  'panels.market.add.restartWhy.host-module': 'the change reaches modules dsh itself loaded',
408
- 'panels.market.add.restartWhy.unmanaged-plugin': 'the change reaches a plugin running outside the plugin list',
409
- 'panels.market.add.restartWhy.module-comparison': 'dsh could not compare loaded plugins with their files',
430
+ 'panels.market.add.restartWhy.unmanaged-plugin': 'the change reaches a plugin that is running but not on the install list',
431
+ 'panels.market.add.restartWhy.module-comparison': 'dsh could not confirm that the running plugin is the copy installed on this machine',
410
432
  'panels.market.add.cancelled': 'Cancelled.',
411
433
  'panels.market.add.failed': "Didn't work: {reason}",
412
434
  'panels.market.add.removed': 'It has been removed.',
@@ -416,10 +438,10 @@ export const en = {
416
438
  'panels.market.add.removedRestart': 'Removed — restart once to finish.',
417
439
  'panels.market.add.remove': 'Remove it',
418
440
  'panels.market.add.problem.invalidSpec': "That is not a spec we accept: it cannot be empty or start with a dash.",
419
- 'panels.market.add.problem.alreadyInstalled': 'That package is already in the profile (add @version to switch versions).',
441
+ 'panels.market.add.problem.alreadyInstalled': 'That package is already in this workbench (add @version to switch versions).',
420
442
  'panels.market.add.problem.notFound': 'No such package on that source. Try another name or version.',
421
443
  'panels.market.add.problem.notAPackage': 'There is no package at that location. Pick a folder that contains a package.json.',
422
- 'panels.market.add.problem.notABundle': 'It is an npm package, but not a dsh plugin (it is missing the dsh wiring declaration).',
444
+ 'panels.market.add.problem.notABundle': 'It is an npm package, but not a dsh plugin (dsh cannot find what to load inside it).',
423
445
  'panels.market.add.problem.network': 'The source is unreachable. Check the network and retry.',
424
446
  'panels.market.add.problem.unknown': 'The check did not pass (reason unknown). Open the install details for the raw output, or retry.',
425
447
  'panels.market.add.reason.invalidSpec': 'the spec is not accepted',
@@ -427,7 +449,7 @@ export const en = {
427
449
  'panels.market.add.reason.notBundle': 'it is not a dsh plugin package',
428
450
  'panels.market.add.reason.notRemovable': 'it ships with the installation and cannot be removed',
429
451
  'panels.market.add.reason.bundleInUse': 'something is still using it',
430
- 'panels.market.add.reason.stopProfile': 'this configuration has no hot reload, so stop the process first',
452
+ 'panels.market.add.reason.stopProfile': 'this configuration needs a restart, so stop the workbench first',
431
453
  'panels.market.add.reason.staleApproval': 'the scripts to allow changed — start over',
432
454
  'panels.market.add.reason.operationError': 'pnpm failed (see the install output)',
433
455
  'panels.market.add.reason.buildBlocked': 'pnpm blocked install scripts',
@@ -457,8 +479,8 @@ export const en = {
457
479
  'blocker.forbidden.titleGeneric': 'No access',
458
480
  'blocker.forbidden.hint': 'You are signed in, but this project/repo is not permitted for your account.',
459
481
  'blocker.forbidden.open': 'Open permissions page',
460
- 'blocker.timeout.hint': 'The headless browser may not have started; retrying usually helps.',
461
- 'blocker.exec.hint': 'Make sure opencli is installed and on PATH (which opencli).',
482
+ 'blocker.timeout.hint': 'The background browser may not have started; retrying usually helps.',
483
+ 'blocker.exec.hint': 'Make sure opencli is installed and runs from your terminal (which opencli).',
462
484
  'blocker.unknown.title': 'Something went wrong',
463
485
  'panels.reading': 'Reading the panel setup…',
464
486
  'panels.cardBroken': 'This module will not render: {unit} ({reason})',
@@ -472,7 +494,7 @@ export const en = {
472
494
  'env.elapsed': 'Waiting {seconds}s',
473
495
  'env.stepFailed.install': 'install failed',
474
496
  'env.stepFailed.path': 'installed but not found',
475
- 'env.stepFailed.shell': 'no shell channel',
497
+ 'env.stepFailed.shell': 'cannot reach the command line',
476
498
  'env.toolchain.ok': 'Looks good',
477
499
  'env.toolchain.warn': 'Almost ready',
478
500
  'env.opencli.ok': 'Ready',
@@ -489,10 +511,10 @@ export const en = {
489
511
  'env.fix.title': 'How to fix it',
490
512
  'env.fix.why': 'The workbench uses its own background tool (installed separately from the one in your terminal), and it is not set up on this machine yet.',
491
513
  'env.fix.whyPath': 'The background tool is installed, but this environment cannot find it \u2014 usually a PATH or shell-session issue.',
492
- 'env.fix.whyShell': 'The workbench has no shell channel, so it cannot fetch data \u2014 you cannot install this from here; someone needs to fix the background environment.',
514
+ 'env.fix.whyShell': 'The workbench cannot reach the command line on this machine, so it cannot fetch data \u2014 you cannot install this from here; someone needs to fix the background environment.',
493
515
  'env.fix.step': 'Run this in a terminal, then hit Retry:',
494
516
  'env.fix.stepPath': 'Open a new terminal window (or make sure npm\u2019s global bin directory is on PATH), then hit Retry.',
495
- 'env.fix.stepShell': 'Restart the workbench (or dsh); if it keeps happening, the shell channel is not wired up.',
517
+ 'env.fix.stepShell': 'Restart the workbench (or dsh); if it keeps happening, the workbench still cannot reach the command line.',
496
518
  'env.fix.permission': 'Permission denied: rerun with sudo, or point npm\u2019s global directory at your home.',
497
519
  'env.fix.network': 'Network unreachable: check your network or proxy, then retry.',
498
520
  'env.fix.details': 'Details (for troubleshooting)',
@@ -521,9 +543,9 @@ export const en = {
521
543
  'env.warm.done': 'Ready ({n} sites)',
522
544
  'env.warm.short': 'Could not sign in early this time \u2014 nothing breaks; the first read fills it in',
523
545
  'env.unit.failed': 'This module cannot start',
524
- 'env.unit.failedHint': 'It failed while assembling itself; other modules are unaffected.',
525
- 'env.unit.unchecked': 'Cannot check: no shell channel',
526
- 'env.unit.uncheckedHint': 'The module is not the problem \u2014 the shell channel is unavailable. Restart the workbench.',
546
+ 'env.unit.failedHint': 'It failed while starting up; other modules are unaffected.',
547
+ 'env.unit.unchecked': 'Cannot check: no command line',
548
+ 'env.unit.uncheckedHint': 'The module is not the problem \u2014 the workbench cannot reach the command line. Restart the workbench.',
527
549
  'adapterSet.not-configured': 'No adapter set configured; using the default.',
528
550
  'adapterSet.no-profile': 'No profile found, so the adapter set cannot load; using the default.',
529
551
  'adapterSet.malformed': 'An adapter set must be a package name (for example @acme/adapters-set); using the default.',
@@ -566,7 +588,7 @@ export const en = {
566
588
  'sidebarEdition.official': 'Official',
567
589
  'sidebarEdition.officialHint': 'Matches dsh exactly and stays stable: search, view options, show more, archive, add workspace — no waiting for us to catch up. Pick it to keep things simple.',
568
590
  'sidebarEdition.custom': 'Custom',
569
- 'sidebarEdition.customHint': 'Built for multi-project work: one project per dropdown, recency-first sessions, edge fades, marquee, DIY new session. Easier when you juggle projects.',
591
+ 'sidebarEdition.customHint': 'Built for multi-project work: one project per dropdown, recency-first sessions, long names scroll, edges fade, DIY new session. Easier when you juggle projects.',
570
592
  'sidebarEdition.saving': 'Configuring…',
571
593
  'sidebarEdition.reloadRequired': 'Saved. Reload the page to apply — no workbench restart needed.',
572
594
  'sidebarEdition.reload': 'Reload page',
@@ -595,25 +617,25 @@ export const en = {
595
617
  'record.act.halt': 'Halt',
596
618
  'approval.record.site': 'Site',
597
619
  'approval.record.what': 'Records',
598
- 'approval.record.lands': 'Lands in',
620
+ 'approval.record.lands': 'Saves to',
599
621
  'approval.record.noUrl': 'no site given (this call gets refused)',
600
622
  'approval.record.oneAction': 'one action',
601
623
  'approval.record.noSwitch': 'it will not switch you back (the leftbar footer shows the state)',
602
- 'approval.longTask.what': 'Stops',
603
- 'approval.longTask.signal': 'stopping only sends an interrupt signal — how the platform-side run finishes is up to the module',
624
+ 'approval.longTask.what': 'Task to stop',
625
+ 'approval.longTask.signal': 'stopping only sends an interrupt signal \u2014 wrapping up the run is up to the module',
604
626
  'approval.publish.module': 'Module',
605
627
  'approval.publish.target': 'Target',
606
628
  'approval.publish.version': 'Version',
607
629
  'approval.publish.author': 'Author',
608
- 'approval.publish.authorMissing': 'unsigned (commits use the local git identity)',
630
+ 'approval.publish.authorMissing': 'unsigned',
609
631
  'approval.publish.registry': 'Registry',
610
632
  'approval.publish.repo': 'Repository',
611
633
  'approval.publish.branch': 'Branch',
612
634
  'approval.publish.defaultBranch': 'default branch',
613
- 'approval.publish.artifacts': 'Artifacts',
635
+ 'approval.publish.artifacts': 'Build files',
614
636
  'approval.publish.index': 'Index',
615
637
  'approval.publish.commits': 'Commits',
616
- 'approval.publish.twoCommits': 'artifacts + market.json in 2 commits, one push',
638
+ 'approval.publish.twoCommits': 'build files + market.json in 2 commits, one push',
617
639
  'approval.publish.reading': 'Reading…',
618
640
  'approval.publish.changelog': 'Changelog for {version}',
619
641
  'approval.publish.changelogMissing': 'No changelog entry for {version}; publishing will be blocked',