@huanlin/dsh-plugin-interpreters 0.2.3 → 0.4.0
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/lib/client.js +975 -964
- package/lib/settings.js +82 -83
- package/lib/types/client/bindSnapshotSelector.js +23 -23
- package/lib/types/client/index.d.ts +39 -39
- package/lib/types/client/index.js +94 -94
- package/lib/types/client/store.d.ts +88 -88
- package/lib/types/client/store.js +196 -196
- package/lib/types/gateway.d.ts +63 -63
- package/lib/types/settings.d.ts +43 -43
- package/lib/types/settings.js +82 -83
- package/package.json +25 -25
package/lib/settings.js
CHANGED
|
@@ -1,83 +1,82 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* settings.ts — host-side bridge between the `interpreters` settings namespace
|
|
3
|
-
* and the plugin's other halves (tool registration + RPC gateway).
|
|
4
|
-
*
|
|
5
|
-
* The composition `Config` (cordis.patch.yml) is the first-boot seed; once the
|
|
6
|
-
* `ctx.settings` service mounts, the user-editable layer takes over and live
|
|
7
|
-
* re-registration follows every committed change. Headless assemblies without
|
|
8
|
-
* a settings provider fall back to the composition config (no persistence, no
|
|
9
|
-
* live reload).
|
|
10
|
-
*
|
|
11
|
-
* The bridge pattern mirrors `dsh-advisor/src/settings.ts`: a `source()` thunk
|
|
12
|
-
* the gateway reads in-process, plus an `onChange()` subscription the host
|
|
13
|
-
* entry uses to re-register the tools. This avoids any wire-layer allowlist
|
|
14
|
-
* (the DSH settings RPC domain only serves a fixed namespace set to browser
|
|
15
|
-
* configuration clients; the gateway bypasses it through `/api`).
|
|
16
|
-
*
|
|
17
|
-
* @module dsh-interpreters/settings
|
|
18
|
-
*/
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @param
|
|
42
|
-
* @
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* settings.ts — host-side bridge between the `interpreters` settings namespace
|
|
3
|
+
* and the plugin's other halves (tool registration + RPC gateway).
|
|
4
|
+
*
|
|
5
|
+
* The composition `Config` (cordis.patch.yml) is the first-boot seed; once the
|
|
6
|
+
* `ctx.settings` service mounts, the user-editable layer takes over and live
|
|
7
|
+
* re-registration follows every committed change. Headless assemblies without
|
|
8
|
+
* a settings provider fall back to the composition config (no persistence, no
|
|
9
|
+
* live reload).
|
|
10
|
+
*
|
|
11
|
+
* The bridge pattern mirrors `dsh-advisor/src/settings.ts`: a `source()` thunk
|
|
12
|
+
* the gateway reads in-process, plus an `onChange()` subscription the host
|
|
13
|
+
* entry uses to re-register the tools. This avoids any wire-layer allowlist
|
|
14
|
+
* (the DSH settings RPC domain only serves a fixed namespace set to browser
|
|
15
|
+
* configuration clients; the gateway bypasses it through `/api`).
|
|
16
|
+
*
|
|
17
|
+
* @module dsh-interpreters/settings
|
|
18
|
+
*/
|
|
19
|
+
import { Config } from './config.js';
|
|
20
|
+
/** Settings namespace under which interpreter paths persist. */
|
|
21
|
+
export const SETTINGS_NAMESPACE = 'interpreters';
|
|
22
|
+
/**
|
|
23
|
+
* Mirror of the dsh-settings internal `isUnloading` guard. The cordis const
|
|
24
|
+
* enum for fiber state is erased at compile time, so the literal states are
|
|
25
|
+
* matched numerically: 4 = DISPOSED, 5 = UNLOADING.
|
|
26
|
+
*/
|
|
27
|
+
function isUnloading(ctx) {
|
|
28
|
+
const state = ctx.fiber?.state;
|
|
29
|
+
return state === 4 || state === 5;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Install the `interpreters` settings namespace and return the bridge.
|
|
33
|
+
*
|
|
34
|
+
* The settings service is reached through `ctx.inject(['settings'], ...)` so a
|
|
35
|
+
* composition without a settings provider still loads the plugin (entry-source
|
|
36
|
+
* fallback, no persistence). Multi-fiber dedupe is handled by catching the
|
|
37
|
+
* `"already registered"` rejection — host composition may mount several
|
|
38
|
+
* concurrent fibers of this plugin, and only the first registration owns the
|
|
39
|
+
* namespace.
|
|
40
|
+
* @param ctx - host context.
|
|
41
|
+
* @param entry - composition-layer config (cordis.patch.yml seed).
|
|
42
|
+
* @returns the bridge the gateway and tool re-registration consume.
|
|
43
|
+
*/
|
|
44
|
+
export function installInterpretersSettings(ctx, entry) {
|
|
45
|
+
const listeners = new Set();
|
|
46
|
+
let source = () => entry;
|
|
47
|
+
const notify = () => {
|
|
48
|
+
for (const listener of [...listeners])
|
|
49
|
+
listener();
|
|
50
|
+
};
|
|
51
|
+
ctx.inject(['settings'], (sctx) => {
|
|
52
|
+
let scope;
|
|
53
|
+
try {
|
|
54
|
+
scope = sctx.settings.register(SETTINGS_NAMESPACE, Config, { base: entry });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Multi-fiber dedupe: the first registration owns the namespace; later
|
|
58
|
+
// fibers stay on the entry source and emit no notifications of their own.
|
|
59
|
+
if (!(error instanceof Error) || !error.message.includes('already registered'))
|
|
60
|
+
throw error;
|
|
61
|
+
ctx.logger('dsh-interpreters').debug('settings namespace already registered — entry-source fallback');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
source = () => scope.get();
|
|
65
|
+
sctx.effect(() => () => {
|
|
66
|
+
if (isUnloading(ctx))
|
|
67
|
+
return;
|
|
68
|
+
source = () => entry;
|
|
69
|
+
notify();
|
|
70
|
+
});
|
|
71
|
+
notify();
|
|
72
|
+
scope.watch(() => {
|
|
73
|
+
if (isUnloading(ctx))
|
|
74
|
+
return;
|
|
75
|
+
notify();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
source: () => source(),
|
|
80
|
+
onChange: (cb) => { listeners.add(cb); },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* uSES bridge: turns any bare observable snapshot source into a typed
|
|
3
|
-
* selector hook. Inlined from dsh-client-ui-renderer/src/client/bind.ts
|
|
4
|
-
* (shell-only glue; business plugins depend on
|
|
5
|
-
*
|
|
6
|
-
* @module dsh-interpreters/client/bindSnapshotSelector
|
|
7
|
-
*/
|
|
8
|
-
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js';
|
|
9
|
-
/**
|
|
10
|
-
* Bind a bare observable source to a typed uSES selector hook.
|
|
11
|
-
* subscribe/getSnapshot are captured once per source into stable closures
|
|
12
|
-
* (also re-binds `this` for method-based sources), so components never
|
|
13
|
-
* resubscribe across renders. Equality defaults to Object.is.
|
|
14
|
-
* @param w - snapshot source (engine store, Session object, store instance).
|
|
15
|
-
* @returns the selector hook.
|
|
16
|
-
*/
|
|
17
|
-
export function bindSnapshotSelector(w) {
|
|
18
|
-
const subscribe = (fn) => w.subscribe(fn);
|
|
19
|
-
const getSnapshot = () => w.getSnapshot();
|
|
20
|
-
return function useSelector(sel, eq) {
|
|
21
|
-
return useSyncExternalStoreWithSelector(subscribe, getSnapshot, undefined, sel, eq);
|
|
22
|
-
};
|
|
23
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* uSES bridge: turns any bare observable snapshot source into a typed
|
|
3
|
+
* selector hook. Inlined from dsh-client-ui-renderer/src/client/bind.ts
|
|
4
|
+
* (shell-only glue; business plugins depend on client-store + ui-slots only).
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-interpreters/client/bindSnapshotSelector
|
|
7
|
+
*/
|
|
8
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js';
|
|
9
|
+
/**
|
|
10
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
11
|
+
* subscribe/getSnapshot are captured once per source into stable closures
|
|
12
|
+
* (also re-binds `this` for method-based sources), so components never
|
|
13
|
+
* resubscribe across renders. Equality defaults to Object.is.
|
|
14
|
+
* @param w - snapshot source (engine store, Session object, store instance).
|
|
15
|
+
* @returns the selector hook.
|
|
16
|
+
*/
|
|
17
|
+
export function bindSnapshotSelector(w) {
|
|
18
|
+
const subscribe = (fn) => w.subscribe(fn);
|
|
19
|
+
const getSnapshot = () => w.getSnapshot();
|
|
20
|
+
return function useSelector(sel, eq) {
|
|
21
|
+
return useSyncExternalStoreWithSelector(subscribe, getSnapshot, undefined, sel, eq);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-interpreters — browser half.
|
|
3
|
-
*
|
|
4
|
-
* Registers the `interpreters` card into the shell-declared
|
|
5
|
-
* `settings.plugin.item` slot (the plugin-config settings page — id
|
|
6
|
-
* `dsh-interpreters`, order 50, after the upstream bash / agent-loop /
|
|
7
|
-
* web-search cards). The card's store reads/writes the `interpreters` config
|
|
8
|
-
* through the host gateway `/api/interpreters/get|set` RPC channel, and keeps
|
|
9
|
-
* fresh on pushed invalidations.
|
|
10
|
-
*
|
|
11
|
-
* Export discipline: the client half value-imports ONLY the frozen platform
|
|
12
|
-
* module table (CLIENT_EXTERNALS); every other `@deepseek-ai/*` import is
|
|
13
|
-
* type-only (erased at build) — values arrive via cordis injection
|
|
14
|
-
* (`ctx.get('connection')`, slot inject faces).
|
|
15
|
-
*
|
|
16
|
-
* @module @huanlin/dsh-plugin-interpreters/client
|
|
17
|
-
*/
|
|
18
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
19
|
-
import { type InterpretersKey } from './locales.ts';
|
|
20
|
-
export type { InterpretersCardInjected, InterpretersCardProps } from './InterpretersCard.tsx';
|
|
21
|
-
export type { InterpretersKey } from './locales.ts';
|
|
22
|
-
export type { InterpretersCardState, InterpretersCardController } from './store.ts';
|
|
23
|
-
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
24
|
-
interface LocaleNamespaceMap {
|
|
25
|
-
/** The interpreters card copy. */
|
|
26
|
-
'interpreters': InterpretersKey;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** Required services (cordis fiber inject). The target slot is declared by
|
|
30
|
-
* ui-plugin-config's apply, whose activation order relative to this one is
|
|
31
|
-
* NOT constrained; registration depends on the slot through `slots.inject()`. */
|
|
32
|
-
export declare const inject: string[];
|
|
33
|
-
/**
|
|
34
|
-
* Register the interpreters card once the `settings.plugin.item` declaration
|
|
35
|
-
* is on the ledger, wire its store to the connection, and keep it fresh on
|
|
36
|
-
* every pushed invalidation.
|
|
37
|
-
* @param ctx - client root context.
|
|
38
|
-
*/
|
|
39
|
-
export declare function apply(ctx: ClientContext): void;
|
|
1
|
+
/**
|
|
2
|
+
* dsh-interpreters — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `interpreters` card into the shell-declared
|
|
5
|
+
* `settings.plugin.item` slot (the plugin-config settings page — id
|
|
6
|
+
* `dsh-interpreters`, order 50, after the upstream bash / agent-loop /
|
|
7
|
+
* web-search cards). The card's store reads/writes the `interpreters` config
|
|
8
|
+
* through the host gateway `/api/interpreters/get|set` RPC channel, and keeps
|
|
9
|
+
* fresh on pushed invalidations.
|
|
10
|
+
*
|
|
11
|
+
* Export discipline: the client half value-imports ONLY the frozen platform
|
|
12
|
+
* module table (CLIENT_EXTERNALS); every other `@deepseek-ai/*` import is
|
|
13
|
+
* type-only (erased at build) — values arrive via cordis injection
|
|
14
|
+
* (`ctx.get('connection')`, slot inject faces).
|
|
15
|
+
*
|
|
16
|
+
* @module @huanlin/dsh-plugin-interpreters/client
|
|
17
|
+
*/
|
|
18
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
19
|
+
import { type InterpretersKey } from './locales.ts';
|
|
20
|
+
export type { InterpretersCardInjected, InterpretersCardProps } from './InterpretersCard.tsx';
|
|
21
|
+
export type { InterpretersKey } from './locales.ts';
|
|
22
|
+
export type { InterpretersCardState, InterpretersCardController } from './store.ts';
|
|
23
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
24
|
+
interface LocaleNamespaceMap {
|
|
25
|
+
/** The interpreters card copy. */
|
|
26
|
+
'interpreters': InterpretersKey;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Required services (cordis fiber inject). The target slot is declared by
|
|
30
|
+
* ui-plugin-config's apply, whose activation order relative to this one is
|
|
31
|
+
* NOT constrained; registration depends on the slot through `slots.inject()`. */
|
|
32
|
+
export declare const inject: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Register the interpreters card once the `settings.plugin.item` declaration
|
|
35
|
+
* is on the ledger, wire its store to the connection, and keep it fresh on
|
|
36
|
+
* every pushed invalidation.
|
|
37
|
+
* @param ctx - client root context.
|
|
38
|
+
*/
|
|
39
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-interpreters — browser half.
|
|
3
|
-
*
|
|
4
|
-
* Registers the `interpreters` card into the shell-declared
|
|
5
|
-
* `settings.plugin.item` slot (the plugin-config settings page — id
|
|
6
|
-
* `dsh-interpreters`, order 50, after the upstream bash / agent-loop /
|
|
7
|
-
* web-search cards). The card's store reads/writes the `interpreters` config
|
|
8
|
-
* through the host gateway `/api/interpreters/get|set` RPC channel, and keeps
|
|
9
|
-
* fresh on pushed invalidations.
|
|
10
|
-
*
|
|
11
|
-
* Export discipline: the client half value-imports ONLY the frozen platform
|
|
12
|
-
* module table (CLIENT_EXTERNALS); every other `@deepseek-ai/*` import is
|
|
13
|
-
* type-only (erased at build) — values arrive via cordis injection
|
|
14
|
-
* (`ctx.get('connection')`, slot inject faces).
|
|
15
|
-
*
|
|
16
|
-
* @module @huanlin/dsh-plugin-interpreters/client
|
|
17
|
-
*/
|
|
18
|
-
import { bindSnapshotSelector } from "./bindSnapshotSelector.js";
|
|
19
|
-
import { InterpretersCard } from "./InterpretersCard.js";
|
|
20
|
-
import { InterpretersCardController, refreshIfLoaded } from "./store.js";
|
|
21
|
-
import { en, NS, zh } from "./locales.js";
|
|
22
|
-
import { dicts } from "./dictionaries.js";
|
|
23
|
-
/** Required services (cordis fiber inject). The target slot is declared by
|
|
24
|
-
* ui-plugin-config's apply, whose activation order relative to this one is
|
|
25
|
-
* NOT constrained; registration depends on the slot through `slots.inject()`. */
|
|
26
|
-
export const inject = ['slots', 'locale', 'connection'];
|
|
27
|
-
/**
|
|
28
|
-
* Register the interpreters card once the `settings.plugin.item` declaration
|
|
29
|
-
* is on the ledger, wire its store to the connection, and keep it fresh on
|
|
30
|
-
* every pushed invalidation.
|
|
31
|
-
* @param ctx - client root context.
|
|
32
|
-
*/
|
|
33
|
-
export function apply(ctx) {
|
|
34
|
-
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-interpreters: dictionaries');
|
|
35
|
-
// Opt-in third-language overrides through @huanlin/dsh-plugin-better-locale.
|
|
36
|
-
// Activation-order-safe: ctx.get('betterLocale') is a non-reactive read, so
|
|
37
|
-
// if better-locale activates after us, the initial read returns undefined.
|
|
38
|
-
// We subscribe to ctx.locale — better-locale bumps its revision on activation
|
|
39
|
-
// (persisted override) and on every override switch — and re-check on each bump.
|
|
40
|
-
ctx.effect(() => {
|
|
41
|
-
let dispose;
|
|
42
|
-
const sync = () => {
|
|
43
|
-
dispose?.();
|
|
44
|
-
dispose = undefined;
|
|
45
|
-
const store = ctx.get('betterLocale');
|
|
46
|
-
if (store !== undefined) {
|
|
47
|
-
dispose = store.register(NS, dicts);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
sync();
|
|
51
|
-
const unsubscribe = ctx.locale.subscribe(sync);
|
|
52
|
-
return () => {
|
|
53
|
-
unsubscribe();
|
|
54
|
-
dispose?.();
|
|
55
|
-
};
|
|
56
|
-
}, 'interpreters: better-locale override dicts');
|
|
57
|
-
// The store reads/writes the interpreters config over the plugin's
|
|
58
|
-
// self-hosted HTTP route (`/interpreters/api/get` + `/interpreters/api/set`).
|
|
59
|
-
const controller = new InterpretersCardController();
|
|
60
|
-
const useSnapshot = bindSnapshotSelector(controller.store);
|
|
61
|
-
// Pushed invalidations converge the open surface without polling. The dsh
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// connection reset invalidates the whole client state. A burst of resets
|
|
65
|
-
// coalesces into a single refetch via the microtask debounce, and
|
|
66
|
-
// `refreshIfLoaded` keeps an unopened card idle.
|
|
67
|
-
ctx.effect(() => {
|
|
68
|
-
let pending = false;
|
|
69
|
-
const refresh = () => {
|
|
70
|
-
if (pending)
|
|
71
|
-
return;
|
|
72
|
-
pending = true;
|
|
73
|
-
queueMicrotask(() => {
|
|
74
|
-
pending = false;
|
|
75
|
-
refreshIfLoaded(controller);
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
const disposers = [ctx.on('connection/reset', refresh)];
|
|
79
|
-
return () => { for (const dispose of disposers)
|
|
80
|
-
dispose(); };
|
|
81
|
-
}, 'dsh-interpreters: pushed invalidations');
|
|
82
|
-
// The card registers into the plugin-config page's card slot with the
|
|
83
|
-
// upstream card shape — generator + `yield`, `locale: NS`, and an inject
|
|
84
|
-
// face carrying ONLY the business surface (controller + useSnapshot). The
|
|
85
|
-
// typed `t` seat is synthesized by the renderer from `locale: NS`.
|
|
86
|
-
ctx.slots.inject('settings.plugin.item', function* () {
|
|
87
|
-
yield ctx.slots.register({
|
|
88
|
-
name: 'settings.plugin.item',
|
|
89
|
-
key: NS,
|
|
90
|
-
locale: NS,
|
|
91
|
-
inject: () => ({ controller, useSnapshot }),
|
|
92
|
-
}, InterpretersCard);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-interpreters — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `interpreters` card into the shell-declared
|
|
5
|
+
* `settings.plugin.item` slot (the plugin-config settings page — id
|
|
6
|
+
* `dsh-interpreters`, order 50, after the upstream bash / agent-loop /
|
|
7
|
+
* web-search cards). The card's store reads/writes the `interpreters` config
|
|
8
|
+
* through the host gateway `/api/interpreters/get|set` RPC channel, and keeps
|
|
9
|
+
* fresh on pushed invalidations.
|
|
10
|
+
*
|
|
11
|
+
* Export discipline: the client half value-imports ONLY the frozen platform
|
|
12
|
+
* module table (CLIENT_EXTERNALS); every other `@deepseek-ai/*` import is
|
|
13
|
+
* type-only (erased at build) — values arrive via cordis injection
|
|
14
|
+
* (`ctx.get('connection')`, slot inject faces).
|
|
15
|
+
*
|
|
16
|
+
* @module @huanlin/dsh-plugin-interpreters/client
|
|
17
|
+
*/
|
|
18
|
+
import { bindSnapshotSelector } from "./bindSnapshotSelector.js";
|
|
19
|
+
import { InterpretersCard } from "./InterpretersCard.js";
|
|
20
|
+
import { InterpretersCardController, refreshIfLoaded } from "./store.js";
|
|
21
|
+
import { en, NS, zh } from "./locales.js";
|
|
22
|
+
import { dicts } from "./dictionaries.js";
|
|
23
|
+
/** Required services (cordis fiber inject). The target slot is declared by
|
|
24
|
+
* ui-plugin-config's apply, whose activation order relative to this one is
|
|
25
|
+
* NOT constrained; registration depends on the slot through `slots.inject()`. */
|
|
26
|
+
export const inject = ['slots', 'locale', 'connection'];
|
|
27
|
+
/**
|
|
28
|
+
* Register the interpreters card once the `settings.plugin.item` declaration
|
|
29
|
+
* is on the ledger, wire its store to the connection, and keep it fresh on
|
|
30
|
+
* every pushed invalidation.
|
|
31
|
+
* @param ctx - client root context.
|
|
32
|
+
*/
|
|
33
|
+
export function apply(ctx) {
|
|
34
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-interpreters: dictionaries');
|
|
35
|
+
// Opt-in third-language overrides through @huanlin/dsh-plugin-better-locale.
|
|
36
|
+
// Activation-order-safe: ctx.get('betterLocale') is a non-reactive read, so
|
|
37
|
+
// if better-locale activates after us, the initial read returns undefined.
|
|
38
|
+
// We subscribe to ctx.locale — better-locale bumps its revision on activation
|
|
39
|
+
// (persisted override) and on every override switch — and re-check on each bump.
|
|
40
|
+
ctx.effect(() => {
|
|
41
|
+
let dispose;
|
|
42
|
+
const sync = () => {
|
|
43
|
+
dispose?.();
|
|
44
|
+
dispose = undefined;
|
|
45
|
+
const store = ctx.get('betterLocale');
|
|
46
|
+
if (store !== undefined) {
|
|
47
|
+
dispose = store.register(NS, dicts);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
sync();
|
|
51
|
+
const unsubscribe = ctx.locale.subscribe(sync);
|
|
52
|
+
return () => {
|
|
53
|
+
unsubscribe();
|
|
54
|
+
dispose?.();
|
|
55
|
+
};
|
|
56
|
+
}, 'interpreters: better-locale override dicts');
|
|
57
|
+
// The store reads/writes the interpreters config over the plugin's
|
|
58
|
+
// self-hosted HTTP route (`/interpreters/api/get` + `/interpreters/api/set`).
|
|
59
|
+
const controller = new InterpretersCardController();
|
|
60
|
+
const useSnapshot = bindSnapshotSelector(controller.store);
|
|
61
|
+
// Pushed invalidations converge the open surface without polling. The dsh
|
|
62
|
+
// client Events vocabulary has no `settings/changed` host passthrough, so
|
|
63
|
+
// convergence rides `connection/reset` — a
|
|
64
|
+
// connection reset invalidates the whole client state. A burst of resets
|
|
65
|
+
// coalesces into a single refetch via the microtask debounce, and
|
|
66
|
+
// `refreshIfLoaded` keeps an unopened card idle.
|
|
67
|
+
ctx.effect(() => {
|
|
68
|
+
let pending = false;
|
|
69
|
+
const refresh = () => {
|
|
70
|
+
if (pending)
|
|
71
|
+
return;
|
|
72
|
+
pending = true;
|
|
73
|
+
queueMicrotask(() => {
|
|
74
|
+
pending = false;
|
|
75
|
+
refreshIfLoaded(controller);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const disposers = [ctx.on('connection/reset', refresh)];
|
|
79
|
+
return () => { for (const dispose of disposers)
|
|
80
|
+
dispose(); };
|
|
81
|
+
}, 'dsh-interpreters: pushed invalidations');
|
|
82
|
+
// The card registers into the plugin-config page's card slot with the
|
|
83
|
+
// upstream card shape — generator + `yield`, `locale: NS`, and an inject
|
|
84
|
+
// face carrying ONLY the business surface (controller + useSnapshot). The
|
|
85
|
+
// typed `t` seat is synthesized by the renderer from `locale: NS`.
|
|
86
|
+
ctx.slots.inject('settings.plugin.item', function* () {
|
|
87
|
+
yield ctx.slots.register({
|
|
88
|
+
name: 'settings.plugin.item',
|
|
89
|
+
key: NS,
|
|
90
|
+
locale: NS,
|
|
91
|
+
inject: () => ({ controller, useSnapshot }),
|
|
92
|
+
}, InterpretersCard);
|
|
93
|
+
});
|
|
94
|
+
}
|