dsh-tiddlywiki 0.30.49 → 0.30.50

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-tiddlywiki",
3
3
  "description": "把 TiddlyWiki 5 变成你和 Agent 共用的本地文档中心(纯文本 + git):你在 DSH 界面里阅读、写作、记日记,Agent 用 15 个 tiddlywiki_* 工具读写同一批笔记;每条笔记都是你能读、能改、能审计、能随时带走的文件——不是自动记忆插件,不做后台抓取。可选一键发布到微信公众号草稿箱。 Turn TiddlyWiki 5 into a document center you and your agent share, in plain text under git — a knowledge base you can read, edit, audit and take with you, not an auto-memory plugin. Mounts via the official dsh plugin system; no DSH source changes.",
4
- "version": "0.30.49",
4
+ "version": "0.30.50",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "dsh",
@@ -0,0 +1,202 @@
1
+ /**
2
+ * dsh-tiddlywiki — host half: the STARTUP stage (v0.30.50).
3
+ *
4
+ * `apply()` used to inline this ~100-line task between the git layer and the
5
+ * wiki views. It is a STAGE, not wiring: it runs exactly once, in a fixed order,
6
+ * and every step after the first await is followed by a `disposed` check.
7
+ *
8
+ * WHAT STAYS IN THE ENTRY POINT
9
+ * -----------------------------
10
+ * - `disposed` — its owner is the teardown effect (which sets it) AND the
11
+ * startup task (which checks it after every await). The task therefore gets it
12
+ * as an **accessor** (`isDisposed`) and, when it notices teardown, stops the
13
+ * child ITSELF (v0.19.1: otherwise the disposer finishes first and the task
14
+ * then spawns an orphan TW child / binds a leaked listener).
15
+ * - `farm` — created BY this task, but owned by the whole plugin (routes, tools,
16
+ * wiki views all read it). So the task creates it through a `setFarm` callback
17
+ * instead of holding it: the entry point stays the single owner of the mutable
18
+ * state, exactly as `index-wikis.ts` / `index-routes.ts` do it.
19
+ * - The control-file cache (`controlRegistry` / `controlSource` /
20
+ * `controlWarnings` / `controlError`) and the session scopes are filled by this
21
+ * task but read everywhere else ⇒ same pattern, one `setControl*` each.
22
+ *
23
+ * ORDER IS SEMANTICS (do not reorder):
24
+ * 1. install the 「拆分知识库」 skill — deliberately BEFORE the wiki, because it
25
+ * must still happen when the wiki is slow, fails, or the config is broken.
26
+ * (It used to sit after `farm.startAll()`: a wiki that never became ready
27
+ * also silently never installed the skill. Found by restarting a real host.)
28
+ * 2. read the CONTROL FILE (mode + list + default id) — it must be read before
29
+ * anything starts, which is why it lives outside every wiki.
30
+ * 3. read the per-session scopes (a preference: an unreadable file just means
31
+ * "no explicit scope").
32
+ * 4. build the farm and `startAll()` — each instance brings ITSELF up (child →
33
+ * its config tiddler → seeds → git → committer); one wiki failing never
34
+ * stops the others.
35
+ * 5. `applyPrompt()` — the prompt section is a plugin-level singleton and the
36
+ * default wiki's config tiddler has only just been loaded.
37
+ * 6. bind the clip bridge once, on the DEFAULT wiki's configured port (serving
38
+ * while disabled is intentional: every request re-checks the effective flag,
39
+ * so the settings toggle works without a dsh web restart).
40
+ *
41
+ * @module dsh-tiddlywiki/index-startup
42
+ */
43
+ import type { GitFace } from './host/git.ts'
44
+ import type { RepoCommitters } from './host/repo-committers.ts'
45
+ import type { BridgeConfig } from './host/clip-bridge.ts'
46
+ import { installSplitSkill } from './host/skill-install.ts'
47
+ import { readSessionScopes } from './host/session-scope.ts'
48
+ import { readLocationState, type WikiLocation } from './host/wiki-location.ts'
49
+ import { WikiInstance, type WikiInstanceBase } from './host/wiki-instance.ts'
50
+ import { WikiFarm } from './host/wiki-farm.ts'
51
+ import { resolveTwRoot } from './host/admin.ts'
52
+ import {
53
+ DEFAULT_WIKI_ID,
54
+ DEFAULT_WIKI_MODE,
55
+ readRegistry,
56
+ singleEntryRegistry,
57
+ type WikiEntry,
58
+ type WikiRegistry,
59
+ } from './host/wiki-registry.ts'
60
+
61
+ /** Where the control-file read landed (the settings page reads this back). */
62
+ export interface ControlFileState {
63
+ registry: WikiRegistry | undefined
64
+ source: 'file' | 'legacy' | 'default'
65
+ warnings: string[]
66
+ error: string | undefined
67
+ }
68
+
69
+ export interface StartupStageDeps {
70
+ /** The resolved cordis config, as every wiki's base layer. */
71
+ config: WikiInstanceBase
72
+ /** One `GitFace` for the whole plugin. */
73
+ git: GitFace
74
+ /** One commit layer for the whole plugin (keyed by repository). */
75
+ repos: RepoCommitters
76
+ /** The cordis-level default location (last resort in the registry chain). */
77
+ defaultLocation: WikiLocation
78
+ /** Pointer file the LEGACY single-wiki switch persists. */
79
+ locationStateFile: string
80
+ /** The multi-wiki control file: mode + wiki list + default id. */
81
+ registryFile: string
82
+ /** File the per-session scopes live in (outside every wiki). */
83
+ sessionScopeFile: string
84
+ /** The same-origin proxy base a wiki's TW frontend must use. */
85
+ proxyBase: (entry: WikiEntry) => string
86
+ /** The plugin has begun tearing down (checked after EVERY await below). */
87
+ isDisposed: () => boolean
88
+ /** Hand the newly built farm back to the entry point (its single owner). */
89
+ setFarm: (farm: WikiFarm<WikiInstance> | undefined) => void
90
+ /** Publish the control-file read (the settings page reads it back). */
91
+ setControl: (state: ControlFileState) => void
92
+ /** Publish the session scopes loaded from disk. */
93
+ setSessionScopes: (scopes: Record<string, string>) => void
94
+ /** Rebuild the plugin-level prompt section once the default config is loaded. */
95
+ applyPrompt: () => void
96
+ /** The clip bridge surface (bound here, owned by the entry point). */
97
+ clipBridge: { start: (port: number) => Promise<void>; stop: () => Promise<void>; port: number }
98
+ /** The EFFECTIVE bridge config (per-wiki override else the cordis base). */
99
+ effectiveBridge: () => BridgeConfig
100
+ /** The wiki's own location surface — only `defaultCurrentLocation` is read here. */
101
+ defaultCurrentLocation: () => WikiLocation
102
+ }
103
+
104
+ /**
105
+ * Build the fire-and-forget startup task. It resolves when startup has either
106
+ * finished or given up; failures are logged and self-healing stays armed.
107
+ */
108
+ export function createStartupStage(deps: StartupStageDeps): { start: () => Promise<void> } {
109
+ /** single 模式的位置:旧指针文件 > cordis 默认(v0.22.0 的优先级,逐字保留)。 */
110
+ const singleModeLocation = async (): Promise<WikiLocation> => {
111
+ const state = await readLocationState(deps.locationStateFile)
112
+ if (state.error !== undefined) console.warn('[dsh-tiddlywiki]', state.error)
113
+ return state.active ?? deps.defaultLocation
114
+ }
115
+
116
+ const start = async (): Promise<void> => {
117
+ try {
118
+ /**
119
+ * Ship the 「拆分知识库」 skill FIRST (v0.28.0).
120
+ *
121
+ * It is deliberately independent of the wiki: it only writes a file into
122
+ * `$DSH_HOME/skills`, and it must still happen when the wiki is slow to
123
+ * start, fails to start, or the user is mid-migration with a broken config.
124
+ * A plugin cannot register a skill ROOT, so writing into the user's own root
125
+ * is the only zero-config path; it only ever touches its own file (the
126
+ * marker line decides — see host/skill-install.ts).
127
+ */
128
+ try {
129
+ const installed = await installSplitSkill()
130
+ if (installed.action === 'failed') console.warn('[dsh-tiddlywiki] 拆库 skill 安装失败:', installed.error)
131
+ else console.info(`[dsh-tiddlywiki] 拆库 skill:${installed.action}(${installed.path})`)
132
+ } catch (err) {
133
+ console.warn('[dsh-tiddlywiki] 拆库 skill 安装异常:', err)
134
+ }
135
+ // THE CONTROL FILE FIRST (v0.28.0): it carries the mode (single/multi), the
136
+ // wiki list and the default id, and it must be read before anything starts
137
+ // — which is exactly why it lives OUTSIDE every wiki (wiki-registry.ts).
138
+ // The control file decides: mode + the wiki list.
139
+ const read = await readRegistry({ file: deps.registryFile, legacyFile: deps.locationStateFile, fallback: deps.defaultLocation })
140
+ // Per-session scopes are a PREFERENCE: an unreadable file just means "no
141
+ // explicit scope" and every session falls back to the default wiki.
142
+ const scopes = await readSessionScopes(deps.sessionScopeFile)
143
+ if (scopes.error !== undefined) console.warn('[dsh-tiddlywiki]', scopes.error)
144
+ deps.setSessionScopes(scopes.scopes)
145
+ deps.setControl({ registry: read.registry, source: read.source, warnings: read.warnings, error: read.error })
146
+ if (read.error !== undefined) console.warn('[dsh-tiddlywiki]', read.error)
147
+ for (const warning of read.warnings) console.warn('[dsh-tiddlywiki]', warning)
148
+ console.info(`[dsh-tiddlywiki] ${read.registry.mode} 模式 · ${read.registry.wikis.length} 个知识库(来源:${read.source})`)
149
+ // single 模式 = 今天的行为,逐字保留:位置仍由旧指针文件(其次 cordis 默认)
150
+ // 决定,registry 此时只记住 `mode` 与候选列表 —— 所以这里合成一份单条清单。
151
+ const runRegistry = read.registry.mode === 'multi'
152
+ ? read.registry
153
+ : singleEntryRegistry(await singleModeLocation(), DEFAULT_WIKI_ID, DEFAULT_WIKI_MODE)
154
+ const farm = new WikiFarm<WikiInstance>(runRegistry, {
155
+ createRuntime: (entry) => new WikiInstance({
156
+ entry,
157
+ base: deps.config,
158
+ git: deps.git,
159
+ twRoot: resolveTwRoot,
160
+ touchCommit: (dir) => { void deps.repos.touch(dir) },
161
+ flushCommits: (dir) => deps.repos.flush(dir),
162
+ proxyBase: () => deps.proxyBase(entry),
163
+ }),
164
+ log: (message) => console.warn('[dsh-tiddlywiki]', message),
165
+ })
166
+ deps.setFarm(farm)
167
+ // Every instance brings ITSELF up (child → its config tiddler → seeds →
168
+ // git → committer), so this one call replaces the old step-by-step startup.
169
+ // One wiki failing never stops the others (the farm reports it).
170
+ const change = await farm.startAll()
171
+ if (change.started.length > 0) console.info(`[dsh-tiddlywiki] 已启动:${change.started.join(', ')}`)
172
+ // The prompt section is a plugin-level singleton: rebuild it now that the
173
+ // default wiki's config tiddler has been loaded.
174
+ deps.applyPrompt()
175
+ if (deps.isDisposed()) {
176
+ await farm.disposeAll()
177
+ return
178
+ }
179
+ // Clip bridge: bind once on the DEFAULT wiki's configured port (works even
180
+ // while disabled — every request re-checks the effective enabled flag, so
181
+ // the settings-page toggle applies without a dsh web restart).
182
+ try {
183
+ await deps.clipBridge.start(deps.effectiveBridge().port)
184
+ console.info(`[dsh-tiddlywiki] clip bridge listening on 127.0.0.1:${deps.clipBridge.port} (enabled=${deps.effectiveBridge().enabled})`)
185
+ } catch (err) {
186
+ console.warn('[dsh-tiddlywiki] clip bridge start:', err)
187
+ }
188
+ if (deps.isDisposed()) {
189
+ try { await deps.clipBridge.stop() } catch { /* already closing */ }
190
+ await farm.disposeAll()
191
+ return
192
+ }
193
+ } catch (err) {
194
+ console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)
195
+ }
196
+ }
197
+
198
+ return { start }
199
+ }
200
+
201
+ /** Re-exported so the entry point keeps one import for the registry types. */
202
+ export type { WikiRegistry }
package/src/index.ts CHANGED
@@ -21,20 +21,24 @@
21
21
  import { AutoCommitter, GitFace } from './host/git.ts'
