@linxin666/dsh-client-ui-task-board 0.4.3 → 0.4.5
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.i18n.yaml +2 -2
- package/README.md +56 -18
- package/README.zh.md +56 -18
- package/lib/client.js +6242 -958
- package/lib/client.js.map +1 -1
- package/lib/index.js +3475 -234
- package/lib/types/client/PluginSettingsCard.d.ts +7 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -3
- package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
- package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
- package/lib/types/client/board/EditTaskModal.d.ts +5 -2
- package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
- package/lib/types/client/board/NewTaskModal.d.ts +4 -1
- package/lib/types/client/board/TagManagerModal.d.ts +26 -0
- package/lib/types/client/board/TaskBoard.d.ts +10 -1
- package/lib/types/client/board/TaskCard.d.ts +14 -1
- package/lib/types/client/board/TaskDetail.d.ts +3 -1
- package/lib/types/client/board/TaskForm.d.ts +23 -2
- package/lib/types/client/board/VerificationReport.d.ts +23 -0
- package/lib/types/client/board/card-view.d.ts +113 -0
- package/lib/types/client/board/icons.d.ts +32 -0
- package/lib/types/client/board/overlay.d.ts +69 -0
- package/lib/types/client/board/preset-label.d.ts +22 -0
- package/lib/types/client/board/status-key.d.ts +15 -0
- package/lib/types/client/board/task-markdown.d.ts +19 -0
- package/lib/types/client/board-card-seat.d.ts +34 -0
- package/lib/types/client/host-api.d.ts +24 -1
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/locales.d.ts +141 -3
- package/lib/types/client/native-panel.d.ts +6 -20
- package/lib/types/client/schedule-zone.d.ts +47 -0
- package/lib/types/client/seats.d.ts +18 -0
- package/lib/types/client/service.d.ts +32 -0
- package/lib/types/client/settings-form.d.ts +16 -0
- package/lib/types/core/controller.d.ts +38 -1
- package/lib/types/core/degraded-reason.d.ts +46 -0
- package/lib/types/core/extension.d.ts +283 -0
- package/lib/types/core/handover.d.ts +13 -1
- package/lib/types/core/issue-form.d.ts +16 -0
- package/lib/types/core/package-name.d.ts +9 -0
- package/lib/types/core/schedule.d.ts +95 -20
- package/lib/types/core/subtask.d.ts +30 -11
- package/lib/types/core/tasks.d.ts +110 -5
- package/lib/types/core/use-cases/task-create.d.ts +2 -1
- package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
- package/lib/types/core/use-cases/task-tag.d.ts +48 -0
- package/lib/types/core/use-cases/task-update.d.ts +5 -3
- package/lib/types/core/verification.d.ts +386 -0
- package/lib/types/core/workspace-target.d.ts +41 -0
- package/lib/types/host/agent-tools.d.ts +1 -1
- package/lib/types/host/extension-registry.d.ts +111 -0
- package/lib/types/host/llm-dispatch.d.ts +63 -0
- package/lib/types/host/verification-gate.d.ts +71 -0
- package/lib/types/host/verification-runner.d.ts +108 -0
- package/lib/types/host/workspace-evidence.d.ts +53 -0
- package/lib/types/host-ledger.d.ts +107 -11
- package/lib/types/host-runner.d.ts +99 -4
- package/lib/types/host-service.d.ts +114 -2
- package/lib/types/index.d.ts +80 -26
- package/lib/types/protocol.d.ts +53 -3
- package/lib/types/tool-surface.d.ts +71 -0
- package/package.json +27 -26
- package/src/client/PluginSettingsCard.tsx +9 -0
- package/src/client/TaskBoardSettingsCard.tsx +304 -65
- package/src/client/board/ClampedMarkdown.tsx +69 -0
- package/src/client/board/ConfirmDialog.tsx +7 -3
- package/src/client/board/EditTaskModal.tsx +5 -2
- package/src/client/board/LinkSubtaskModal.tsx +30 -15
- package/src/client/board/NewTaskModal.tsx +288 -151
- package/src/client/board/TagManagerModal.tsx +235 -0
- package/src/client/board/TaskBoard.tsx +259 -103
- package/src/client/board/TaskCard.tsx +238 -86
- package/src/client/board/TaskDetail.tsx +154 -59
- package/src/client/board/TaskForm.tsx +61 -6
- package/src/client/board/VerificationReport.tsx +203 -0
- package/src/client/board/card-view.ts +220 -0
- package/src/client/board/icons.tsx +89 -0
- package/src/client/board/overlay.tsx +189 -0
- package/src/client/board/preset-label.ts +44 -0
- package/src/client/board/status-key.ts +27 -0
- package/src/client/board/task-markdown.module.css +137 -0
- package/src/client/board/task-markdown.tsx +106 -0
- package/src/client/board-card-seat.ts +102 -0
- package/src/client/board-settings.module.css +66 -0
- package/src/client/board.module.css +602 -68
- package/src/client/host-api.ts +109 -6
- package/src/client/index.ts +48 -6
- package/src/client/locales.ts +290 -12
- package/src/client/native-panel.tsx +18 -2
- package/src/client/schedule-zone.ts +135 -0
- package/src/client/seats.tsx +58 -0
- package/src/client/service.ts +94 -0
- package/src/client/settings-card.module.css +31 -5
- package/src/client/settings-form.ts +27 -1
- package/src/core/controller.ts +101 -5
- package/src/core/degraded-reason.ts +81 -0
- package/src/core/extension.ts +376 -0
- package/src/core/freeze-snapshot.ts +2 -2
- package/src/core/handover.ts +16 -1
- package/src/core/issue-form.ts +19 -0
- package/src/core/package-name.ts +9 -0
- package/src/core/schedule.ts +211 -55
- package/src/core/store.ts +26 -1
- package/src/core/subtask.ts +77 -22
- package/src/core/tasks.ts +138 -10
- package/src/core/use-cases/task-create.ts +14 -2
- package/src/core/use-cases/task-parent.ts +3 -2
- package/src/core/use-cases/task-schedule.ts +17 -6
- package/src/core/use-cases/task-tag.ts +110 -0
- package/src/core/use-cases/task-update.ts +13 -4
- package/src/core/verification.ts +826 -0
- package/src/core/workspace-target.ts +59 -0
- package/src/host/agent-tools.ts +58 -24
- package/src/host/extension-registry.ts +509 -0
- package/src/host/llm-dispatch.ts +87 -0
- package/src/host/verification-gate.ts +331 -0
- package/src/host/verification-runner.ts +535 -0
- package/src/host/workspace-evidence.ts +176 -0
- package/src/host-ai.ts +15 -9
- package/src/host-ledger.ts +342 -51
- package/src/host-routes.ts +15 -2
- package/src/host-runner.ts +219 -20
- package/src/host-service.ts +346 -26
- package/src/index.ts +204 -16
- package/src/protocol.ts +122 -9
- package/src/tool-surface.ts +95 -0
- package/lib/types/host/run-guarded.d.ts +0 -39
- package/src/host/run-guarded.ts +0 -76
package/src/client/host-api.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { degradedRowOf, ledgerOwnerPid } from '../core/degraded-reason.ts'
|
|
2
|
+
import { TASK_BOARD_PACKAGE } from '../core/package-name.ts'
|
|
1
3
|
import type { TaskRecord } from '../core/tasks.ts'
|
|
2
4
|
import { t } from './locales.ts'
|
|
3
5
|
import {
|
|
@@ -37,7 +39,20 @@ const TASK_PARSE_TIMEOUT_SECONDS = 45
|
|
|
37
39
|
* "Unexpected token 'o', \"not found\" is not valid JSON" is what a missing
|
|
38
40
|
* Host half looks like today.
|
|
39
41
|
*/
|
|
40
|
-
export type HostApiFailure =
|
|
42
|
+
export type HostApiFailure =
|
|
43
|
+
/** The board's own routes are not registered and nothing explains why. */
|
|
44
|
+
| 'not-mounted'
|
|
45
|
+
/** Another live DSH process owns the ledger, so this instance registered no board routes (#1730). */
|
|
46
|
+
| 'ledger-locked'
|
|
47
|
+
/** This row degraded during start for some other reason the family health route reports (#1730). */
|
|
48
|
+
| 'degraded'
|
|
49
|
+
| 'unauthorized'
|
|
50
|
+
| 'forbidden'
|
|
51
|
+
| 'locked'
|
|
52
|
+
| 'rejected'
|
|
53
|
+
| 'timeout'
|
|
54
|
+
| 'unreachable'
|
|
55
|
+
| 'unexpected'
|
|
41
56
|
|
|
42
57
|
/** Transport failure carrying a stable class next to its user-facing message. */
|
|
43
58
|
export class HostApiError extends Error {
|
|
@@ -47,6 +62,86 @@ export class HostApiError extends Error {
|
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Same-origin family health route served by the dsh-web-all shell: the
|
|
67
|
+
* degraded ledger, in which a family row that failed during apply appears WITH
|
|
68
|
+
* its reason. DOCUMENT-RELATIVE for the same reason as the board's own routes
|
|
69
|
+
* (issue #1707). A 404 here means the family shell is absent or older than the
|
|
70
|
+
* route, which is not itself a failure of the board.
|
|
71
|
+
*/
|
|
72
|
+
const FAMILY_DEGRADED_ROUTE = 'api/dsh-web-all/degraded'
|
|
73
|
+
|
|
74
|
+
/** Health-route ceiling: an unresponsive family shell must not hold the board's first paint. */
|
|
75
|
+
const DEGRADED_LOOKUP_TIMEOUT_MS = 2_000
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The failure text for a row this browser knows failed during start.
|
|
79
|
+
*
|
|
80
|
+
* A ledger lock is the one class with an owner the user can act on, so its
|
|
81
|
+
* reason is unpacked into the pid plus what to do about it; every other
|
|
82
|
+
* degraded row is reported with the shell's own reason verbatim. The raw
|
|
83
|
+
* reason rides along either way, because it is the only text that names the
|
|
84
|
+
* lock file or the recovery step when the pid is not the whole story.
|
|
85
|
+
* @param reason - the shell's one-line failure reason (never empty).
|
|
86
|
+
*/
|
|
87
|
+
function degradedFailureMessage(reason: string): { failure: 'ledger-locked' | 'degraded'; message: string } {
|
|
88
|
+
const pid = ledgerOwnerPid(reason)
|
|
89
|
+
if (pid === undefined) return { failure: 'degraded', message: t('board.hostError.degraded', { detail: reason }) }
|
|
90
|
+
return { failure: 'ledger-locked', message: t('board.hostError.ledgerLocked', { pid: String(pid), detail: reason }) }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The degraded record the family shell holds for THIS board, or undefined when
|
|
95
|
+
* the row is healthy, the shell is absent, or the answer is unusable. The
|
|
96
|
+
* lookup is best-effort by contract: any uncertainty keeps the caller's own
|
|
97
|
+
* failure text instead of replacing it with a weaker one.
|
|
98
|
+
* @param fetchImpl - fetch implementation (injected for tests).
|
|
99
|
+
* @returns the ledger's reason for this row, or undefined when it names none.
|
|
100
|
+
*/
|
|
101
|
+
async function fetchDegradedReason(fetchImpl: typeof fetch): Promise<string | undefined> {
|
|
102
|
+
const controller = new AbortController()
|
|
103
|
+
const timer = globalThis.setTimeout(() => { controller.abort() }, DEGRADED_LOOKUP_TIMEOUT_MS)
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetchImpl(FAMILY_DEGRADED_ROUTE, { signal: controller.signal, headers: { accept: 'application/json' }, cache: 'no-store' })
|
|
106
|
+
if (!response.ok) return undefined
|
|
107
|
+
return degradedRowOf(await response.json(), TASK_BOARD_PACKAGE)?.reason
|
|
108
|
+
} catch {
|
|
109
|
+
return undefined
|
|
110
|
+
} finally {
|
|
111
|
+
globalThis.clearTimeout(timer)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Name the real reason this board's routes are missing (issue #1730).
|
|
117
|
+
*
|
|
118
|
+
* The core webserver answers an unmounted route with a bare 404, which cannot
|
|
119
|
+
* distinguish "this deployment never loaded the plugin" from "this row
|
|
120
|
+
* degraded during start" — a second DSH process holding `ledger-v2.lock` is
|
|
121
|
+
* the case that matters, because the panel's advice ("restart the service")
|
|
122
|
+
* is then false: the lock belongs to the other process, and restarting cannot
|
|
123
|
+
* win it. The family health route knows the difference, so the 404 is
|
|
124
|
+
* disambiguated there before anything is shown.
|
|
125
|
+
* @param fallback - copy for the plain "routes are not there" failure, which
|
|
126
|
+
* is route-specific (the board's state route and the AI-parse route phrase
|
|
127
|
+
* their own absence); it is also what is shown when the ledger names nothing.
|
|
128
|
+
* @param fetchImpl - fetch implementation (injected for tests).
|
|
129
|
+
* @returns the HostApiError to throw for a missing board route.
|
|
130
|
+
*/
|
|
131
|
+
export async function missingRouteFailure(
|
|
132
|
+
fallback: string = t('board.hostError.notMounted'),
|
|
133
|
+
fetchImpl: typeof fetch = fetch,
|
|
134
|
+
): Promise<HostApiError> {
|
|
135
|
+
const absent = (): HostApiError => new HostApiError('not-mounted', fallback, 404)
|
|
136
|
+
const reason = await fetchDegradedReason(fetchImpl)
|
|
137
|
+
// A shell that predates the reason field (or recorded none) tells us the row
|
|
138
|
+
// is degraded but not why; the route's own wording is more honest than an
|
|
139
|
+
// empty "because".
|
|
140
|
+
if (reason === undefined || reason === '') return absent()
|
|
141
|
+
const described = degradedFailureMessage(reason)
|
|
142
|
+
return new HostApiError(described.failure, described.message, 404)
|
|
143
|
+
}
|
|
144
|
+
|
|
50
145
|
function uuid(): string {
|
|
51
146
|
return globalThis.crypto?.randomUUID?.() ?? `browser-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
52
147
|
}
|
|
@@ -79,14 +174,18 @@ async function readJson<T>(response: Response): Promise<T> {
|
|
|
79
174
|
// The Host answered with a reason of its own. A ledger lock is the one
|
|
80
175
|
// class worth naming in the panel's words; the authentication fence and
|
|
81
176
|
// every other action rejection are reported as they arrive.
|
|
82
|
-
if (hostError === 'forbidden') throw new HostApiError('
|
|
177
|
+
if (hostError === 'forbidden') throw new HostApiError('forbidden', t('board.hostError.forbidden'), response.status)
|
|
83
178
|
if (response.status === 503 || /lock/i.test(hostError)) {
|
|
84
179
|
throw new HostApiError('locked', t('board.hostError.locked', { detail: hostError }), response.status)
|
|
85
180
|
}
|
|
86
181
|
throw new HostApiError('rejected', hostError, response.status)
|
|
87
182
|
}
|
|
88
|
-
|
|
89
|
-
|
|
183
|
+
// A bare 404 is the ONLY signal that this board's routes are absent; the
|
|
184
|
+
// family health route is what turns it into the real reason when this row
|
|
185
|
+
// degraded during start (issue #1730).
|
|
186
|
+
if (response.status === 404) throw await missingRouteFailure()
|
|
187
|
+
if (response.status === 403) throw new HostApiError('forbidden', t('board.hostError.forbidden'), 403)
|
|
188
|
+
if (response.status === 401) throw new HostApiError('unauthorized', t('board.hostError.unauthorized'), 401)
|
|
90
189
|
throw new HostApiError('unexpected', t('board.hostError.unexpected', { status: String(response.status) }), response.status)
|
|
91
190
|
}
|
|
92
191
|
|
|
@@ -193,8 +292,12 @@ export class HttpTaskBoardHostTransport implements TaskBoardHostTransport {
|
|
|
193
292
|
if (record !== undefined && isTaskParseDraft(record.draft)) return record.draft
|
|
194
293
|
throw new HostApiError('unexpected', t('new.aiParseFailed', { error: t('board.hostError.unexpected', { status: String(response.status) }) }), response.status)
|
|
195
294
|
}
|
|
196
|
-
|
|
197
|
-
|
|
295
|
+
// The parse route lives beside the board's own routes, so a 404 means the
|
|
296
|
+
// same thing here: absent routes, with the same health-ledger detail and
|
|
297
|
+
// this route's own wording as the fallback.
|
|
298
|
+
if (response.status === 404) throw await missingRouteFailure(t('new.aiParseUnavailable'))
|
|
299
|
+
if (response.status === 403) throw new HostApiError('forbidden', t('board.hostError.forbidden'), 403)
|
|
300
|
+
if (response.status === 401) throw new HostApiError('unauthorized', t('board.hostError.unauthorized'), 401)
|
|
198
301
|
const code = typeof record?.code === 'string' ? record.code : undefined
|
|
199
302
|
if (code === 'no-model') throw new HostApiError('rejected', t('new.aiParseNoModel'), response.status)
|
|
200
303
|
if (code === 'timeout') throw new HostApiError('timeout', t('new.aiParseTimeout', { seconds: String(TASK_PARSE_TIMEOUT_SECONDS) }), response.status)
|
package/src/client/index.ts
CHANGED
|
@@ -33,8 +33,16 @@ import { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardS
|
|
|
33
33
|
import { en, zh, setRuntimeTranslate, type TaskBoardKey } from './locales.ts'
|
|
34
34
|
import { HttpTaskBoardHostTransport } from './host-api.ts'
|
|
35
35
|
import { reportDailyHeartbeat } from './telemetry.ts'
|
|
36
|
-
import {
|
|
36
|
+
import { installBoardCard } from './board-card-seat.ts'
|
|
37
37
|
import { createServedEntryForm } from './settings-entry-form.ts'
|
|
38
|
+
import { TaskBoardClientService } from './service.ts'
|
|
39
|
+
import {
|
|
40
|
+
TASK_BOARD_SERVICE_NAME,
|
|
41
|
+
type TaskBoardCardDecorationProps,
|
|
42
|
+
type TaskBoardDetailSectionProps,
|
|
43
|
+
type TaskBoardHostFace,
|
|
44
|
+
type TaskBoardSettingsSectionProps,
|
|
45
|
+
} from '../core/extension.ts'
|
|
38
46
|
|
|
39
47
|
/** Locale namespace this plugin owns. */
|
|
40
48
|
const NS = 'task-board'
|
|
@@ -95,6 +103,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
95
103
|
* package can register without depending on the sibling UI package.
|
|
96
104
|
*/
|
|
97
105
|
'web-ui.plugin.item': { kind: 'list'; scope: 'root'; owner: SettingsPluginItemOwnerProps }
|
|
106
|
+
|
|
107
|
+
/** Provider task-detail section; the board declares and renders it. */
|
|
108
|
+
'task-board.detail.section': { kind: 'list'; scope: 'root'; owner: TaskBoardDetailSectionProps }
|
|
109
|
+
/** Provider settings section; the board declares and renders it. */
|
|
110
|
+
'task-board.settings.section': { kind: 'list'; scope: 'root'; owner: TaskBoardSettingsSectionProps }
|
|
111
|
+
/** Provider card decoration; the board declares and renders it. */
|
|
112
|
+
'task-board.card.decoration': { kind: 'list'; scope: 'root'; owner: TaskBoardCardDecorationProps }
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
|
|
@@ -211,12 +226,26 @@ export function apply(ctx: ClientContext): void {
|
|
|
211
226
|
// namespace, contributed to whichever plugin-card seat this host declares
|
|
212
227
|
// (the family group's list seat, or the official bundle-configuration seat).
|
|
213
228
|
const settingsForm = bindSettingsForm(ctx)
|
|
214
|
-
|
|
215
|
-
|
|
229
|
+
// The board's browser-half extension service. It is provided before any
|
|
230
|
+
// controller exists (the UI mounts only once the settings form settles), so
|
|
231
|
+
// providers can subscribe and register visibility immediately.
|
|
232
|
+
const clientService = new TaskBoardClientService()
|
|
233
|
+
// A capture-only test context implements no service registry; the board's
|
|
234
|
+
// own surfaces still mount.
|
|
235
|
+
if (typeof (ctx as { provide?: unknown }).provide === 'function') {
|
|
236
|
+
;(ctx.provide as unknown as (name: string, value: unknown) => void)(TASK_BOARD_SERVICE_NAME, clientService as unknown as TaskBoardHostFace)
|
|
237
|
+
}
|
|
238
|
+
ctx.effect(() => () => { clientService.detach() }, 'task-board: extension service')
|
|
239
|
+
|
|
240
|
+
const settingsCard = new TaskBoardSettingsCardController(settingsForm, request => clientService.dispatch(request))
|
|
241
|
+
installBoardCard(ctx, {
|
|
216
242
|
bundle: '@linxin666/dsh-client-ui-task-board',
|
|
217
243
|
id: 'task-board',
|
|
218
244
|
order: 110,
|
|
219
245
|
locale: NS,
|
|
246
|
+
// The board's own settings card declares the provider settings seat; the
|
|
247
|
+
// provider registers into it and this card renders it.
|
|
248
|
+
children: { 'task-board.settings.section': { kind: 'list', scope: 'root' } },
|
|
220
249
|
inject: () => settingsCard.inject(),
|
|
221
250
|
component: TaskBoardSettingsCard,
|
|
222
251
|
})
|
|
@@ -263,6 +292,9 @@ export function apply(ctx: ClientContext): void {
|
|
|
263
292
|
},
|
|
264
293
|
})
|
|
265
294
|
controller.start()
|
|
295
|
+
// The extension service follows the live controller: providers observe the
|
|
296
|
+
// board mirror and dispatch through it while the board runs.
|
|
297
|
+
clientService.attach(controller)
|
|
266
298
|
|
|
267
299
|
const disposers: Array<() => void> = []
|
|
268
300
|
|
|
@@ -372,8 +404,12 @@ export function apply(ctx: ClientContext): void {
|
|
|
372
404
|
console.error('[dsh-task-board] model options read failed', error)
|
|
373
405
|
}
|
|
374
406
|
}
|
|
407
|
+
void pushPresetOptions()
|
|
375
408
|
void pushModelOptions()
|
|
376
|
-
disposers.push(ctx.on('connection/reset', () => {
|
|
409
|
+
disposers.push(ctx.on('connection/reset', () => {
|
|
410
|
+
void pushPresetOptions()
|
|
411
|
+
void pushModelOptions()
|
|
412
|
+
}))
|
|
377
413
|
// Native panel surfaces: one sidebar row and one center-column page, both
|
|
378
414
|
// through the official slot seats the shell itself renders. The layout's
|
|
379
415
|
// panel selection is reconciled back into the controller so the board's own
|
|
@@ -396,6 +432,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
396
432
|
|
|
397
433
|
uiDisposer = () => {
|
|
398
434
|
for (const dispose of disposers.splice(0)) dispose()
|
|
435
|
+
clientService.detach()
|
|
399
436
|
controller.dispose()
|
|
400
437
|
uiDisposer = undefined
|
|
401
438
|
}
|
|
@@ -405,8 +442,13 @@ export function apply(ctx: ClientContext): void {
|
|
|
405
442
|
const enabled = snapshot.status === 'ready'
|
|
406
443
|
? snapshot.value?.enabled ?? true
|
|
407
444
|
: snapshot.status === 'unavailable'
|
|
408
|
-
if (enabled)
|
|
409
|
-
|
|
445
|
+
if (enabled) {
|
|
446
|
+
mountUi()
|
|
447
|
+
clientService.setEnabled(true)
|
|
448
|
+
} else {
|
|
449
|
+
clientService.setEnabled(false)
|
|
450
|
+
uiDisposer?.()
|
|
451
|
+
}
|
|
410
452
|
}
|
|
411
453
|
const unsubscribeSettings = settingsForm.subscribe(syncEnabled)
|
|
412
454
|
// The mounted surfaces and the settings subscription belong to this fiber.
|