@chengzhao-dev/dsh-cpp-workflow 0.6.2 → 0.6.4

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.en.md CHANGED
@@ -19,13 +19,13 @@ The exception material follows the C++ Core Guidelines' Error handling section a
19
19
  Pinned npm install:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.2
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.4
23
23
  ```
24
24
 
25
25
  The matching Git tag:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.2
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.4
29
29
  ```
30
30
 
31
31
  The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
package/README.md CHANGED
@@ -19,13 +19,13 @@
19
19
  npm 安装钉死版本:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.2
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.4
23
23
  ```
24
24
 
25
25
  同版本的 Git 标签:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.2
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.4
29
29
  ```
30
30
 
31
31
  桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
package/lib/client.js CHANGED
@@ -42,6 +42,7 @@ const TAG_TEXT = {
42
42
  };
43
43
  /** 开关型标签在设置页的标题。 */
44
44
  const TAG_LABEL = {
45
+ file: '@file',
45
46
  brief: '@brief',
46
47
  author: '@author',
47
48
  date: '@date',
@@ -194,10 +195,7 @@ function snapshotText(raw) {
194
195
 
195
196
  // 客户端半侧:设置导航里的「C++ 工程流程」页与插件页的配置卡片共用同一个组件。
196
197
  // 标签策略在本文件上方内联(由 scripts/building-client.mjs 从 src/comment-policy.js
197
- // 拼进来),所以预览、默认值与服务端快照永远同源;这里只做表单与排版。
198
- //
199
- // 版式参照 VS Code 设置页:左侧标签与说明、右侧控件,行间细分隔线;
200
- // 修改过默认值的行在标签前加一个圆点,底部固定一条保存栏。
198
+ // 拼进来),所以预览、默认值与服务端快照永远同源;这里只做表单与外观。
201
199
  const React = require('react')
202
200
  const h = React.createElement
203
201
 
@@ -206,67 +204,75 @@ const PACKAGE_NAME = '@chengzhao-dev/dsh-cpp-workflow'
206
204
  const STRINGS = {
207
205
  zh: {
208
206
  nav: 'C++ 工程流程',
209
- title: '文档注释',
210
- intro: '设置文件头与函数声明注释里默认写出的 Doxygen 标签;改动影响之后写出的注释,已有源码保持原样。',
207
+ eyebrow: '文档注释',
208
+ title: '注释默认带哪些标签',
209
+ intro: '这里只决定代理新写注释时的默认值。源码里已有的注释不会被改写;保存后,下一次读取技能时生效。',
211
210
  header: '文件头',
212
- headerHint: '每个手写源文件开头的一段 /// 注释。',
213
- contract: '函数契约',
214
- contractHint: '函数声明注释里要求的参数与失败信息。',
211
+ headerHint: '每个手写源文件开头的 /// 注释块。',
212
+ briefHint: '一句话说明这个文件定义或实现什么。留空时,每个文件自己写。',
213
+ authorHint: '作者署名。默认不写;打开并填写后才写进文件头。',
214
+ dateHint: '创建或最后修改日期。默认不写。',
215
+ copyrightHint: '版权声明。默认不写。',
216
+ versionHint: '文件版本号。默认不写。',
217
+ paramHint: '函数参数的含义、范围或所有权。',
218
+ returnHint: '返回值在什么条件下成立。返回类型自明时可以省略。',
219
+ throwsHint: '抛出的异常类型与条件。不抛异常的函数不写。',
220
+ contractHint: '函数声明的注释里必须交代的参数与失败信息。',
215
221
  extra: '更多标签',
216
- extraHint: '需要时在单个函数上补写。',
217
- file: '@file',
218
- fileHint: '取自当前文件名。',
219
- briefHint: '一句话说明这个文件定义或实现什么;留空则由文件自述。',
220
- authorHint: '作者署名;留空不写。',
221
- dateHint: '创建或修改日期;留空不写。',
222
- copyrightHint: '版权声明;留空不写。',
223
- versionHint: '文件版本号;留空不写。',
224
- emptyValue: '留空不写',
225
- briefEmpty: '留空则由文件自述',
222
+ extraHint: '默认关闭,需要时在单个函数上补写。',
226
223
  preview: '预览',
227
- previewHint: '按当前开关生成;文件名以占位符表示。',
224
+ previewHint: '按当前开关生成,文件名用占位符表示。',
228
225
  save: '保存',
229
- reset: '重置',
226
+ reset: '恢复出厂值',
230
227
  saved: '已保存,下一次读取技能时生效。',
231
- rejected: '宿主拒绝了这次保存:配置可能已在别处修改,请重置后再试。',
232
- unsaved: '有未保存的改动。',
228
+ rejected: '保存被拒绝:配置可能已在别处修改,请恢复出厂值后再试。',
229
+ unsaved: '有未保存的改动',
233
230
  loading: '正在读取配置…',
234
231
  unreadable: '当前账户不能修改这份配置。',
235
- readOnly: '此处显示出厂值;修改在「插件」页的配置卡片里进行。',
232
+ readOnly: '这里显示出厂值。要改,请到「插件」页打开 dsh-cpp-workflow 的配置卡片。',
236
233
  openSettings: '打开插件配置',
237
- modified: '与出厂值不同',
234
+ fixed: '跟随文件名',
235
+ fixedHint: '值由实际文件决定,不写进配置。',
236
+ emptyValue: '留空则由文件自己写',
237
+ blankBrief: '留空则文件自述',
238
+ on: '写出',
239
+ off: '不写',
238
240
  },
239
241
  en: {
240
242
  nav: 'C++ Workflow',
241
- title: 'Documentation comments',
242
- intro: 'Choose the Doxygen tags written by default in file headers and declaration comments. Changes affect new comments; existing sources stay as they are.',
243
+ eyebrow: 'Doxygen comments',
244
+ title: 'Which tags comments carry by default',
245
+ intro: 'These defaults apply to comments the agent writes from now on. Existing sources are never rewritten; saving takes effect on the next skill read.',
243
246
  header: 'File header',
244
- headerHint: 'The /// block at the top of every hand-written source.',
245
- contract: 'Function contract',
246
- contractHint: 'Parameter and failure tags required in declaration comments.',
247
+ headerHint: 'The /// block at the top of each hand-written source file.',
248
+ briefHint: 'One sentence saying what the file defines or implements. Leave the value to each file.',
249
+ authorHint: 'Author name. Off by default; written only when filled.',
250
+ dateHint: 'Created or last modified date. Off by default.',
251
+ copyrightHint: 'Copyright notice. Off by default.',
252
+ versionHint: 'File version. Off by default.',
253
+ paramHint: 'Meaning, range, or ownership of a parameter.',
254
+ returnHint: 'When the returned value holds. Optional when the type is self-evident.',
255
+ throwsHint: 'Exception type and condition. Omitted when the function does not throw.',
256
+ contractHint: 'Parameters and failure information every function comment must carry.',
247
257
  extra: 'More tags',
248
- extraHint: 'Add them per function when needed.',
249
- file: '@file',
250
- fileHint: 'Taken from the current file name.',
251
- briefHint: 'One sentence about what this file defines; leave empty and the file writes its own.',
252
- authorHint: 'Author credit; left empty, omitted.',
253
- dateHint: 'Creation or change date; left empty, omitted.',
254
- copyrightHint: 'Copyright notice; left empty, omitted.',
255
- versionHint: 'File version; left empty, omitted.',
256
- emptyValue: 'Empty means omit',
257
- briefEmpty: 'Empty: the file writes its own',
258
+ extraHint: 'Off by default; add them per function when needed.',
258
259
  preview: 'Preview',
259
- previewHint: 'Generated from the switches; the file name is a placeholder.',
260
+ previewHint: 'Generated from the switches above; the file name is a placeholder.',
260
261
  save: 'Save',
261
- reset: 'Reset',
262
+ reset: 'Restore defaults',
262
263
  saved: 'Saved; the next skill read uses it.',
263
- rejected: 'The host rejected the save: the config changed elsewhere. Reset and try again.',
264
- unsaved: 'Unsaved changes.',
264
+ rejected: 'The host rejected the save: the config changed elsewhere. Restore defaults and try again.',
265
+ unsaved: 'Unsaved changes',
265
266
  loading: 'Reading configuration…',
266
267
  unreadable: 'This account cannot edit the configuration.',
267
- readOnly: 'Factory values are shown here; edit them on the Plugins page.',
268
+ readOnly: 'Showing factory defaults. Edit the dsh-cpp-workflow card on the Plugins page.',
268
269
  openSettings: 'Open plugin settings',
269
- modified: 'Differs from the factory value',
270
+ fixed: 'Follows the file name',
271
+ fixedHint: 'Derived from the file itself, never stored in the config.',
272
+ emptyValue: 'Leave empty and each file writes its own',
273
+ blankBrief: 'Leave empty for each file',
274
+ on: 'Write',
275
+ off: 'Omit',
270
276
  },
271
277
  }
272
278
 
@@ -279,201 +285,183 @@ function t() {
279
285
  return STRINGS[language()]
280
286
  }
281
287
 
282
- const palette = {
283
- label: 'var(--dsw-alias-label-primary, inherit)',
284
- muted: 'var(--dsw-alias-label-secondary, #6b7280)',
285
- line: 'var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.24))',
286
- surface: 'var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.06))',
287
- field: 'var(--dsw-alias-bg-layer-1, rgba(127, 127, 127, 0.04))',
288
- brand: 'var(--dsw-alias-brand-primary, #4f6bed)',
289
- mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
290
- }
288
+ const STYLE_ID = 'dsh-cpp-workflow-settings'
291
289
 
292
- const descriptionStyle = () => ({ margin: '2px 0 0', fontSize: 12, lineHeight: 1.55, color: palette.muted })
293
- const sectionTitleStyle = () => ({ margin: '0 0 2px', fontSize: 13, fontWeight: 600, color: palette.label })
290
+ // 外观只用宿主变量与两档中性色;没有变量时回落到浅色主题的一组合法颜色。
291
+ const CSS = `
292
+ .dcw-page{--dcw-fg:var(--dsw-alias-label-primary,#1f2328);--dcw-muted:var(--dsw-alias-label-secondary,#69707d);--dcw-faint:var(--dsw-alias-label-tertiary,#8a919c);--dcw-border:var(--dsw-alias-border-l2,rgba(31,35,40,.14));--dcw-bg1:var(--dsw-alias-bg-layer-1,#fff);--dcw-bg2:var(--dsw-alias-bg-layer-2,rgba(31,35,40,.035));--dcw-accent:var(--dsw-alias-brand-primary,#2f6fed);--dcw-danger:var(--dsw-alias-state-error-primary,#c0392b);color:var(--dcw-fg);max-width:760px;padding:2px 0 12px;font-size:13px;line-height:1.6}
293
+ .dcw-head{margin:0 0 14px}
294
+ .dcw-eyebrow{margin:0 0 4px;color:var(--dcw-muted);font-size:11.5px;letter-spacing:.02em}
295
+ .dcw-title{margin:0 0 6px;font-size:16px;font-weight:620;letter-spacing:.005em}
296
+ .dcw-lede{margin:0;color:var(--dcw-muted);font-size:12.5px;line-height:1.65;max-width:64ch}
297
+ .dcw-note{display:flex;gap:8px;align-items:center;margin:12px 0 0;padding:9px 11px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg2);color:var(--dcw-muted);font-size:12px;line-height:1.55}
298
+ .dcw-note[data-tone="warn"]{color:var(--dcw-danger);border-color:color-mix(in srgb,var(--dcw-danger) 35%,transparent)}
299
+ .dcw-note-dot{flex:none;width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.55}
300
+ .dcw-card{margin:14px 0 0;border:1px solid var(--dcw-border);border-radius:10px;background:var(--dcw-bg1);overflow:hidden}
301
+ .dcw-card-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;padding:11px 14px 9px;border-bottom:1px solid var(--dcw-border);background:var(--dcw-bg2)}
302
+ .dcw-card-title{margin:0;font-size:12.5px;font-weight:620;letter-spacing:.01em}
303
+ .dcw-card-hint{margin:0;color:var(--dcw-muted);font-size:11.5px;line-height:1.5}
304
+ .dcw-row{display:grid;grid-template-columns:88px minmax(0,1fr);gap:2px 14px;padding:9px 14px;border-top:1px solid var(--dcw-border)}
305
+ .dcw-row:first-of-type{border-top:0}
306
+ .dcw-row-label{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;color:var(--dcw-fg);padding-top:3px}
307
+ .dcw-row-control{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;min-width:0}
308
+ .dcw-row-hint{flex-basis:100%;margin:0;color:var(--dcw-muted);font-size:11.5px;line-height:1.55}
309
+ .dcw-row-fixed .dcw-row-control{gap:8px}
310
+ .dcw-static{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border:1px dashed var(--dcw-border);border-radius:6px;background:var(--dcw-bg2);color:var(--dcw-muted);font-size:12px}
311
+ .dcw-static svg{flex:none;opacity:.7}
312
+ .dcw-switch{position:relative;display:inline-flex;align-items:center;flex:none;width:34px;height:20px;cursor:pointer}
313
+ .dcw-switch input{position:absolute;inset:0;margin:0;opacity:0;cursor:pointer}
314
+ .dcw-track{position:absolute;inset:0;border-radius:999px;background:color-mix(in srgb,var(--dcw-fg) 18%,transparent);transition:background .16s ease}
315
+ .dcw-knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .16s ease}
316
+ .dcw-switch input:checked + .dcw-track{background:var(--dcw-accent)}
317
+ .dcw-switch input:checked + .dcw-track .dcw-knob{transform:translateX(14px)}
318
+ .dcw-switch input:focus-visible + .dcw-track{outline:2px solid var(--dcw-accent);outline-offset:2px}
319
+ .dcw-switch input:disabled + .dcw-track{opacity:.4}
320
+ .dcw-switch input:disabled{cursor:default}
321
+ .dcw-state{color:var(--dcw-faint);font-size:11.5px;min-width:28px}
322
+ .dcw-input{flex:1 1 220px;min-width:0;height:28px;padding:0 9px;border:1px solid var(--dcw-border);border-radius:6px;background:transparent;color:var(--dcw-fg);font:inherit;font-size:12.5px}
323
+ .dcw-input::placeholder{color:var(--dcw-faint)}
324
+ .dcw-input:focus{outline:none;border-color:var(--dcw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--dcw-accent) 18%,transparent)}
325
+ .dcw-input:disabled{background:var(--dcw-bg2);color:var(--dcw-faint)}
326
+ .dcw-flags{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:0 16px;padding:4px 14px 8px}
327
+ .dcw-flag{display:flex;flex-direction:column;gap:3px;padding:7px 0}
328
+ .dcw-flag .dcw-row-label{padding-top:0;display:flex;align-items:center;gap:8px;justify-content:space-between}
329
+ .dcw-card>summary{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:11px 14px;cursor:pointer;list-style:none}
330
+ .dcw-card details>summary::-webkit-details-marker{display:none}
331
+ .dcw-card details>summary::marker{content:''}
332
+ .dcw-card>summary:hover{background:var(--dcw-bg2)}
333
+ .dcw-chevron{transition:transform .16s ease;color:var(--dcw-muted)}
334
+ .dcw-card[open] .dcw-chevron{transform:rotate(90deg)}
335
+ .dcw-preview{margin:0;padding:11px 14px 13px;border-top:1px solid var(--dcw-border);overflow-x:auto;background:var(--dcw-bg2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;white-space:pre;color:var(--dcw-fg)}
336
+ .dcw-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:14px;padding:11px 2px 4px;border-top:1px solid var(--dcw-border)}
337
+ .dcw-btn{height:30px;padding:0 13px;border:1px solid var(--dcw-border);border-radius:7px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px;cursor:pointer}
338
+ .dcw-btn:hover:enabled{border-color:color-mix(in srgb,var(--dcw-fg) 32%,transparent)}
339
+ .dcw-btn:focus-visible{outline:2px solid var(--dcw-accent);outline-offset:1px}
340
+ .dcw-btn:disabled{opacity:.45;cursor:default}
341
+ .dcw-btn-primary{background:var(--dcw-accent);border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}
342
+ .dcw-btn-primary:hover:enabled{filter:brightness(1.06)}
343
+ .dcw-status{flex:1 1 200px;margin:0;color:var(--dcw-muted);font-size:12px}
344
+ .dcw-status[data-tone="warn"]{color:var(--dcw-danger)}
345
+ .dcw-status[data-tone="ok"]{color:var(--dsw-alias-state-success-primary,#1a7f37)}
346
+ @media (max-width:640px){.dcw-row{grid-template-columns:1fr;gap:4px}.dcw-row-label{padding-top:0}.dcw-flags{grid-template-columns:1fr}.dcw-flag:nth-child(2){border-top:1px solid var(--dcw-border)}}
347
+ `
294
348
 
295
- function Section(props) {
349
+ function ensureStyles() {
350
+ if (typeof document === 'undefined' || !document.createElement) return
351
+ if (document.querySelector && document.querySelector(`style[data-plugin-css="${STYLE_ID}"]`)) return
352
+ const style = document.createElement('style')
353
+ style.dataset.pluginCss = STYLE_ID
354
+ style.textContent = CSS
355
+ if (document.head && document.head.appendChild) document.head.appendChild(style)
356
+ }
357
+
358
+ function Switch(props) {
296
359
  return h(
297
- 'section',
298
- {
299
- style: {
300
- marginTop: props.first ? 16 : 22,
301
- paddingTop: props.first ? 0 : 14,
302
- borderTop: props.first ? 'none' : `1px solid ${palette.line}`,
303
- },
304
- },
305
- h('h4', { style: sectionTitleStyle() }, props.title),
306
- props.description ? h('p', { style: descriptionStyle() }, props.description) : null,
307
- h('div', { style: { marginTop: 6 } }, props.children),
360
+ 'span',
361
+ { className: 'dcw-switch' },
362
+ h('input', {
363
+ type: 'checkbox',
364
+ checked: Boolean(props.checked),
365
+ disabled: Boolean(props.disabled),
366
+ 'aria-label': props.label,
367
+ onChange: (event) => props.onChange(event.target.checked),
368
+ }),
369
+ h('span', { className: 'dcw-track' }, h('span', { className: 'dcw-knob' })),
308
370
  )
309
371
  }
310
372
 
311
- function Row(props) {
373
+ function LockIcon() {
312
374
  return h(
313
- 'div',
314
- {
315
- style: {
316
- display: 'grid',
317
- gridTemplateColumns: 'minmax(0, 1fr) minmax(160px, 240px)',
318
- columnGap: 24,
319
- alignItems: 'start',
320
- padding: '10px 0',
321
- borderBottom: `1px solid ${palette.line}`,
322
- },
323
- },
324
- h(
325
- 'div',
326
- { style: { minWidth: 0 } },
327
- h(
328
- 'div',
329
- { style: { display: 'flex', alignItems: 'center', gap: 6, minHeight: 20 } },
330
- props.modified
331
- ? h('span', {
332
- key: 'dot',
333
- title: props.modifiedLabel,
334
- 'aria-label': props.modifiedLabel,
335
- style: { width: 6, height: 6, borderRadius: '50%', background: palette.brand, flex: 'none' },
336
- })
337
- : null,
338
- h('span', { style: { fontSize: 13, lineHeight: '20px', color: palette.label, fontFamily: props.mono ? palette.mono : 'inherit' } }, props.label),
339
- ),
340
- props.description ? h('p', { style: descriptionStyle() }, props.description) : null,
341
- ),
342
- h(
343
- 'div',
344
- { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8, minHeight: 20 } },
345
- props.control,
346
- ),
375
+ 'svg',
376
+ { width: 12, height: 12, viewBox: '0 0 16 16', 'aria-hidden': 'true', focusable: 'false' },
377
+ h('path', {
378
+ fill: 'currentColor',
379
+ d: 'M8 1a3 3 0 0 0-3 3v2H4.5A1.5 1.5 0 0 0 3 7.5v5A1.5 1.5 0 0 0 4.5 14h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 6H11V4a3 3 0 0 0-3-3Zm2 5H6V4a2 2 0 1 1 4 0v2Z',
380
+ }),
347
381
  )
348
382
  }
349
383
 
350
- function Checkbox(props) {
384
+ function Chevron() {
351
385
  return h(
352
- 'button',
353
- {
354
- type: 'button',
355
- role: 'checkbox',
356
- 'aria-checked': Boolean(props.checked),
357
- 'aria-label': props.label,
358
- disabled: Boolean(props.disabled),
359
- onClick: () => props.onChange(!props.checked),
360
- style: {
361
- flex: 'none',
362
- width: 16,
363
- height: 16,
364
- padding: 0,
365
- borderRadius: 4,
366
- border: `1px solid ${props.checked ? palette.brand : palette.line}`,
367
- background: props.checked ? palette.brand : 'transparent',
368
- display: 'inline-flex',
369
- alignItems: 'center',
370
- justifyContent: 'center',
371
- cursor: props.disabled ? 'default' : 'pointer',
372
- opacity: props.disabled ? 0.55 : 1,
373
- transition: 'background-color 0.12s ease, border-color 0.12s ease',
374
- },
375
- },
376
- h(
377
- 'svg',
378
- { width: 11, height: 11, viewBox: '0 0 12 12', 'aria-hidden': 'true' },
379
- h('path', {
380
- d: 'M2.6 6.3 5 8.7l4.4-5.2',
381
- fill: 'none',
382
- stroke: '#fff',
383
- strokeWidth: 1.7,
384
- strokeLinecap: 'round',
385
- strokeLinejoin: 'round',
386
- opacity: props.checked ? 1 : 0,
387
- }),
388
- ),
386
+ 'svg',
387
+ { className: 'dcw-chevron', width: 12, height: 12, viewBox: '0 0 16 16', 'aria-hidden': 'true', focusable: 'false' },
388
+ h('path', { fill: 'currentColor', d: 'M6 3.5 10.5 8 6 12.5 4.9 11.4 8.3 8 4.9 4.6Z' }),
389
389
  )
390
390
  }
391
391
 
392
+ const HINT_KEY = {
393
+ brief: 'briefHint',
394
+ author: 'authorHint',
395
+ date: 'dateHint',
396
+ copyright: 'copyrightHint',
397
+ version: 'versionHint',
398
+ param: 'paramHint',
399
+ returns: 'returnHint',
400
+ throws: 'throwsHint',
401
+ }
402
+
392
403
  function ValueRow(props) {
393
404
  const s = props.strings
394
405
  const entry = props.entry
395
- return h(Row, {
396
- label: TAG_LABEL[props.tag],
397
- mono: true,
398
- description: props.hint,
399
- modified: props.modified,
400
- modifiedLabel: s.modified,
401
- control: [
402
- h(Checkbox, {
403
- key: 'check',
406
+ return h(
407
+ 'div',
408
+ { className: 'dcw-row' },
409
+ h('div', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
410
+ h(
411
+ 'div',
412
+ { className: 'dcw-row-control' },
413
+ h(Switch, {
404
414
  label: TAG_LABEL[props.tag],
405
415
  checked: entry.enabled,
406
416
  disabled: props.disabled,
407
417
  onChange: (enabled) => props.onChange({ ...entry, enabled }),
408
418
  }),
419
+ h('span', { className: 'dcw-state', 'aria-hidden': 'true' }, entry.enabled ? s.on : s.off),
409
420
  h('input', {
410
- key: 'input',
421
+ className: 'dcw-input',
411
422
  type: 'text',
412
423
  value: entry.value,
413
- placeholder: props.tag === 'brief' ? s.briefEmpty : s.emptyValue,
424
+ placeholder: props.tag === 'brief' ? s.blankBrief : s.emptyValue,
414
425
  disabled: props.disabled || !entry.enabled,
415
- 'aria-label': TAG_LABEL[props.tag],
426
+ 'aria-label': `${TAG_LABEL[props.tag]} 的值`,
416
427
  onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
417
- style: {
418
- width: '100%',
419
- minWidth: 0,
420
- fontSize: 13,
421
- lineHeight: '18px',
422
- padding: '3px 7px',
423
- borderRadius: 4,
424
- border: `1px solid ${palette.line}`,
425
- background: entry.enabled && !props.disabled ? palette.field : 'transparent',
426
- color: entry.enabled ? 'inherit' : palette.muted,
427
- fontFamily: palette.mono,
428
- boxSizing: 'border-box',
429
- },
430
428
  }),
431
- ],
432
- })
429
+ h('p', { className: 'dcw-row-hint' }, s[HINT_KEY[props.tag]] || ''),
430
+ ),
431
+ )
433
432
  }
434
433
 
435
- function FlagRow(props) {
436
- return h(Row, {
437
- label: TAG_LABEL[props.tag],
438
- mono: true,
439
- description: props.hint,
440
- modified: props.modified,
441
- modifiedLabel: props.strings.modified,
442
- control: h(Checkbox, {
443
- label: TAG_LABEL[props.tag],
444
- checked: Boolean(props.checked),
445
- disabled: props.disabled,
446
- onChange: (value) => props.onChange(value),
447
- }),
448
- })
434
+ function Flag(props) {
435
+ return h(
436
+ 'div',
437
+ { className: 'dcw-flag' },
438
+ h(
439
+ 'div',
440
+ { className: 'dcw-row-label' },
441
+ TAG_LABEL[props.tag],
442
+ h(Switch, {
443
+ label: TAG_LABEL[props.tag],
444
+ checked: props.checked,
445
+ disabled: props.disabled,
446
+ onChange: props.onChange,
447
+ }),
448
+ ),
449
+ h('p', { className: 'dcw-row-hint' }, props.hint || ''),
450
+ )
449
451
  }
450
452
 
451
453
  function Preview(props) {
452
454
  const s = props.strings
453
455
  return h(
454
456
  'section',
455
- { 'aria-label': s.preview, style: { marginTop: 22, paddingTop: 14, borderTop: `1px solid ${palette.line}` } },
456
- h('h4', { style: sectionTitleStyle() }, s.preview),
457
- h('p', { style: descriptionStyle() }, s.previewHint),
457
+ { className: 'dcw-card', 'aria-label': s.preview },
458
458
  h(
459
- 'pre',
460
- {
461
- style: {
462
- margin: '8px 0 0',
463
- padding: '10px 12px',
464
- borderRadius: 6,
465
- background: palette.surface,
466
- border: `1px solid ${palette.line}`,
467
- overflowX: 'auto',
468
- fontSize: 12.5,
469
- lineHeight: 1.65,
470
- fontFamily: palette.mono,
471
- whiteSpace: 'pre',
472
- color: palette.label,
473
- },
474
- },
475
- previewText(props.config),
459
+ 'div',
460
+ { className: 'dcw-card-head' },
461
+ h('h4', { className: 'dcw-card-title' }, s.preview),
462
+ h('p', { className: 'dcw-card-hint' }, s.previewHint),
476
463
  ),
464
+ h('pre', { className: 'dcw-preview' }, previewText(props.config, SAMPLE_FILE)),
477
465
  )
478
466
  }
479
467
 
@@ -487,12 +475,9 @@ function diffOperations(draft, base) {
487
475
  return operations
488
476
  }
489
477
 
490
- function isModified(key, draft) {
491
- return JSON.stringify(draft[key]) !== JSON.stringify(DEFAULT_CONFIG[key])
492
- }
493
-
494
478
  function ConfigPage(props) {
495
479
  const s = t()
480
+ ensureStyles()
496
481
  const form = props.form
497
482
  const initial = React.useMemo(() => normalizeConfig(form && form.state ? form.state.value : undefined), [])
498
483
  const [draft, setDraft] = React.useState(initial)
@@ -500,8 +485,11 @@ function ConfigPage(props) {
500
485
  const [dirty, setDirty] = React.useState(false)
501
486
  const [busy, setBusy] = React.useState(false)
502
487
  const [message, setMessage] = React.useState('')
488
+ const [tone, setTone] = React.useState('')
503
489
  const revision = form && form.state ? form.state.revision : undefined
504
- const ready = Boolean(form && form.state && form.state.status === 'ready' && form.state.writable && form.state.mode === 'host')
490
+ const ready = Boolean(
491
+ form && form.state && form.state.status === 'ready' && form.state.writable && form.state.mode === 'host',
492
+ )
505
493
  const status = form && form.state ? form.state.status : 'missing'
506
494
 
507
495
  React.useEffect(() => {
@@ -515,12 +503,14 @@ function ConfigPage(props) {
515
503
  setDraft((current) => ({ ...current, ...patch }))
516
504
  setDirty(true)
517
505
  setMessage('')
506
+ setTone('')
518
507
  }
519
508
 
520
509
  const reset = () => {
521
510
  setDraft(base)
522
511
  setDirty(false)
523
512
  setMessage('')
513
+ setTone('')
524
514
  }
525
515
 
526
516
  const save = async () => {
@@ -534,190 +524,167 @@ function ConfigPage(props) {
534
524
  setBase(draft)
535
525
  setDirty(false)
536
526
  setMessage(s.saved)
527
+ setTone('ok')
537
528
  } else {
538
529
  setMessage(s.rejected)
530
+ setTone('warn')
539
531
  }
540
532
  } catch (error) {
541
533
  setMessage(error instanceof Error ? error.message : String(error))
534
+ setTone('warn')
542
535
  } finally {
543
536
  setBusy(false)
544
537
  }
545
538
  }
546
539
 
547
540
  const editable = Boolean(form)
548
- const headerHints = {
549
- brief: s.briefHint,
550
- author: s.authorHint,
551
- date: s.dateHint,
552
- copyright: s.copyrightHint,
553
- version: s.versionHint,
554
- }
541
+ const disabled = !ready
542
+ const navigation = props.pluginNavigation
555
543
 
556
544
  return h(
557
545
  'div',
558
- { style: { display: 'flex', flexDirection: 'column', padding: '2px 0 8px', maxWidth: 760, color: palette.label } },
559
- h('h3', { style: { margin: '0 0 2px', fontSize: 15, fontWeight: 600 } }, s.title),
560
- h('p', { style: descriptionStyle() }, s.intro),
546
+ { className: 'dcw-page' },
547
+ h(
548
+ 'header',
549
+ { className: 'dcw-head' },
550
+ h('p', { className: 'dcw-eyebrow' }, s.eyebrow),
551
+ h('h3', { className: 'dcw-title' }, s.title),
552
+ h('p', { className: 'dcw-lede' }, s.intro),
553
+ ),
561
554
  !editable
562
555
  ? h(
563
556
  'div',
564
- {
565
- role: 'status',
566
- style: {
567
- display: 'flex',
568
- alignItems: 'center',
569
- gap: 10,
570
- flexWrap: 'wrap',
571
- marginTop: 10,
572
- padding: '8px 10px',
573
- borderRadius: 6,
574
- background: palette.surface,
575
- border: `1px solid ${palette.line}`,
576
- },
577
- },
578
- h('p', { style: { ...descriptionStyle(), margin: 0, flex: '1 1 200px' } }, s.readOnly),
579
- props.openPluginPage
580
- ? h('button', { type: 'button', onClick: props.openPluginPage, style: secondaryButton(true) }, s.openSettings)
557
+ { className: 'dcw-note', role: 'status' },
558
+ h('span', { className: 'dcw-note-dot' }),
559
+ h('span', null, s.readOnly),
560
+ navigation && typeof navigation.openBundle === 'function'
561
+ ? h(
562
+ 'button',
563
+ {
564
+ type: 'button',
565
+ className: 'dcw-btn',
566
+ onClick: () => navigation.openBundle(PACKAGE_NAME),
567
+ },
568
+ s.openSettings,
569
+ )
581
570
  : null,
582
571
  )
583
572
  : null,
584
573
  editable && !ready && status !== 'missing'
585
- ? h('p', { role: 'status', style: descriptionStyle() }, status === 'loading' ? s.loading : s.unreadable)
574
+ ? h(
575
+ 'div',
576
+ { className: 'dcw-note', role: 'status' },
577
+ h('span', { className: 'dcw-note-dot' }),
578
+ h('span', null, status === 'loading' ? s.loading : s.unreadable),
579
+ )
586
580
  : null,
587
581
  h(
588
- Section,
589
- { title: s.header, description: s.headerHint, first: true },
590
- h(Row, {
591
- label: s.file,
592
- mono: true,
593
- description: s.fileHint,
594
- control: null,
595
- }),
582
+ 'section',
583
+ { className: 'dcw-card', 'aria-labelledby': 'dcw-header-title' },
584
+ h(
585
+ 'div',
586
+ { className: 'dcw-card-head' },
587
+ h('h4', { className: 'dcw-card-title', id: 'dcw-header-title' }, s.header),
588
+ h('p', { className: 'dcw-card-hint' }, s.headerHint),
589
+ ),
590
+ h(
591
+ 'div',
592
+ { className: 'dcw-row dcw-row-fixed' },
593
+ h('div', { className: 'dcw-row-label' }, TAG_LABEL.file),
594
+ h(
595
+ 'div',
596
+ { className: 'dcw-row-control' },
597
+ h('span', { className: 'dcw-static' }, h(LockIcon), s.fixed),
598
+ h('p', { className: 'dcw-row-hint' }, s.fixedHint),
599
+ ),
600
+ ),
596
601
  ...HEADER_TAGS.map((tag) =>
597
602
  h(ValueRow, {
598
603
  key: tag,
599
604
  tag,
600
- hint: headerHints[tag],
601
605
  strings: s,
602
- disabled: !editable,
603
- modified: editable && isModified(tag, draft),
604
606
  entry: draft[tag],
607
+ disabled,
605
608
  onChange: (entry) => update({ [tag]: entry }),
606
609
  }),
607
610
  ),
608
611
  ),
609
612
  h(
610
- Section,
611
- { title: s.contract, description: s.contractHint },
612
- ...CONTRACT_TAGS.map((tag) =>
613
- h(FlagRow, {
614
- key: tag,
615
- tag,
616
- hint: TAG_TEXT[tag],
617
- strings: s,
618
- disabled: !editable,
619
- modified: editable && isModified(tag, draft),
620
- checked: draft[tag],
621
- onChange: (value) => update({ [tag]: value }),
622
- }),
613
+ 'section',
614
+ { className: 'dcw-card', 'aria-labelledby': 'dcw-contract-title' },
615
+ h(
616
+ 'div',
617
+ { className: 'dcw-card-head' },
618
+ h('h4', { className: 'dcw-card-title', id: 'dcw-contract-title' }, s.contract),
619
+ h('p', { className: 'dcw-card-hint' }, s.contractHint),
620
+ ),
621
+ h(
622
+ 'div',
623
+ { className: 'dcw-flags' },
624
+ ...CONTRACT_TAGS.map((tag) =>
625
+ h(Flag, {
626
+ key: tag,
627
+ tag,
628
+ checked: draft[tag],
629
+ disabled,
630
+ hint: s[HINT_KEY[tag]],
631
+ onChange: (value) => update({ [tag]: value }),
632
+ }),
633
+ ),
623
634
  ),
624
635
  ),
625
636
  h(
626
- Section,
627
- { title: s.extra, description: s.extraHint },
628
- ...EXTRA_TAGS.map((tag) =>
629
- h(FlagRow, {
630
- key: tag,
631
- tag,
632
- hint: TAG_TEXT[tag],
633
- strings: s,
634
- disabled: !editable,
635
- modified: editable && isModified(tag, draft),
636
- checked: draft[tag],
637
- onChange: (value) => update({ [tag]: value }),
638
- }),
637
+ 'details',
638
+ { className: 'dcw-card' },
639
+ h('summary', null, h('span', { className: 'dcw-card-title' }, s.extra), h(Chevron)),
640
+ h('p', { className: 'dcw-card-hint', style: { margin: '0 14px 2px' } }, s.extraHint),
641
+ h(
642
+ 'div',
643
+ { className: 'dcw-flags' },
644
+ ...EXTRA_TAGS.map((tag) =>
645
+ h(Flag, {
646
+ key: tag,
647
+ tag,
648
+ checked: draft[tag],
649
+ disabled,
650
+ onChange: (value) => update({ [tag]: value }),
651
+ }),
652
+ ),
639
653
  ),
640
654
  ),
641
655
  h(Preview, { config: draft, strings: s }),
642
656
  editable
643
657
  ? h(
644
658
  'div',
645
- {
646
- style: {
647
- display: 'flex',
648
- alignItems: 'center',
649
- gap: 10,
650
- marginTop: 20,
651
- paddingTop: 12,
652
- borderTop: `1px solid ${palette.line}`,
653
- flexWrap: 'wrap',
654
- },
655
- },
659
+ { className: 'dcw-actions' },
656
660
  h(
657
661
  'button',
658
662
  {
659
663
  type: 'button',
664
+ className: 'dcw-btn dcw-btn-primary',
660
665
  onClick: save,
661
666
  disabled: busy || !ready || !dirty,
662
- style: primaryButton(dirty && ready && !busy),
663
667
  },
664
668
  s.save,
665
669
  ),
670
+ h('button', { type: 'button', className: 'dcw-btn', onClick: reset, disabled: busy || !dirty }, s.reset),
666
671
  h(
667
- 'button',
668
- {
669
- type: 'button',
670
- onClick: reset,
671
- disabled: busy || !dirty,
672
- style: secondaryButton(dirty && !busy),
673
- },
674
- s.reset,
672
+ 'p',
673
+ { className: 'dcw-status', role: 'status', 'data-tone': tone },
674
+ message || (dirty ? s.unsaved : ''),
675
675
  ),
676
- h('p', { role: 'status', style: { ...descriptionStyle(), margin: 0, flex: '1 1 200px' } }, message || (dirty ? s.unsaved : '')),
677
676
  )
678
677
  : null,
679
678
  )
680
679
  }
681
680
 
682
- function primaryButton(enabled) {
683
- return {
684
- fontSize: 13,
685
- lineHeight: '20px',
686
- padding: '4px 12px',
687
- borderRadius: 4,
688
- border: '1px solid transparent',
689
- background: enabled ? palette.brand : 'var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, 0.2))',
690
- color: enabled ? '#fff' : palette.muted,
691
- cursor: enabled ? 'pointer' : 'default',
692
- }
693
- }
694
-
695
- function secondaryButton(enabled) {
696
- return {
697
- fontSize: 13,
698
- lineHeight: '20px',
699
- padding: '4px 12px',
700
- borderRadius: 4,
701
- border: `1px solid ${palette.line}`,
702
- background: 'transparent',
703
- color: enabled ? palette.label : palette.muted,
704
- cursor: enabled ? 'pointer' : 'default',
705
- }
706
- }
707
-
708
- function page(ownerProps, openPluginPage) {
681
+ function page(ownerProps, pluginNavigation) {
709
682
  const props = ownerProps || {}
710
- return h(ConfigPage, { ...props, openPluginPage })
683
+ return h(ConfigPage, { ...props, pluginNavigation })
711
684
  }
712
685
 
713
686
  function apply(ctx) {
714
- let openPluginPage
715
- try {
716
- const navigation = typeof ctx.get === 'function' ? ctx.get('pluginNavigation') : undefined
717
- if (navigation && typeof navigation.openBundle === 'function') {
718
- openPluginPage = () => navigation.openBundle(PACKAGE_NAME)
719
- }
720
- } catch {}
687
+ const navigation = typeof ctx.get === 'function' ? ctx.get('pluginNavigation') : undefined
721
688
  ctx.slots.inject('settings.section', () =>
722
689
  ctx.slots.register(
723
690
  {
@@ -726,7 +693,7 @@ function apply(ctx) {
726
693
  order: 60,
727
694
  label: () => t().nav,
728
695
  },
729
- (ownerProps) => page(ownerProps, openPluginPage),
696
+ (ownerProps) => page(ownerProps, navigation),
730
697
  ),
731
698
  )
732
699
  ctx.slots.inject('plugins.bundle.config', () =>
@@ -736,7 +703,7 @@ function apply(ctx) {
736
703
  key: PACKAGE_NAME,
737
704
  label: () => t().nav,
738
705
  },
739
- (ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps, undefined)),
706
+ (ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps, navigation)),
740
707
  ),
741
708
  )
742
709
  }
@@ -35,6 +35,7 @@ export const TAG_TEXT = {
35
35
  };
36
36
  /** 开关型标签在设置页的标题。 */
37
37
  export const TAG_LABEL = {
38
+ file: '@file',
38
39
  brief: '@brief',
39
40
  author: '@author',
40
41
  date: '@date',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-cpp-workflow",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "写 C++ 源码、配 CMake、用 Clang 构建;文档注释统一为 /// Doxygen,文件头标签可在设置里改。 · Write C++ sources, configure CMake, and build with Clang; /// Doxygen comments, with file-header tags you can change in Settings.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -24,15 +24,15 @@ for (const auto& position : empty_cells) {
24
24
  **好**:
25
25
 
26
26
  ```cpp
27
- // 先找能立刻赢的格子:启发式只在「马上赢」和「马上挡」之间做选择,
28
- // 不在这里做更深的前瞻,深搜留给 MinimaxAi。
27
+ /// 先找能立刻赢的格子:启发式只在「马上赢」和「马上挡」之间做选择,
28
+ /// 不在这里做更深的前瞻,深搜留给 MinimaxAi。
29
29
  for (const auto& position : empty_cells) {
30
30
  if (game.CompletesLine(position, CellType::kX)) {
31
31
  return position;
32
32
  }
33
33
  }
34
34
 
35
- // 没有必胜格,交给下一层策略;这里不猜后续几步。
35
+ /// 没有必胜格,交给下一层策略;这里不猜后续几步。
36
36
  return std::nullopt;
37
37
  ```
38
38
 
@@ -115,7 +115,7 @@ constexpr std::array<std::array<CellPosition, 3>, 8> kLines{{
115
115
 
116
116
  ## 推演一步读原数据
117
117
 
118
- **差**:先复制整盘,再在副本上落子,最后判断成线;调用方看不出副本只是中间量。
118
+ **差**:先复制整个棋盘,再在副本上落子,最后判断成线;调用方看不出副本只是中间量。
119
119
 
120
120
  ```cpp
121
121
  Board hypothetical = *this;