22
22
  import { type SessionsFace, type WebServerFace } from './host/routes.ts'
23
23
  import { type PluginConfigShape } from './host/config.ts'
24
- import { resolveTwRoot } from './host/admin.ts'
25
24
  import { TiddlyWebClient, isBinaryType, TEXT_LIST_FILTER } from './host/tw-api.ts'
26
25
  import { WechatPublishRunner } from './host/wechat-publish.ts'
27
26
  import { registerTiddlywikiTools, type ToolsDeps, type ToolScope } from './host/tools.ts'
28
27
  import {
29
28
  defaultLocationStateFile,
30
29
  locationPath,
30
+ // v0.30.49 / v0.30.50:readLocationState / readRegistry **只**被下面的 barrel
31
+ // 再导出使用(`export { … } from '…'` 是独立语句、不消费 import 绑定)⇒
32
+ // `noUnusedLocals` 把它们点成「未使用」。**必须无视这条 error 并留着**:
33
+ // 删掉它们会让 lib/index.js 少一个导出(v0.30.48/v0.30.50 各实测一次 ——
34
+ // typecheck / verify:unit / verify:static 全绿,只有 e2e 才崩)。
35
+ // 判据:scripts/verify-index-barrel.mjs(进 verify:static)。
31
36
  readLocationState,
32
37
  type WikiLocation,
33
38
  } from './host/wiki-location.ts'
