@klarkxy/dsh-git-commit 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,13 +23,14 @@ Replace `web` with your profile name and restart it if DSH asks.
23
23
 
24
24
  Under **Plugins → Git Commit** you can choose the **Commit planning model** (empty follows the current session model) and turn on **Commit even without a model plan**, which makes one commit when the model is unavailable.
25
25
 
26
- If the model list cannot load, or some providers fail, use **Retry** beside the model picker. Your saved model stays selected.
26
+ If the model list cannot load, or some providers fail, use **Retry** beside the model picker; your saved model stays selected.
27
27
 
28
28
  ## Good to know
29
29
 
30
- - All uncommitted changes in the repository are included, including new files.
30
+ - The repository follows the conversation's current working directory; changing directories while a commit is being planned stops the run.
31
+ - All uncommitted changes are included, including new files.
31
32
  - It only commits; it never pushes.
32
- - If the run stops partway, finished commits stay and the panel shows what was left. Nothing is rolled back or retried automatically.
33
+ - If the run stops partway, finished commits stay and the panel shows what was left; nothing is rolled back or retried automatically.
33
34
  - The button is unavailable while the session is running.
34
35
 
35
36
  ## License
@@ -21,13 +21,14 @@ dsh plugin --profile web add @klarkxy/dsh-git-commit
21
21
  3. 确认分支、文件数和模型后提交。
22
22
  4. 面板会列出创建的提交。
23
23
 
24
- 在「插件 → Git 提交」里可以选择**提交规划模型**(留空跟随当前会话模型),也可以打开**模型不可用时仍然提交**,这时所有改动合并为一个提交。
24
+ 在「插件 → Git 提交」里可以选择**提交规划模型**(留空跟随当前会话模型),也可以打开**模型不可用时仍然提交**,模型不可用时改为只做一次提交。
25
25
 
26
- 模型列表加载失败或部分提供商不可用时,可在模型选择器旁点击「重试」。已保存的模型选择会保留。
26
+ 模型列表加载失败或部分提供商不可用时,可在模型选择器旁点击「重试」;已保存的模型选择会保留。
27
27
 
28
28
  ## 注意
29
29
 
30
- - 会提交仓库里的全部未提交改动,包括新文件。
30
+ - 仓库跟随对话当前的工作目录;规划提交期间切换目录会停止本次提交。
31
+ - 会提交仓库里全部未提交的改动,包括新文件。
31
32
  - 只提交,不推送。
32
33
  - 中途停止时,已完成的提交会保留,面板显示剩余内容;不会自动回滚或重试。
33
34
  - 会话运行期间按钮不可用。
@@ -262,7 +262,14 @@ function useModelCatalog(load, options = {}) {
262
262
  refresh
263
263
  };
264
264
  }
265
- /** Also accepts a mapped state from a caller that owns broader async orchestration. */
265
+ /**
266
+ * Also accepts a mapped state from a caller that owns broader async orchestration.
267
+ *
268
+ * The dsh-ui-* classes carry the styling and resolve through the consuming
269
+ * plugin's own officialUiCss scope (every consumer mounts its root class, so
270
+ * e.g. `.dsh-model-hub .dsh-ui-loading` paints this); dsh-model-catalog-status
271
+ * stays as a structural hook for tests and layout, never a colour source.
272
+ */
266
273
  function ModelCatalogStatus({ state, locale }) {
267
274
  const zh = locale?.toLowerCase().startsWith("zh") === true;
268
275
  const copy = zh ? {
@@ -278,15 +285,17 @@ function ModelCatalogStatus({ state, locale }) {
278
285
  };
279
286
  if (!state.loading && !state.error && state.failures.length === 0) return null;
280
287
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
281
- className: "dsh-model-catalog-status",
288
+ className: "dsh-model-catalog-status dsh-ui-stack",
282
289
  lang: zh ? "zh" : "en",
283
290
  children: [
284
291
  state.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
285
292
  role: "status",
293
+ className: "dsh-ui-loading",
286
294
  children: copy.loading
287
295
  }) : null,
288
296
  state.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
289
297
  role: "alert",
298
+ className: "dsh-ui-error dsh-ui-wrap",
290
299
  children: [
291
300
  copy.error,
292
301
  ": ",
@@ -295,20 +304,27 @@ function ModelCatalogStatus({ state, locale }) {
295
304
  }) : null,
296
305
  state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
297
306
  role: "status",
298
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
299
- failure.name || failure.id,
300
- ": ",
301
- failure.message
302
- ] }, `${failure.id}:${index}`)) })]
307
+ className: "dsh-ui-meta",
308
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: copy.partial }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
309
+ className: "dsh-ui-list",
310
+ children: state.failures.map((failure, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
311
+ failure.name || failure.id,
312
+ ": ",
313
+ failure.message
314
+ ] }, `${failure.id}:${index}`))
315
+ })]
303
316
  }) : null,
