dsh-claude-style 0.13.2 → 0.14.0-dev.12

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/LICENSE +0 -5
  3. package/README.en.md +1 -1
  4. package/README.md +2 -2
  5. package/THIRD_PARTY_NOTICES.md +282 -6
  6. package/docs/FEATURES.en.md +13 -14
  7. package/docs/FEATURES.md +13 -14
  8. package/docs/SETTINGS.en.md +21 -12
  9. package/docs/SETTINGS.md +21 -12
  10. package/docs/STYLE.md +151 -12
  11. package/lib/assets/1952ecd55916.js.br +0 -0
  12. package/lib/assets/3b91bf51db72.js.br +0 -0
  13. package/lib/assets/4ec527e58330.js.br +0 -0
  14. package/lib/assets/7323c8c86470.js.br +0 -0
  15. package/lib/assets/755d09170f37.js.br +0 -0
  16. package/lib/assets/c02a70a798bc.js.br +0 -0
  17. package/lib/assets/d8df7a57fbe3.js.br +0 -0
  18. package/lib/assets/manifest.json +28 -28
  19. package/lib/client.js +11 -8
  20. package/lib/host/brand.js +136 -0
  21. package/lib/host/http.js +74 -0
  22. package/lib/host/peakrate.js +7 -0
  23. package/lib/host/routes.js +26 -88
  24. package/lib/host/search.js +8 -1
  25. package/lib/host/settings.js +5 -0
  26. package/lib/model-descriptions.json +195 -43
  27. package/package.json +12 -4
  28. package/docs/decisions/D11-private-routes.md +0 -26
  29. package/docs/decisions/D12-fail-fast.md +0 -36
  30. package/docs/decisions/D14-account-surface.md +0 -26
  31. package/docs/decisions/D15-hdsl-contract.md +0 -24
  32. package/docs/decisions/D16-popover-baseline.md +0 -23
  33. package/docs/decisions/D17-permission-presets.md +0 -26
  34. package/docs/decisions/D19-host-contracts.md +0 -27
  35. package/docs/decisions/D20-sliding-pill.md +0 -26
  36. package/docs/decisions/D21-archived-list.md +0 -24
  37. package/docs/decisions/D22-search-panel.md +0 -30
  38. package/docs/decisions/D24-mascot.md +0 -32
  39. package/docs/decisions/D25-ban-screen-titlebar.md +0 -24
  40. package/docs/decisions/D26-motion-preference.md +0 -23
  41. package/docs/decisions/D27-context-stats.md +0 -35
  42. package/docs/decisions/D28-desktop-band.md +0 -23
  43. package/docs/decisions/D29-feature-switches.md +0 -24
  44. package/docs/decisions/D3-host-selectors.md +0 -28
  45. package/docs/decisions/D30-palette-and-typeface.md +0 -25
  46. package/docs/decisions/D31-windows-titlebar.md +0 -24
  47. package/docs/decisions/D32-chat-interactions.md +0 -33
  48. package/docs/decisions/D33-stylesheet-ownership.md +0 -26
  49. package/docs/decisions/D34-turn-navigator.md +0 -29
  50. package/docs/decisions/D35-selection-scope.md +0 -23
  51. package/docs/decisions/D36-modules-and-bundling.md +0 -35
  52. package/docs/decisions/D38-assets.md +0 -28
  53. package/docs/decisions/D39-on-demand-loading.md +0 -33
  54. package/docs/decisions/D4-composer-gate.md +0 -22
  55. package/docs/decisions/D40-observation-bus.md +0 -36
  56. package/docs/decisions/D41-scroll-owner.md +0 -40
  57. package/docs/decisions/D42-feature-manifest.md +0 -28
  58. package/docs/decisions/D43-overlay-and-upstream.md +0 -27
  59. package/docs/decisions/D44-contract-module.md +0 -36
  60. package/docs/decisions/D45-testing.md +0 -30
  61. package/docs/decisions/D46-monorepo.md +0 -31
  62. package/docs/decisions/D47-build-output.md +0 -30
  63. package/docs/decisions/D48-documentation.md +0 -28
  64. package/docs/decisions/D49-visual-yield.md +0 -30
  65. package/docs/decisions/D5-model-copy.md +0 -29
  66. package/docs/decisions/D50-brand.md +0 -24
  67. package/docs/decisions/D51-css-toolchain.md +0 -34
  68. package/docs/decisions/D52-header-band.md +0 -31
  69. package/docs/decisions/D53-usage-merge.md +0 -33
  70. package/docs/decisions/D54-peakrate-meter.md +0 -33
  71. package/docs/decisions/D58-redraw-retired.md +0 -29
  72. package/docs/decisions/D60-settings-transport.md +0 -31
  73. package/docs/decisions/D61-wallpaper-under-one-mask.md +0 -28
  74. package/docs/decisions/D62-file-row-merge.md +0 -28
  75. package/docs/decisions/D63-turn-status-retired.md +0 -29
  76. package/docs/decisions/D7-one-package.md +0 -23
  77. package/docs/decisions/D9-style-performance.md +0 -25
  78. package/docs/decisions/README.md +0 -86
  79. package/lib/assets/2c01384360ac.js.map.br +0 -0
  80. package/lib/assets/31253ed42440.js.br +0 -0
  81. package/lib/assets/5063c46824b6.js.br +0 -0
  82. package/lib/assets/55a6154f87b5.js.map.br +0 -0
  83. package/lib/assets/5e05b29d5005.js.br +0 -0
  84. package/lib/assets/668d06ca781b.js.map.br +0 -0
  85. package/lib/assets/930546669455.js.map.br +0 -0
  86. package/lib/assets/968fcbfc3e22.js.br +0 -0
  87. package/lib/assets/abf0547f08a4.js.br +0 -0
  88. package/lib/assets/ac9b0e625837.js.map.br +0 -0
  89. package/lib/assets/ad2905345a69.js.br +0 -0
  90. package/lib/assets/ba68f60ecc71.js.map.br +0 -0
  91. package/lib/assets/c52e41b4842b.js.map.br +0 -0
  92. package/lib/assets/e1291f79b7c9.js.br +0 -0
  93. package/lib/client.js.map +0 -7