34
39
  import { WikiInstance } from './host/wiki-instance.ts'
35
40
  import { WikiFarm, resolveAgentScope, targetRuntimeFor } from './host/wiki-farm.ts'
36
- import { defaultSessionScopeFile, readSessionScopes } from './host/session-scope.ts'
37
- import { installSplitSkill } from './host/skill-install.ts'
41
+ import { defaultSessionScopeFile } from './host/session-scope.ts'
38
42
  import {
39
43
  DEFAULT_WIKI_ID,
40
44
  DEFAULT_WIKI_MODE,
@@ -56,6 +60,7 @@ import { resolveConfig, type ResolvedConfig, type TiddlywikiConfig } from './ind
56
60
  import { createMutationLock } from './host/mutation-lock.ts'
57
61
  import { createGitLayer } from './index-git.ts'
58
62
  import { createRouteStage } from './index-routes.ts'
63
+ import { createStartupStage } from './index-startup.ts'
59
64
  import { createPromptSurface, createServerTuning } from './index-prompt.ts'
60
65
  import { createClipSurface } from './index-clip.ts'
61
66
 
@@ -161,7 +166,6 @@ export {
161
166
  listWikiCandidates,
162
167
  locationPath,
163
168
  normalizeLocation,
164
- readLocationState,
165
169
  // v0.30.49:这条 **barrel 再导出** 是给 `lib/index.js` 的**外部**使用者的
166
170
  // (scripts/verify-wiki-switch.mjs 就 import 它)。⚠️ 再导出是**独立语句**,
167
171
  // 它不消费上面那条 import 的绑定 —— 所以 `noUnusedLocals` 会把 import 里的
@@ -181,6 +185,15 @@ export {
181
185
  type WikiLocationSource,
182
186
  type WikiLocationState,
183
187
  } from './host/wiki-location.ts'
188
+ // v0.30.49 / v0.30.50 —— **为什么这两条单独写在 `from` 形式之外**:
189
+ // 同一个名字既要在 `lib/index.js` 上存在(barrel 公共面),又会让
190
+ // `export { X } from '…'` 那种写法**不消费上面 import 的绑定** ⇒
191
+ // `noUnusedLocals` 报「声明了但没读」,而删掉 import 就静默少一个导出
192
+ // (v0.30.48 / v0.30.50 各踩一次;只有 e2e 能发现)。
193
+ // 用「无 from 的 export」= **真的读一次那个绑定**,两个要求同时满足:
194
+ // 绑定被消费、导出照样在。判据仍是 scripts/verify-index-barrel.mjs。
195
+ export { readLocationState }
196
+ export { readRegistry }
184
197
  export { switchWiki, type WikiSwitchResult, type WikiSwitchDeps } from './host/wiki-switch.ts'
185
198
  export {
186
199
  DEFAULT_WIKI_ID,
@@ -199,7 +212,6 @@ export {
199
212
  normalizeEntry,
200
213
  normalizeWikiIcon,
201
214
  normalizeWikiId,
202
- readRegistry,
203
215
  removeWiki,
204
216
  singleEntryRegistry,
205
217
  upsertWiki,
@@ -525,12 +537,37 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
525
537
  */
526
538
  let switching = false
527
539
 
528
- /** single 模式的位置:旧指针文件 > cordis 默认(v0.22.0 的优先级,逐字保留)。 */
529
- const singleModeLocation = async (): Promise<WikiLocation> => {
530
- const state = await readLocationState(locationStateFile)
531
- if (state.error !== undefined) console.warn('[dsh-tiddlywiki]', state.error)
532
- return state.active ?? defaultLocation
533
- }
540
+ // ── Startup stage (v0.30.50) ──────────────────────────────────────────────
541
+ // 整个自举任务(skill → 控制文件 → 会话作用域 → 建 farm → startAll →
542
+ // prompt → 剪藏桥)搬进 index-startup.ts。`disposed` 与 `farm` 仍归本文件
543
+ // (前者是拆卸 effect 与这个任务的共同主人,后者路由/工具/视图都要读),
544
+ // 所以它们以**回调**形式传进去 —— 与 index-wikis / index-routes 同一手法。
545
+ //
546
+ // ⚠️ 顺序本身是语义,见 index-startup.ts 的模块头(尤其是「skill 必须在
547
+ // wiki 之前装」那条,它是真实事故的产物)。
548
+ const startupStage = createStartupStage({
549
+ config,
550
+ git,
551
+ repos,
552
+ defaultLocation,
553
+ locationStateFile,
554
+ registryFile,
555
+ sessionScopeFile,
556
+ proxyBase,
557
+ isDisposed: () => disposed,
558
+ setFarm: (next) => { farm = next },
559
+ setControl: (state) => {
560
+ controlRegistry = state.registry
561
+ controlSource = state.source
562
+ controlWarnings = state.warnings
563
+ controlError = state.error
564
+ },
565
+ setSessionScopes: (scopes) => { sessionScopes = scopes },
566
+ applyPrompt,
567
+ clipBridge,
568
+ effectiveBridge,
569
+ defaultCurrentLocation: () => wikiViews.defaultCurrentLocation(),
570
+ })
534
571
 
535
572
  /**
536
573
  * The CONTROL FILE as the settings page edits it — deliberately NOT the
@@ -546,93 +583,10 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
546
583
  const controlRegistryNow = (): WikiRegistry =>
547
584
  controlRegistry ?? singleEntryRegistry(defaultCurrentLocation(), DEFAULT_WIKI_ID, DEFAULT_WIKI_MODE)
548
585
 
549
- const startupTask = (async () => {
550
- try {
551
- /**
552
- * Ship the 「拆分知识库」 skill FIRST (v0.28.0).
553
- *
554
- * It is deliberately independent of the wiki: it only writes a file into
555
- * `$DSH_HOME/skills`, and it must still happen when the wiki is slow to
556
- * start, fails to start, or the user is mid-migration with a broken config.
557
- * (It used to sit after `farm.startAll()` — which meant a wiki that never
558
- * became ready also silently never installed the skill. Found by restarting
559
- * a real host, not by a test.)
560
- *
561
- * A plugin cannot register a skill ROOT, so writing into the user's own root
562
- * is the only zero-config path; it only ever touches its own file (the
563
- * marker line decides — see host/skill-install.ts).
564
- */
565
- try {
566
- const installed = await installSplitSkill()
567
- if (installed.action === 'failed') console.warn('[dsh-tiddlywiki] 拆库 skill 安装失败:', installed.error)
568
- else console.info(`[dsh-tiddlywiki] 拆库 skill:${installed.action}(${installed.path})`)
569
- } catch (err) {
570
- console.warn('[dsh-tiddlywiki] 拆库 skill 安装异常:', err)
571
- }
572
- // THE CONTROL FILE FIRST (v0.28.0): it carries the mode (single/multi), the
573
- // wiki list and the default id, and it must be read before anything starts
574
- // — which is exactly why it lives OUTSIDE every wiki (wiki-registry.ts).
575
- // The control file decides: mode + the wiki list.
576
- const read = await readRegistry({ file: registryFile, legacyFile: locationStateFile, fallback: defaultLocation })
577
- // Per-session scopes are a PREFERENCE: an unreadable file just means "no
578
- // explicit scope" and every session falls back to the default wiki.
579
- const scopes = await readSessionScopes(sessionScopeFile)
580
- if (scopes.error !== undefined) console.warn('[dsh-tiddlywiki]', scopes.error)
581
- sessionScopes = scopes.scopes
582
- controlRegistry = read.registry
583
- controlSource = read.source
584
- controlWarnings = read.warnings
585
- controlError = read.error
586
- if (read.error !== undefined) console.warn('[dsh-tiddlywiki]', read.error)
587
- for (const warning of read.warnings) console.warn('[dsh-tiddlywiki]', warning)
588
- console.info(`[dsh-tiddlywiki] ${read.registry.mode} 模式 · ${read.registry.wikis.length} 个知识库(来源:${read.source})`)
589
- // single 模式 = 今天的行为,逐字保留:位置仍由旧指针文件(其次 cordis 默认)
590
- // 决定,registry 此时只记住 `mode` 与候选列表 —— 所以这里合成一份单条清单。
591
- const runRegistry = read.registry.mode === 'multi'
592
- ? read.registry
593
- : singleEntryRegistry(await singleModeLocation(), DEFAULT_WIKI_ID, 'single')
594
- farm = new WikiFarm<WikiInstance>(runRegistry, {
595
- createRuntime: (entry) => new WikiInstance({
596
- entry,
597
- base: config,
598
- git,
599
- twRoot: resolveTwRoot,
600
- touchCommit: (dir) => { void repos.touch(dir) },
601
- flushCommits: (dir) => repos.flush(dir),
602
- proxyBase: () => proxyBase(entry),
603
- }),
604
- log: (message) => console.warn('[dsh-tiddlywiki]', message),
605
- })
606
- // Every instance brings ITSELF up (child → its config tiddler → seeds →
607
- // git → committer), so this one call replaces the old step-by-step startup.
608
- // One wiki failing never stops the others (the farm reports it).
609
- const change = await farm.startAll()
610
- if (change.started.length > 0) console.info(`[dsh-tiddlywiki] 已启动:${change.started.join(', ')}`)
611
- // The prompt section is a plugin-level singleton: rebuild it now that the
612
- // default wiki's config tiddler has been loaded.
613
- applyPrompt()
614
- if (disposed) {
615
- await farm.disposeAll()
616
- return
617
- }
618
- // Clip bridge: bind once on the DEFAULT wiki's configured port (works even
619
- // while disabled — every request re-checks the effective enabled flag, so
620
- // the settings-page toggle applies without a dsh web restart).
621
- try {
622
- await clipBridge.start(effectiveBridge().port)
623
- console.info(`[dsh-tiddlywiki] clip bridge listening on 127.0.0.1:${clipBridge.port} (enabled=${effectiveBridge().enabled})`)
624
- } catch (err) {
625
- console.warn('[dsh-tiddlywiki] clip bridge start:', err)
626
- }
627
- if (disposed) {
628
- try { await clipBridge.stop() } catch { /* already closing */ }
629
- await farm.disposeAll()
630
- return
631
- }
632
- } catch (err) {
633
- console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)
634
- }
635
- })()
586
+ // The task is fire-and-forget (v0.19.1): teardown can run while it still
587
+ // awaits, so the task itself stops the child when it notices `disposed`.
588
+ const startupPromise = startupStage.start()
589
+ void startupPromise
636
590
 
637
591
  /**
638
592
  * The folder the default wiki occupies, running or not.
@@ -719,7 +673,7 @@ export function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {
719
673
  disposed = true
720
674
  try {
721
675
  await Promise.race([
722
- startupTask.catch(() => undefined),
676
+ startupPromise.catch(() => undefined),
723
677
  new Promise<void>((r) => { setTimeout(r, 3_000).unref?.() }),
724
678
  ])
725
679
  } catch { /* startup issues are already logged */ }