@huanlin/dsh-plugin-sidebar-brand-text 0.4.2 → 0.6.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/README.md +130 -128
- package/lib/client.js +1066 -1066
- package/lib/client.js.map +1 -1
- package/lib/index.js +233 -234
- package/lib/types/client/controller.d.ts +66 -66
- package/lib/types/client/index.d.ts +38 -38
- package/lib/types/client/titleWriter.d.ts +29 -29
- package/lib/types/settings.d.ts +31 -31
- package/package.json +23 -28
- package/lib/invariant.js +0 -21
- package/lib/types/invariant.d.ts +0 -10
package/lib/index.js
CHANGED
|
@@ -1,235 +1,234 @@
|
|
|
1
|
-
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
//#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* @param
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (method === "
|
|
148
|
-
else
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
if (key === "
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
bytes
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
res.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
//#
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* @param
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
//#endregion
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
|
|
3
|
+
//#region src/config.ts
|
|
4
|
+
/** Schemastery schema for the composition entry and the settings namespace. */
|
|
5
|
+
const Config = z.object({
|
|
6
|
+
name: z.string().default("DSH Local Build").description("Brand name text shown in the sidebar next to the logo."),
|
|
7
|
+
revision: z.string().default("").description("Revision badge text shown beside the brand name. Empty string hides the badge.")
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a raw config object into a complete {@link BrandTextConfig}.
|
|
11
|
+
*
|
|
12
|
+
* Unknown keys are dropped; missing or wrong-typed keys fall back to
|
|
13
|
+
* the defaults. This runs on every gateway read so the client always
|
|
14
|
+
* sees a well-formed value.
|
|
15
|
+
* @param config - raw config (entry source or settings layer).
|
|
16
|
+
* @returns the resolved config with defaults applied.
|
|
17
|
+
*/
|
|
18
|
+
function resolveConfig(config = {}) {
|
|
19
|
+
return {
|
|
20
|
+
name: typeof config.name === "string" ? config.name : DEFAULT.name,
|
|
21
|
+
revision: typeof config.revision === "string" ? config.revision : DEFAULT.revision
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Defaults used when no config arrives (defensive only). */
|
|
25
|
+
const DEFAULT = {
|
|
26
|
+
name: "DSH Local Build",
|
|
27
|
+
revision: ""
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/settings.ts
|
|
32
|
+
/** Settings namespace under which brand-text config persists. */
|
|
33
|
+
const SETTINGS_NAMESPACE = "sidebar-brand-text";
|
|
34
|
+
/**
|
|
35
|
+
* Mirror of the dsh-settings internal `isUnloading` guard. The cordis const
|
|
36
|
+
* enum for fiber state is erased at compile time, so the literal states are
|
|
37
|
+
* matched numerically: 4 = DISPOSED, 5 = UNLOADING.
|
|
38
|
+
*/
|
|
39
|
+
function isUnloading(ctx) {
|
|
40
|
+
const state = ctx.fiber?.state;
|
|
41
|
+
return state === 4 || state === 5;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Install the `sidebar-brand-text` settings namespace and return the bridge.
|
|
45
|
+
*
|
|
46
|
+
* @param ctx - host context.
|
|
47
|
+
* @param entry - raw composition-layer config seed.
|
|
48
|
+
* @returns the settings bridge.
|
|
49
|
+
*/
|
|
50
|
+
function installBrandTextSettings(ctx, entry) {
|
|
51
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
52
|
+
let source = () => entry;
|
|
53
|
+
const notify = () => {
|
|
54
|
+
for (const listener of [...listeners]) listener();
|
|
55
|
+
};
|
|
56
|
+
ctx.inject(["settings"], (sctx) => {
|
|
57
|
+
let scope;
|
|
58
|
+
try {
|
|
59
|
+
scope = sctx.settings.register(SETTINGS_NAMESPACE, Config, { base: entry });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof Error) || !error.message.includes("already registered")) throw error;
|
|
62
|
+
ctx.logger("sidebar-brand-text")?.debug("settings namespace already registered — entry-source fallback");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
source = () => scope.get();
|
|
66
|
+
sctx.effect(() => () => {
|
|
67
|
+
if (isUnloading(ctx)) return;
|
|
68
|
+
source = () => entry;
|
|
69
|
+
notify();
|
|
70
|
+
});
|
|
71
|
+
notify();
|
|
72
|
+
scope.watch(() => {
|
|
73
|
+
if (isUnloading(ctx)) return;
|
|
74
|
+
notify();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
source: () => source(),
|
|
79
|
+
onChange: (cb) => {
|
|
80
|
+
listeners.add(cb);
|
|
81
|
+
return () => {
|
|
82
|
+
listeners.delete(cb);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/gateway.ts
|
|
90
|
+
/** HTTP route prefix owning every sidebar-brand-text API request. */
|
|
91
|
+
const API_PREFIX = "/sbbt/api";
|
|
92
|
+
/** Config keys the `set` endpoint accepts (allow-list; unknown keys are dropped). */
|
|
93
|
+
const ALLOWED_KEYS = new Set(["name", "revision"]);
|
|
94
|
+
/**
|
|
95
|
+
* Register the `/sbbt/api` HTTP route on the host's web server.
|
|
96
|
+
*
|
|
97
|
+
* @param ctx - host context carrying `webServer`.
|
|
98
|
+
* @param bridge - the settings bridge the route reads through.
|
|
99
|
+
*/
|
|
100
|
+
function registerBrandTextGateway(ctx, bridge) {
|
|
101
|
+
let settings;
|
|
102
|
+
ctx.inject(["settings"], (sctx) => {
|
|
103
|
+
settings = sctx.settings;
|
|
104
|
+
return () => {
|
|
105
|
+
settings = void 0;
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
ctx.effect(() => {
|
|
109
|
+
const webServer = ctx.webServer;
|
|
110
|
+
if (!webServer || typeof webServer.register !== "function") return () => {};
|
|
111
|
+
return webServer.register({
|
|
112
|
+
kind: "prefix",
|
|
113
|
+
path: API_PREFIX,
|
|
114
|
+
handler: async (req, res) => {
|
|
115
|
+
if ((req.method ?? "") !== "POST") {
|
|
116
|
+
writeJson(res, 405, envelopeError("method-not-allowed", "POST only"));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const origin = req.headers.origin;
|
|
120
|
+
if (typeof origin === "string" && origin) {
|
|
121
|
+
let originHost;
|
|
122
|
+
try {
|
|
123
|
+
originHost = new URL(origin).host;
|
|
124
|
+
} catch {
|
|
125
|
+
writeJson(res, 400, envelopeError("invalid-origin", "invalid Origin header"));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const reqHost = req.headers.host;
|
|
129
|
+
if (typeof reqHost === "string" && originHost !== reqHost) {
|
|
130
|
+
writeJson(res, 403, envelopeError("origin-not-allowed", "same-origin requests only"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
|
|
135
|
+
writeJson(res, 415, envelopeError("content-type-not-supported", "application/json required"));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
|
139
|
+
const method = pathname.startsWith(`${API_PREFIX}/`) ? pathname.slice(`${API_PREFIX}/`.length) : void 0;
|
|
140
|
+
if (method === void 0 || method.includes("/")) {
|
|
141
|
+
writeJson(res, 404, envelopeError("not-found", "unknown sidebar-brand-text API method"));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const body = await readJsonBody(req);
|
|
146
|
+
if (method === "get") writeJson(res, 200, envelopeOk({ config: resolveConfig(bridge.source()) }));
|
|
147
|
+
else if (method === "set") writeJson(res, 200, envelopeOk(await handleSet(body, settings, bridge)));
|
|
148
|
+
else writeJson(res, 404, envelopeError("not-found", `unknown sidebar-brand-text API method "${method}"`));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
writeJson(res, 500, envelopeError("internal", error instanceof Error ? error.message : String(error)));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}, "sidebar-brand-text: /sbbt/api routes");
|
|
155
|
+
}
|
|
156
|
+
/** Handle the `set` method: validate patch, write user layer, return resolved config. */
|
|
157
|
+
async function handleSet(body, settings, bridge) {
|
|
158
|
+
const patch = extractPatch(body);
|
|
159
|
+
if (Object.keys(patch).length === 0) return { config: resolveConfig(bridge.source()) };
|
|
160
|
+
if (settings === void 0) throw new Error("sidebar-brand-text: settings service is unavailable — configuration cannot be written");
|
|
161
|
+
await settings.update(SETTINGS_NAMESPACE, patch);
|
|
162
|
+
return { config: resolveConfig(bridge.source()) };
|
|
163
|
+
}
|
|
164
|
+
/** Extract and validate the patch from the request body. */
|
|
165
|
+
function extractPatch(body) {
|
|
166
|
+
if (typeof body !== "object" || body === null) return {};
|
|
167
|
+
const raw = Reflect.get(body, "patch");
|
|
168
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
169
|
+
const normalized = {};
|
|
170
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
171
|
+
if (!ALLOWED_KEYS.has(key)) continue;
|
|
172
|
+
if (value === null || value === void 0) continue;
|
|
173
|
+
if (typeof value === "string") {
|
|
174
|
+
if (key === "name") normalized.name = value;
|
|
175
|
+
else if (key === "revision") normalized.revision = value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return normalized;
|
|
179
|
+
}
|
|
180
|
+
/** Read and parse a JSON body from a node:http request. */
|
|
181
|
+
async function readJsonBody(req, maxBytes = 8192) {
|
|
182
|
+
const chunks = [];
|
|
183
|
+
let bytes = 0;
|
|
184
|
+
for await (const chunk of req) {
|
|
185
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
186
|
+
bytes += buffer.length;
|
|
187
|
+
if (bytes > maxBytes) throw new Error("request body too large");
|
|
188
|
+
chunks.push(buffer);
|
|
189
|
+
}
|
|
190
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
191
|
+
if (text === "") return {};
|
|
192
|
+
return JSON.parse(text);
|
|
193
|
+
}
|
|
194
|
+
/** Write a JSON response envelope. */
|
|
195
|
+
function writeJson(res, status, body) {
|
|
196
|
+
const json = JSON.stringify(body);
|
|
197
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
198
|
+
res.end(json);
|
|
199
|
+
}
|
|
200
|
+
/** Build a success envelope. */
|
|
201
|
+
function envelopeOk(value) {
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
value
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Build an error envelope. */
|
|
208
|
+
function envelopeError(code, message) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: {
|
|
212
|
+
code,
|
|
213
|
+
message
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/index.ts
|
|
220
|
+
const name = "sidebar-brand-text";
|
|
221
|
+
/** `webServer` is required for the HTTP gateway that backs the settings card. */
|
|
222
|
+
const inject = ["webServer"];
|
|
223
|
+
/**
|
|
224
|
+
* Plugin body: install the settings bridge and register the HTTP gateway.
|
|
225
|
+
*
|
|
226
|
+
* @param ctx - host context carrying `webServer`.
|
|
227
|
+
* @param config - resolved config (seed values).
|
|
228
|
+
*/
|
|
229
|
+
function apply(ctx, config) {
|
|
230
|
+
registerBrandTextGateway(ctx, installBrandTextSettings(ctx, resolveConfig(config)));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
235
234
|
export { Config, apply, inject, name };
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `BrandTextSettingsController` — client-side state store for the
|
|
3
|
-
* sidebar-brand-text config.
|
|
4
|
-
*
|
|
5
|
-
* Loads the config from the host's `/sbbt/api/get` route, stages edits,
|
|
6
|
-
* and saves via `/sbbt/api/set`. The `sidebar.brand.name` slot occupant
|
|
7
|
-
* and the `settings.plugin.item` card both read from the same store via
|
|
8
|
-
* `bindSnapshotSelector`, so a save is instantly reflected in the sidebar
|
|
9
|
-
* without a DOM event or page reload.
|
|
10
|
-
*
|
|
11
|
-
* Mirrors the ego-browser `EgoBrowserSettingsController` pattern.
|
|
12
|
-
*
|
|
13
|
-
* @module @huanlin/dsh-plugin-sidebar-brand-text/client/controller
|
|
14
|
-
*/
|
|
15
|
-
import { type SnapshotStore } from '@deepseek-ai/dsh-client-
|
|
16
|
-
import { type BrandTextConfig } from '../types.ts';
|
|
17
|
-
/** The controller's snapshot state. */
|
|
18
|
-
export interface BrandTextState {
|
|
19
|
-
/** 'idle' | 'loading' | 'ready' */
|
|
20
|
-
status: 'idle' | 'loading' | 'ready';
|
|
21
|
-
/** True after a successful `/sbbt/api/get`; false when the route is unreachable. */
|
|
22
|
-
available: boolean;
|
|
23
|
-
/** False when the settings service is absent (read-only). */
|
|
24
|
-
writable: boolean;
|
|
25
|
-
/** Current draft values (edited by the card, read by the brand slot). */
|
|
26
|
-
draft: BrandTextConfig;
|
|
27
|
-
/** True when the draft differs from the last-saved config. */
|
|
28
|
-
dirty: boolean;
|
|
29
|
-
/** Apply lifecycle: 'idle' | 'saving' | 'saved' | 'error'. */
|
|
30
|
-
applyState: {
|
|
31
|
-
kind: 'idle';
|
|
32
|
-
} | {
|
|
33
|
-
kind: 'saving';
|
|
34
|
-
} | {
|
|
35
|
-
kind: 'saved';
|
|
36
|
-
} | {
|
|
37
|
-
kind: 'error';
|
|
38
|
-
message: string;
|
|
39
|
-
};
|
|
40
|
-
/** Card expand state (toggled by the card header button). */
|
|
41
|
-
_open: boolean;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Controller managing the brand-text config lifecycle.
|
|
45
|
-
*
|
|
46
|
-
* Constructed once in the client `apply()` and shared between the
|
|
47
|
-
* `sidebar.brand.name` slot and the `settings.plugin.item` card.
|
|
48
|
-
*/
|
|
49
|
-
export declare class BrandTextSettingsController {
|
|
50
|
-
readonly store: SnapshotStore<BrandTextState>;
|
|
51
|
-
loaded: boolean;
|
|
52
|
-
private generation;
|
|
53
|
-
constructor();
|
|
54
|
-
/** Fetch the config from `/sbbt/api/get` and update the store. */
|
|
55
|
-
load(): Promise<void>;
|
|
56
|
-
/** Stage an edit to a field (does not save). */
|
|
57
|
-
edit(field: 'name' | 'revision', value: string): void;
|
|
58
|
-
/** Discard staged edits and reload from the host. */
|
|
59
|
-
discard(): void;
|
|
60
|
-
/** Save the staged draft via `/sbbt/api/set`. */
|
|
61
|
-
save(): Promise<void>;
|
|
62
|
-
/** Toggle the card's expand state (mirrors ego-browser `controller.toggle()`). */
|
|
63
|
-
toggle(): void;
|
|
64
|
-
/** Mark the store as unavailable (route unreachable or settings service absent). */
|
|
65
|
-
private markUnavailable;
|
|
66
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `BrandTextSettingsController` — client-side state store for the
|
|
3
|
+
* sidebar-brand-text config.
|
|
4
|
+
*
|
|
5
|
+
* Loads the config from the host's `/sbbt/api/get` route, stages edits,
|
|
6
|
+
* and saves via `/sbbt/api/set`. The `sidebar.brand.name` slot occupant
|
|
7
|
+
* and the `settings.plugin.item` card both read from the same store via
|
|
8
|
+
* `bindSnapshotSelector`, so a save is instantly reflected in the sidebar
|
|
9
|
+
* without a DOM event or page reload.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the ego-browser `EgoBrowserSettingsController` pattern.
|
|
12
|
+
*
|
|
13
|
+
* @module @huanlin/dsh-plugin-sidebar-brand-text/client/controller
|
|
14
|
+
*/
|
|
15
|
+
import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
16
|
+
import { type BrandTextConfig } from '../types.ts';
|
|
17
|
+
/** The controller's snapshot state. */
|
|
18
|
+
export interface BrandTextState {
|
|
19
|
+
/** 'idle' | 'loading' | 'ready' */
|
|
20
|
+
status: 'idle' | 'loading' | 'ready';
|
|
21
|
+
/** True after a successful `/sbbt/api/get`; false when the route is unreachable. */
|
|
22
|
+
available: boolean;
|
|
23
|
+
/** False when the settings service is absent (read-only). */
|
|
24
|
+
writable: boolean;
|
|
25
|
+
/** Current draft values (edited by the card, read by the brand slot). */
|
|
26
|
+
draft: BrandTextConfig;
|
|
27
|
+
/** True when the draft differs from the last-saved config. */
|
|
28
|
+
dirty: boolean;
|
|
29
|
+
/** Apply lifecycle: 'idle' | 'saving' | 'saved' | 'error'. */
|
|
30
|
+
applyState: {
|
|
31
|
+
kind: 'idle';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'saving';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'saved';
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'error';
|
|
38
|
+
message: string;
|
|
39
|
+
};
|
|
40
|
+
/** Card expand state (toggled by the card header button). */
|
|
41
|
+
_open: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Controller managing the brand-text config lifecycle.
|
|
45
|
+
*
|
|
46
|
+
* Constructed once in the client `apply()` and shared between the
|
|
47
|
+
* `sidebar.brand.name` slot and the `settings.plugin.item` card.
|
|
48
|
+
*/
|
|
49
|
+
export declare class BrandTextSettingsController {
|
|
50
|
+
readonly store: SnapshotStore<BrandTextState>;
|
|
51
|
+
loaded: boolean;
|
|
52
|
+
private generation;
|
|
53
|
+
constructor();
|
|
54
|
+
/** Fetch the config from `/sbbt/api/get` and update the store. */
|
|
55
|
+
load(): Promise<void>;
|
|
56
|
+
/** Stage an edit to a field (does not save). */
|
|
57
|
+
edit(field: 'name' | 'revision', value: string): void;
|
|
58
|
+
/** Discard staged edits and reload from the host. */
|
|
59
|
+
discard(): void;
|
|
60
|
+
/** Save the staged draft via `/sbbt/api/set`. */
|
|
61
|
+
save(): Promise<void>;
|
|
62
|
+
/** Toggle the card's expand state (mirrors ego-browser `controller.toggle()`). */
|
|
63
|
+
toggle(): void;
|
|
64
|
+
/** Mark the store as unavailable (route unreachable or settings service absent). */
|
|
65
|
+
private markUnavailable;
|
|
66
|
+
}
|