feat: translate code comments, reaching zero Chinese in the served UI
The last Chinese lived in source comments: notes the plugin authors wrote
about why a module exists and which bug it fixed, plus two stylesheet
comment blocks. Comments never render, but they still shipped Chinese, and
the user asked for all of it gone.
- src/dictionary.comments.json: 274 whole comments (block and line form).
- src/rewrite.ts: rewriteComments replaces a comment only on an exact
whole-comment match, so box-drawing runs and the `*` alignment that give
these notes their structure cannot be reflowed by a partial replacement.
Comment rewriting runs on the source between literals; running it over
the assembled output would also scan literal contents, where a URL like
`https://…` reads as a line comment.
- scripts/merge-comments.mjs emits the dictionary and enforces the
invariants: no CJK, delimiters preserved and balanced, line count
unchanged, box runs preserved in block comments. In a single-line `//`
banner the run is decoration and may shift with the text.
Verified on the bytes a fresh instance actually served, escapes decoded:
total Chinese 55,345 -> 111 (99.80% removed). In the browser, both visible
text and the full page HTML report 0 CJK.
The 111 characters left are inside functional regular expressions that
match Chinese *input*: /失败|错误|无法/ classifies a status message,
dshmarket's PREVIEW_WORDS match Chinese plugin descriptions, and
/^[一-龥]{2}$/ validates a two-character name. Translating those would
break plugin behaviour, so they are deliberately left alone.
This commit is contained in:
parent
e99d4fee26
commit
23327d2bd5
6 changed files with 769 additions and 14 deletions
353
lib/index.js
353
lib/index.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -38,9 +38,9 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"mt": "node scripts/mt-fill.mjs",
|
"mt": "node scripts/mt-fill.mjs",
|
||||||
"dict": "node scripts/merge-dict.mjs",
|
"dict": "node scripts/merge-dict.mjs && node scripts/merge-comments.mjs",
|
||||||
"build": "node scripts/build.mjs",
|
"build": "node scripts/build.mjs",
|
||||||
"build:dict": "node scripts/mt-fill.mjs && node scripts/merge-dict.mjs && node scripts/build.mjs"
|
"build:dict": "node scripts/mt-fill.mjs && node scripts/merge-dict.mjs && node scripts/merge-comments.mjs && node scripts/build.mjs"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@deepseek-ai/cordis": ">=4.0.0"
|
"@deepseek-ai/cordis": ">=4.0.0"
|
||||||
|
|
|
||||||
62
scripts/merge-comments.mjs
Normal file
62
scripts/merge-comments.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Builds src/dictionary.comments.json from the translated comments.
|
||||||
|
//
|
||||||
|
// Two shapes feed this and both become whole delimited comments, which is what
|
||||||
|
// the rewriter matches:
|
||||||
|
// - Comment BODIES (text between `/*` and `*/`) from the stylesheet pass,
|
||||||
|
// re-wrapped in their delimiters.
|
||||||
|
// - Whole comments FROM the code-comment pass, already delimited.
|
||||||
|
//
|
||||||
|
// Inputs: ~/.cache/c2e/cmt-authored.json {wholeComment: translatedComment}
|
||||||
|
// ~/.cache/c2e/css-authored.json {body: translatedBody}
|
||||||
|
// Output: src/dictionary.comments.json {wholeComment: translatedComment}
|
||||||
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||||
|
|
||||||
|
const C2E = "/home/oiupoyt/.cache/c2e";
|
||||||
|
const OUT = "/home/oiupoyt/my stuff yea/projects/chinese2english/src/dictionary.comments.json";
|
||||||
|
|
||||||
|
const HAN = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/;
|
||||||
|
const BOX = /[\u2500-\u257f\u2580-\u259f]/g;
|
||||||
|
const boxOf = (t) => (t.match(BOX) ?? []).sort().join("");
|
||||||
|
const newlines = (t) => (t.match(/\n/g) ?? []).length;
|
||||||
|
|
||||||
|
function load(name) {
|
||||||
|
const path = `${C2E}/${name}`;
|
||||||
|
return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const pairs = new Map();
|
||||||
|
for (const [zh, en] of Object.entries(load("cmt-authored.json"))) pairs.set(zh, en);
|
||||||
|
for (const [zh, en] of Object.entries(load("css-authored.json"))) pairs.set(`/*${zh}*/`, `/*${en}*/`);
|
||||||
|
|
||||||
|
const clean = {};
|
||||||
|
const reasons = { cjk: 0, latin: 0, identity: 0, box: 0, lines: 0, syntax: 0 };
|
||||||
|
|
||||||
|
for (const [zh, en] of pairs) {
|
||||||
|
if (typeof en !== "string" || !en.trim()) continue;
|
||||||
|
if (HAN.test(en)) { reasons.cjk++; continue; }
|
||||||
|
if (!/[A-Za-z]/.test(en)) { reasons.latin++; continue; }
|
||||||
|
if (en === zh) { reasons.identity++; continue; }
|
||||||
|
// Replacing a comment must keep it a well-formed comment, or the emitted
|
||||||
|
// bundle stops parsing.
|
||||||
|
if (zh.includes("/*") !== en.includes("/*")) { reasons.syntax++; continue; }
|
||||||
|
if (zh.includes("*/") !== en.includes("*/")) { reasons.syntax++; continue; }
|
||||||
|
if (zh.startsWith("//") !== en.startsWith("//")) { reasons.syntax++; continue; }
|
||||||
|
// Box-drawing runs are the structure of block comments, where they align the
|
||||||
|
// `*` column. In a single-line `//` banner the run is pure decoration and its
|
||||||
|
// length shifts freely with the translated text.
|
||||||
|
const isLineComment = zh.startsWith("//");
|
||||||
|
if (!isLineComment && boxOf(zh) !== boxOf(en)) { reasons.box++; continue; }
|
||||||
|
if (newlines(zh) !== newlines(en)) { reasons.lines++; continue; }
|
||||||
|
clean[zh] = en;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = Object.fromEntries(
|
||||||
|
Object.entries(clean).sort((a, b) => b[0].length - a[0].length || a[0].localeCompare(b[0])),
|
||||||
|
);
|
||||||
|
writeFileSync(OUT, JSON.stringify(sorted, null, 1) + "\n");
|
||||||
|
const dropped = Object.values(reasons).reduce((a, b) => a + b, 0);
|
||||||
|
console.log(
|
||||||
|
`comments: ${Object.keys(sorted).length} entries (dropped ${dropped}` +
|
||||||
|
Object.entries(reasons).filter(([, v]) => v).map(([k, v]) => `, ${k} ${v}`).join("") +
|
||||||
|
")",
|
||||||
|
);
|
||||||
276
src/dictionary.comments.json
Normal file
276
src/dictionary.comments.json
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
{
|
||||||
|
"/**\n\t\t* One provider's editor card, hand-written per adapter family: the primary\n\t\t* field is a single write-only **API key** input (the page never asks for an\n\t\t* environment-variable name — a typed key stores through `credentials/set`\n\t\t* under the profile's reference, deriving `<ROUTE>_API_KEY` when the profile\n\t\t* has none. The pi-ai profile records that derivation as `apiKeyEnv` only when\n\t\t* a key is entered; a blank key materializes a reference-free profile for\n\t\t* provider-native authentication);\n\t\t* the collapsed 自定义设置 area carries the per-family extras (`baseURL` for\n\t\t* both families, DeepSeek's id/name/context-window model catalog, and the\n\t\t* display name and wire protocol of a pi-ai route the adapter does not ship —\n\t\t* the two fields the create card asked that route for, editable here for the\n\t\t* same reason).\n\t\t* Reasoning effort is deliberately absent: it is a per-MODEL capability, and\n\t\t* the models under one provider disagree about it, so a provider-scoped\n\t\t* control can only be set to a value some of them reject. The composer's\n\t\t* model picker offers each model its own levels; `cordis.patch.yml` keeps the\n\t\t* profile field for a deployment that knows its route. Everything else stays\n\t\t* owned by `cordis.patch.yml`. Profile edits land as minimal `settings.mutate`\n\t\t* path ops against the stored section — the card names only the fields it can\n\t\t* see instead of rebuilding the whole subtree from a partial descriptor.\n\t\t*/": "/**\n\t\t* One provider's editor card, hand-written per adapter family: the primary\n\t\t* field is a single write-only **API key** input (the page never asks for an\n\t\t* environment-variable name -- a typed key stores through `credentials/set`\n\t\t* under the profile's reference, deriving `<ROUTE>_API_KEY` when the profile\n\t\t* has none. The pi-ai profile records that derivation as `apiKeyEnv` only when\n\t\t* a key is entered; a blank key materializes a reference-free profile for\n\t\t* provider-native authentication);\n\t\t* the collapsed Custom settings area carries the per-family extras (`baseURL` for\n\t\t* both families, DeepSeek's id/name/context-window model catalog, and the\n\t\t* display name and wire protocol of a pi-ai route the adapter does not ship --\n\t\t* the two fields the create card asked that route for, editable here for the\n\t\t* same reason).\n\t\t* Reasoning effort is deliberately absent: it is a per-MODEL capability, and\n\t\t* the models under one provider disagree about it, so a provider-scoped\n\t\t* control can only be set to a value some of them reject. The composer's\n\t\t* model picker offers each model its own levels; `cordis.patch.yml` keeps the\n\t\t* profile field for a deployment that knows its route. Everything else stays\n\t\t* owned by `cordis.patch.yml`. Profile edits land as minimal `settings.mutate`\n\t\t* path ops against the stored section -- the card names only the fields it can\n\t\t* see instead of rebuilding the whole subtree from a partial descriptor.\n\t\t*/",
|
||||||
|
"/**\n\t\t* Format one target instant as a localized month-and-day date with its time, the\n\t\t* form the mock's task list shows and the same `Intl` field pair the universal\n\t\t* cards use.\n\t\t*\n\t\t* The month is a locale-owned name, not a zero-padded number: `en` renders\n\t\t* `Dec 31, 9:00 AM` and `zh-CN` renders `12月31日 09:00`, so neither locale can\n\t\t* produce a `12-31` string. The year appears only when the instant falls outside\n\t\t* the current year in the displayed zone, so a same-year target or delivery\n\t\t* stays compact while an older record still dates itself.\n\t\t*\n\t\t* `timeZone` carries the rule's own zone for a daily, weekly, or cron record, so\n\t\t* its occurrence reads in the task's zone. A one-shot `at` or `after` record\n\t\t* stores only the UTC instant, so callers pass no zone and it formats in the\n\t\t* browser zone.\n\t\t* @param scheduledAt - durable UTC target.\n\t\t* @param locale - BCP-47 locale owning the month name, day order, and clock.\n\t\t* @param timeZone - IANA zone of the task's own rule, or undefined for the browser zone.\n\t\t* @returns the localized month, day, and time, with the year when it is not the\n\t\t* current one, or the raw instant when the value cannot be parsed.\n\t\t*/": "/**\n\t\t* Format one target instant as a localized month-and-day date with its time, the\n\t\t* form the mock's task list shows and the same `Intl` field pair the universal\n\t\t* cards use.\n\t\t*\n\t\t* The month is a locale-owned name, not a zero-padded number: `en` renders\n\t\t* `Dec 31, 9:00 AM` and `zh-CN` renders `Dec 31, 09:00`, so neither locale can\n\t\t* produce a `12-31` string. The year appears only when the instant falls outside\n\t\t* the current year in the displayed zone, so a same-year target or delivery\n\t\t* stays compact while an older record still dates itself.\n\t\t*\n\t\t* `timeZone` carries the rule's own zone for a daily, weekly, or cron record, so\n\t\t* its occurrence reads in the task's zone. A one-shot `at` or `after` record\n\t\t* stores only the UTC instant, so callers pass no zone and it formats in the\n\t\t* browser zone.\n\t\t* @param scheduledAt - durable UTC target.\n\t\t* @param locale - BCP-47 locale owning the month name, day order, and clock.\n\t\t* @param timeZone - IANA zone of the task's own rule, or undefined for the browser zone.\n\t\t* @returns the localized month, day, and time, with the year when it is not the\n\t\t* current one, or the raw instant when the value cannot be parsed.\n\t\t*/",
|
||||||
|
"/**\n\t\t* What the browser can see about the market that the server cannot.\n\t\t*\n\t\t* This exists because of a pattern, not a hypothesis. #293 (\"市场显示空白\")\n\t\t* and #384 (\"lightbox cannot be closed\") are both reproducible for their\n\t\t* reporters and for nobody else, on clean profiles, across host versions.\n\t\t* Both investigations then stalled in the same place: I asked the reporter to\n\t\t* open a console and paste the result of an expression. That costs a day per\n\t\t* question, asks a non-developer to run code from a stranger, and is the one\n\t\t* step where a report goes quiet.\n\t\t*\n\t\t* Meanwhile the exported log — which the issue template already asks for, and\n\t\t* which reporters do send — describes only the server: dependencies, bundle\n\t\t* resolution, events. Everything it says was already true of a machine where\n\t\t* the bug does not happen. The interesting half was in a page nobody looked\n\t\t* at.\n\t\t*\n\t\t* So the browser answers the questions I have actually had to ask, in the\n\t\t* artefact people already know how to produce. Nothing here diagnoses\n\t\t* anything on its own; it is evidence, collected before it is needed.\n\t\t*/": "/**\n\t\t* What the browser can see about the market that the server cannot.\n\t\t*\n\t\t* This exists because of a pattern, not a hypothesis. #293 (\"the market shows blank\")\n\t\t* and #384 (\"lightbox cannot be closed\") are both reproducible for their\n\t\t* reporters and for nobody else, on clean profiles, across host versions.\n\t\t* Both investigations then stalled in the same place: I asked the reporter to\n\t\t* open a console and paste the result of an expression. That costs a day per\n\t\t* question, asks a non-developer to run code from a stranger, and is the one\n\t\t* step where a report goes quiet.\n\t\t*\n\t\t* Meanwhile the exported log -- which the issue template already asks for, and\n\t\t* which reporters do send -- describes only the server: dependencies, bundle\n\t\t* resolution, events. Everything it says was already true of a machine where\n\t\t* the bug does not happen. The interesting half was in a page nobody looked\n\t\t* at.\n\t\t*\n\t\t* So the browser answers the questions I have actually had to ask, in the\n\t\t* artefact people already know how to produce. Nothing here diagnoses\n\t\t* anything on its own; it is evidence, collected before it is needed.\n\t\t*/",
|
||||||
|
"/**\n\t\t* Format one target as an absolute time in this device's zone.\n\t\t*\n\t\t* A task whose stored rule names its own zone still shows its next run in the\n\t\t* reader's zone: the instant is the same one, and the reader compares it with\n\t\t* their own clock. The locale owns the month name, the field order, and the\n\t\t* separators, so the stamp reads `Sep 19, 2026, 15:51` in English and\n\t\t* `9月19日 15:51` in Chinese.\n\t\t*\n\t\t* Whether a bare date reads the year is a per-language typographic choice, and\n\t\t* the languages the design pins are stated in {@link YEAR_LANGUAGES} and\n\t\t* {@link NO_YEAR_LANGUAGES}. Every language not listed there states the year:\n\t\t* dropping it silently would hide the year of a target that can sit months or a\n\t\t* year away, which is worse than one field more than the reader needs.\n\t\t* @param scheduledAt - durable UTC target.\n\t\t* @param locale - BCP-47 locale owning the month name, field order, and clock.\n\t\t* @returns the localized absolute next run in the device zone, or the raw instant when it cannot be parsed.\n\t\t*/": "/**\n\t\t* Format one target as an absolute time in this device's zone.\n\t\t*\n\t\t* A task whose stored rule names its own zone still shows its next run in the\n\t\t* reader's zone: the instant is the same one, and the reader compares it with\n\t\t* their own clock. The locale owns the month name, the field order, and the\n\t\t* separators, so the stamp reads `Sep 19, 2026, 15:51` in English and\n\t\t* `Sep 19, 15:51` in Chinese.\n\t\t*\n\t\t* Whether a bare date reads the year is a per-language typographic choice, and\n\t\t* the languages the design pins are stated in {@link YEAR_LANGUAGES} and\n\t\t* {@link NO_YEAR_LANGUAGES}. Every language not listed there states the year:\n\t\t* dropping it silently would hide the year of a target that can sit months or a\n\t\t* year away, which is worse than one field more than the reader needs.\n\t\t* @param scheduledAt - durable UTC target.\n\t\t* @param locale - BCP-47 locale owning the month name, field order, and clock.\n\t\t* @returns the localized absolute next run in the device zone, or the raw instant when it cannot be parsed.\n\t\t*/",
|
||||||
|
"/**\n\t\t* 账号库的「什么时候该重读」判据 + 内容签名(纯函数,无依赖,便于单测)。\n\t\t*\n\t\t* ── 为什么要单独有这么一个模块(2026-09-15 用户反馈)──\n\t\t*\n\t\t* 现象:**新登录一个账号后,账号库列表不会自己更新**,要关掉设置页再打开才看得到。\n\t\t*\n\t\t* 根因:账号库列表**不在状态轮询(`/status`)里**。面板每 2 秒轮询一次,但只读\n\t\t* `/status` 并渲染**当前账号**的信息;列表本身只有 `loadAccounts()` 被显式调用的那几个\n\t\t* 时刻才重读(面板初始化 / 切号 / 移除 / 重命名 / 重新登录 / 添加 / 导入)。\n\t\t*\n\t\t* 而登录捕获**是异步落地的**:\n\t\t* - CDP 那条路(宿主在 utility 进程时的常态)要等用户在浏览器里登录完;\n\t\t* - 能开 Electron 窗口那条路更直接 —— `openLoginWindow()` 是\"开窗即返回\",\n\t\t* 捕获发生在响应之后。\n\t\t* ⇒ 「加完账号」的捕获很可能晚于那次 `loadAccounts()`,列表就停在旧快照上,\n\t\t* 而且**此后不会再更新**(轮询不碰账号库)。\n\t\t*\n\t\t* 最直接的证据是代码里一句注释(client 的「登录新账号」处理里):\n\t\t* 「期间保持轮询更密一点,万一捕获是异步落地的也能及时刷出来」\n\t\t* —— 作者本来就指望轮询兜住这件事,但轮询只读 `/status`,那个兜底**从未生效**。\n\t\t*\n\t\t* 同源的第二个缺口:探活会更新记录(补上账号名、限制状态、失败标记),\n\t\t* 而**账号名是捕获之后由校验拿到的**(见 index.ts 里那段回写),\n\t\t* 所以\"列表里只有一串 `acc_xxxxxxxx`\"这件事也要等下一次刷新才消失。\n\t\t*\n\t\t* 所以修法是:把轮询的兜底做成真的 —— 按下面的节拍重读 `/accounts`,\n\t\t* 并用 `accountsSignature()` 判断内容是否真的变了(没变就不重建 DOM,\n\t\t* 免得每几秒把用户正在点的按钮换掉)。\n\t\t*/": "/**\n\t\t* Criteria for 'when the account store should re-read' + content signature (pure function, no dependencies, easy to unit test).\n\t\t*\n\t\t* ── Why this deserves its own module (2026-09-15 user feedback) ──\n\t\t*\n\t\t* Symptom: **after logging in a new account, the account list does not update on its own**; you must close the settings page and reopen it to see it.\n\t\t*\n\t\t* Root cause: the account list is **not part of the status poll (`/status`)**. The panel polls every 2 seconds, but only reads\n\t\t* `/status` and renders the **current account**; the list itself is re-read only at the few moments when\n\t\t* `loadAccounts()` is called explicitly (panel init / switch account / remove / rename / re-login / add / import).\n\t\t*\n\t\t* And login capture **lands asynchronously**:\n\t\t* - the CDP path (the norm when the host runs in the utility process) waits for the user to finish logging in the browser;\n\t\t* - the path that can open an Electron window is more direct -- `openLoginWindow()` is \"open the window and return\",\n\t\t* the capture happens after the response.\n\t\t* => the capture for \"account added\" most likely lands after that `loadAccounts()`, so the list stays on the old snapshot,\n\t\t* and **never updates after that** (the poll does not touch the account store).\n\t\t*\n\t\t* The most direct evidence is one comment in the code (in the client's \"log in a new account\" handler):\n\t\t* \"keep polling a bit denser meanwhile, so that if the capture lands asynchronously it still shows up promptly\"\n\t\t* -- the author expected the poll to cover this, but the poll only reads `/status`, so that fallback **never took effect**.\n\t\t*\n\t\t* The second gap from the same source: the liveness probe updates records (fills in the account name, limit state, failure marker),\n\t\t* and **the account name is obtained by validation after capture** (see that write-back in index.ts),\n\t\t* so \"the list only shows a string of `acc_xxxxxxxx`\" also disappears only on the next refresh.\n\t\t*\n\t\t* So the fix is: make the poll's fallback real -- re-read `/accounts` at the cadence below,\n\t\t* and use `accountsSignature()` to tell whether the content really changed (if not, do not rebuild the DOM,\n\t\t* so the button the user is clicking is not swapped out every few seconds).\n\t\t*/",
|
||||||
|
"/**\n\t\t* Category chip order: collapsed with an active non-'all' chip that would\n\t\t* otherwise be clipped out of the two-row preview, the active one moves to\n\t\t* the front so it stays visible.\n\t\t*\n\t\t* Reported as \"点了某个分类,标签就跑到前面来了,好奇怪\": the earlier version\n\t\t* moved the active chip to the front unconditionally, so clicking a category\n\t\t* that was ALREADY visible inside the two rows still reshuffled it — and\n\t\t* every chip after it — for no reason, since nothing was at risk of being\n\t\t* hidden. `visibleCount` is how many chips (the 'all' chip included) the\n\t\t* two-row clip fits; a category already within that budget in its natural\n\t\t* position is left exactly where it was.\n\t\t*\n\t\t* `visibleCount === null` (not yet measured, e.g. the very first collapsed\n\t\t* render) keeps the old unconditional behaviour: with no measurement to\n\t\t* check against, guaranteeing visibility is the safe default.\n\t\t*/": "/**\n\t\t* Category chip order: collapsed with an active non-'all' chip that would\n\t\t* otherwise be clipped out of the two-row preview, the active one moves to\n\t\t* the front so it stays visible.\n\t\t*\n\t\t* Reported as \"clicking a category makes the tag jump to the front, how weird\": the earlier version\n\t\t* moved the active chip to the front unconditionally, so clicking a category\n\t\t* that was ALREADY visible inside the two rows still reshuffled it -- and\n\t\t* every chip after it -- for no reason, since nothing was at risk of being\n\t\t* hidden. `visibleCount` is how many chips (the 'all' chip included) the\n\t\t* two-row clip fits; a category already within that budget in its natural\n\t\t* position is left exactly where it was.\n\t\t*\n\t\t* `visibleCount === null` (not yet measured, e.g. the very first collapsed\n\t\t* render) keeps the old unconditional behaviour: with no measurement to\n\t\t* check against, guaranteeing visibility is the safe default.\n\t\t*/",
|
||||||
|
"/**\n\t\t\t* Decision table, enter column. Strong-waits the session's catalog (a\n\t\t\t* warmup failure rejects — never a silent downgrade). Contributions and\n\t\t\t* bare host commands act on the bare token only; leadingInput claims\n\t\t\t* args-tolerant.\n\t\t\t*\n\t\t\t* Envelope policy: an enter submission carrying attachments resolves only\n\t\t\t* through a command declaring attachment acceptance. Every other submitting\n\t\t\t* route — popup, non-accepting claim, bare detached execute — throws the\n\t\t\t* refusal so the machine surfaces one composer notice and the draft and\n\t\t\t* attachments stay in place; nothing executes and nothing is dropped. An\n\t\t\t* action submits nothing and runs regardless.\n\t\t\t*\n\t\t\t* A typed token is resolved through the localized claim tokens, so a line\n\t\t\t* written as `/计划` reaches the `plan` descriptor and executes as `/plan`.\n\t\t\t*/": "/**\n\t\t\t* Decision table, enter column. Strong-waits the session's catalog (a\n\t\t\t* warmup failure rejects -- never a silent downgrade). Contributions and\n\t\t\t* bare host commands act on the bare token only; leadingInput claims\n\t\t\t* args-tolerant.\n\t\t\t*\n\t\t\t* Envelope policy: an enter submission carrying attachments resolves only\n\t\t\t* through a command declaring attachment acceptance. Every other submitting\n\t\t\t* route -- popup, non-accepting claim, bare detached execute -- throws the\n\t\t\t* refusal so the machine surfaces one composer notice and the draft and\n\t\t\t* attachments stay in place; nothing executes and nothing is dropped. An\n\t\t\t* action submits nothing and runs regardless.\n\t\t\t*\n\t\t\t* A typed token is resolved through the localized claim tokens, so a line\n\t\t\t* written as `/plan` reaches the `plan` descriptor and executes as `/plan`.\n\t\t\t*/",
|
||||||
|
"/**\n\t\t* 样式表 —— 全部走宿主的主题令牌(`--dsw-alias-*`)。\n\t\t*\n\t\t* 历史坑(2026-09-12 用户实测):以前用的是 `var(--theme-text, #ddd)` 这套**并不存在**的变量名,\n\t\t* 于是每个颜色都落到兜底值上;兜底又全是深色 → 浅色主题下整个面板仍是黑底,\n\t\t* 灰色文字贴在深底上几乎读不出来。\n\t\t*\n\t\t* 现在的做法:\n\t\t* 1. 颜色优先取宿主令牌(`--dsw-alias-label-primary` / `bg-layer-*` / `border-l*` / `state-*` …),\n\t\t* 这些令牌由 DSH 按当前主题(浅 / 深 / 皮肤)重定义,插件无需自己判断主题;\n\t\t* 2. 令牌缺失时(老宿主 / 令牌改名)退回 `--fb-*`,而 `--fb-*` 由 `prefers-color-scheme` 切换,\n\t\t* 保证「浅色主题不会出现黑界面」这条底线永远成立;\n\t\t* 3. 正文改用系统无衬线字体(原来是等宽字体铺满全屏,观感像终端日志),等宽只留给命令与代码。\n\t\t*/": "/**\n\t\t* Stylesheet -- everything goes through the host's theme tokens (`--dsw-alias-*`).\n\t\t*\n\t\t* Historical trap (2026-09-12, measured on a real machine by the user): we used to use the `var(--theme-text, #ddd)` set of variable names, which **do not exist**,\n\t\t* so every colour landed on its fallback value; the fallbacks were all dark, so under a light theme the whole panel still had a black background and\n\t\t* grey text on a dark background was nearly unreadable.\n\t\t*\n\t\t* What we do now:\n\t\t* 1. colours prefer host tokens (`--dsw-alias-label-primary` / `bg-layer-*` / `border-l*` / `state-*` ...),\n\t\t* DSH redefines these tokens for the current theme (light / dark / skin), so the plugin does not judge the theme itself;\n\t\t* 2. when a token is missing (old host / renamed token) fall back to `--fb-*`, and `--fb-*` is switched by `prefers-color-scheme`,\n\t\t* guaranteeing that the floor \"a light theme never gets a black UI\" always holds;\n\t\t* 3. body text now uses the system sans-serif font (it used to be monospace filling the whole screen, reading like a terminal log); monospace is kept only for commands and code.\n\t\t*/",
|
||||||
|
"/** While the sticky header is pinned, expansion is this flag — not\n\t\t\t* `catsOpen`. Becoming stuck collapses on the SAME render (stuckExpanded\n\t\t\t* starts false) instead of a follow-up `useLayoutEffect` that flipped\n\t\t\t* `catsOpen` and forced a second commit; that delayed height change is\n\t\t\t* what lined up with the host Settings dialog hitching after tab 收放.\n\t\t\t* An explicit chevron click while stuck sets this true and keeps\n\t\t\t* `catsOpen` in sync so unstuck restores the user's choice. */": "/** While the sticky header is pinned, expansion is this flag -- not\n\t\t\t* `catsOpen`. Becoming stuck collapses on the SAME render (stuckExpanded\n\t\t\t* starts false) instead of a follow-up `useLayoutEffect` that flipped\n\t\t\t* `catsOpen` and forced a second commit; that delayed height change is\n\t\t\t* what lined up with the host Settings dialog hitching after the tab collapse/expand.\n\t\t\t* An explicit chevron click while stuck sets this true and keeps\n\t\t\t* `catsOpen` in sync so unstuck restores the user's choice. */",
|
||||||
|
"/**\n\t\t* 账号库响应的内容签名 —— 变了才重建列表。\n\t\t*\n\t\t* 只取**界面真正渲染的东西**(账号数组 + 分组定义 + 分组分区 + 当前账号 id),不取 `footprint`:\n\t\t* 后者是给台账页用的文件统计,跟列表无关,把它算进来只会平白触发重建。\n\t\t*\n\t\t* ⚠️ `groups` / `sections` 必须是**顶层字段显式列进来**:整包 stringify 只覆盖这里写出的键,\n\t\t* 漏一个就会出现\"新建了组、列表却不刷新\"—— 与 0.1.63(读错字段名)和 0.1.67(数据压根不在\n\t\t* `/status` 里)是同一类坑:**界面渲染什么,签名就得覆盖什么。**\n\t\t*\n\t\t* 整包 `JSON.stringify` 而不是逐个字段挑:**按构造就是完整的** ——\n\t\t* 将来 `/accounts` 多返回一个会被渲染的字段,这里不用改也不会漏。\n\t\t* (代价:若哪天往 accounts 里塞了每调用一次都不同的值,会变成每轮都重建;\n\t\t* 真出现那种字段,应该在这里显式排除。)\n\t\t*/": "/**\n\t\t* Content signature of the account store response -- rebuild the list only when it changes.\n\t\t*\n\t\t* Only **what the UI actually renders** (account array + group definitions + group partitions + current account id), not `footprint`:\n\t\t* the latter is a file statistic for the ledger page, unrelated to the list, and counting it in only triggers rebuilds for nothing.\n\t\t*\n\t\t* ⚠️ `groups` / `sections` must be **listed explicitly as top-level fields**: a whole-payload stringify only covers the keys written out here,\n\t\t* miss one and you get \"created a group, but the list does not refresh\" -- the same class of trap as 0.1.63 (read the wrong field name) and 0.1.67 (the data is not in\n\t\t* `/status` at all): **whatever the UI renders, the signature must cover.**\n\t\t*\n\t\t* A whole-payload `JSON.stringify` instead of picking fields one by one: **complete by construction** --\n\t\t* if `/accounts` later returns one more rendered field, nothing here needs changing and nothing is missed.\n\t\t* (Cost: if some day a value that differs on every call is put into accounts, it becomes a rebuild every round;\n\t\t*\n\t\t*/",
|
||||||
|
"/**\n\t\t\t* A fresh install (hotUrls/hotNames) and a toggle/group action\n\t\t\t* (refreshNames) both end in the same place — \"reload the page\" — and\n\t\t\t* used to render as two near-identical banners stacked on top of each\n\t\t\t* other when both happened in one session (reported as \"为啥有三个状态横幅\n\t\t\t* 啊,太奇怪了\"). They're merged into one count and one banner; only the\n\t\t\t* restart banner (a full host restart, a different action entirely) stays\n\t\t\t* separate.\n\t\t\t*/": "/**\n\t\t\t* A fresh install (hotUrls/hotNames) and a toggle/group action\n\t\t\t* (refreshNames) both end in the same place -- \"reload the page\" -- and\n\t\t\t* used to render as two near-identical banners stacked on top of each\n\t\t\t* other when both happened in one session (reported as \"why are there three status banners\").\n\t\t\t* They're merged into one count and one banner; only the\n\t\t\t* restart banner (a full host restart, a different action entirely) stays\n\t\t\t* separate.\n\t\t\t*/",
|
||||||
|
"/**\n* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`\n* that opens a two-row menu (standard/fast with descriptions, check mark on\n* the current tier). The host pushes nothing on a model switch, so the\n* control re-reads on a slow poll with a single-flight guard; a failed read\n* keeps the last known state, so a transient RPC failure can never lock the\n* toggle away (the earlier mount-only load had no recovery path).\n*/": "/**\n* The composer Speed control: a trigger reading `Speed · Fast`/`Speed · Standard`\n* that opens a two-row menu (standard/fast with descriptions, check mark on\n* the current tier). The host pushes nothing on a model switch, so the\n* control re-reads on a slow poll with a single-flight guard; a failed read\n* keeps the last known state, so a transient RPC failure can never lock the\n* toggle away (the earlier mount-only load had no recovery path).\n*/",
|
||||||
|
"/**\n * 省 Token: the three savers and what they have actually saved.\n *\n * Every control posts and then renders the host's own answer; nothing here\n * shows a switch state the settings file does not have. The dashboard reads\n * the measurement the host keeps, and says so explicitly when there is none —\n * an empty meter is not a claim that nothing was saved, it is the absence of a\n * measurement.\n */": "/**\n * Token saving: the three savers and what they have actually saved.\n *\n * Every control posts and then renders the host's own answer; nothing here\n * shows a switch state the settings file does not have. The dashboard reads\n * the measurement the host keeps, and says so explicitly when there is none --\n * an empty meter is not a claim that nothing was saved, it is the absence of a\n * measurement.\n */",
|
||||||
|
"/* ══ 三页外壳:极光底 · 毛玻璃导航 · 页过渡 ═════════════════════════════\n The aurora is a fixed layer *inside* our root: a flat shell background gives\n backdrop-filter nothing to sample, so every glass surface would read as a\n plain translucent box. It sits at z-index:-1 with isolation:isolate on the\n shell, which keeps it behind our content and above the app background without\n ever escaping into the shell's own stacking order. */": "/* ══ Three-page shell: aurora base · frosted-glass nav · page transition ═════════════════════════════\n The aurora is a fixed layer *inside* our root: a flat shell background gives\n backdrop-filter nothing to sample, so every glass surface would read as a\n plain translucent box. It sits at z-index:-1 with isolation:isolate on the\n shell, which keeps it behind our content and above the app background without\n ever escaping into the shell's own stacking order. */",
|
||||||
|
"/**\n * 聚合渠道: the registry, the configured providers and their editors.\n *\n * The catalog is read from the summary the shell already holds, so the\n * \"contributes N models\" line is the picker's own view rather than a second\n * derivation — and every write asks the shell to re-read it, because saving a\n * provider changes what the model picker offers.\n */": "/**\n * Aggregated channels: the registry, the configured providers and their editors.\n *\n * The catalog is read from the summary the shell already holds, so the\n * \"contributes N models\" line is the picker's own view rather than a second\n * derivation -- and every write asks the shell to re-read it, because saving a\n * provider changes what the model picker offers.\n */",
|
||||||
|
"/**\n * 路由: the stored fallback chains and their editor.\n *\n * The resolution shown per chain is derived from the model list this page\n * already holds — the same intersection the host uses to build the virtual\n * catalog row — so it survives a reload, and the `usable` array a save answers\n * with is preferred when it is there.\n */": "/**\n * Routing: the stored fallback chains and their editor.\n *\n * The resolution shown per chain is derived from the model list this page\n * already holds -- the same intersection the host uses to build the virtual\n * catalog row -- so it survives a reload, and the `usable` array a save answers\n * with is preferred when it is there.\n */",
|
||||||
|
"/* ═══════════════════════════════════════════════════════════════════════\n Our Free Model — visual system\n 统一令牌:所有色值取自宿主 --dsw-alias-* 变量,昼夜由宿主切换。\n 层级约定:layer-3 凹槽/页面底 · layer-2 面板 · layer-1 浮起元素。\n 动效约定:transform/opacity 优先;常规 150–300ms、覆盖层 300–400ms。\n ═══════════════════════════════════════════════════════════════════════ */": "/* ═══════════════════════════════════════════════════════════════════════\n Our Free Model - visual system\n Unified tokens: all color values come from host --dsw-alias-* variables; light/dark is switched by the host.\n Layer convention: layer-3 recess/page base, layer-2 panel, layer-1 raised element.\n Motion: prefer transform/opacity; 150-300ms standard, 300-400ms for overlays.\n ═══════════════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ── 设置项 + 卡片盒(0.8.0):对齐 Our Free Model 的设置页 ────────────────\n 改造前是横排的「label · 控件 · value」:一行里三样东西抢宽度,\n 一句说明塞不下,只能全堆到卡片尾的 dsw-hint,读的人得来回扫。\n 参考页(表单型设置页)的做法是**一项占一行**:左边标题带一句副说明、右边控件,\n 项与项之间一条细线。好处是**每一项自解释**,不必回页尾找补充说明。\n ⚠️ 两种布局各有适用面:数据型内容(账号行、统计)仍走原来的 dsw-kv /\n dsw-gate-row,它们信息密度高、没有\"每个字段配一句说明\"的必要。 */": "/* ── Settings item + card box (0.8.0): aligned with the Our Free Model settings page ────────────────\n It used to be a horizontal \"label · control · value\": three things fighting for width in one row,\n no room for a sentence of description, so everything piled into the dsw-hint at the card end and readers had to scan back and forth.\n The reference page (a form-style settings page) does **one item per row**: title plus a sub-description on the left, control on the right,\n a thin line between items. The win is that **each item explains itself**, with no trip to the page end for supplemental notes.\n ⚠️ Each layout has its scope: data-style content (account rows, stats) still uses the original dsw-kv /\n dsw-gate-row, since it is dense and has no need for \"one description per field\". */",
|
||||||
|
"/**\n\t\t* A single pass that inserts a separator after the character on the left of\n\t\t* every Han ↔ Latin/number boundary. Punctuation is normalized separately\n\t\t* below. Keeping this generic lets `MCP管理`, `管理MCP`, and `OAuth2授权`\n\t\t* behave like their space-separated forms without product-specific aliases.\n\t\t*/": "/**\n\t\t* A single pass that inserts a separator after the character on the left of\n\t\t* every Han ↔ Latin/number boundary. Punctuation is normalized separately\n\t\t* below. Keeping this generic lets `MCP` + a Han word, the reverse, and\n\t\t* `OAuth2` + a Han word act like their space-separated forms, no aliases needed.\n\t\t*/",
|
||||||
|
"/**\n* The IP 池 page on the Plugins page (`plugins.item`).\n*\n* The Plugins page owns the card chrome and the save control: for\n* `view: 'summary'` this returns the one-liner the page shows under the entry\n* title, and for `view: 'page'` it returns the form bound to the entry's\n* served settings.\n*/": "/**\n* The IP pool page on the Plugins page (`plugins.item`).\n*\n* The Plugins page owns the card chrome and the save control: for\n* `view: 'summary'` this returns the one-liner the page shows under the entry\n* title, and for `view: 'page'` it returns the form bound to the entry's\n* served settings.\n*/",
|
||||||
|
"/**\n\t\t\t\t* 后台同步账号库(静默;只在内容变了时重建列表)。\n\t\t\t\t*\n\t\t\t\t* 为什么必须有它:账号库列表**不在 `/status` 里**,登录捕获又是异步落地的\n\t\t\t\t* (CDP 那条路要等用户在浏览器里登录完;能开 Electron 窗口那条路更是\"开窗即返回\")\n\t\t\t\t* ⇒ 只靠上面几个显式调用,捕获晚一步就永远不显示,得关掉设置页再打开。\n\t\t\t\t*\n\t\t\t\t* 后台同步失败**不写进 accountsMsg** —— 那是给用户操作反馈用的,\n\t\t\t\t* 被一个后台轮询的偶发失败覆盖掉会更让人困惑(下一次成功就自然好了)。\n\t\t\t\t*/": "/**\n\t\t\t\t* Synchronise the account store in the background (silently; rebuild the list only when the content changed).\n\t\t\t\t*\n\t\t\t\t* Why it is required: the account list is **not in `/status`**, and login capture lands asynchronously\n\t\t\t\t* (the CDP path waits for the user to finish logging in the browser; the path that can open an Electron window is even more \"open the window and return\")\n\t\t\t\t* => with only the explicit calls above, a capture one step late never shows at all, and you must close the settings page and reopen it.\n\t\t\t\t*\n\t\t\t\t* A background sync failure is **not written into accountsMsg** -- that is for user-facing operation feedback, and\n\t\t\t\t* overwriting it with an occasional background poll failure is more confusing (the next success just fixes it).\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t* 一句话说清 cookie 的寿命构成(界面直接展示)。\n\t\t*\n\t\t* ⚠️ **刻意不报\"还剩多少天\"**(2026-10-01 由用户反馈删掉):那个数字来自持久级 cookie\n\t\t* (实测 `thumbcache_*` 这类还剩 399 天),而**真正鉴权的是 token,寿命只有几小时** ——\n\t\t* 两个数量级放在一起只会让人读成\"这号还能用 399 天\",然后发现几小时就得重登,反而更困惑。\n\t\t* 现在只报构成(共几项 / 会话级几个 / 持久级几个),需要更细的信息去看 `/status` 原始字段。\n\t\t*/": "/**\n\t\t* State the cookie lifetime breakdown in one line (shown directly in the UI).\n\t\t*\n\t\t* ⚠️ **deliberately does not report \"how many days are left\"** (removed on 2026-10-01 from user feedback): that number comes from a persistent-level cookie\n\t\t* (measured on a real machine, `thumbcache_*` and the like still have 399 days left), while **what actually authenticates is the token, whose lifetime is only a few hours** --\n\t\t* putting two orders of magnitude together only makes people read \"this account is good for another 399 days\", and then they find they must re-login within hours, which is more confusing.\n\t\t* Now it reports the breakdown only (how many items total / how many session-level / how many persistent-level); for finer detail look at the raw `/status` fields.\n\t\t*/",
|
||||||
|
"/**\n* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`\n* that opens a two-row menu (standard/fast with descriptions, check mark on\n* the current tier). Mount and every open reload the host state so a model\n* switch made since the last open self-corrects.\n*/": "/**\n* The composer Speed control: a trigger reading `Speed · Fast`/`Speed · Standard`\n* that opens a two-row menu (standard/fast with descriptions, check mark on\n* the current tier). Mount and every open reload the host state so a model\n* switch made since the last open self-corrects.\n*/",
|
||||||
|
"/**\n\t\t\t\t* 一句话反馈节点:给它的 `textContent` 赋值时**自动选样式** ——\n\t\t\t\t* 命中「失败/错误/无法」→ 红框红字(醒目),以「已…」开头 → 绿框,其余 → 灰色小字。\n\t\t\t\t*\n\t\t\t\t* 为什么自动判定、而不是让每个调用点传 kind(2026-09-15 用户反馈):\n\t\t\t\t* 「账号切换失败」原来和旁边的说明文字长得一模一样(都是灰色小字),根本注意不到。\n\t\t\t\t* 这三个节点加起来 40 多个赋值点,靠人手传 kind 迟早会漏 —— 而漏掉的那个往往正是报错。\n\t\t\t\t*/": "/**\n\t\t\t\t* One-line feedback node: assigning to its `textContent` **picks the style automatically** --\n\t\t\t\t* matches \"failed/error/unable\" -> red border, red text (conspicuous), starts with \"already...\" -> green border, otherwise -> small grey text.\n\t\t\t\t*\n\t\t\t\t* Why auto-detect instead of having every call site pass a kind (2026-09-15 user feedback):\n\t\t\t\t* \"account switch failed\" used to look exactly like the explanatory text beside it (both small grey text), so it could not be noticed at all.\n\t\t\t\t* These three nodes have 40-odd assignment sites in total; passing kind by hand would miss one sooner or later -- and the one missed is usually the error.\n\t\t\t\t*/",
|
||||||
|
"/* ── 推荐项目页:两张「去点 Star」的卡片 ─────────────────────────────────\n 这一页只有一件事要促成——点开仓库、点下 Star。所以卡片比常规模型卡大一档,\n 入场逐张错峰(--i 由调用方给),悬停抬升;主 CTA 是渐变星按钮,带一圈呼吸\n 光环与悬停扫光;数字用 count-up 落地。全部只动 transform/opacity/颜色。\n 动效降级:文件末尾的 reduced-motion 块统一关掉位移与动画。 */": "/* ── Recommended projects page: the two \"go star it\" cards ─────────────────────────────────\n This page has exactly one thing to drive -- open the repo, click Star. So the cards sit one size up from a standard model card,\n enter staggered one by one (--i is supplied by the caller), and lift on hover; the primary CTA is a gradient star button with a breathing\n halo and a hover sheen; numbers land via count-up. Everything only touches transform/opacity/color.\n Motion fallback: the reduced-motion block at the end of the file disables displacement and animation wholesale. */",
|
||||||
|
"/**\n\t\t* Pick one language from a `中文 / English` (or reverse) pair. Ambiguous\n\t\t* strings stay unchanged. Callers that prepend `t(…)` must localize the\n\t\t* server half first, then concatenate — this function does not strip UI chrome.\n\t\t*/": "/**\n\t\t* Pick one language from a `Chinese / English` (or reverse) pair. Ambiguous\n\t\t* strings stay unchanged. Callers that prepend `t(…)` must localize the\n\t\t* server half first, then concatenate -- this function does not strip UI chrome.\n\t\t*/",
|
||||||
|
"/**\n\t\t* dsh-deepseek-web-login — client 设置页(slots: settings.section)。\n\t\t*\n\t\t* 与 host 通过同源 fetch 通信:/deepseek-web-login/api/*\n\t\t* 渲染契约(沿用生态实测结论):槽位 register 必须带 name 字段,\n\t\t* 渲染函数返回 React 元素(createElement),React #130 的坑即来自返回非元素。\n\t\t*/": "/**\n\t\t* dsh-deepseek-web-login -- client settings page (slots: settings.section).\n\t\t*\n\t\t* Talks to the host over same-origin fetch: /deepseek-web-login/api/*\n\t\t* Rendering contract (carried over from measured ecosystem findings): the slot register must carry a name field,\n\t\t* and the render function returns a React element (createElement); the React #130 trap comes from returning a non-element.\n\t\t*/",
|
||||||
|
"/* 分组不再用\"七个一模一样的描边盒子\"。\n 盒子本身不表达任何层级,连堆七张就是 AI 默认长相 —— 按 redesign 技能的判据,\n card 只在 elevation 真的表达层级时才用(我们这里没有那种层级)。\n 数据密集面板的正解是「分区标题 + 负空间」:标题吃字重,分组吃间距,\n 行与行之间的细线交给 .dsw-kv 自己承担。这样页面上唯一有实底的块\n 就只剩账号行和反馈条,它们的重要度反而被衬出来了。 */": "/* Groups no longer use \"seven identical outlined boxes\".\n The box itself expresses no hierarchy; stacking seven of them is the default AI look -- per the redesign skill criteria,\n card is only used when elevation genuinely expresses hierarchy (we have no such hierarchy here).\n The right answer for a data-dense panel is \"section heading + negative space\": headings carry weight, groups carry spacing,\n and the thin line between rows is left to .dsw-kv itself. That way the only blocks on the page with a solid fill\n are the account rows and the feedback bar, which makes their importance stand out. */",
|
||||||
|
"/**\n* Probe-model multi-select over the bridge /models rows: selected models as\n* removable chips above a dropdown of S3-verified + live-catalog options\n* (docs §5.2 探活模型). Empty selection = the S3-first default (big-pickle).\n*/": "/**\n* Probe-model multi-select over the bridge /models rows: selected models as\n* removable chips above a dropdown of S3-verified + live-catalog options\n* (docs §5.2 liveness-probe models). Empty selection = the S3-first default (big-pickle).\n*/",
|
||||||
|
"/**\n\t\t* 弹系统「打开」框选一个 JSON。返回 undefined 表示用户取消。\n\t\t*\n\t\t* 用 `<input type=\"file\">` 而不是 `showOpenFilePicker`:前者在**任何** Chromium\n\t\t* 环境里都可用(包括跨域 iframe,Chromium 150 起第三方 iframe 是被禁止弹\n\t\t* File System Access 文件框的),是我们最可靠的\"让人选文件\"手段。\n\t\t*/": "/**\n\t\t* Open the system Open dialog to pick a JSON. Returning undefined means the user cancelled.\n\t\t*\n\t\t* Using `<input type=\"file\">` instead of `showOpenFilePicker`: the former works in **any** Chromium\n\t\t* environment (including a cross-origin iframe; from Chromium 150 on, a third-party iframe is forbidden from opening\n\t\t* File System Access file dialogs), so it is our most reliable way to \"let someone pick a file\".\n\t\t*/",
|
||||||
|
"/* ── 尺度层 ──────────────────────────────────────────────────────────\n 改造前的问题:fontSize 有 5 档(11/12/12.5/13/14)、圆角 6 档(6/8/10/12/999)、\n 间距散落 7 种值(2/4/6/8/9/10/12)。同一个界面里数字一多,眼睛就觉得\"乱\"却\n 说不出哪乱 —— 所以先把尺度收敛,下面所有组件只能引用这里的变量。 */": "/* ── Scale layer ──────────────────────────────────────────────────────────\n Problems before the redesign: fontSize had 5 steps (11/12/12.5/13/14), corner radius had 6 steps (6/8/10/12/999),\n spacing was scattered across 7 values (2/4/6/8/9/10/12). Once one screen carries too many numbers the eye reads \"messy\" but\n cannot say where -- so converge the scale first; every component below may only reference the variables defined here. */",
|
||||||
|
"/**\n\t\t\t\t* 把结果写进**某一行的行内提示条**(按账号 id 找回节点 —— 列表重建过也能写回新的那条)。\n\t\t\t\t*\n\t\t\t\t* 为什么要有它(2026-09-30 用户反馈「点切换没反应,报错在下面显示,一点也不明显」):\n\t\t\t\t* 切换的反馈原先只写卡片底部那一条,而用户的视线还停在他点的那颗按钮上 ——\n\t\t\t\t* 列表一长,那条消息在屏幕外,体感就是\"点了没反应\"。所以结果必须在**那一行**里出现。\n\t\t\t\t*/": "/**\n\t\t\t\t* Write the result into **that row's inline hint bar** (find the node by account id -- so it can be written back into the new row after the list was rebuilt).\n\t\t\t\t*\n\t\t\t\t* Why it exists (2026-09-30 user feedback \"clicked switch, nothing happened, the error shows down below, not obvious at all\"):\n\t\t\t\t* the switch feedback used to be written only into the single bar at the bottom of the card, while the user's eyes were still on the button they clicked --\n\t\t\t\t* once the list is long that message is off screen, and the felt experience is \"clicked and nothing happened\". So the result must appear **in that row**.\n\t\t\t\t*/",
|
||||||
|
"/* ── 聚合渠道 / 省 Token / 路由:新页面的表面 ─────────────────────────────\n 动效一律走 150–250ms 的短过渡,不弹跳;reduced-motion 下由文件末尾的\n 媒体查询整体关掉。ofm_rise 用 backwards 而不是 both:动画结束后元素回到\n 样式表里的 transform,卡片的 hover 抬升才不会被动画的终态钉死。 */": "/* ── Aggregate channels / save Token / routing: the surface of the new pages ─────────────────────────────\n All motion goes through short 150-250ms transitions, no bounce; under reduced-motion the\n media query at the end of the file turns it off wholesale. ofm_rise uses backwards rather than both: once the animation ends the element returns\n to the transform in the stylesheet, so the card hover lift is not pinned by the animation end state. */",
|
||||||
|
"/**\n\t\t* Marker the localized occupancy sentence is split on, so the panel headline\n\t\t* keeps the reading in its own tone while each locale still owns the word\n\t\t* order (`45% of context used` / `上下文已用 45%`).\n\t\t*/": "/**\n\t\t* Marker the localized occupancy sentence is split on, so the panel headline\n\t\t* keeps the reading in its own tone while each locale still owns the word\n\t\t* order (`45% of context used` / `context used 45%`).\n\t\t*/",
|
||||||
|
"/**\n\t\t* 弹系统「另存为」,并把 `produce()` 产出的文本写进用户选定的位置。\n\t\t*\n\t\t* ⚠️ 为什么收一个回调而不是直接收文本:**必须先弹框、再取内容**。\n\t\t* Chromium 的瞬时用户激活是有时限的,若调用方先 `await` 一次网络请求\n\t\t* (取备份内容)再弹框,很可能被判成\"没有用户手势\"而直接抛错。\n\t\t* 把取数包成回调,顺序就锁在这个函数里,调用方想写错都难。\n\t\t*/": "/**\n\t\t* Open the system Save As dialog and write the text produced by `produce()` into the location the user picked.\n\t\t*\n\t\t* ⚠️ Why it takes a callback instead of the text directly: **the dialog must open first, then the content is fetched**.\n\t\t* Chromium's transient user activation is time-limited, so if the caller `await`s a network request\n\t\t* (fetching the backup content) before opening the dialog, it is likely judged as \"no user gesture\" and throws outright.\n\t\t* Wrapping the fetch in a callback locks the order inside this function; the caller can hardly get it wrong.\n\t\t*/",
|
||||||
|
"/**\n* One account's handle and plan: `★ ys@example.com · 计划:pro`. The star\n* marks the default account (the one direct routes serve and the collapsed\n* pill reads), the same glyph Settings → 订阅 uses.\n*/": "/**\n* One account's handle and plan: `★ ys@example.com · plan: pro`. The star\n* marks the default account (the one direct routes serve and the collapsed\n* pill reads), the same glyph Settings → Subscription uses.\n*/",
|
||||||
|
"/**\n\t\t\t\t* 「重新登录这个账号」:给凭证失效(探活失败)的账号用。\n\t\t\t\t*\n\t\t\t\t* 与「登录新账号」的唯一区别是**不清浏览器登录态**:后者要先清干净才能加到别的号,\n\t\t\t\t* 而修同一个号正相反 —— 留着才可能一打开就复用上。落库仍走添加模式(只入库、不切换),\n\t\t\t\t* 所以修好它也不会顶掉你正在用的账号;若它本来就是当前账号,那正是你要的效果。\n\t\t\t\t*/": "/**\n\t\t\t\t* \"Re-login this account\": for accounts with expired credentials (liveness probe failed).\n\t\t\t\t*\n\t\t\t\t* The only difference from \"log in a new account\" is that it **does not clear the browser session**: the latter must be wiped clean before adding another account,\n\t\t\t\t* while repairing the same account is the opposite -- keeping it is what may let it be reused the moment it opens. It still lands in the store in add mode (store only, no switch),\n\t\t\t\t* so repairing it does not displace the account you are using; and if it already is the current account, that is exactly the effect you want.\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t\t\t* 渲染账号库,但**内容没变就不重建 DOM**。\n\t\t\t\t*\n\t\t\t\t* 为什么要有这道守卫:轮询会按秒级节拍重读 `/accounts`(见下面的 syncAccounts),\n\t\t\t\t* 无脑重建会把用户正在悬停/正要点的按钮换掉(重命名输入框更糟 —— 直接失焦)。\n\t\t\t\t* 签名取的是界面真正渲染的东西,见 account-sync.ts。\n\t\t\t\t*/": "/**\n\t\t\t\t* Render the account store, but **do not rebuild the DOM when the content is unchanged**.\n\t\t\t\t*\n\t\t\t\t* Why this guard: the poll re-reads `/accounts` on a second-level cadence (see syncAccounts below),\n\t\t\t\t* and a blind rebuild would swap out the button the user is hovering / about to click (a rename input is worse -- it loses focus outright).\n\t\t\t\t* The signature covers what the UI actually renders, see account-sync.ts.\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t* Pick the locale half of a server bilingual string (`中文 / English` or\n\t\t* `English / 中文`). Multiline input is handled line by line. Ambiguous\n\t\t* strings are returned unchanged.\n\t\t*/": "/**\n\t\t* Pick the locale half of a server bilingual string (`Chinese / English` or\n\t\t* `English / Chinese`). Multiline input is handled line by line. Ambiguous\n\t\t* strings are returned unchanged.\n\t\t*/",
|
||||||
|
"/**\n\t\t\t\t* 分组动作:**只有显示方式会变,账号与凭证一律不动。**\n\t\t\t\t*\n\t\t\t\t* 每次操作后都 `loadAccounts()` 重读,而不是就地改 DOM:\n\t\t\t\t* 宿主的 `/accounts` 是唯一事实来源;重读既能拿到组的新状态,也会顺带走一遍内容签名 ——\n\t\t\t\t* 签名没变就不重建,正在输入的框不会被打断。\n\t\t\t\t*/": "/**\n\t\t\t\t* Group actions: **only the display changes; accounts and credentials are never touched.**\n\t\t\t\t*\n\t\t\t\t* Every action is followed by a `loadAccounts()` re-read rather than editing the DOM in place:\n\t\t\t\t* the host's `/accounts` is the single source of truth; re-reading both gets the new group state and runs the content signature --\n\t\t\t\t* if the signature is unchanged nothing is rebuilt, so a field being typed into is not interrupted.\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t* 把界面选中的 File 还原成**真实磁盘路径**。\n\t\t*\n\t\t* 这一步的价值:拿到路径后,宿主可以**自己去读那个文件**,\n\t\t* 于是导入的凭证明文根本不经过 HTTP 请求体 —— 只是把\"文件在哪\"告诉宿主。\n\t\t* 桥不可用(宿主没注入 / 非磁盘来源的 File)就返回 undefined,调用方改用内容导入。\n\t\t*/": "/**\n\t\t* Turn the selected File back into a **real disk path**.\n\t\t*\n\t\t* The value of this step: with the path, the host can **read that file itself**,\n\t\t* so the imported credential plaintext never goes through an HTTP request body -- we only tell the host \"where the file is\".\n\t\t* If the bridge is unavailable (not injected by the host / a File not from disk) return undefined, and the caller falls back to content import.\n\t\t*/",
|
||||||
|
"/* ── Token 统计页 ────────────────────────────────────────────────────\n 图全是手写 SVG:插件不引第三方图表库(多一个依赖就多一份与宿主版本的耦合)。\n 高度写 auto 而不是固定值 —— viewBox 自带比例,固定高度会把图拉变形或留白。 */": "/* ── Token stats page ────────────────────────────────────────────────────\n Every chart is hand-written SVG: the plugin pulls in no third-party chart library (one more dependency is one more coupling to the host version).\n Height is written as auto rather than a fixed value -- the viewBox carries the ratio, and a fixed height would stretch the chart or leave padding. */",
|
||||||
|
"/**\n\t\t* 备份文件建议名。\n\t\t*\n\t\t* 刻意只用 **数字 / 字母 / 短横线 / 点**:这个字符串会进系统另存为框的\n\t\t* `suggestedName`,冒号、斜杠在 Windows 上是非法文件名字符(`toISOString()` 恰好\n\t\t* 带冒号,直接拿来当文件名会踩坑)。\n\t\t*/": "/**\n\t\t* Suggested backup filename.\n\t\t*\n\t\t* Deliberately only **digits / letters / hyphens / dots**: this string goes into the system Save As dialog's\n\t\t* `suggestedName`, and colons and slashes are illegal filename characters on Windows (`toISOString()` happens to\n\t\t* carry a colon, so using it directly as a filename is a trap).\n\t\t*/",
|
||||||
|
"/**\n* The `settings.ip-pool` locale dictionaries for the IP 池 card (docs/ip-pool.md\n* §5.2 block table). Keys track exactly the UI-surfaced strings.\n*/": "/**\n* The `settings.ip-pool` locale dictionaries for the IP pool card (docs/ip-pool.md\n* §5.2 block table). Keys track exactly the UI-surfaced strings.\n*/",
|
||||||
|
"/* ── 标签页:把原来一页到底的 7 张卡拆成 4 页 ────────────────────────\n 标签栏做成带边框的圆角容器。未选中态改用**颜色**表达(原来是 opacity .7 —— \n 透明度既压暗了文字又让 hover 没有明确的落点),当前页用下划线。 */": "/* ── Tabs: split the original 7-card single page into 4 pages ────────────────────────\n The tab bar becomes a bordered container. The unselected state now uses **color** (it was opacity .7 -- \n alpha both dimmed the text and left hover with no clear target), and the current page uses an underline. */",
|
||||||
|
"/**\n\t\t\t\t* 一个组的标题行:折叠箭头 + 组名 + 数量 + (真实组才有)改名 / 删除。\n\t\t\t\t*\n\t\t\t\t* 「未分组」是**伪组**(`groupId === null`),不给改名/删除入口 —— 它不是一个实体,\n\t\t\t\t* 只是\"没归组的账号\"的落脚处。\n\t\t\t\t*/": "/**\n\t\t\t\t* One group's title row: collapse arrow + group name + count + (real groups only) rename / delete.\n\t\t\t\t*\n\t\t\t\t* \"Ungrouped\" is a **pseudo-group** (`groupId === null`) and gets no rename/delete entry -- it is not a real entity,\n\t\t\t\t* just the landing spot for \"accounts that were never grouped\".\n\t\t\t\t*/",
|
||||||
|
"/* 行内结果条:由**某一行上的按钮**触发的操作(切换 / 重登),结果必须留在那一行。\n 为什么(2026-09-30 用户反馈「点切换没反应,报错在下面显示,一点也不明显」):\n 反馈原先只写卡片底部那条消息,用户点完按钮视线还在原处 —— 列表一长,等于没反馈。 */": "/* Inline result bar: for actions triggered by **a button on one specific row** (switch / re-login), the result must stay on that row.\n Why (2026-09-30 user feedback \"clicked switch, nothing happened, the error shows below, not obvious at all\"):\n feedback used to write only the message at the card bottom, while the user's eyes were still on the button -- with a long list that is as good as no feedback. */",
|
||||||
|
"/**\n\t\t\t\t* 二次确认:退出账号是不可逆操作(凭证 + 浏览器登录态都会被清),\n\t\t\t\t* 所以第一次点击只把按钮变成「确认退出?」,3 秒内再点一次才真的执行。\n\t\t\t\t* 不用 window.confirm(插件面板里被宿主拦截的风险,且样式不可控)。\n\t\t\t\t*/": "/**\n\t\t\t\t* Second confirmation: logging out an account is an irreversible operation (credentials + browser session are both cleared),\n\t\t\t\t* so the first click only turns the button into \"Confirm logout?\"; a second click within 3 seconds really executes.\n\t\t\t\t* Not using window.confirm (risk of being intercepted by the host inside a plugin panel, and the styling is uncontrollable).\n\t\t\t\t*/",
|
||||||
|
"/** Copy control for the install command. Success shows 「已复制」 beside the\n\t\t* icon; the command stays selectable if both copy paths fail. */": "/** Copy control for the install command. Success shows 「Copied」 beside the\n\t\t* icon; the command stays selectable if both copy paths fail. */",
|
||||||
|
"/**\n\t\t\t\t* 最近一次 `/accounts` 载荷。\n\t\t\t\t*\n\t\t\t\t* 折叠是**纯前端状态**(不进账号库、也不该进重建签名),但切换折叠要重画列表 ——\n\t\t\t\t* 所以留一份载荷,让\"折叠\"能直接触发一次局部重绘,而不必多打一次 HTTP。\n\t\t\t\t*/": "/**\n\t\t\t\t* The most recent `/accounts` payload.\n\t\t\t\t*\n\t\t\t\t* Collapse state is **purely client-side** (it does not enter the account store, and should not enter the rebuild signature), but toggling collapse must redraw the list --\n\t\t\t\t* so we keep a payload, letting \"collapse\" trigger a partial redraw directly without making an extra HTTP request.\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t* 现在该不该重读账号库。\n\t\t*\n\t\t* @param now 当前时间戳\n\t\t* @param lastSyncedAt 上次重读的时刻(从未读过传 0)\n\t\t* @param active 登录流程是否进行中(窗口开着,或在操作后的加速窗口内)\n\t\t*/": "/**\n\t\t* Whether the account store should be re-read now.\n\t\t*\n\t\t* @param now current timestamp\n\t\t* @param lastSyncedAt the moment of the last re-read (pass 0 when never read)\n\t\t* @param active whether a login flow is in progress (window open, or inside the accelerated window after an action)\n\t\t*/",
|
||||||
|
"/**\n\t\t\t\t* 手动刷新账号状态:只读探活,零额度。\n\t\t\t\t*\n\t\t\t\t* 按钮会**禁用并改文案** —— 库里账号是串行校验的,7 个要十几秒;不置灰的话\n\t\t\t\t* 用户会以为没反应而反复点(宿主侧也有互斥,但界面先挡住更好)。\n\t\t\t\t*/": "/**\n\t\t\t\t* Manually refresh account status: read-only liveness probe, zero quota.\n\t\t\t\t*\n\t\t\t\t* The button is **disabled and relabelled** -- accounts in the store are validated serially, 7 of them take a dozen-plus seconds; without greying out\n\t\t\t\t* the user assumes nothing happened and clicks repeatedly (the host side is mutexed too, but blocking in the UI first is better).\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t\t\t* 按天趋势图。手写 SVG(不引图表库)。\n\t\t\t\t* 左轴是 token、右轴是调用次数 —— 两把尺子只共用画布,不共用刻度(把它们画在同一\n\t\t\t\t* 坐标系里是最常见的图表错误:量级差几百倍时折线会贴死在底部)。\n\t\t\t\t*/": "/**\n\t\t\t\t* Daily trend chart. Hand-written SVG (no chart library).\n\t\t\t\t* Left axis is tokens, right axis is call count -- the two rulers share only the canvas, not the scale (drawing them in the same\n\t\t\t\t* coordinate system is the most common charting mistake: when the magnitudes differ by hundreds of times the line sticks to the bottom).\n\t\t\t\t*/",
|
||||||
|
"/**\n* Mount the IP 池 page whenever the Host serves this entry's settings.\n* @param ctx - the browser plugin context.\n*/": "/**\n* Mount the IP pool page whenever the Host serves this entry's settings.\n* @param ctx - the browser plugin context.\n*/",
|
||||||
|
"/* .ofm_newsbody table 是内容型表格,保持朴素;仅给数据表 .ofm_table 统一竖条。\n 同时把 .ofm_kvc / .ofm_stat 等\"类表格\"卡片也纳入 hover 微提亮,保持语言一致 */": "/* .ofm_newsbody table is a content table, keep it plain; only give the data table .ofm_table the unified rail.\n Also fold the \"table-like\" cards such as .ofm_kvc / .ofm_stat into the hover micro-lift, so the language stays consistent */",
|
||||||
|
"/* 精密仪器·样板间覆盖层:1px细边+内高光,无外投影;颜色全部听宿主 var(--dsw-alias-*)。\n 夜(暗)高光更刃、纹理更显;昼(亮)高光收敛、纹理近乎哑。reduced-motion 去位移留状态。 */": "/* Precision instrument showroom overlay: 1px hairline + inner highlight, no outer shadow; all colors obey the host var(--dsw-alias-*).\n Dark (night) sharpens the highlight and shows the texture more; light (day) restrains the highlight and the texture goes nearly matte. reduced-motion drops movement, keeps state. */",
|
||||||
|
"/** 导入文件的大小上限:与宿主侧一致(实测单账号 ~1.7 KB、账号数上限 500 → ~850 KB,\n\t\t* 取 2 MiB 留 2 倍余量)。两边都要有 —— 前端挡是为了不白传,宿主挡才是真边界。 */": "/** Import file size cap: same as the host side (measured on a real machine ~1.7 KB per account, account count cap 500 -> ~850 KB,\n\t\t* so 2 MiB leaves a 2x margin). Both sides need it -- the frontend check avoids wasted uploads, the host check is the real boundary. */",
|
||||||
|
"/**\n\t\t\t\t* 上下文页的状态渲染。卡片在下面才建,这里用\"先声明后赋值\"的方式接上 ——\n\t\t\t\t* 直接引用下面的 const 会在时序上踩 TDZ(applyStatus 早于卡片构造)。\n\t\t\t\t*/": "/**\n\t\t\t\t* Status rendering for the context page. The cards are built further down, so this links up via \"declare first, assign later\" --\n\t\t\t\t* referencing the const below directly would hit the TDZ in time order (applyStatus runs before the cards are constructed).\n\t\t\t\t*/",
|
||||||
|
"/**\n\t\t* 把用户选中的文件变成\"宿主能用的东西\"。\n\t\t*\n\t\t* 优先走路径(凭证不进 HTTP),拿不到路径才退回内容 —— 后者是必要的兜底:\n\t\t* 宿主没注入路径桥时功能不该整体失效。\n\t\t*/": "/**\n\t\t* Turn the file the user picked into \"something the host can use\".\n\t\t*\n\t\t* Prefer the path (credentials never go through HTTP); fall back to the content only when no path is available -- that fallback is necessary:\n\t\t* the feature should not fail wholesale when the host injected no path bridge.\n\t\t*/",
|
||||||
|
"/* 账号行:从\"bg2 卡片里的凹陷块\"改成\"页面上的凸起块\"。\n 卡片没了之后 bg1 跟页面同色看不见,必须换成 bg2 —— 顺带让账号库成为\n 页面上唯一有实底的一组,重要度自然凸显。 */": "/* Account row: changed from \"a recessed block inside a bg2 card\" to \"a raised block on the page\".\n With the card gone, bg1 matches the page and is invisible, so it must become bg2 -- which also makes the account library\n the only group on the page with a solid fill, so its importance surfaces on its own. */",
|
||||||
|
"/**\n\t\t\t\t* 首次切到某页时才拉的数据。**不做成\"启动时全拉\"**:Token 统计要扫最多 90 天的\n\t\t\t\t* jsonl,而多数人根本不打开这一页 —— 不为没看的页付代价。\n\t\t\t\t*/": "/**\n\t\t\t\t* Data fetched only on the first switch to a page. **Not made \"fetch everything at startup\"**: token stats must scan up to 90 days of\n\t\t\t\t* jsonl, and most people never open this page -- do not pay for pages nobody looks at.\n\t\t\t\t*/",
|
||||||
|
"/* 按钮:hover 不再整体调 opacity —— 那会把文字一起变淡,看着像被禁用了。\n 改成背景微调 + 按下时 1px 位移(物理感);focus-visible 补上键盘可达性。 */": "/* Buttons: hover no longer tweaks opacity on the whole element -- that fades the text too and reads as disabled.\n Instead a subtle background shift + a 1px displacement on press (physical feel); focus-visible adds keyboard accessibility. */",
|
||||||
|
"/* ═══ 精密仪器精修层(世界观:原厂仪器手感):颜色听宿主,质感在阻尼。曲线 --ease-instrument 冲得快落得稳,--ease-settle 极轻过冲。退场比进场快。 ═══ */": "/* ═══ Precision instrument polish layer (worldview: factory instrument feel): color obeys the host, texture lives in the damping. The --ease-instrument curve snaps fast and settles steady, --ease-settle overshoots only slightly. Exits are faster than entrances. ═══ */",
|
||||||
|
"/* 二级(子页面)标签:**分段胶囊**。\n 刻意和一级标签长得不一样(一级是\"下划线 + 大字号\"),这样一眼能看出自己在第几层;\n 否则两层长得一样、用户会以为回到了同一层。 */": "/* Secondary (sub-page) tabs: **segmented pills**.\n Deliberately shaped differently from the primary tabs (primary is \"underline + large type\") so you can tell at a glance which level you are on;\n otherwise the two levels look alike and users think they are back on the same level. */",
|
||||||
|
"/* L1130 .ofm_starbtn:hover 混 #3ECFA0、L1181 .ofm_navmark 混 #3ECFA0 —— 统一改混 label-primary,零品牌色 */": "/* L1130 .ofm_starbtn:hover mixes #3ECFA0, L1181 .ofm_navmark mixes #3ECFA0 -- switch both to mixing label-primary, zero brand color */",
|
||||||
|
"/* SEASON 数组在 JS(L1663) 里、靠内联 style 注入,CSS 改不了值;\n 收编它落地的元素,让质感听宿主:色板加高光、chip 选中不再花、渠道卡顶条收敛 */": "/* The SEASON array lives in JS (L1663) and injects through inline styles, so CSS cannot change its values.\n Reclaim the elements it lands on and let the finish obey the host: palette gains a highlight, selected chip stops looking gaudy, channel card top bar is reined in */",
|
||||||
|
"/* 打开态用主色 —— 改造前是 --err(红)。开关的\"开\"表达的是**状态**不是危险,\n 整排红色开关看着像报错。危险色留给真正会造成破坏的动作(armed 按钮)。 */": "/* On state uses the primary color -- it was --err (red) before the redesign. A switch turned on expresses **state**, not danger,\n and a whole row of red switches reads like an error. Save the danger color for actions that really do damage (armed buttons). */",
|
||||||
|
"/* 卡片盒:**只有设置型内容**用它。数据型分组继续走\"分区标题 + 负空间\"\n (另见 dsw-card 的说明:盒子本身不表达层级,连堆七张就是 AI 默认长相)。 */": "/* Card box: **settings-style content only**. Data-style groups keep going through \"section heading + negative space\"\n (see also the dsw-card note: the box itself expresses no hierarchy; stacking seven of them is the default AI look). */",
|
||||||
|
"/* 选中态:底色用 bg2 铺在 bg1 上,深色主题里这两档只差一点点,\n 所以必须再加一圈描边(bd2 比 bd 明显)才立得住 —— 实测深色下不加就跟没选中一样。 */": "/* Selected state: the fill lays bg2 over bg1, and in dark themes those two steps differ only slightly,\n so a border ring (bd2 is more visible than bd) is required to hold it up -- in testing, dark mode without it looks unselected. */",
|
||||||
|
"/* 页面标题:整页唯一的 display 级文字。字重 600 + 轻微负字距 ——\n 原来是 14px/500,跟 13px 的正文只差 1px,整页因此没有\"起点\"。 */": "/* Page title: the only display-level text on the whole page. Weight 600 + slight negative tracking --\n it used to be 14px/500, only 1px off the 13px body text, so the page had no \"starting point\". */",
|
||||||
|
"/* 「上下限」成对的一行:两个滑块并排,右边一个合成值(如「6~10 个」)。\n —— 比\"每个边界各占一行\"省一半高度,而且两个边界的相对位置一眼能比出来。 */": "/* Paired \"lower/upper bound\" row: two sliders side by side, with one combined value on the right (e.g. \"6~10 items\").\n -- it saves half the height of \"one row per bound\" and lets you compare the two bounds' relative positions at a glance. */",
|
||||||
|
"/* 账号行是可交互的整块 —— 必须给 hover 反馈。改造前它完全静态,鼠标扫过去没有任何回应。\n 当前账号再叠一层主色淡底,跟\"只是鼠标划过\"区分开。 */": "/* The account row is an interactive block -- it must give hover feedback. Before the redesign it was completely static, with no response as the mouse swept over it.\n The current account gets one more layer, a light primary tint, to separate it from \"just a mouse pass\". */",
|
||||||
|
"// (onboarding /announcement、设置页 /announcements /update/status、面板 /summary /stats": "// (onboarding /announcement, settings page /announcements /update/status, panel /summary /stats",
|
||||||
|
"/* 状态底色:从语义色现算,不再写死 rgba。\n 写死的 rgba 只对其中一档主题成立 —— 按浅色底调出来的那组绿/橙/红贴到深色底上会发脏。 */": "/* Status backgrounds: computed from the semantic colors, no more hardcoded rgba.\n A hardcoded rgba only holds for one of the two themes -- the green/orange/red tuned for a light background goes muddy on a dark one. */",
|
||||||
|
"/* 表针走格:离散百分比快照模拟 steps 顿挫——同值连帧=停格,瞬间跳格=机械感;@keyframes 里用不了 steps() 函数,用帧堆出来 */": "/* Needle stepping: discrete percentage snapshots simulate steps detents -- repeated frames at the same value = hold, an instant jump = mechanical; the steps() function cannot be used inside @keyframes, so the frames are stacked by hand */",
|
||||||
|
"/* 分布排行:一行 = 名称 + 数值 + 占比 + 横条。条宽按**最大值**归一(不是按总量),\n 否则只有第一名看得见,其余全是一根短刺。 */": "/* Distribution ranking: one row = name + value + share + bar. Bar width normalizes to the **max value** (not the total),\n otherwise only the top entry is visible and the rest are a short spike. */",
|
||||||
|
"/** Localized compact relative time (\"刚刚\"/\"5分钟\" in zh, \"now\"/\"5min\" in en). */": "/** Localized compact relative time (\"just now\"/\"5min\" in zh, \"now\"/\"5min\" in en). */",
|
||||||
|
"/* ── ⑥ 徽章/药丸/标签 .ofm_badge/.ofm_pill/.ofm_tag/.ofm_chantag/.ofm_chip ── */": "/* ── 6 Badges / pills / tags .ofm_badge/.ofm_pill/.ofm_tag/.ofm_chantag/.ofm_chip ── */",
|
||||||
|
"/* 行首 2px 主色竖条用 transform:scaleY 展开(不触发布局);exit(.12s) 比 entrance(.16s) 快 */": "/* A 2px brand-color bar at the row start expands with transform:scaleY (no layout trigger); exit (.12s) is faster than entrance (.16s) */",
|
||||||
|
"/* ══ GitHub star 按钮 ══════════════════════════════════════════════════ */": "/* ══ GitHub star button ══════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 模态 ══════════════════════════════════════════════════════════════ */": "/* ══ Modal ══════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 筛选 chips ════════════════════════════════════════════════════════ */": "/* ══ Filter chips ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 表单 ══════════════════════════════════════════════════════════════ */": "/* ══ Forms ══════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 面板 ══════════════════════════════════════════════════════════════ */": "/* ══ Panel ══════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 页头 hero ═════════════════════════════════════════════════════════ */": "/* ══ Page header hero ═════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 提示条 ════════════════════════════════════════════════════════════ */": "/* ══ Banner ════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 进度条 ════════════════════════════════════════════════════════════ */": "/* ══ Progress bar ════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 骨架屏 ════════════════════════════════════════════════════════════ */": "/* ══ Skeleton screen ════════════════════════════════════════════════════════════ */",
|
||||||
|
"/* kinetic tension:外壳 active 陷落(scale .985),内部 ::after 扫光条反向滑过形成\"外陷内顶\" */": "/* kinetic tension: the shell sinks while active (scale .985), and the inner ::after sweep bar slides the opposite way, so outside sinks while inside lifts */",
|
||||||
|
"/* ══ 公告中心 ══════════════════════════════════════════════════════════ */": "/* ══ Announcement center ══════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 数据看板 KPI ══════════════════════════════════════════════════════ */": "/* ══ KPI dashboard ══════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 数据表格 ══════════════════════════════════════════════════════════ */": "/* ══ Data table ══════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 登录引导 ══════════════════════════════════════════════════════════ */": "/* ══ Login onboarding ══════════════════════════════════════════════════════════ */",
|
||||||
|
"// (最新在前)\", built with `unshift`), so it is used as served. Reversing it": "// (newest first)\", built with `unshift`), so it is used as served. Reversing it",
|
||||||
|
"// ── 推荐项目页 ────────────────────────────────────────────────────────────": "// ── Recommended projects page ────────────────────────────────────────",
|
||||||
|
"// Left-aligned and outlined, away from 保存: this one is for uninstalling": "// Left-aligned and outlined, away from Save: this one is for uninstalling",
|
||||||
|
"/* ══ 分段选择器 ════════════════════════════════════════════════════════ */": "/* ══ Segmented control ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 区块与分节 ════════════════════════════════════════════════════════ */": "/* ══ Sections and subsections ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 小组件统计 ════════════════════════════════════════════════════════ */": "/* ══ Widget stats ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 渠道接入页 ════════════════════════════════════════════════════════ */": "/* ══ Channel setup page ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 请求热度图 ════════════════════════════════════════════════════════ */": "/* ══ Request heatmap ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 账号池状态 ════════════════════════════════════════════════════════ */": "/* ══ Account pool status ════════════════════════════════════════════════════════ */",
|
||||||
|
"/* 键值表:标签提一档到次级色 —— 原来用三级色,深色主题下几乎读不出来。\n 行距 5→6px;分隔线只做分组暗示,不做表格线。 */": "/* Key/value table: labels move up one step to the secondary color -- they used the tertiary color before and were nearly unreadable in dark themes.\n Line spacing 5->6px; dividers only hint at grouping, they are not table rules. */",
|
||||||
|
"/** Raised by the card marker, so \"查看详情\" lands on the record itself. */": "/** Raised by the card marker, so \"view details\" lands on the record itself. */",
|
||||||
|
"// ── 聚合渠道 / 省 Token / 路由 ─────────────────────────────────────────────": "// ── Aggregated channels / Token saving / Routing ────────────────────",
|
||||||
|
"// configured count), and the load verdict — 畅通 / 繁忙 / 过载 — is computed": "// configured count), and the load verdict -- clear / busy / overloaded -- is computed",
|
||||||
|
"/* ══ 开关(spring 滑块) ═══════════════════════════════════════════════ */": "/* ══ Switch (spring slider) ═══════════════════════════════════════════════ */",
|
||||||
|
"// ── 白嫖模型接入页 ────────────────────────────────────────────────────────": "// ── Free-model access page ─────────────────────────────────────────",
|
||||||
|
"// 白嫖模型接入 the thirteen enabled account channels and their logins.": "// Free-model access the thirteen enabled account channels and their logins.",
|
||||||
|
"// ── EAC 渠道授权(GitHub 登录 + Star) ────────────────────────────────────": "// ── EAC channel authorization (GitHub login + Star) ───────────────",
|
||||||
|
"// 本组件是 settings.onboarding 协调器最先执行的 step(order:-50),complete 依赖 ack;": "// this component is the first step the settings.onboarding coordinator runs (order:-50), and complete depends on ack;",
|
||||||
|
"/* ── ③ 表格行 hover .ofm_table tbody tr ─────────────────────────── */": "/* ── ③ Table row hover .ofm_table tbody tr ─────────────────────────── */",
|
||||||
|
"/* ══ 公告(首次引导) ══════════════════════════════════════════════════ */": "/* ══ Announcement (first-run onboarding) ══════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 按钮(分层状态) ══════════════════════════════════════════════════ */": "/* ══ Buttons (layered states) ══════════════════════════════════════════════════ */",
|
||||||
|
"// The pack's ring is already newest-first (`token-ledger.ts`: \"最近明细": "// The pack's ring is already newest-first (`token-ledger.ts`: \"latest detail",
|
||||||
|
"/* ══ 真实图标与积分展示 ════════════════════════════════════════════════ */": "/* ══ Real icons and credits display ════════════════════════════════════════════════ */",
|
||||||
|
"/* ══ 鱼缸加大版(页头) ════════════════════════════════════════════════ */": "/* ══ Fish tank, large (page header) ════════════════════════════════════════════════ */",
|
||||||
|
"// 今日 = the pack's live snapshot (its in-memory day tree); 全部 = the": "// Today = the pack's live snapshot (its in-memory day tree); All = the",
|
||||||
|
"/* ── ① 面板卡片 .ofm_panel / .ofm_card ─────────────────────────── */": "/* ── ① Panel card .ofm_panel / .ofm_card ─────────────────────────── */",
|
||||||
|
"// cumulative number always reads the history total — even on 今日 —": "// cumulative number always reads the history total -- even on Today --",
|
||||||
|
"/* ── ⑤ 开关 .ofm_switch:spring 滑块阻尼 ─────────────────────────── */": "/* ── ⑤ Switch .ofm_switch: spring slider damping ─────────────────────────── */",
|
||||||
|
"/** Flatten history days into one merged tree (the 全部历史 view). */": "/** Flatten history days into one merged tree (the full history view). */",
|
||||||
|
"// ── 数据看板 / 运行日志 / 网关设置 ────────────────────────────────────────": "// ── Dashboard / Runtime log / Gateway settings ────────────────",
|
||||||
|
"/* hover:kinetic tension —— 位移压浅(-1px),去掉外投影,靠边色提亮+高光增强传递\"浮起\" */": "/* hover: kinetic tension -- the offset flattens (-1px), the outer shadow is dropped, and a brighter edge color + stronger highlight carry the \"lifted\" feel */",
|
||||||
|
"// 免费模型 the no-account lane (this plugin's own gateway),": "// Free models the no-account lane (this plugin's own gateway),",
|
||||||
|
"/* 渠道标:--chan-accent 兜底本来就是宿主变量,收编写死的 #fff / #101828,外投影换内高光 */": "/* Channel mark: the --chan-accent fallback is already a host variable; replace the hardcoded #fff / #101828 and swap the outer shadow for an inner highlight */",
|
||||||
|
"/* ── ⑦ 原生件接管:滚动条 / selection / caret / 聚焦环 ─────────────── */": "/* ── 7 Native control takeover: scrollbar / selection / caret / focus ring ─────────────── */",
|
||||||
|
"/* ══ Toast(fixed 到视口,浮在所有层之上) ════════════════════════════ */": "/* ══ Toast (fixed to viewport, floats above all layers) ════════════════════════════ */",
|
||||||
|
"/* 双基底:拉丝细纹(repeating-linear-gradient)垫底,实体色覆盖在上,从边缘隐约透出拉丝感 */": "/* Double base: a brushed fine texture (repeating-linear-gradient) underneath, a solid color on top, with the brushed feel faintly visible at the edges */",
|
||||||
|
"// EAC 模型 the desktop co-paid lane behind GitHub auth,": "// EAC models the desktop co-paid lane behind GitHub auth,",
|
||||||
|
"// ack 永远 undefined 会永久卡住 onboarding 流程(连带阻塞后续 step 与主题启动画面)。": "// an ack that stays undefined forever wedges the onboarding flow (blocking later steps and the theme splash along with it).",
|
||||||
|
"/* ── ⑩【返工·收编 SEASON 落地件】色板/选中 chip/渠道卡顶条 ─────────────── */": "/* ── 10 [Rework: take back SEASON landings] Palette / selected chip / channel card top bar ─────────────── */",
|
||||||
|
"/* ── ④ 输入框 .ofm_input:仪器凹槽手感 ─────────────────────────── */": "/* ── ④ Input .ofm_input: instrument groove feel ─────────────────────────── */",
|
||||||
|
"// /forward/key 等)无任何超时,一个不响应端点即可阻塞 settings.onboarding 协调流程": "// /forward/key etc.) have no timeout at all, one unresponsive endpoint is enough to block the settings.onboarding coordination flow",
|
||||||
|
"/* ══ 新品/热门徽标与高速标签(置顶的 opencode 免费模型) ═════════════════ */": "/* ══ New/hot badges and speed tags (pinned opencode free model) ═════════════════ */",
|
||||||
|
"/* provider 名带连字符,万一折行会断成 deepseek- / web(看着像故障)—— 整词不拆 */": "/* provider names contain hyphens, so if they wrap they break as deepseek- / web (looks like a bug) -- never break the whole word */",
|
||||||
|
"/* ── ⑩【返工·接上 pl5-tick】KPI/统计数字获得表针走格感 ─────────────── */": "/* ── 10 [Rework: hook up pl5-tick] KPI / stat numbers get a gauge-needle stepping feel ─────────────── */",
|
||||||
|
"/* ══ 卡片(双层 bezel:hairline 边框 + 顶部内高光 + 柔和外阴影) ═══════ */": "/* ══ Card (double bezel: hairline border + top inner highlight + soft outer shadow) ═══════ */",
|
||||||
|
"/* ══ 鱼缸(品牌装饰:玻璃缸 + 水位 + 波光 + 鱼群) ════════════════════ */": "/* ══ Fish tank (brand decoration: glass tank + water level + ripples + school of fish) ════════════════════ */",
|
||||||
|
"/* active:快速陷落(.09s) + 内阴影加深=物理按压;松开靠 --ease-settle 回弹 */": "/* active: fast sink (.09s) + deeper inner shadow = physical press; release springs back on --ease-settle */",
|
||||||
|
"/** 用户取消选择时 Chromium 抛的是 AbortError,要和其他失败区分开(取消不是错误)。 */": "/** When the user cancels the picker Chromium throws AbortError, which must be told apart from other failures (cancelling is not an error). */",
|
||||||
|
"// 用页面 base 解析,而不是 origin-absolute 的「/api/...」:DSH 挂在反向代理": "// Resolve against the page base, not an origin-absolute \"/api/...\": when DSH sits behind a reverse proxy",
|
||||||
|
"// 首帧动辄数十秒),/update/apply 要下载+校验+安装整包,/refresh /reprobe 要": "// the first frame often takes tens of seconds), /update/apply must download+verify+install the whole package, /refresh /reprobe need",
|
||||||
|
"/* ── ⑧【师傅批改②】昼模式高光压暗:45-62% → 30-38%,更克制 ─────────── */": "/* ── 8 [Master review 2] Light-mode highlights dimmed: 45-62% -> 30-38%, more restrained ─────────── */",
|
||||||
|
"/* 渠道卡:随接入状态着色顶部 hairline 与外发光(已接入=品牌色、关闭=灰、待接入=琥珀弱化) */": "/* Channel card: tints the top hairline and outer glow by connection state (connected = brand color, off = gray, pending = muted amber) */",
|
||||||
|
"/** 显式读取(失败要说给用户听):初始化 / 切号 / 移除 / 添加 / 重新登录 / 导入之后用。 */": "/** Explicit read (failures must be reported to the user): used after init / switch account / remove / add / re-login / import. */",
|
||||||
|
"// 子路径下(https://<host>/<prefix>/)时,前导斜杠会把 <prefix> 丢掉,请求": "// under a sub-path (https://<host>/<prefix>/), the leading slash drops <prefix> and the request",
|
||||||
|
"/* 卡片头的副说明:与 dsw-field-desc 同色同级,让\"组说明\"和\"项说明\"读起来是一套。 */": "/* Card header sub-description: same color and level as dsw-field-desc, so the \"group description\" and the \"item description\" read as one set. */",
|
||||||
|
"/* ── ⑩【返工·收编租界】星按钮/导航标/渠道标 全部听宿主变量 ─────────────── */": "/* ── 10 [Rework: take back the concessions] Star button / nav mark / channel mark all obey host variables ─────────────── */",
|
||||||
|
"/* ── reduced-motion 降级:去位移/回弹/扫光/顿挫,保留颜色与不透明度反馈 ── */": "/* ── reduced-motion fallback: drop movement/bounce/sweep/detents, keep color and opacity feedback ── */",
|
||||||
|
"/* hover:扫光滑过(exit slower than press);去掉原外投影,仅留顶部高光 */": "/* hover: the sweep glides across (exit slower than press); the old outer shadow is dropped, only the top highlight remains */",
|
||||||
|
"/* 常态凹槽(inset 顶高光+底内阴影);focus 时凹槽不动,只换边色+外发光,像仪器被点亮 */": "/* At rest a groove (inset: top highlight + bottom inner shadow); on focus the groove stays put and only the edge color + outer glow change, like an instrument lighting up */",
|
||||||
|
"/* 按钮:异步进行中显示内联 spinner(aria-busy 锚点,文字仍保留占位避免布局跳动) */": "/* Button: shows an inline spinner while async work is in flight (aria-busy anchor; the label keeps its placeholder so the layout does not jump) */",
|
||||||
|
"// 登录流(发起、轮询、busy/notice)住在 SettingsPage 级的 hook 里:页头的": "// the login flow (start, poll, busy/notice) lives in a SettingsPage-level hook: the header's",
|
||||||
|
"/* ── 昼(not-dark)收敛:高光更哑、拉丝更隐、回弹更小 ─────────────── */": "/* ── Light (not-dark) restraint: duller highlight, subtler brushed texture, smaller bounce ─────────────── */",
|
||||||
|
"/* ── 键盘可达性:只给键盘聚焦画聚焦环,鼠标点击不出现 ─────────────────── */": "/* ── Keyboard accessibility: draw a focus ring only for keyboard focus, never on mouse click ─────────────────── */",
|
||||||
|
"/* ══ 动效降级:用户系统开启\"减少动态效果\"时全部收敛 ═══════════════════ */": "/* ══ Motion fallback: everything collapses when the user's system turns on \"reduce motion\" ═══════════════════ */",
|
||||||
|
"/* 导航标:去掉 #3ECFA0 混色,改混 label-primary;外投影换成内高光+轻凹槽 */": "/* Nav mark: drop the #3ECFA0 mix, mix label-primary instead; outer shadow becomes an inner highlight + light groove */",
|
||||||
|
"/* 渠道卡顶条(::before 用 --chan-accent):收敛高度与发光,听宿主凹槽语言 */": "/* Channel card top bar (::before uses --chan-accent): rein in height and glow, follow the host groove language */",
|
||||||
|
"/** 「新建分组」的内联输入框(与「备注」同款交互:Enter 提交、Esc 取消、失焦也提交)。 */": "/** The inline input for \"new group\" (same interaction as \"note\": Enter submits, Esc cancels, blur also submits). */",
|
||||||
|
"/** 分布排行。条宽按**最大值**归一 —— 按总量归一的话只有第一名看得见,其余全是一根短刺。 */": "/** Distribution ranking. Bar width is normalised by **the maximum** -- normalising by the total makes only the first place visible, the rest all a short spike. */",
|
||||||
|
"/* ── 毛玻璃表面:只给大块面用 backdrop-filter,小卡片用半透明叠层,滚动更顺 */": "/* ── Frosted surfaces: use backdrop-filter only on large panels, small cards use a translucent overlay, so scrolling stays smoother */",
|
||||||
|
"/** token 数转人话:1M / 512K / 128K …… 与「模型」页的写法保持一致。 */": "/** Token count in human terms: 1M / 512K / 128K ... consistent with the wording on the \"Models\" page. */",
|
||||||
|
"/* —— 交互锚点增强:激活态 / 状态色 / 加载态(additive,不改既有规则) —— */": "/* -- Interaction anchor enhancement: active state / state color / loading state (additive, no changes to existing rules) -- */",
|
||||||
|
"/** 一对「上下限」滑块:一行两个 range + 右侧合成值。拖动中只更新数字,松手才提交。 */": "/** A pair of lower/upper sliders: two ranges on one row + the composed value on the right. Dragging updates only the number; releasing commits. */",
|
||||||
|
"/** 空闲时的重读间隔:只为让探活补上的账号名 / 限制状态 / 失败标记自己出现,不必太快。 */": "/** Idle re-read interval: only so the account name / limit state / failure marker filled in by the liveness probe appear on their own; it need not be quick. */",
|
||||||
|
"// 并长期占用同源连接池。超时按 AbortError 抛给调用方,走各自 .catch 降级路径。": "// and holds a same-origin connection pool long-term. Timeouts are thrown to the caller as AbortError, taking each one's .catch fallback path.",
|
||||||
|
"// 慢路由按需放宽(options.timeout,毫秒):/bench 要等一整段真实生成(本车道": "// slow routes are relaxed as needed (options.timeout, milliseconds): /bench has to wait out a whole real generation (this lane's",
|
||||||
|
"/* ── ② 按钮 .ofm_btn ─────────────────────────── */": "/* ── ② Buttons .ofm_btn ─────────────────────────── */",
|
||||||
|
"/* 数字列表针走格感:tabular-nums + pl5-tick 机械顿挫(steps) */": "/* Numeric list needle stepping: tabular-nums + pl5-tick mechanical detents (steps) */",
|
||||||
|
"/** 上次同步账号库的时刻(0 = 还没读过;初始化那次 refresh 之前会先读一次)。 */": "/** Timestamp of the last account store sync (0 = not read yet; it is read once before the init refresh). */",
|
||||||
|
"/** 最近一次自动换号(后端只在**自动**切号时写;手动切不记)。null = 还没有过。 */": "/** The last automatic account rotation (the backend writes it only on an **automatic** switch; manual switches are not recorded). null = never yet. */",
|
||||||
|
"/* ── ⑧【师傅批改①】拉丝纹理增强:金属感要真的能摸到 ─────────────── */": "/* ── 8 [Master review 1] Brushed texture upgrade: the metal feel has to be actually touchable ─────────────── */",
|
||||||
|
"/* 挂到 KPI 大数字与统计卡数字:hover 时数字以机械顿挫走一格,模拟仪表指针跳动 */": "/* Attach to the KPI big number and the stat card numbers: on hover the digits step one notch with a mechanical jolt, mimicking a gauge needle */",
|
||||||
|
"// because \"累计\" that resets at midnight is a lie.": "// because \"cumulative\" that resets at midnight is a lie.",
|
||||||
|
"/* chip 选中态:JS 注入彩色背景,这里压回宿主语言——主色薄涂+主色边,去掉杂色 */": "/* chip selected state: JS injects a colored background, so push it back to the host language here -- light primary tint + primary border, no stray colors */",
|
||||||
|
"/* 滑块用 --ease-settle 带回弹;按下时滑块变宽=被\"捏扁\",松开回弹归位 */": "/* The slider uses --ease-settle with bounce; on press it widens = gets \"squashed\", on release it springs back into place */",
|
||||||
|
"// 8 秒超时兜底:后端路由未注册/挂起时 fetch 不再永久挂起——此前全部 api 调用": "// 8s timeout fallback: when a backend route is unregistered/hung, fetch no longer hangs forever -- previously every api call",
|
||||||
|
"/* ── ⑧【师傅批改③】表格竖条语言统一:所有表格行 hover 都带主色竖条 ── */": "/* ── 8 [Master review 3] Unify the table rail language: every table row hover carries the primary-color rail ── */",
|
||||||
|
"/* 夜模式 3% → 4.5%,昼模式 1.5% → 2.8%,纹理间隔收紧让拉丝更密 */": "/* Dark mode 3% -> 4.5%, light mode 1.5% -> 2.8%; tighten the texture spacing so the brushed grain is denser */",
|
||||||
|
"/** 一格总览。`set` 一次给值和注释,避免三处各写一遍 textContent。 */": "/** One overview cell. `set` takes the value and the note in one call, so textContent is not written three times over. */",
|
||||||
|
"/** 登录流程进行中(窗口开着 / 刚操作过)的重读间隔:够快,用户看不到\"没更新\"。 */": "/** Re-read interval while a login flow is in progress (window open / just acted): fast enough that the user never sees \"not updated\". */",
|
||||||
|
"// 相对路径随页面 base 走,根路径部署下与原值完全一致。解析不了(无 document": "// relative paths follow the page base, identical to the original value when deployed at the root path. If it cannot be resolved (no document",
|
||||||
|
"// 问题」,动作是「一眼能看到的星按钮」,数字是宿主 /picks 取回的真实 Star /": "// problem\", the action is \"a Star button you cannot miss\", the numbers are the real Star the host fetches via /picks",
|
||||||
|
"/** 用最近一次载荷重画(折叠这类纯前端状态用它,不走重建签名、也不打 HTTP)。 */": "/** Redraw from the last payload (used for purely client-side state like collapse; no rebuild signature, no HTTP request). */",
|
||||||
|
"// 实时积分:每个账号的剩余余额(credits.balances),卡片汇总 + 账号行": "// Live credits: each account's remaining balance (credits.balances), card total + account row",
|
||||||
|
"// 超时兜底:3 秒拿不到 ack(后端挂起/超时/异常)→ 当作已 ack 主动放行——": "// timeout fallback: no ack in 3 seconds (backend hung/timed out/threw) -> treat as acked and let it through --",
|
||||||
|
"/* 整行染成纯错误色在深色底上会刺眼且发暗;混一点正文色压回来,色条仍然说明性质。 */": "/* Painting a whole row in the pure error color is harsh and dark on a dark background; mix in a bit of the body text color to pull it back, and the bar still states the nature. */",
|
||||||
|
"/* 聚焦环:仪器刻度环——细环+外晕,用 --ease-instrument 淡入 */": "/* Focus ring: an instrument dial ring -- thin ring + outer glow, faded in with --ease-instrument */",
|
||||||
|
"/** 面板本体:命令式 DOM(生态既有插件同款做法),挂在 React 容器里。 */": "/** The panel itself: imperative DOM (the same approach as existing ecosystem plugins), mounted inside a React container. */",
|
||||||
|
"// 三种状态如实呈现——未登录 / 已登录但未 star / 已授权——并且登录全程无需": "// three states shown truthfully -- not logged in / logged in but not starred / authorized -- and the whole login needs no",
|
||||||
|
"// 断连保护:服务端不可达时 EventSource 默认无限自动重连(持续占连接池)。": "// Disconnect guard: when the server is unreachable, EventSource retries forever by default (holding the connection pool).",
|
||||||
|
"// 重连轮次必须活在 open() 之外:open() 每次重连都会重入,计数器若在里面": "// the reconnect round must live outside open(): open() re-enters on every reconnect, and if the counter is inside",
|
||||||
|
"/* 扫光条改走 --ease-instrument,与 .ofm_btn 同语言 */": "/* Sheen bar moves to --ease-instrument, same language as .ofm_btn */",
|
||||||
|
"/* 滚动条:仪器滑轨——thumb 加顶部高光与底部凹槽,hover 时轻微增亮 */": "/* Scrollbar: an instrument rail -- thumb gets a top highlight and a bottom groove, and brightens slightly on hover */",
|
||||||
|
"/** 折叠的组 key。读一次就常驻,避免每行都去碰 localStorage。 */": "/** Collapsed group keys. Read once and kept, so every row does not touch localStorage. */",
|
||||||
|
"/** 账号 id → 给人看的名字(与调用台账同一套映射,拿不到就退回 id)。 */": "/** Account id -> a human-readable name (the same mapping as the call ledger; fall back to the id when it is unavailable). */",
|
||||||
|
"/* 说明文字:限宽 64ch —— 满宽的一行小字读起来很累,且会拖长整张卡。 */": "/* Helper text: capped at 64ch -- a full-width line of small text is tiring to read and stretches the whole card. */",
|
||||||
|
"// GitHub 授权状态由这一层持有:模型卡的锁标记与 EAC 面板必须看到同一个": "// GitHub authorization state is held by this layer: the model card's lock marker and the EAC panel must see the same",
|
||||||
|
"// 只有拿到过明确判定(网关答过 /eac/status)且未授权时才上锁:状态未知": "// Lock only when there is an explicit verdict (the gateway answered /eac/status) and it is unauthorized: unknown state",
|
||||||
|
"// 默认档(balanced)而不是 deep:测速要回答的是“这个模型日常多快”,": "// default tier (balanced) rather than deep: a speed test answers \"how fast is this model day to day\",",
|
||||||
|
"/** 最近一次 /usage 的响应(切换\"按账号/按模型\"时不必重新拉)。 */": "/** The most recent /usage response (no need to refetch when switching \"by account / by model\"). */",
|
||||||
|
"/** 渲染进程是否支持 File System Access 的「另存为」。 */": "/** Whether the renderer supports File System Access \"Save As\". */",
|
||||||
|
"// Fork / 最近提交——拿不到就整块不显示,绝不用占位数字凑版面(这块页面的": "// Fork / latest commit -- if unavailable the whole block is hidden, never padded with placeholder numbers (this section's",
|
||||||
|
"// 最近一回合(#26):取最新一条调用样本,输入/输出 token、首帧、速度,": "// Latest round (#26): take the newest call sample, input/output token, first frame, speed,",
|
||||||
|
"// 判定,登录 / 退出后两边同时更新;60 秒轮询兜住别处(另一台机器)的变动。": "// verdict, both sides update together on login / logout; a 60s poll catches changes elsewhere (another machine).",
|
||||||
|
"// 默认档就是日常档;deep 每次最多烧 32K 输出 token,且经常把固定": "// the default tier is the everyday tier; deep burns up to 32K output tokens per run, and often the fixed",
|
||||||
|
"/* primary 按钮保留主色渐变,仅把外投影换成更克制的内高光+轻凹槽 */": "/* primary buttons keep the brand gradient; only the outer shadow is swapped for a more restrained inner highlight + light groove */",
|
||||||
|
"/* 统一加顶部发丝高光;状态色只用 color-mix 调透明度,不换色相 */": "/* Add a top hairline highlight to all of them; status colors only use color-mix to adjust alpha, never shift hue */",
|
||||||
|
"// 全部说服力都在数字是真的)。每张卡片的文案按语言直写 t('字面量'),字典": "// all the persuasion is that the numbers are real). Each card's copy is written inline per language as t('literal'); the dictionary",
|
||||||
|
"// 共享同一个进行中的会话(同一轮询、同一条提示),见 useEacLogin。": "// share one in-progress session (same poll, same notice), see useEacLogin.",
|
||||||
|
"// 直接打到站点根被前置网关 404,设置面板报「无法连接插件后端」(#54)。": "// hitting the site root directly gets a 404 from the fronting gateway, and the settings panel reports \"cannot connect to the plugin backend\" (#54).",
|
||||||
|
"// 这一页只有一个任务:让人点开仓库、点下 Star。所以文案是「说清它解决什么": "// This page has exactly one job: get someone to open the repo and hit Star. So the copy is \"state clearly what it solves\"",
|
||||||
|
"// 连续失败达到上限先关闭让出连接,但按退避(30s 翻倍、封顶 5 分钟)定时": "// after consecutive failures hit the cap, close first to free the connection, but reschedule by backoff (30s doubling, capped at 5 minutes)",
|
||||||
|
"/* 中性信息标志(邮箱 / 手机号):不属于状态,别用红黄绿去干扰状态色 */": "/* Neutral info markers (email / phone): not status, so do not use red/yellow/green and disturb the status colors */",
|
||||||
|
"/* 控件需要占满整行时(滑块 + 数值)用这个变体:文字在上、控件在下。 */": "/* Use this variant when a control must span the full row (slider + value): text on top, control below. */",
|
||||||
|
"/** 24 根小柱:每小时调用量。高度按最大值归一,失败多的时段用红色。 */": "/** 24 small bars: calls per hour. Height normalised by the maximum; hours with many failures are red. */",
|
||||||
|
"/** 与现有 RPC 的能力对应:余额、每日领取及独立签到状态分别判断。 */": "/** Maps to the capabilities of the existing RPCs: balance, daily claim, and standalone check-in state are judged separately. */",
|
||||||
|
"// 这一回合的 token 去向。聊天窗属于宿主内核,插件无法在对话流里注入,": "// where this round's token went. The chat window belongs to the host kernel, the plugin cannot inject into the conversation stream,",
|
||||||
|
"// 重新打开——永久关闭让公告/更新/升级推送在宿主重启或休眠后整体静默失效,": "// reopen it -- a permanent close leaves announcement/update/upgrade pushes silently dead across host restarts or sleep,",
|
||||||
|
"// 闸门在服务器:网关拒绝没有用户令牌的对话,而这个面板是取得令牌的唯一入口。": "// The gate is server-side: the gateway rejects conversations with no user token, and this panel is the only way in to get one.",
|
||||||
|
"/* 状态点呼吸灯保留,但改成 pl5- 前缀、机械顿挫更明显的两拍节奏 */": "/* Keep the breathing status dot, but move it under the pl5- prefix and give it a two-beat rhythm with a sharper mechanical step */",
|
||||||
|
"// 「最近一回合」跟着一个轻量轮询走(#26):看板其余部分仍是页面加载时的": "// \"Latest round\" rides a lightweight poll (#26): the rest of the board is still the one from page load",
|
||||||
|
"// 免费车道转发端口的接入配方。它和上面这块网关是两条独立监听:这里的地址与": "// The connection recipe for the free lane's forwarding port. It and the gateway above are two independent listeners: the address here and",
|
||||||
|
"// 复制粘贴:后端把授权页交给系统浏览器,前端轮询网关领取令牌。令牌本身从不": "// Copy-paste: the backend hands the authorization page to the system browser, the frontend polls the gateway for the token. The token itself never",
|
||||||
|
"// 密钥由宿主按实际绑定的端口生成,端口没开就只给一句说明而不是一堆会骗人的": "// Keys are generated by the host for the port actually bound; if the port never opened, hand back one line of explanation instead of a pile of deceptive",
|
||||||
|
"// 未授权按钮、上锁模型卡的「去授权」和这个面板必须共享同一个进行中的会话,": "// The unauthorized button, the locked model card's \"Authorize\" and this panel must share one in-progress session,",
|
||||||
|
"// 登录流只有这一份:EAC 面板、页头的未授权按钮、上锁模型卡的「去授权」": "// There is only one login flow: the EAC panel, the header's unauthorized button, the locked model card's \"Authorize\"",
|
||||||
|
"// 跑完一整轮探测——8 秒兜底套在它们身上只会“前端报失败、后端继续烧”,": "// complete a whole probe round -- an 8s fallback on them would only mean \"the frontend reports failure while the backend keeps burning\",",
|
||||||
|
"/** 字符数 → 「万字符」。界面上出现一长串 0 没人看得出差别。 */": "/** Character count -> \"10k chars\". A long string of 0s in the UI lets nobody tell the difference. */",
|
||||||
|
"/** 相对时间:刚刚 / N 分钟前 / N 小时前 / N 天前。 */": "/** Relative time: just now / N minutes ago / N hours ago / N days ago. */",
|
||||||
|
"/** 上次渲染用的内容签名(见 account-sync.ts)。 */": "/** Content signature used for the last render (see account-sync.ts). */",
|
||||||
|
"// 失败的调用也如实标出。数据源优先用轮询到的新快照,退回页面加载时的。": "// Failed calls are reported truthfully too. The data source prefers the fresh polled snapshot, falling back to the one at page load.",
|
||||||
|
"// 快照,这一行单独刷新,让「刚发完一条消息」的用户不用整页重载就能看到": "// snapshot. This row refreshes on its own so a user who just sent a message can see it without reloading the whole page,",
|
||||||
|
"/** 把任意值吸附到最近的档位(配置里可能是旧值或手改过的值)。 */": "/** Snap any value to the nearest tier (the config may hold an old value or a hand-edited one). */",
|
||||||
|
"// 配方(未启用时 /clients 回的是占位端口与占位 Key)。": "// recipe (when not enabled /clients returns placeholder ports and a placeholder Key).",
|
||||||
|
"/* tab:激活项在滑块底衬之上再补一道品牌下划线,选中更明确 */": "/* tab: the active item adds a brand underline on top of the slider track, so the selection reads more clearly */",
|
||||||
|
"// 的宿主、非常规 base)就退回根路径原值——行为不比修复前差。": "// host, unusual base) fall back to the root-path original -- no worse than before the fix.",
|
||||||
|
"// 会被清零,退避就永远是第一档而不是 30s 翻倍到 5 分钟。": "// gets zeroed, so the backoff would forever sit at the first tier instead of doubling from 30s to 5 minutes.",
|
||||||
|
"// 保持可 grep;各仓库的地址与「第二个动作」在这里各写一份。": "// keep it greppable; each repo's address and \"second action\" are written once here.",
|
||||||
|
"/* 统一过渡降级:所有交互件只留颜色/不透明度反馈,去位移 */": "/* Unified transition fallback: every interactive element keeps only color/opacity feedback, no movement */",
|
||||||
|
"// 只有刷新页面才能恢复;退避重连在恢复连接性的同时保住连接池。": "// only a page refresh recovers it; backoff reconnection restores connectivity while keeping the connection pool.",
|
||||||
|
"// 未授权时把登录入口放到页头:撞到「没授权」的用户第一眼看到的": "// When unauthorized, put the login entry in the header: a user who runs into \"not authorized\" first sees",
|
||||||
|
"// 逐条展示;随账号列表与领积分动作刷新,另有一个慢轮询兜底。": "// shown row by row; refreshes with the account list and the claim-credits action, with a slow poll as backup.",
|
||||||
|
"/** 渲染宿主返回的节流设置(含可选档位与清理策略)。 */": "/** Render the throttle settings returned by the host (including selectable tiers and the cleanup policy). */",
|
||||||
|
"/** 短时间:今天只显示时分,其它显示月/日 时:分。 */": "/** Short time: for today show only hour:minute, otherwise show month/day hour:minute. */",
|
||||||
|
"/** 倒计时:还剩 2 小时 13 分 / 已解除。 */": "/** Countdown: 2 hours 13 minutes left / lifted. */",
|
||||||
|
"// 不该显示一把凭空的锁,网关不可达时本地记录仍算已授权。": "// must not show a lock out of nowhere; when the gateway is unreachable the local record still counts as authorized.",
|
||||||
|
"/* 用户开了\"减少动态效果\"就别再让元素滑来滑去。 */": "/* If the user turned on \"reduce motion\", stop sliding elements around. */",
|
||||||
|
"/* 分段控件选中块:加顶部高光,按下时微微陷落 */": "/* Segmented control selected block: add a top highlight; on press it sinks slightly */",
|
||||||
|
"/* 分组:组标题行 + 组内缩进 + 归组下拉 */": "/* Grouping: group title row + indentation inside the group + group dropdown */",
|
||||||
|
"/* 操作反馈:不归属任何一页,常驻在标签栏之上 */": "/* Action feedback: belongs to no page, sits permanently above the tab bar */",
|
||||||
|
"/* 文本选择:主色薄涂,昼模式再淡一点避免刺眼 */": "/* Text selection: a light primary tint; in light mode go even fainter to avoid glare */",
|
||||||
|
"/* 兜底色:令牌缺失时才用到,随系统主题切换 */": "/* Fallback colors: used only when a token is missing; follows the system theme */",
|
||||||
|
"// 位置就该有钥匙,而不是要先找到下方的授权区。": "// is where the key belongs, not having to find the authorization section below first.",
|
||||||
|
"// 到达浏览器,这里只显示登录名与判定结果。": "// reach the browser; here only the login name and the verdict are shown.",
|
||||||
|
"/* 表针走格动画停走,但保留数字本身 */": "/* Needle stepping animation stops, but the numbers stay */",
|
||||||
|
"// 这是插件表面能做到的最接近实时的位置。": "// This is the closest to real-time the plugin surface can get.",
|
||||||
|
"/* 小组件(备注输入、归组下拉) */": "/* Small widgets (note input, group dropdown) */",
|
||||||
|
"// bench 会话自己打到 429。": "// the bench session hits 429 on its own.",
|
||||||
|
"// 各挂一份轮询会互相抢令牌的领取权。": "// hanging a poll on each of them would fight over the right to claim the token.",
|
||||||
|
"// 测速按钮因此几乎必失败而配额照付。": "// so the speed-test button almost always fails while the quota is paid anyway.",
|
||||||
|
"/* 分区标题:字重整 600。 */": "/* Section headings: weight bumped to 600. */",
|
||||||
|
"/* 呼吸灯停跳,但保留颜色状态 */": "/* Breathing light stops pulsing, but keeps its color state */",
|
||||||
|
"/* 去位移/回弹/陷落 */": "/* No movement/bounce/sink */",
|
||||||
|
"/* 按钮 / 输入框 */": "/* Buttons / inputs */",
|
||||||
|
"/* 光标:跟随主色 */": "/* Caret: follows the primary color */",
|
||||||
|
"/* 开关捏扁还原 */": "/* Switch squash and release */",
|
||||||
|
"/* 卡片等容器 */": "/* Containers such as cards */",
|
||||||
|
"/* 扫光条砍掉 */": "/* Sweep bar removed */",
|
||||||
|
"/* 调用台账 */": "/* Call ledger */",
|
||||||
|
"/* 已关闭 */": "/* Closed */",
|
||||||
|
"/* 语义层 */": "/* Semantic layer */",
|
||||||
|
"/* 账号库 */": "/* Account library */"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import DICTIONARY from "./dictionary.json";
|
import DICTIONARY from "./dictionary.json";
|
||||||
|
import COMMENTS from "./dictionary.comments.json";
|
||||||
import { decodeRaw, encodeValue, isObjectKeyPosition, scanLiterals } from "./strings.js";
|
import { decodeRaw, encodeValue, isObjectKeyPosition, scanLiterals } from "./strings.js";
|
||||||
|
|
||||||
const CHINESE = /[\u4e00-\u9fa5]/;
|
const CHINESE = /[\u4e00-\u9fa5]/;
|
||||||
|
|
@ -174,11 +175,15 @@ export function encodeInterpolated(value: string, quote: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrites Chinese UI text inside JavaScript string literals.
|
* Rewrites Chinese UI text inside JavaScript string literals, and Chinese
|
||||||
|
* inside source comments.
|
||||||
*
|
*
|
||||||
* Returns the original string unchanged when nothing was translated, so the
|
* Returns the original string unchanged when nothing was translated, so the
|
||||||
* caller can skip rebuilding the response. Object keys are deliberately left
|
* caller can skip rebuilding the response. Object keys are deliberately left
|
||||||
* alone: translating `{ "中文": 1 }` would break the lookup that reads it.
|
* alone: translating `{ "中文": 1 }` would break the lookup that reads it.
|
||||||
|
*
|
||||||
|
* Comments are rewritten in the gaps between literals, which is where the
|
||||||
|
* scanner leaves them.
|
||||||
*/
|
*/
|
||||||
export function rewriteScript(source: string): string {
|
export function rewriteScript(source: string): string {
|
||||||
// Bundlers emit non-ASCII as `\uXXXX` escapes, so the source can be entirely
|
// Bundlers emit non-ASCII as `\uXXXX` escapes, so the source can be entirely
|
||||||
|
|
@ -195,18 +200,85 @@ export function rewriteScript(source: string): string {
|
||||||
if (isObjectKeyPosition(source, span)) continue;
|
if (isObjectKeyPosition(source, span)) continue;
|
||||||
|
|
||||||
const value = decodeRaw(span.raw);
|
const value = decodeRaw(span.raw);
|
||||||
if (!hasChinese(value)) continue;
|
// A stylesheet literal may hold Chinese only in its comments, which the
|
||||||
|
// dictionary cannot cover; translate those bodies first.
|
||||||
|
const commented = rewriteComments(value);
|
||||||
|
if (!hasChinese(commented) && commented === value) continue;
|
||||||
|
|
||||||
// `${...}` is only an interpolation inside a template literal.
|
// `${...}` is only an interpolation inside a template literal.
|
||||||
const next = span.quote === "`" ? substituteInterpolated(value) : substitute(value);
|
const substituted = span.quote === "`" ? substituteInterpolated(commented) : substitute(commented);
|
||||||
|
const next = substituted === commented ? commented : substituted;
|
||||||
if (next === value) continue;
|
if (next === value) continue;
|
||||||
|
|
||||||
out += source.slice(cursor, span.start) + span.quote + encodeInterpolated(next, span.quote) + span.quote;
|
// The gap before this literal is ordinary source, so any comment there is
|
||||||
|
// translated too. Only gaps are passed to rewriteComments: running it over
|
||||||
|
// the assembled output would also scan literal contents, where a URL like
|
||||||
|
// `https://…` reads as a line comment.
|
||||||
|
out += rewriteComments(source.slice(cursor, span.start)) + span.quote + encodeInterpolated(next, span.quote) + span.quote;
|
||||||
cursor = span.end;
|
cursor = span.end;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed ? out + source.slice(cursor) : source;
|
out += rewriteComments(source.slice(cursor));
|
||||||
|
|
||||||
|
return out === source ? source : out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates whole `//` and `/* ... */` comments inside a decoded literal.
|
||||||
|
*
|
||||||
|
* The last Chinese in the served bundles sits in code comments: notes the
|
||||||
|
* plugin authors wrote about why a module exists and which bug it fixed. They
|
||||||
|
* never render, but leaving them Chinese means the bundle still ships Chinese,
|
||||||
|
* so they are translated too.
|
||||||
|
*
|
||||||
|
* Only a whole-comment exact match is replaced. Partial replacement inside a
|
||||||
|
* comment could reflow the box-drawing runs and the `*` alignment that give
|
||||||
|
* these notes their structure, and a mangled comment can break the bundle.
|
||||||
|
*/
|
||||||
|
export function rewriteComments(text: string): string {
|
||||||
|
if (!text.includes("//") && !text.includes("/*")) return text;
|
||||||
|
let out = "";
|
||||||
|
let cursor = 0;
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
while (cursor < text.length) {
|
||||||
|
const openBlock = text.indexOf("/*", cursor);
|
||||||
|
const openLine = text.indexOf("//", cursor);
|
||||||
|
let open: number;
|
||||||
|
let block: boolean;
|
||||||
|
if (openBlock < 0 && openLine < 0) break;
|
||||||
|
if (openBlock >= 0 && (openLine < 0 || openBlock < openLine)) {
|
||||||
|
open = openBlock;
|
||||||
|
block = true;
|
||||||
|
} else {
|
||||||
|
open = openLine;
|
||||||
|
block = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let end: number;
|
||||||
|
if (block) {
|
||||||
|
const close = text.indexOf("*/", open + 2);
|
||||||
|
if (close < 0) break;
|
||||||
|
end = close + 2;
|
||||||
|
} else {
|
||||||
|
const nl = text.indexOf("\n", open);
|
||||||
|
end = nl < 0 ? text.length : nl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const comment = text.slice(open, end);
|
||||||
|
const hit = COMMENTS[comment];
|
||||||
|
out += text.slice(cursor, open);
|
||||||
|
if (hit !== undefined && hit !== comment) {
|
||||||
|
out += hit;
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
out += comment;
|
||||||
|
}
|
||||||
|
cursor = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed ? out + text.slice(cursor) : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compiles the candidate so a bad rewrite can never reach the browser. */
|
/** Compiles the candidate so a bad rewrite can never reach the browser. */
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue