@fonlan/dsh-task-kanban 0.2.5 → 0.2.7

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,35 +1,15 @@
1
1
  /**
2
2
  * User-facing plugin settings (global parallel worker count, per-session-type
3
- * model/effort/preset defaults), persisted through the settings service
4
- * namespace `task-kanban`. The DSH settings service requires a lowercase
5
- * kebab-case namespace (/^[a-z][a-z0-9-]*$/), so the scoped package name
6
- * cannot be used here.
3
+ * model/effort/preset defaults), carried by this bundle's entry config: dsh
4
+ * >= 0.1.7 owns plugin settings as entry config (the plugin's `Config`
5
+ * schema), so the face reads the config it was applied with and persists
6
+ * updates through `settings.update`.
7
7
  */
8
8
  import type { Context } from '@deepseek-ai/cordis';
9
9
  import z from '@deepseek-ai/schemastery';
10
10
  import type { KanbanSessionKind, KanbanSettingsShape } from '../shared/card.js';
11
11
  export declare const KANBAN_SETTINGS_NS = "task-kanban";
12
- export declare const KanbanSettingsSchema: z<Schemastery.ObjectS<{
13
- maxParallelWorkers: z<number, number>;
14
- refinementModel: z<string, string>;
15
- refinementProvider: z<string, string>;
16
- refinementReasoningEffort: z<string, string>;
17
- refinementPreset: z<string, string>;
18
- phaseModel: z<string, string>;
19
- phaseProvider: z<string, string>;
20
- phaseReasoningEffort: z<string, string>;
21
- phasePreset: z<string, string>;
22
- }>, Schemastery.ObjectT<{
23
- maxParallelWorkers: z<number, number>;
24
- refinementModel: z<string, string>;
25
- refinementProvider: z<string, string>;
26
- refinementReasoningEffort: z<string, string>;
27
- refinementPreset: z<string, string>;
28
- phaseModel: z<string, string>;
29
- phaseProvider: z<string, string>;
30
- phaseReasoningEffort: z<string, string>;
31
- phasePreset: z<string, string>;
32
- }>>;
12
+ export declare const KanbanSettingsSchema: z<KanbanSettingsShape>;
33
13
  /** The per-session-type default slice a session kind resolves. */
34
14
  export interface KanbanSessionDefaults {
35
15
  model: string;
@@ -52,4 +32,4 @@ export interface KanbanSettingsFace {
52
32
  */
53
33
  sessionDefaults(kind: KanbanSessionKind): KanbanSessionDefaults;
54
34
  }
55
- export declare function registerSettings(ctx: Context): KanbanSettingsFace;
35
+ export declare function registerSettings(ctx: Context, config: KanbanSettingsShape): KanbanSettingsFace;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonlan/dsh-task-kanban",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "DSH web plugin: a task kanban that refines requirements into phased plans, auto-executes them in git worktrees (one session per phase), and auto-merges into the main branch on completion.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -80,52 +80,52 @@
80
80
  "@deepseek-ai/cordis": "^4.0.2",
81
81
  "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
82
82
  "@deepseek-ai/dsh-agent-presets": "^0.1.5-rc.2",
83
- "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
84
- "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
85
- "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
86
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
87
- "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
88
- "@deepseek-ai/schemastery": "^3.18.2",
89
83
  "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
90
84
  "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
91
85
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
92
86
  "@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2",
87
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
93
88
  "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
94
89
  "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
90
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
95
91
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
96
92
  "@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2",
97
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
98
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
99
- "react": "^18.2.0",
100
- "react-dom": "^18.2.0"
101
- },
102
- "devDependencies": {
103
- "@types/node": "^22.0.0",
104
- "@types/react": "~18.3.1",
105
- "@types/react-dom": "~18.3.0",
106
- "tsdown": "^0.22.14",
107
- "typescript": "^5.9.0",
108
- "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
109
- "@deepseek-ai/dsh-agent-presets": "^0.1.5-rc.2",
110
- "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
111
- "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
112
93
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
113
94
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
114
95
  "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
115
96
  "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
97
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
116
98
  "@deepseek-ai/schemastery": "^3.18.2",
99
+ "react": "^18.2.0",
100
+ "react-dom": "^18.2.0"
101
+ },
102
+ "devDependencies": {
103
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
104
+ "@deepseek-ai/dsh-agent-presets": "^0.1.5-rc.2",
117
105
  "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
118
106
  "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
119
107
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
120
108
  "@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2",
109
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
121
110
  "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
122
111
  "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
112
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
113
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
123
114
  "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.5-rc.2",
124
115
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
125
116
  "@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2",
126
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
127
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
128
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
117
+ "@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
118
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
119
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
120
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
121
+ "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
122
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
123
+ "@deepseek-ai/schemastery": "^3.18.3",
124
+ "@types/node": "^22.0.0",
125
+ "@types/react": "~18.3.1",
126
+ "@types/react-dom": "~18.3.0",
127
+ "tsdown": "^0.22.14",
128
+ "typescript": "^5.9.0",
129
129
  "vitest": "^3.2.0"
130
130
  },