304
- state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
305
- type: "button",
306
- size: "sm",
307
- disabled: state.loading,
308
- onClick: () => {
309
- state.refresh();
310
- },
311
- children: copy.retry
317
+ state.error || state.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
318
+ className: "dsh-ui-actions",
319
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
320
+ type: "button",
321
+ size: "sm",
322
+ disabled: state.loading,
323
+ onClick: () => {
324
+ state.refresh();
325
+ },
326
+ children: copy.retry
327
+ })
312
328
  }) : null
313
329
  ]
314
330
  });
@@ -328,7 +344,8 @@ function ModelCatalogStatus({ state, locale }) {
328
344
  * Sticky group headings, subsequence ranking, highlight walking and focus
329
345
  * hand-back all match the host. SYNC POINT: when the host's ModelSelect or
330
346
  * ModelSelect.module.css changes, this copy must be re-transcribed; styles
331
- * below may only reference --dsw-* theme tokens, never literal colors.
347
+ * below may only reference allowlisted theme tokens (the --dsw-* set plus the
348
+ * host's --ds-* motion tokens), never literal colors.
332
349
  *
333
350
  * It statically imports the primitives, so only browser clients should load
334
351
  * this entry; host code and node tests use the core entry (`./index.ts`).
@@ -836,7 +853,8 @@ function ModelMenu(props) {
836
853
  * sticky group headings (MenuGroup's own CSS), and the l1-stroke prominent
837
854
  * elevation at portal z-index 1100. The trigger mirrors the composer's 28px
838
855
  * model pill, chevron rotating open. MenuSurface paints fill and backdrop.
839
- * Only --dsw-* theme tokens may appear here — a literal color is a bug.
856
+ * Only allowlisted --dsw- and --ds- theme tokens may appear here — a literal
857
+ * color is a bug.
840
858
  */
841
859
  const modelMenuCss = `
842
860
  .dsh-model-menu-root { min-width: 0; position: relative; display: inline-flex; }
@@ -859,12 +877,14 @@ const modelMenuCss = `
859
877
  display: flex;
860
878
  }
861
879
  .dsh-model-menu-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
862
- .dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
880
+ .dsh-model-menu-trigger:focus-visible:not([data-selection-focus]) { box-shadow: 0 0 0 var(--dsw-focus-ring-width) var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
863
881
  .dsh-model-menu-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
864
882
  .dsh-model-menu-triggerLabel { text-overflow: ellipsis; white-space: nowrap; min-width: 0; overflow: hidden; }
865
883
  .dsh-model-menu-triggerEffort { text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--dsw-alias-label-caption); flex-shrink: 1000; overflow: hidden; }
866
- .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform 0.12s; }
884
+ .dsh-model-menu-chevron { color: var(--dsw-alias-label-caption); flex: none; transition: transform var(--ds-transition-duration) var(--ds-ease-in-out); }
867
885
  .dsh-model-menu-chevronOpen { transform: rotate(180deg); }
886
+ /* Portal overlay: the host publishes no z-index token, so the card keeps the
887
+ * native menu layer's 1100 verbatim. */
868
888
  .dsh-model-menu-menu {
869
889
  z-index: 1100;
870
890
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
@@ -890,6 +910,8 @@ const modelMenuCss = `
890
910
  .dsh-model-menu-searchWithQuery { padding-right: 34px; }
891
911
  .dsh-model-menu-searchRow .dsh-model-menu-search input { padding: 0; font-size: 12px; line-height: normal; }
892
912
  .dsh-model-menu-searchRow .dsh-model-menu-search input::placeholder { color: var(--dsw-alias-label-caption); }
913
+ /* The clear button is a circle by geometry (border-radius: 50%), a shape the
914
+ * radius token scale does not describe. */
893
915
  .dsh-model-menu-searchClear {
894
916
  width: 24px;
895
917
  height: 24px;
@@ -1137,6 +1159,17 @@ ${scope} {
1137
1159
  /* --- Layout scaffolding. --- */
1138
1160
  & .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
1139
1161
  & .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
1162
+ /* Plugin detail pages share one reading column. These are layout recipes;
1163
+ * controls keep their own primitive styles. Form spacing follows the host's
1164
+ * settings-form/fields.module.css and SettingsForm.module.css. */
1165
+ & .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
1166
+ & .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
1167
+ &.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
1168
+ & .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
1169
+ & .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
1170
+ @media (max-width: 560px) {
1171
+ & .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
1172
+ }
1140
1173
  & .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
1141
1174
  & .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
1142
1175
  & .dsh-ui-spacer { flex: 1; min-width: 0; }
@@ -1164,6 +1197,9 @@ ${scope} {
1164
1197
  * official primitive, so this only owns the vertical rhythm. --- */
1165
1198
  & .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
1166
1199
  & .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
1200
+ /* Grid fields share a row, so the stacked-field divider must not win later in
1201
+ * the cascade. Reset it here, after the field recipe. */
1202
+ & .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
1167
1203
  & .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
1168
1204
  & .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
1169
1205
  & .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
@@ -1354,29 +1390,22 @@ ${scope} {
1354
1390
  & *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
1355
1391
  }
1356
1392
  }
1357
- `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
1393
+ `.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
1358
1394
  }
1359
1395
  //#endregion
1360
1396
  //#region ../dsh-plugin-kit/src/contracts.ts
1361
- /**
1362
- * Remote contribution for model-settings change notifications.
1363
- * Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
1364
- * subscribers degrade silently when that package is not installed.
1365
- * The stream carries invalidations only — `ready` is never a settings snapshot.
1366
- */
1367
- const MODEL_SETTINGS_CHANGES_REMOTE = {
1368
- package: "@klarkxy/dsh-model-hub",
1369
- descriptors: [{
1370
- id: "modelSettingsChanges/watch",
1371
- service: "modelSettingsChangesController",
1372
- namespace: "modelSettingsChanges",
1373
- method: "watch",
1374
- mode: "stream",
1375
- invocation: { kind: "direct" },
1376
- parameters: [],
1377
- cancellation: { parameter: "signal" }
1378
- }]
1379
- };
1397
+ /** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
1398
+ function modelSettingsChangesIdentity(packageName) {
1399
+ const scoped = /^@([^/]+)\/(.+)$/.exec(packageName);
1400
+ const parts = (scoped ? `${scoped[1]}/${scoped[2]}` : packageName).split(/[^A-Za-z0-9]+/).filter(Boolean);
1401
+ if (!parts.length) throw new Error("Model settings package name has no RPC segment");
1402
+ const segment = `${parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}ModelSettingsChanges`;
1403
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(segment)) throw new Error(`Model settings sync name is not an RPC segment: ${segment}`);
1404
+ return {
1405
+ service: segment,
1406
+ namespace: segment
1407
+ };
1408
+ }
1380
1409
  //#endregion
1381
1410
  //#region ../dsh-plugin-kit/src/model-settings-client.ts
1382
1411
  /** This stream carries invalidations only; ready is never a settings snapshot. */
@@ -1391,17 +1420,44 @@ function parseModelSettingsChangeFrame(value) {
1391
1420
  };
1392
1421
  throw new Error("Invalid model settings change frame");
1393
1422
  }
1394
- MODEL_SETTINGS_CHANGES_REMOTE.package, MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
1395
- ...descriptor,
1396
- result: {
1397
- mode: "strict",
1398
- typeSymbol: "ModelSettings.ChangeFrame",
1399
- create: () => ({ parse: parseModelSettingsChangeFrame })
1400
- }
1401
- }));
1423
+ /** One owner's contribution. Each package mounts only its own endpoint. */
1424
+ function modelSettingsChangesRemote(packageName, identity = modelSettingsChangesIdentity(packageName)) {
1425
+ return {
1426
+ package: packageName,
1427
+ descriptors: [{
1428
+ id: `${identity.namespace}/watch`,
1429
+ service: identity.service,
1430
+ namespace: identity.namespace,
1431
+ method: "watch",
1432
+ mode: "stream",
1433
+ invocation: { kind: "direct" },
1434
+ parameters: [],
1435
+ cancellation: { parameter: "signal" },
1436
+ result: {
1437
+ mode: "strict",
1438
+ typeSymbol: "ModelSettings.ChangeFrame",
1439
+ create: () => ({ parse: parseModelSettingsChangeFrame })
1440
+ }
1441
+ }]
1442
+ };
1443
+ }
1444
+ /** Mount from a synchronous apply. Disposal waits for an in-flight mount and
1445
+ * then withdraws it, so the contribution cannot outlive the plugin. */
1446
+ function mountModelSettingsChanges(ctx, packageName) {
1447
+ const contribution = modelSettingsChangesRemote(packageName);
1448
+ ctx.inject(["remote"], (injected) => {
1449
+ const scope = injected;
1450
+ const mounting = scope.remote.$mount(contribution);
1451
+ mounting.catch(() => {});
1452
+ scope.effect(() => async () => {
1453
+ await (await mounting.catch(() => void 0))?.();
1454
+ });
1455
+ });
1456
+ }
1402
1457
  /** Native RemoteStream owns carrier reconnects; no timers or browser transport.
1403
1458
  * A factory shares one logical stream while at least one subscriber remains. */
1404
- function createModelSettingsSubscriber(ctx, namespace) {
1459
+ function createModelSettingsSubscriber(ctx, packageName, identity = modelSettingsChangesIdentity(packageName), accept = (ns) => ns === packageName) {
1460
+ const endpoint = identity;
1405
1461
  const listeners = /* @__PURE__ */ new Set();
1406
1462
  let injection;
1407
1463
  let current;
@@ -1427,12 +1483,18 @@ function createModelSettingsSubscriber(ctx, namespace) {
1427
1483
  };
1428
1484
  const start = () => {
1429
1485
  const life = ++lifecycle;
1430
- injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
1486
+ injection = ctx.inject(["remote", `remote.${endpoint.namespace}`], (injected) => {
1431
1487
  if (life !== lifecycle || !listeners.size) return;
1432
1488
  const scope = injected;
1489
+ const service = scope.remote[endpoint.namespace];
1490
+ if (!service || !("watch" in service) || !service.watch) {
1491
+ report("Model settings sync service unavailable");
1492
+ return;
1493
+ }
1494
+ const watch = service.watch;
1433
1495
  const stream = scope.remote.$stream({
1434
1496
  name: "Model settings sync",
1435
- open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
1497
+ open: (signal) => watch(signal),
1436
1498
  ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
1437
1499
  });
1438
1500
  const session = {
@@ -1467,7 +1529,7 @@ function createModelSettingsSubscriber(ctx, namespace) {
1467
1529
  invalidate();
1468
1530
  } else {
1469
1531
  if (!ready) throw new Error("Model settings sync change arrived before ready");
1470
- if (namespace === "*" || frame.ns === namespace) invalidate();
1532
+ if (accept(frame.ns)) invalidate();
1471
1533
  }
1472
1534
  }
1473
1535
  } catch (error) {
@@ -2013,11 +2075,10 @@ const clientCss = `${officialUiCss("gcm-root")}${modelMenuCss}
2013
2075
  * of at the trigger. Naming both classes puts this rule at the same weight,
2014
2076
  * later in the sheet, so it wins. */
2015
2077
  .gcm-root.gcm-panel { position: fixed; width: min(360px, calc(100vw - 32px)); max-height: min(560px, calc(100vh - 32px)); overflow: auto; }
2016
- .gcm-model { display: flex; align-items: center; gap: 6px; min-width: 0; margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-primary); }
2017
- .gcm-commits { margin: 0; padding-inline-start: 18px; display: grid; gap: 2px; font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
2018
- .gcm-commits code { font-size: 12px; background: var(--dsw-alias-bg-layer-2); padding: 0 4px; border-radius: var(--dsw-radius-sm); }
2019
- .gcm-settings { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
2020
- .gcm-settings-status { display: flex; flex-direction: column; gap: 4px; }
2078
+ .gcm-root .gcm-model { display: flex; align-items: center; gap: 6px; min-width: 0; margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-primary); }
2079
+ .gcm-root .gcm-commits { margin: 0; padding-inline-start: 18px; display: grid; gap: 2px; font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
2080
+ .gcm-root .gcm-commits code { font-size: 12px; background: var(--dsw-alias-bg-layer-2); padding: 0 4px; border-radius: var(--dsw-radius-sm); }
2081
+ .gcm-root .gcm-settings-status { display: flex; flex-direction: column; gap: 4px; }
2021
2082
  `;
2022
2083
  /** Host UI language, following live switches with the host's English fallback. */
2023
2084
  function useHostLocale(client) {
@@ -2179,7 +2240,7 @@ function GitCommitSettingsPanel(props) {
2179
2240
  text.stale
2180
2241
  ]);
2181
2242
  if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2182
- className: "gcm-root gcm-settings",
2243
+ className: "gcm-root gcm-settings dsh-ui-page",
2183
2244
  children: [
2184
2245
  error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2185
2246
  role: "alert",
@@ -2212,7 +2273,7 @@ function GitCommitSettingsPanel(props) {
2212
2273
  const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
2213
2274
  const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
2214
2275
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2215
- className: "gcm-root gcm-settings",
2276
+ className: "gcm-root gcm-settings dsh-ui-page",
2216
2277
  "data-testid": "git-commit-settings",
2217
2278
  children: [
2218
2279
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2339,6 +2400,7 @@ function GitCommitSettingsPanel(props) {
2339
2400
  }
2340
2401
  function apply(ctx) {
2341
2402
  const client = ctx;
2403
+ mountModelSettingsChanges(ctx, PLUGIN_NAME);
2342
2404
  const settingsSubscriber = createModelSettingsSubscriber(ctx, PLUGIN_NAME);
2343
2405
  const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
2344
2406
  ctx.provide(GIT_COMMIT_CLIENT_SERVICE, { active: true });