@@ -0,0 +1,136 @@
1
+ // packages/contracts/src/brand.ts
2
+ var BRAND_IMAGE_KINDS = ["mark", "wordmark"];
3
+ var BRAND_IMAGE_TYPES = {
4
+ "image/png": "png",
5
+ "image/svg+xml": "svg",
6
+ "image/webp": "webp"
7
+ };
8
+ var BRAND_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
9
+
10
+ // packages/contracts/src/routes.ts
11
+ var ROUTE_PREFIX = "/dsh-claude-style";
12
+ var USERNAME_PATH = `${ROUTE_PREFIX}/username`;
13
+ var HDSL_PATH = `${ROUTE_PREFIX}/hdsl`;
14
+ var HDSL_SKIN_PATH = `${ROUTE_PREFIX}/hdsl-skin.png`;
15
+ var SESSION_DELETE_PATH = `${ROUTE_PREFIX}/session-delete`;
16
+ var USAGE_PATH = `${ROUTE_PREFIX}/usage`;
17
+ var SESSION_SEARCH_PATH = `${ROUTE_PREFIX}/session-search`;
18
+ var BRAND_PATH = `${ROUTE_PREFIX}/brand`;
19
+ var BRAND_MARK_PATH = `${ROUTE_PREFIX}/brand/mark`;
20
+ var BRAND_WORDMARK_PATH = `${ROUTE_PREFIX}/brand/wordmark`;
21
+ var PEAKRATE_PATH = `${ROUTE_PREFIX}/peakrate`;
22
+
23
+ // packages/host/src/brand.ts
24
+ import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ import { harnessPath } from "./harness-home.js";
27
+ import { fenceRefused, methodRefused, readRequestBody, sendJson } from "./http.js";
28
+ var IMAGE_PATHS = { mark: BRAND_MARK_PATH, wordmark: BRAND_WORDMARK_PATH };
29
+ var IMAGE_HEADERS = {
30
+ "cache-control": "no-cache",
31
+ "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
32
+ "x-content-type-options": "nosniff"
33
+ };
34
+ var SIGNATURES = {
35
+ "image/png": [{ offset: 0, bytes: Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]) }],
36
+ "image/webp": [{ offset: 0, bytes: Buffer.from("RIFF", "latin1") }, { offset: 8, bytes: Buffer.from("WEBP", "latin1") }]
37
+ };
38
+ function createBrandStore(ctx) {
39
+ const dir = () => harnessPath(ctx, "dsh-claude-style", "brand");
40
+ const find = (kind) => {
41
+ for (const [type, extension] of Object.entries(BRAND_IMAGE_TYPES)) {
42
+ const path = join(dir(), `${kind}.${extension}`);
43
+ const stat = statSync(path, { throwIfNoEntry: false });
44
+ if (stat?.isFile() !== true) continue;
45
+ return { path, type, state: { type, version: `${Math.round(stat.mtimeMs)}-${stat.size}` } };
46
+ }
47
+ return null;
48
+ };
49
+ const answer = () => ({ ok: true, mark: find("mark")?.state ?? null, wordmark: find("wordmark")?.state ?? null });
50
+ const write = (kind, type, bytes) => {
51
+ const extension = BRAND_IMAGE_TYPES[type];
52
+ mkdirSync(dir(), { recursive: true });
53
+ const target = join(dir(), `${kind}.${extension}`);
54
+ const temporary = `${target}.${process.pid}.tmp`;
55
+ writeFileSync(temporary, bytes);
56
+ renameSync(temporary, target);
57
+ for (const other of Object.values(BRAND_IMAGE_TYPES)) {
58
+ if (other !== extension) rmSync(join(dir(), `${kind}.${other}`), { force: true });
59
+ }
60
+ };
61
+ const remove = (kind) => {
62
+ for (const extension of Object.values(BRAND_IMAGE_TYPES)) rmSync(join(dir(), `${kind}.${extension}`), { force: true });
63
+ };
64
+ return { find, answer, write, remove };
65
+ }
66
+ function matchesSignature(type, bytes) {
67
+ const marks = SIGNATURES[type];
68
+ if (marks === void 0) return true;
69
+ return marks.every(({ offset, bytes: expected }) => bytes.subarray(offset, offset + expected.length).equals(expected));
70
+ }
71
+ async function storeImage(store, kind, req, res) {
72
+ const type = (req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
73
+ if (BRAND_IMAGE_TYPES[type] === void 0) {
74
+ sendJson(res, 415, { ok: false, error: "unsupported image type" });
75
+ return;
76
+ }
77
+ const bytes = await readRequestBody(req, BRAND_IMAGE_MAX_BYTES);
78
+ if (bytes === null) {
79
+ res.once("finish", () => req.destroy());
80
+ sendJson(res, 413, { ok: false, error: "image too large" });
81
+ return;
82
+ }
83
+ if (bytes.length === 0 || !matchesSignature(type, bytes)) {
84
+ sendJson(res, 400, { ok: false, error: "not an image of the declared type" });
85
+ return;
86
+ }
87
+ store.write(kind, type, bytes);
88
+ sendJson(res, 200, store.answer());
89
+ }
90
+ function brandRoutes(ctx) {
91
+ const store = createBrandStore(ctx);
92
+ const status = {
93
+ kind: "exact",
94
+ path: BRAND_PATH,
95
+ handler: (req, res) => {
96
+ if (methodRefused(req, res, ["GET"])) return;
97
+ if (fenceRefused(ctx, req, res)) return;
98
+ sendJson(res, 200, store.answer());
99
+ }
100
+ };
101
+ const images = BRAND_IMAGE_KINDS.map((kind) => ({
102
+ label: `brand ${kind}`,
103
+ route: {
104
+ kind: "exact",
105
+ path: IMAGE_PATHS[kind],
106
+ handler: (req, res) => {
107
+ if (methodRefused(req, res, ["GET", "HEAD", "PUT", "DELETE"])) return;
108
+ if (fenceRefused(ctx, req, res)) return;
109
+ if (req.method === "PUT" || req.method === "DELETE") {
110
+ const done = req.method === "PUT" ? storeImage(store, kind, req, res) : Promise.resolve().then(() => {
111
+ store.remove(kind);
112
+ sendJson(res, 200, store.answer());
113
+ });
114
+ void done.catch((error) => {
115
+ if (res.headersSent) res.destroy();
116
+ else sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
117
+ });
118
+ return;
119
+ }
120
+ const found = store.find(kind);
121
+ if (found === null) {
122
+ res.writeHead(404, { "cache-control": "no-store" });
123
+ res.end();
124
+ return;
125
+ }
126
+ const body = readFileSync(found.path);
127
+ res.writeHead(200, { ...IMAGE_HEADERS, "content-type": found.type, "content-length": String(body.byteLength) });
128
+ res.end(req.method === "HEAD" ? void 0 : body);
129
+ }
130
+ }
131
+ }));
132
+ return [{ label: "brand status", route: status }, ...images];
133
+ }
134
+ export {
135
+ brandRoutes
136
+ };
@@ -0,0 +1,74 @@
1
+ // packages/host/src/http.ts
2
+ function refusalOf(ctx, req) {
3
+ const connection = ctx.get("connection");
4
+ if (typeof connection?.requestRejection === "function") return connection.requestRejection(req);
5
+ if (!isLoopbackPeer(req.socket.remoteAddress)) return 403;
6
+ const host = req.headers.host;
7
+ if (host !== void 0) {
8
+ if (!URL.canParse(`http://${host}`)) return 403;
9
+ const name = new URL(`http://${host}`).hostname;
10
+ if (name !== "localhost" && name !== "[::1]" && !/^127\.\d+\.\d+\.\d+$/.test(name)) return 403;
11
+ }
12
+ const site = req.headers["sec-fetch-site"];
13
+ if (site !== void 0 && site !== "same-origin" && site !== "none") return 403;
14
+ const origin = req.headers.origin;
15
+ if (origin === void 0) return void 0;
16
+ if (!URL.canParse(origin)) return 403;
17
+ return new URL(origin).host === host ? void 0 : 403;
18
+ }
19
+ function isLoopbackPeer(address) {
20
+ if (address === void 0) return false;
21
+ const plain = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
22
+ return plain === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(plain);
23
+ }
24
+ function sendJson(res, status, payload) {
25
+ const body = Buffer.from(JSON.stringify(payload));
26
+ res.writeHead(status, {
27
+ "content-type": "application/json; charset=utf-8",
28
+ "content-length": String(body.byteLength),
29
+ "cache-control": "no-store"
30
+ });
31
+ res.end(body);
32
+ }
33
+ function methodRefused(req, res, methods) {
34
+ if (req.method !== void 0 && methods.includes(req.method)) return false;
35
+ res.writeHead(405, { allow: methods.join(", ") });
36
+ res.end();
37
+ return true;
38
+ }
39
+ function fenceRefused(ctx, req, res) {
40
+ const refused = refusalOf(ctx, req);
41
+ if (refused === void 0) return false;
42
+ sendJson(res, refused, { ok: false, error: refused === 401 ? "unauthorized" : "forbidden" });
43
+ return true;
44
+ }
45
+ function readRequestBody(req, limit) {
46
+ return new Promise((settle) => {
47
+ const chunks = [];
48
+ let size = 0;
49
+ let over = false;
50
+ req.on("data", (chunk) => {
51
+ if (over) return;
52
+ size += chunk.length;
53
+ if (size > limit) {
54
+ over = true;
55
+ settle(null);
56
+ return;
57
+ }
58
+ chunks.push(Buffer.from(chunk));
59
+ });
60
+ req.on("end", () => {
61
+ if (!over) settle(Buffer.concat(chunks));
62
+ });
63
+ req.on("error", () => {
64
+ if (!over) settle(null);
65
+ });
66
+ });
67
+ }
68
+ export {
69
+ fenceRefused,
70
+ methodRefused,
71
+ readRequestBody,
72
+ refusalOf,
73
+ sendJson
74
+ };
@@ -17,6 +17,7 @@ function createPeakRate(ctx) {
17
17
  let fetchedAt = 0;
18
18
  let loaded = false;
19
19
  let timer;
20
+ let inFlight = null;
20
21
  let lastAskedAt = 0;
21
22
  const warn = (message) => ctx.logger?.warn?.(message);
22
23
  function shippedCatalog() {
@@ -56,6 +57,7 @@ function createPeakRate(ctx) {
56
57
  let raw;
57
58
  try {
58
59
  const controller = new AbortController();
60
+ inFlight = controller;
59
61
  const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
60
62
  try {
61
63
  const response = await fetch(PEAKRATE_CATALOG_URL, { signal: controller.signal });
@@ -66,6 +68,7 @@ function createPeakRate(ctx) {
66
68
  raw = await response.json();
67
69
  } finally {
68
70
  clearTimeout(timeout);
71
+ if (inFlight === controller) inFlight = null;
69
72
  }
70
73
  } catch (error) {
71
74
  warn(`dsh-claude-style: peak rate source unreachable, keeping the current catalog: ${error.message}`);
@@ -119,6 +122,10 @@ function createPeakRate(ctx) {
119
122
  payload,
120
123
  dispose() {
121
124
  stopTimer();
125
+ if (inFlight !== null) {
126
+ inFlight.abort();
127
+ inFlight = null;
128
+ }
122
129
  }
123
130
  };
124
131
  }
@@ -6,6 +6,9 @@ var HDSL_SKIN_PATH = `${ROUTE_PREFIX}/hdsl-skin.png`;
6
6
  var SESSION_DELETE_PATH = `${ROUTE_PREFIX}/session-delete`;
7
7
  var USAGE_PATH = `${ROUTE_PREFIX}/usage`;
8
8
  var SESSION_SEARCH_PATH = `${ROUTE_PREFIX}/session-search`;
9
+ var BRAND_PATH = `${ROUTE_PREFIX}/brand`;
10
+ var BRAND_MARK_PATH = `${ROUTE_PREFIX}/brand/mark`;
11
+ var BRAND_WORDMARK_PATH = `${ROUTE_PREFIX}/brand/wordmark`;
9
12
  var PEAKRATE_PATH = `${ROUTE_PREFIX}/peakrate`;
10
13
 
11
14
  // packages/host/src/routes.ts
@@ -13,7 +16,9 @@ import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync }
13
16
  import { userInfo } from "node:os";
14
17
  import { join, resolve, sep } from "node:path";
15
18
  import { brotliDecompressSync } from "node:zlib";
19
+ import { brandRoutes } from "./brand.js";
16
20
  import { harnessPath } from "./harness-home.js";
21
+ import { fenceRefused, methodRefused, readRequestBody, refusalOf, sendJson } from "./http.js";
17
22
  import { createHdslAccount } from "./hdsl.js";
18
23
  import { packageRoot } from "./package-root.js";
19
24
  import { createPeakRate } from "./peakrate.js";
@@ -33,37 +38,6 @@ var ASSETS_MANIFEST = "manifest.json";
33
38
  var USAGE_TTL_MS = 5 * 60 * 1e3;
34
39
  var SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
35
40
  var DELETE_BODY_MAX = 4096;
36
- function refusalOf(ctx, req) {
37
- const connection = ctx.get("connection");
38
- if (typeof connection?.requestRejection === "function") return connection.requestRejection(req);
39
- if (!isLoopbackPeer(req.socket.remoteAddress)) return 403;
40
- const host = req.headers.host;
41
- if (host !== void 0) {
42
- if (!URL.canParse(`http://${host}`)) return 403;
43
- const name = new URL(`http://${host}`).hostname;
44
- if (name !== "localhost" && name !== "[::1]" && !/^127\.\d+\.\d+\.\d+$/.test(name)) return 403;
45
- }
46
- const site = req.headers["sec-fetch-site"];
47
- if (site !== void 0 && site !== "same-origin" && site !== "none") return 403;
48
- const origin = req.headers.origin;
49
- if (origin === void 0) return void 0;
50
- if (!URL.canParse(origin)) return 403;
51
- return new URL(origin).host === host ? void 0 : 403;
52
- }
53
- function isLoopbackPeer(address) {
54
- if (address === void 0) return false;
55
- const plain = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
56
- return plain === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(plain);
57
- }
58
- function sendJson(res, status, payload) {
59
- const body = Buffer.from(JSON.stringify(payload));
60
- res.writeHead(status, {
61
- "content-type": "application/json; charset=utf-8",
62
- "content-length": String(body.byteLength),
63
- "cache-control": "no-store"
64
- });
65
- res.end(body);
66
- }
67
41
  function sessionIsLive(ctx, sessionId) {
68
42
  const sessions = ctx.get("sessions");
69
43
  if (sessions === null || sessions === void 0) return false;
@@ -79,29 +53,6 @@ function sessionIsLive(ctx, sessionId) {
79
53
  }
80
54
  return true;
81
55
  }
82
- function readRequestBody(req, limit) {
83
- return new Promise((settle) => {
84
- const chunks = [];
85
- let size = 0;
86
- let over = false;
87
- req.on("data", (chunk) => {
88
- if (over) return;
89
- size += chunk.length;
90
- if (size > limit) {
91
- over = true;
92
- settle(null);
93
- return;
94
- }
95
- chunks.push(Buffer.from(chunk));
96
- });
97
- req.on("end", () => {
98
- if (!over) settle(Buffer.concat(chunks).toString("utf8"));
99
- });
100
- req.on("error", () => {
101
- if (!over) settle(null);
102
- });
103
- });
104
- }
105
56
  async function unarchiveSession(ctx, sessionId) {
106
57
  const registry = ctx.get("workspaceRegistry");
107
58
  if (typeof registry?.unarchiveSession !== "function") return;
@@ -119,7 +70,7 @@ async function deleteSession(ctx, req, res) {
119
70
  }
120
71
  let request = null;
121
72
  try {
122
- request = raw === null ? null : JSON.parse(raw);
73
+ request = raw === null ? null : JSON.parse(raw.toString("utf8"));
123
74
  } catch {
124
75
  request = null;
125
76
  }
@@ -217,18 +168,6 @@ function registerRoutes(ctx, scope) {
217
168
  return void 0;
218
169
  }
219
170
  };
220
- const methodRefused = (req, res, methods) => {
221
- if (req.method !== void 0 && methods.includes(req.method)) return false;
222
- res.writeHead(405, { allow: methods.join(", ") });
223
- res.end();
224
- return true;
225
- };
226
- const fenceRefused = (req, res) => {
227
- const refused = refusalOf(ctx, req);
228
- if (refused === void 0) return false;
229
- sendJson(res, refused, { ok: false, error: refused === 401 ? "unauthorized" : "forbidden" });
230
- return true;
231
- };
232
171
  scope.effect(() => {
233
172
  const disposers = [];
234
173
  const report = (message, error) => ctx.logger?.warn?.(`dsh-claude-style: ${message}: ${error?.message ?? String(error)}`);
@@ -249,6 +188,13 @@ function registerRoutes(ctx, scope) {
249
188
  report(`${label} route unavailable`, error);
250
189
  }
251
190
  };
191
+ const sendPayload = (res, answer) => {
192
+ void answer.then((value) => {
193
+ sendJson(res, 200, { ok: true, ...value });
194
+ }, (error) => {
195
+ sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
196
+ });
197
+ };
252
198
  register("model copy", {
253
199
  kind: "prefix",
254
200
  path: ROUTE_PREFIX,
@@ -289,7 +235,7 @@ function registerRoutes(ctx, scope) {
289
235
  path: USERNAME_PATH,
290
236
  handler: (req, res) => {
291
237
  if (methodRefused(req, res, ["GET", "HEAD"])) return;
292
- if (fenceRefused(req, res)) return;
238
+ if (fenceRefused(ctx, req, res)) return;
293
239
  const username = userInfo().username || "";
294
240
  const body = Buffer.from(JSON.stringify({ ok: true, username }));
295
241
  res.writeHead(200, {
@@ -305,13 +251,11 @@ function registerRoutes(ctx, scope) {
305
251
  path: HDSL_PATH,
306
252
  handler: (req, res) => {
307
253
  if (methodRefused(req, res, ["GET", "HEAD"])) return;
308
- if (fenceRefused(req, res)) return;
309
- hdsl.read().then((profile) => {
254
+ if (fenceRefused(ctx, req, res)) return;
255
+ sendPayload(res, hdsl.read().then((profile) => {
310
256
  const { skinFile, ...account } = profile;
311
- sendJson(res, 200, { ok: true, ...account });
312
- }, (error) => {
313
- sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
314
- });
257
+ return account;
258
+ }));
315
259
  }
316
260
  });
317
261
  register("HDSL skin", {
@@ -319,7 +263,7 @@ function registerRoutes(ctx, scope) {
319
263
  path: HDSL_SKIN_PATH,
320
264
  handler: (req, res) => {
321
265
  if (methodRefused(req, res, ["GET", "HEAD"])) return;
322
- if (fenceRefused(req, res)) return;
266
+ if (fenceRefused(ctx, req, res)) return;
323
267
  return hdsl.read().then((profile) => {
324
268
  if (typeof profile.skinFile !== "string" || !existsSync(profile.skinFile)) {
325
269
  res.writeHead(404, { "cache-control": "no-store" });
@@ -352,7 +296,7 @@ function registerRoutes(ctx, scope) {
352
296
  path: USAGE_PATH,
353
297
  handler: (req, res) => {
354
298
  if (methodRefused(req, res, ["GET", "HEAD"])) return;
355
- if (fenceRefused(req, res)) return;
299
+ if (fenceRefused(ctx, req, res)) return;
356
300
  let snapshot = usage.snapshot();
357
301
  const stale = snapshot.value === null || snapshot.computing === true || Date.now() - (snapshot.value?.computedAt ?? 0) > USAGE_TTL_MS;
358
302
  if (stale) {
@@ -372,14 +316,10 @@ function registerRoutes(ctx, scope) {
372
316
  path: SESSION_SEARCH_PATH,
373
317
  handler: (req, res) => {
374
318
  if (methodRefused(req, res, ["GET"])) return;
375
- if (fenceRefused(req, res)) return;
319
+ if (fenceRefused(ctx, req, res)) return;
376
320
  const query = (new URL(req.url ?? "/", "http://local").searchParams.get("q") ?? "").trim().slice(0, QUERY_MAX);
377
321
  const answer = query === "" ? sessionSearch.warm().then(() => ({ sessions: [] })) : sessionSearch.search(query);
378
- void answer.then((value) => {
379
- sendJson(res, 200, { ok: true, ...value });
380
- }, (error) => {
381
- sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
382
- });
322
+ sendPayload(res, answer);
383
323
  }
384
324
  });
385
325
  register("peak rate", {
@@ -387,19 +327,17 @@ function registerRoutes(ctx, scope) {
387
327
  path: PEAKRATE_PATH,
388
328
  handler: (req, res) => {
389
329
  if (methodRefused(req, res, ["GET", "POST"])) return;
390
- if (fenceRefused(req, res)) return;
330
+ if (fenceRefused(ctx, req, res)) return;
391
331
  peakrate.ensure();
392
332
  const answer = req.method === "POST" ? peakrate.refresh().then(() => peakrate.payload()) : Promise.resolve(peakrate.payload());
393
- void answer.then((payload) => {
394
- sendJson(res, 200, { ok: true, ...payload });
395
- }, (error) => {
396
- sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
397
- });
333
+ sendPayload(res, answer);
398
334
  }
399
335
  });
336
+ for (const { label, route } of brandRoutes(ctx)) register(label, route);
400
337
  return () => {
401
338
  usage.dispose();
402
339
  peakrate.dispose();
340
+ sessionSearch.dispose();
403
341
  for (const dispose of disposers) {
404
342
  try {
405
343
  dispose();
@@ -201,7 +201,14 @@ function createSessionSearch(ctx) {
201
201
  hits.sort((a, b) => b.time - a.time);
202
202
  return { sessions: hits.slice(0, SESSION_LIMIT), scanned: cache.size };
203
203
  }
204
- return { search, warm: () => catchUp(true) };
204
+ function dispose() {
205
+ cache.clear();
206
+ lower.clear();
207
+ writtenAt.clear();
208
+ stored = null;
209
+ refreshing = null;
210
+ }
211
+ return { search, warm: () => catchUp(true), dispose };
205
212
  }
206
213
  export {
207
214
  QUERY_MAX,
@@ -1,7 +1,12 @@
1
1
  // packages/contracts/src/prefs.ts
2
2
  var PREFS_DEFAULT = Object.freeze({
3
3
  brand: "claude",
4
+ brandName: "",
5
+ brandInk: "original",
6
+ brandAccent: "",
4
7
  motion: "system",
8
+ statusMotion: "sweep",
9
+ statusDetail: "precise",
5
10
  collapseFooter: true,
6
11
  autoPopover: "all",
7
12
  composerScope: "all",