@linxin666/dsh-client-ui-task-board 0.1.9 → 0.1.11

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.
@@ -29,7 +29,7 @@ export interface PluginSettingsCardProps {
29
29
  /**
30
30
  * Render one plugin settings card.
31
31
  * @param props - the plugin's copy keys, its form state, and its controls.
32
- * @returns the card, or nothing when the namespace is unavailable.
32
+ * @returns the card, or nothing while the namespace is still loading.
33
33
  */
34
34
  export declare function PluginSettingsCard(props: PluginSettingsCardProps): import("react").JSX.Element | null;
35
35
  /** Props every field control needs regardless of its value type. */
@@ -83,6 +83,7 @@ export declare const zh: {
83
83
  'settings.off': string;
84
84
  'settings.overridden': string;
85
85
  'settings.reset': string;
86
+ 'settings.notExposed': string;
86
87
  'settings.readOnly': string;
87
88
  'settings.expand': string;
88
89
  'settings.collapse': string;
@@ -37,8 +37,16 @@ export interface FieldState {
37
37
  }
38
38
  /** Form state every plugin settings card shares. */
39
39
  export interface CardShell {
40
- /** False while the namespace is not served to this client; the card renders nothing. */
40
+ /** False while the namespace is still loading; the card renders nothing. */
41
41
  available: boolean;
42
+ /**
43
+ * Whether the namespace is actually served to this client. False when the
44
+ * Host deployment does not expose it (e.g. the official apiproxy settings
45
+ * allowlist omits third-party namespaces): the card renders an explanation
46
+ * instead of its form, so a missing namespace never looks like a missing
47
+ * plugin.
48
+ */
49
+ exposed: boolean;
42
50
  /** Whether the Host document accepts writes. */
43
51
  writable: boolean;
44
52
  /** Whether the form holds edits that a save would write. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-client-ui-task-board",
3
3
  "description": "Task board for the dsh web GUI: a sidebar entry plus a multi-column kanban view with local persistence, task details, and real execution through dsh sessions (session.prompt). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules symlink, no dsh source changes.",
4
- "version": "0.1.9",
4
+ "version": "0.1.11",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -33,7 +33,7 @@ export interface PluginSettingsCardProps {
33
33
  /**
34
34
  * Render one plugin settings card.
35
35
  * @param props - the plugin's copy keys, its form state, and its controls.
36
- * @returns the card, or nothing when the namespace is unavailable.
36
+ * @returns the card, or nothing while the namespace is still loading.
37
37
  */
38
38
  export function PluginSettingsCard(props: PluginSettingsCardProps) {
39
39
  const [open, setOpen] = useState(false)
@@ -41,6 +41,36 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
41
41
  if (!state.available) return null
42
42
  const title = props.t(props.titleKey)
43
43
  const blocked = !state.dirty || state.invalid || state.saving
44
+ // The namespace exists but the Host does not serve it to this client (the
45
+ // official settings allowlist omits third-party namespaces): show a card
46
+ // that explains the gap instead of vanishing, so a missing card never
47
+ // reads as a missing plugin.
48
+ if (!state.exposed) {
49
+ return (
50
+ <li className={css.card}>
51
+ <button
52
+ type="button"
53
+ className={css.header}
54
+ aria-expanded={open}
55
+ aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}
56
+ onClick={() => { setOpen(!open) }}
57
+ >
58
+ <span className={css.headText}>
59
+ <span className={css.name}>{title}</span>
60
+ <span className={css.description}>{props.t(props.descriptionKey)}</span>
61
+ </span>
62
+ <span className={open ? css.chevronOpen : css.chevron}>▾</span>
63
+ </button>
64
+ {open
65
+ ? (
66
+ <div className={css.body}>
67
+ <p className={css.notExposed} role="status">{props.t('settings.notExposed')}</p>
68
+ </div>
69
+ )
70
+ : null}
71
+ </li>
72
+ )
73
+ }
44
74
  return (
45
75
  <li className={css.card}>
46
76
  <button
@@ -16,7 +16,7 @@
16
16
  position: absolute;
17
17
  inset: 0;
18
18
  display: none;
19
- z-index: 5;
19
+ z-index: 60;
20
20
  }
21
21
 
22
22
  /* The center column is single-occupant; the :not() guards keep the two
@@ -27,9 +27,13 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-v
27
27
  }
28
28
 
29
29
  /* While the board is active, the conversation content underneath is hidden
30
- (it stays mounted and stateful). */
30
+ (it stays mounted and stateful). The !important is required: the dsh shell
31
+ (0.1.0-rc.6) wraps the conversation view in a node with an inline
32
+ `display: contents`, and inline styles beat a plain stylesheet rule. Without
33
+ it the composer (input card) stays visible at the bottom and paints over the
34
+ board modals' footer, hiding the run/delete buttons (issue #76). */
31
35
  html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane='conversation'] > :not([data-dsh-taskboard-view]) {
32
- display: none;
36
+ display: none !important;
33
37
  }
34
38
 
35
39
  /* --- sidebar entry row ------------------------------------------------------- */
@@ -426,7 +430,7 @@ html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane='conversat
426
430
  .modalBackdrop {
427
431
  position: fixed;
428
432
  inset: 0;
429
- z-index: 40;
433
+ z-index: 1300;
430
434
  display: flex;
431
435
  align-items: center;
432
436
  justify-content: center;
@@ -85,6 +85,7 @@ export const zh = {
85
85
  'settings.off': '关',
86
86
  'settings.overridden': '已覆盖',
87
87
  'settings.reset': '恢复默认',
88
+ 'settings.notExposed': '当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。',
88
89
  'settings.readOnly': '当前部署的设置只读。',
89
90
  'settings.expand': '展开设置',
90
91
  'settings.collapse': '收起设置',
@@ -176,6 +177,7 @@ export const en: Record<keyof typeof zh, string> = {
176
177
  'settings.off': 'Off',
177
178
  'settings.overridden': 'Overridden',
178
179
  'settings.reset': 'Reset to default',
180
+ 'settings.notExposed': 'This DSH version does not expose this plugin\'s settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy\'s WEB_SETTINGS_NAMESPACES allowlist and restart.',
179
181
  'settings.readOnly': 'This deployment stores settings read-only.',
180
182
  'settings.expand': 'Show settings',
181
183
  'settings.collapse': 'Hide settings',
@@ -74,6 +74,13 @@
74
74
  color: var(--dsw-alias-label-tertiary);
75
75
  }
76
76
 
77
+ .notExposed {
78
+ margin: 0;
79
+ font-size: 12px;
80
+ line-height: 1.5;
81
+ color: var(--dsw-alias-state-warn-primary);
82
+ }
83
+
77
84
  .footer {
78
85
  display: flex;
79
86
  align-items: center;
@@ -40,8 +40,16 @@ export interface FieldState {
40
40
 
41
41
  /** Form state every plugin settings card shares. */
42
42
  export interface CardShell {
43
- /** False while the namespace is not served to this client; the card renders nothing. */
43
+ /** False while the namespace is still loading; the card renders nothing. */
44
44
  available: boolean
45
+ /**
46
+ * Whether the namespace is actually served to this client. False when the
47
+ * Host deployment does not expose it (e.g. the official apiproxy settings
48
+ * allowlist omits third-party namespaces): the card renders an explanation
49
+ * instead of its form, so a missing namespace never looks like a missing
50
+ * plugin.
51
+ */
52
+ exposed: boolean
45
53
  /** Whether the Host document accepts writes. */
46
54
  writable: boolean
47
55
  /** Whether the form holds edits that a save would write. */
@@ -157,7 +165,8 @@ export class CardForm<T> {
157
165
  const snapshot = this.scope.getSnapshot()
158
166
  const plan = this.plan()
159
167
  return {
160
- available: snapshot.status === 'ready',
168
+ available: snapshot.status !== 'loading',
169
+ exposed: snapshot.status === 'ready',
161
170
  writable: snapshot.writable,
162
171
  dirty: plan.length > 0,
163
172
  invalid: plan.some(item => item.run === undefined),
@@ -139,11 +139,15 @@ export function mountSidebarEntry(controller: BoardController): () => void {
139
139
  }
140
140
  })
141
141
 
142
- // Reflect the board's open state on the row (active highlight).
143
- const unsubscribe = controller.subscribe(() => {
144
- entry.dataset.active = controller.getSnapshot().boardOpen ? 'true' : undefined
145
- })
146
- entry.dataset.active = controller.getSnapshot().boardOpen ? 'true' : undefined
142
+ // Reflect the board's open state on the row (active highlight). Note: assigning
143
+ // undefined to dataset.active materializes data-active="undefined" and keeps the
144
+ // row permanently highlighted — delete the attribute instead.
145
+ const syncActive = () => {
146
+ if (controller.getSnapshot().boardOpen) entry.dataset.active = 'true'
147
+ else delete entry.dataset.active
148
+ }
149
+ const unsubscribe = controller.subscribe(syncActive)
150
+ syncActive()
147
151
 
148
152
  tryPlace()
149
153