131
131
  "packageManager": "pnpm@11.21.0"
@@ -358,157 +358,31 @@
358
358
  .kb-settings-saved { font-size: 12px; color: #4ade80; }
359
359
  .kb-settings-group { border-top: 1px solid rgba(128, 128, 128, 0.22); padding-top: 12px; }
360
360
  .kb-settings-group-title { font-size: 12px; font-weight: 600; opacity: 0.9; margin-bottom: 10px; }
361
- /* Settings card (设置 → 插件配置): mirrors the built-in plugin cards
362
- (dsh-client-ui-settings-plugins PluginCard) — same surface, border, head
363
- layout, and disclosure chevron, spelled with kb-settings-card prefixed
364
- classes. The prefix deliberately does NOT reuse the board's `.kb-card*`
365
- classes: the board card carries its own padding/cursor/user-select, and
366
- sharing the root class would leak those onto the settings card. */
367
- .kb-settings-card {
368
- border: 1px solid var(--dsw-alias-border-l2);
369
- background: var(--dsw-alias-bg-layer-3);
370
- border-radius: 12px;
371
- list-style: none;
372
- transition: border-color .16s, background .16s;
373
- }
374
- .kb-settings-card:hover { border-color: var(--dsw-alias-label-dimmed); }
375
- .kb-settings-card[data-open] {
376
- background: var(--dsw-alias-bg-layer-2);
377
- border-color: var(--dsw-alias-label-dimmed);
378
- }
379
- .kb-settings-card-head {
380
- appearance: none;
381
- width: 100%;
382
- font: inherit;
383
- color: inherit;
384
- text-align: left;
385
- cursor: pointer;
386
- background: transparent;
387
- border: 0;
388
- border-radius: 12px;
389
- align-items: center;
390
- gap: 12px;
391
- padding: 14px 16px;
361
+ /* Settings page (设置 -> 侧栏「任务看板」): the page owns one entry in the
362
+ settings sidebar and renders into the panel's content column, so it draws
363
+ its own header (title plus a one-line summary) above the always-visible
364
+ body. The body keeps the board's kb-settings control classes. */
365
+ .kb-page {
392
366
  display: flex;
367
+ flex-direction: column;
368
+ gap: 16px;
369
+ max-width: 720px;
393
370
  }
394
- .kb-settings-card-head:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
395
- .kb-settings-card-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }
396
- .kb-settings-card-title { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }
397
- .kb-settings-card-sub { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }
398
- .kb-settings-card-chevron {
399
- color: var(--dsw-alias-label-tertiary);
400
- flex: none;
401
- transition: transform .16s;
371
+ .kb-page-head {
402
372
  display: flex;
403
- align-items: center;
404
- justify-content: center;
405
- }
406
- .kb-settings-card-chevron[data-open] { transform: rotate(180deg); }
407
- .kb-settings-card-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding: 12px 0 8px; }
408
- .kb-footer-action {
409
- display: inline-flex;
410
- align-items: center;
411
- justify-content: center;
373
+ flex-direction: column;
412
374
  gap: 6px;
413
- width: 32px;
414
- height: 32px;
415
- border-radius: 6px;
416
- border: none;
417
- background: transparent;
418
- /* same foreground as the settings trigger (its trigger css uses this var) */
419
- color: var(--dsw-alias-label-primary, inherit);
420
- cursor: pointer;
421
- }
422
- .kb-footer-action:hover { background: rgba(128, 128, 128, 0.18); }
423
- .kb-footer-action-active { background: rgba(59, 130, 246, 0.25); }
424
- /* Wide mode borrows the settings trigger's metrics (34px tall, 14px label,
425
- 18px icon) so both controls read as siblings on the same line. */
426
- .kb-footer-action-wide {
427
- width: auto;
428
- height: 34px;
429
- padding: 0 8px;
430
- border-radius: 12px;
431
- font-size: 14px;
432
- }
433
- .kb-footer-action-wide .kb-footer-label {
434
- font-size: 14px;
435
- line-height: 22px;
436
- }
437
- /* Theme following is handled by the --dsw-alias-* tokens above: they change
438
- value with the shell's selected light/dark appearance, so no OS-level
439
- prefers-color-scheme override is needed (and one would fight the app
440
- theme when the two disagree). */
441
- /* Same-line placement WITHOUT moving any DOM: the sidebar foot area becomes
442
- a reversed flex row, so its LAST child (the settings area) renders on the
443
- LEFT and the FIRST child (footer actions = the kanban button) on the RIGHT
444
- — exactly "settings, then kanban". The settings trigger flexes to fill the
445
- remaining width; the button keeps its content width. */
446
- .kb-foot-row {
447
- display: flex;
448
- flex-direction: row-reverse;
449
- align-items: stretch;
450
- gap: 4px;
451
- }
452
- .kb-foot-row > div:first-child {
453
- flex: none;
454
- width: auto;
455
- min-width: 0;
456
- display: flex;
457
- align-items: center;
458
375
  }
459
- .kb-foot-row > div:last-child {
460
- flex: 1;
461
- min-width: 0;
462
- }
463
- /* Rail (collapsed sidebar): column-reverse stacks the settings circle on top
464
- and the kanban icon below it, both centered; the kanban button borrows the
465
- settings trigger's rail chrome (36px circle) so the two match. */
466
- .kb-foot-rail {
467
- flex-direction: column-reverse;
468
- align-items: center;
469
- }
470
- .kb-foot-rail .kb-footer-action {
471
- width: 36px;
472
- height: 36px;
473
- border-radius: 50%;
474
- margin: 8px 0 10px;
376
+ .kb-page-title {
377
+ margin: 0;
378
+ color: var(--dsw-alias-label-primary);
379
+ font-size: 15px;
380
+ font-weight: 600;
381
+ line-height: 1.4;
475
382
  }
476
- .kb-footer-label {
477
- max-width: 110px;
478
- overflow: hidden;
479
- text-overflow: ellipsis;
480
- white-space: nowrap;
383
+ .kb-page-sub {
384
+ margin: 0;
385
+ color: var(--dsw-alias-label-tertiary);
386
+ font-size: 13px;
387
+ line-height: 1.5;
481
388
  }
482
- /* Narrow screens (mobile drawer / auto-collapse breakpoint): the drawer's
483
- foot area is ~256-280px but the footer actions row carries the kanban
484
- button PLUS mobile-nav's drawer actions (Files / session log), which
485
- together exceed the row and squeeze the settings trigger to zero width
486
- (row-reverse overflows left, clipping the button). Below the shell's
487
- auto-collapse breakpoint, stack the settings row above the actions and
488
- let the actions wrap so nothing is squeezed or clipped. */
489
- @media (max-width: 1023px) {
490
- .kb-foot-row {
491
- flex-direction: column-reverse;
492
- align-items: stretch;
493
- }
494
- .kb-foot-row > div:first-child {
495
- flex: none;
496
- width: 100%;
497
- display: flex;
498
- flex-wrap: wrap;
499
- justify-content: flex-start;
500
- gap: 4px;
501
- padding-top: 4px;
502
- }
503
- .kb-foot-row > div:last-child {
504
- flex: none;
505
- width: 100%;
506
- }
507
- /* The footer-actions entries are display:contents wrappers, so the kanban
508
- button itself is a flex item of the wrapping actions row; pull it to the
509
- first wrap position so it sits directly below the settings row (the
510
- mobile-nav drawer actions follow on their own wrapped line). */
511
- .kb-foot-row .kb-footer-action {
512
- order: -1;
513
- }
514
- }
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import type { KanbanCard, Lane, PhaseAttempt, PlanPhase } from '../shared/card'
3
3
  import { DROP_RULES, laneOf } from '../shared/lanes'
4
4
  import { api, gatewaySkillList, type SkillOption } from './api'
5
- import { exitBoard, getClient } from './kanban-state'
5
+ import { getClient, leaveKanbanPanel } from './kanban-state'
6
6
  import { en, zh } from './locales'
7
7
  import './board.css'
8
8
 
@@ -158,7 +158,10 @@ export function BoardRoot(props: BoardProps): JSX.Element {
158
158
 
159
159
  const openSession = useCallback((sessionId: string) => {
160
160
  const ctx = getClient()
161
- exitBoard()
161
+ // Return the center column to the Conversation first: `sessions.open` is
162
+ // wrapped by bindSessionNavigation as well, but a runtime without that
163
+ // binding must still leave the board before the session renders.
164
+ leaveKanbanPanel(ctx)
162
165
  if (ctx === null) return
163
166
  try {
164
167
  ;(ctx.sessions as { open(id: string): void }).open(sessionId)
@@ -0,0 +1,74 @@
1
+ import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
2
+
3
+ /** Props every shell icon accepts: square edge in px, color rides currentColor. */
4
+ export interface IconProps {
5
+ size?: number
6
+ className?: string
7
+ }
8
+
9
+ /** One shell icon component. */
10
+ export type IconComponent = (props: IconProps) => JSX.Element
11
+
12
+ /** The runtime export table, read by capability instead of by one hard-coded name. */
13
+ const table = primitives as unknown as Record<string, IconComponent | undefined>
14
+
15
+ /** First name the runtime actually provides (a missing export is `undefined`). */
16
+ function firstDefined(...names: string[]): IconComponent | undefined {
17
+ for (const name of names) {
18
+ let candidate: IconComponent | undefined
19
+ try {
20
+ candidate = table[name]
21
+ } catch {
22
+ // Some module namespaces reject unknown names instead of yielding
23
+ // `undefined`; a rename must never throw on its way to the fallback.
24
+ continue
25
+ }
26
+ if (typeof candidate === 'function') return candidate
27
+ }
28
+ return undefined
29
+ }
30
+
31
+ /**
32
+ * Self-contained 16×16 checklist glyph (the 0.1.7 artwork) used when the shell
33
+ * primitives expose none of the names above: the footer entry must render
34
+ * something even on a runtime whose icon exports we do not know.
35
+ */
36
+ function ChecklistFallback({ size = 14, className }: IconProps): JSX.Element {
37
+ return (
38
+ <svg
39
+ width={size}
40
+ height={size}
41
+ className={className}
42
+ viewBox="0 0 16 16"
43
+ fill="none"
44
+ xmlns="http://www.w3.org/2000/svg"
45
+ aria-hidden="true"
46
+ strokeWidth={1}
47
+ >
48
+ <path d="M3.75 6.25C4.7165 6.25 5.5 5.4665 5.5 4.5C5.5 3.5335 4.7165 2.75 3.75 2.75C2.7835 2.75 2 3.5335 2 4.5C2 5.4665 2.7835 6.25 3.75 6.25Z" stroke="currentColor" />
49
+ <path d="M7.5 4.5H13.5" stroke="currentColor" />
50
+ <path d="M3.75 13.25C4.7165 13.25 5.5 12.4665 5.5 11.5C5.5 10.5335 4.7165 9.75 3.75 9.75C2.7835 9.75 2 10.5335 2 11.5C2 12.4665 2.7835 13.25 3.75 13.25Z" stroke="currentColor" />
51
+ <path d="M7.5 11.5H13.5" stroke="currentColor" />
52
+ </svg>
53
+ )
54
+ }
55
+
56
+ /**
57
+ * The checklist icon used by the sidebar panel row entry.
58
+ *
59
+ * DSH 0.1.7 renamed every size-suffixed shell icon (`IconChecklistOutline14`)
60
+ * into stroke-weight variants (`…Regular` 1px / `…Medium` 1.3px) inside
61
+ * `@deepseek-ai/dsh-client-ui-primitives`. A stale named import resolves to
62
+ * `undefined` and React then throws "Element type is invalid" while rendering
63
+ * the slot entry — the entry is dropped and the button silently disappears
64
+ * (exactly what upgrading to 0.1.7 did to this plugin). Resolve the name at
65
+ * runtime, newest first, so the next rename degrades to another shipped
66
+ * variant instead of taking the button down with it.
67
+ */
68
+ export const ChecklistIcon: IconComponent =
69
+ firstDefined(
70
+ 'IconChecklistOutlineRegular',
71
+ 'IconChecklistOutlineMedium',
72
+ 'IconChecklistOutline',
73
+ 'IconChecklistOutline14',
74
+ ) ?? ChecklistFallback
@@ -4,11 +4,14 @@ import type {} from '@deepseek-ai/dsh-client-locale/client'
4
4
  import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
5
5
  import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
6
6
  import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
7
- import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
7
+ import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
8
8
  import { BoardRoot } from './board'
9
- import { KanbanFooterButton, KanbanSettingsCard } from './sections'
9
+ import { KanbanPanelIcon, KanbanSettingsSection } from './sections'
10
10
  import { LOCALE_NS, zh, en } from './locales'
11
- import { setClient, setBoardRoot, bindSessionNavigation } from './kanban-state'
11
+ import { KANBAN_PANEL_ID, setClient, bindSessionNavigation } from './kanban-state'
12
+
13
+ /** The settings sidebar entry this plugin's page owns (must stay stable). */
14
+ const SETTINGS_SECTION_ID = 'task-kanban'
12
15
 
13
16
  /** Services required before mounting (provided by the client runtime). */
14
17
  export const inject = ['slots', 'sessions', 'workspaces', 'locale']
@@ -16,8 +19,7 @@ export const inject = ['slots', 'sessions', 'workspaces', 'locale']
16
19
  /** Client plugin body. */
17
20
  export function apply(ctx: ClientContext): void {
18
21
  setClient(ctx)
19
- setBoardRoot(BoardRoot as unknown as (props: Record<string, unknown>) => React.ReactNode)
20
- // Selecting a session while the board is open must leave the board first.
22
+ // Selecting a session while the board is open must return to the Conversation.
21
23
  bindSessionNavigation(ctx)
22
24
 
23
25
  // Live translate bound to the active locale (labels re-read it per call).
@@ -27,25 +29,43 @@ export function apply(ctx: ClientContext): void {
27
29
  return () => off()
28
30
  }, 'task-kanban: dictionaries')
29
31
 
30
- ctx.slots.inject('sidebar.footer.action', () =>
32
+ // The board is a GLOBAL MAIN PANEL: the shell renders one `main` entry per
33
+ // selected panel id, so keying it (instead of replacing `main.conversation`)
34
+ // keeps the Conversation mounted and selectable beside the board, and the
35
+ // sidebar's own panel row owns the button that selects it (the
36
+ // `sidebar.panellist` entry below). Nothing of this plugin sits in the
37
+ // sidebar foot any more, so the Settings trigger can never be squeezed.
38
+ ctx.slots.inject('main', () =>
39
+ ctx.slots.register({
40
+ name: 'main',
41
+ key: KANBAN_PANEL_ID,
42
+ locale: LOCALE_NS,
43
+ } as never, BoardRoot as never),
44
+ )
45
+
46
+ // Sidebar panel row: the shell renders the row (label, tooltip, active state)
47
+ // and calls `layout.selectPanel(id)` on click; the entry supplies the glyph.
48
+ ctx.slots.inject('sidebar.panellist', () =>
31
49
  ctx.slots.register({
32
- name: 'sidebar.footer.action',
33
- id: 'task-kanban',
34
- order: 60,
50
+ name: 'sidebar.panellist',
51
+ id: KANBAN_PANEL_ID,
52
+ order: 20,
35
53
  label: () => t('kanban'),
36
54
  locale: LOCALE_NS,
37
- }, KanbanFooterButton as never),
55
+ }, KanbanPanelIcon as never),
38
56
  )
39
57
 
40
- // The plugin's own Settings Card (设置 → 插件配置) rides the `task-kanban`
41
- // settings namespace: registering into the keyed `settings.plugin.item` slot
42
- // with the namespace string makes the configurable-plugins tab dispatch the
43
- // card next to the built-in ones (bash / agent loop / web search).
44
- ctx.slots.inject('settings.plugin.item', () =>
58
+ // The plugin's own settings page (设置 -> 侧栏「任务看板」) rides the
59
+ // task-kanban settings namespace and registers into the settings.section
60
+ // list slot, which gives it one entry in the settings sidebar and renders
61
+ // its content into the panel's content column.
62
+ ctx.slots.inject('settings.section', () =>
45
63
  ctx.slots.register({
46
- name: 'settings.plugin.item',
47
- key: 'task-kanban',
64
+ name: 'settings.section',
65
+ id: SETTINGS_SECTION_ID,
66
+ order: 200,
67
+ label: () => t('settingsTitle'),
48
68
  locale: LOCALE_NS,
49
- }, KanbanSettingsCard as never),
69
+ }, KanbanSettingsSection as never),
50
70
  )
51
71
  }