@coreness/dsh 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,38 +1,33 @@
1
1
  # Coreness for DeepSeek Harness
2
2
 
3
- A kit of plugins a DeepSeek Harness profile works by: a route to the Qoder models, the journal the environment and its participants write, the picture and the form of the profile's MCP servers, the rate gate that holds a request rather than refusing it and limits nobody as it ships, and a page of the product's own that draws the overview and the settings.
3
+ Coreness is a set of plugins a DeepSeek Harness profile lives by: one package that brings the profile a page of its own, the picture and the form of its MCP servers, a route to the Qoder models, the journal the environment and its participants write, and a limiter that holds a request instead of refusing it. It installs into a profile as it is — the browser halves are plain files, served as they lie — so nothing is built, and what is installed is what runs.
4
4
 
5
- The whole of it is one package, `@coreness/dsh`: the manifest at the root of this repository, the plugin folders beside it, and `bundles/coreness.patch.yml` — the patch that names every row the package mounts.
5
+ ## What a person gets
6
6
 
7
- ## What it needs
8
-
9
- - **A DeepSeek Harness runtime of the version this package was built against — `0.2.0-rc.2`.** `peerDependencies` declares it, and the platform compares that declaration with its own version before a profile imports a plugin: a mismatch leaves the plugin out and reports the package as incompatible instead of failing quietly later.
10
- - **A Qoder account and its personal access token.** The token is not a line of a command and not a file: it is written once through the product's own page — the Qoder section of the Coreness page — into the platform's credential store, under the name the row declares (`apiKeyEnv: QODER_PERSONAL_ACCESS_TOKEN` in `bundles/coreness.patch.yml`). The platform's own Settings → Models shows the row and cannot fill it; no secret stands in the package, in the patch or in a log.
11
- - **Nothing to build.** The client halves are plain `.js` files served as they lie, so what is packed is what runs.
12
-
13
- ## How to install
7
+ - **A page of its own in the application** — the profile's overview: what it carries, which bundles and rows stand and which are off, and the settings a session or a plugin has to be told.
8
+ - **The MCP servers of the profile, seen and edited** — one card per server: its kind, its address, the environment it needs, and switches whose effect lands in the profile rather than in a copy.
9
+ - **A route to the Qoder models** — the account's own models, each with the reasoning levels it accepts and the output budget it allows.
10
+ - **The journal** — the environment's memory: what changed in the profile and what a participant did, kept as records that carry a kind, an area, a name and an author.
11
+ - **A limiter that holds a request rather than refusing it** — a call over the limit waits its turn; nothing is limited as the package ships.
14
12
 
15
- Two acts of the platform's launcher — an install, then a boot:
13
+ ## Install
16
14
 
17
15
  ```
18
- npm pack # writes coreness-dsh-<version>.tgz
19
- dsh plugin --profile web add ./coreness-dsh-*.tgz
16
+ dsh plugin --profile web add @coreness/dsh
20
17
  dsh web
21
18
  ```
22
19
 
23
- The install is the whole of the setup, and it takes the same four forms the application's own **Plugins** page takes in its *Add plugin* dialog: a tarball as above, the package's registry name once it is published, a git address, or an absolute local path. A profile of that name does not exist on a fresh machine, and the platform creates it there from its own shipped `web` template — the base bundle and the web application — before installing this package into it. A profile named otherwise is created from the base bundle alone and carries no interface, which is why the shipped template's own name stands in the line.
20
+ The first line installs the package into a profile, the second opens the application, which reads the profile's composition at start-up. A profile of that name need not exist beforehand: the platform creates it from its own shipped template — the base bundle and the web application — and then installs this package into it. The same install stands in the application's own **Plugins** page — its *Add plugin* dialog takes a package name, a tarball, a git address or a local path — so none of this needs a terminal.
21
+
22
+ ## What it needs
24
23
 
25
- The boot is its own act because the install door only runs the package manager and exits; opening the application is the same act by another hand. The composition of a profile is read when the application starts, so the package appears after a restart of it, while a change of a plugin's code is visible without one. Where the installation does not put `dsh` on `PATH`, the launcher lies in the installation under `resources/runtime/cli/bin/`; the application's own **Plugins** page takes the same four forms in its *Add plugin* dialog and needs no terminal at all.
24
+ - **DeepSeek Harness `0.2.0-rc.2`** — the version this package was built against. The manifest declares it, and the application compares that declaration with its own version before a profile imports a plugin: a mismatch leaves the plugin out and says so, instead of failing quietly later.
25
+ - **A Qoder account and its personal access token** — the token is entered once, on the product's own page, and the platform's store keeps it. No value of it stands in the package, in the profile's patch or in a log; whether the vendor accepts it is asked there and answered there.
26
26
 
27
- ## What it does not do
27
+ ## Where the details are
28
28
 
29
- - **It does not manage the platform's plugins.** The product's page counts and reads; the platform's own Plugins page is what switches.
30
- - **It does not keep an MCP client, a connection state or a registry of its own.** The platform owns the servers; the product owns the picture and the form, writes a row through the Loader, and reads the result back rather than calling a write saved.
31
- - **It does not see the MCP servers a session declares for itself**: those live in that session and never in the profile's file.
32
- - **It does not count the price of a task.**
33
- - **It does not draw a statistics view over the journal**: the journal is the agents' tool and shows nothing on the page.
34
- - **It does not hold the token**: the value lies in the platform's store, and the page reads its state back rather than keeping a notion of its own.
29
+ Each plugin carries its own README — `plugins/page`, `plugins/mcp`, `plugins/provider/qoder`, `plugins/journal`, `plugins/rate-gate` — with what it takes, what it draws and what it refuses. The picture of the project, its boundaries and the reasons behind both are in `PROJECT.md`, and the conventions of work in `AGENTS.md`.
35
30
 
36
- ## Rules of work
31
+ ## License
37
32
 
38
- The conventions of work in this repository are a snapshot of the [Coreness Kit](https://github.com/Vensus137/Coreness-Kit): the file `AGENTS.md`, in full, which is not edited here. The picture of the project is `PROJECT.md`, read first by whoever enters the work; `NOTES.md` stands beside it with what is noticed and not settled.
33
+ MIT.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreness/dsh",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The Coreness kit for DeepSeek Harness: a page of its own, the journal, the picture and the form of the profile's MCP servers, and the Qoder route — one bundle for a profile.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -47,7 +47,7 @@ One row per kind of change this page takes: where it lands, and what says it is
47
47
  | the section that draws the picker: which values it offers, and what a choice writes | the chunk — the `picker` pull, the `IdPicker` call site (`client.provider.js`, `client.gate.js`, `client.mcp.js`), and the act the choice calls | the batch; the seam between them is the pull, and a chunk that never arrives leaves its section out of the rail and is named on the console |
48
48
  | the MCP roster: a card, its disclosure, the editor in place, the kind of a row, the removal's confirmation | `client.mcp.js` — `cardOf`, `editorOf`, `dialogOf`, `rowMenu`, `TRANSPORTS`, `REFUSALS`, and the `picker` pull its section is seated on | the batch, then `node scripts/page-form.mjs` — without a step it drives the roster's own scenario in order, and `remove` and `sweep` are named in §Check; `--help` lists every step |
49
49
  | the outcome of a write: where its word stands, and how long it stands | `client.mcp.js` — the note itself (`setNote`), the strip it is drawn in (`client.mcp.js`'s `cardOf`, `coreness-mcp-answer`; `client.provider.js`'s `headOf`, `coreness-qoder-answer`), the answers that cannot stand in a card at all (`client.mcp.js`'s `editorOf`, its own line for a row that is not there yet; its `dialogOf`; the roster's line for a row that is gone), and its lifetime (`client.hold.js`'s `holdOf`) | the batch (`client.test.mjs`: one number written in one file and read by every chunk — no copy anywhere — the word's place in each case, and a verdict that goes when the value it judged goes), then `node scripts/page-form.mjs note` |
50
- | the two genres of an answer, and how a reader tells them apart without being told | a **state** is what stands without a press — a card's own word (`Connected: … · 15 models`, `enabled`, `readonly`, the catalogue's news of itself) and the verdict about the value it judged — and it stands in the state's room in the card's header, drawn in the host's state genre (`client.provider.js`'s `stateOf`, its `coreness-qoder-state`); **news** is the answer to a press, and it stands in the strip on the right of the header of the card the press was made in, then fades (`client.mcp.js`'s `answerOf` and `sweepOf`, the strip's own class) — **one exception, named rather than left silent**: where that card is gone by the time the answer exists, the word stands in the section's card and the sentence names the row it was about. The case is a fresh row taken off the draft by its own `Remove`: the press removes the row, the card goes with it, and the drawing reads 3 cards → 2 (measured in the harness, by a probe over the mounted section — `tmp/` holds the run's log, and a copy of it is not kept by design), so no card of that row is left to hold the word. The sentence carries the pair for exactly that reason (`client.gate.js`'s `reason.removed`, `{pair}`) | the batch — the states and the news are read from their own two rooms in the same run, and a word is required to stand in exactly one of them; the live walk reads the same two (`scripts/page-form.mjs qoder-check` reads `[data-answer]` in the header after the press) |
50
+ | the two genres of an answer, and how a reader tells them apart without being told | a **state** is what stands without a press — a card's own word (`Connected: … · 15 models`, `enabled`, `readonly`, the catalogue's news of itself) and the verdict about the value it judged — and it stands in the state's room in the card's header, drawn in the host's state genre (`client.provider.js`'s `stateOf`, its `coreness-qoder-state`); **news** is the answer to a press, and it stands in the strip on the right of the header of the card the press was made in, then fades (`client.mcp.js`'s `answerOf` and `sweepOf`, the strip's own class) — **and only there**: a press made in a row answers in that row's card, a press made in a section's own card answers in the section's, and where the pressed card is gone by the time the answer exists the word stands **nowhere** rather than in a neighbour. The case is a fresh row taken off the draft by its own `Remove`: the press removes the row, the card goes with it (the drawing reads 3 cards → 2, measured by a probe over the mounted section), and the leaving is the whole answer — the section's card is the system defaults and says nothing about what was done to a row | the batch — the states and the news are read from their own two rooms in the same run, and a word is required to stand in exactly one of them; the live walk reads the same two (`scripts/page-form.mjs qoder-check` reads `[data-answer]` in the header after the press) |
51
51
  | the Qoder form: a field, the credential's write and clear, the vendor's verdict on the token, the models editor, the reasoning levels a row offers, the node sweep | `client.provider.js` — `QoderSection`, `writeSettings`, `saveCredential`, `clearCredential`, `askSweep`, `readCatalogue`, `CATALOGUE_ACTION`, `askVerify`, `verdictNote`, `VERIFY_ACTION`, `checkRows`, `effortChoices`, and the `picker` pull its section is seated on; the control it draws is `client.picker.js` | the batch, then the step: `node scripts/page-form.mjs <step>`, whose steps §Check names — the levels a row offers and the three verdicts are also driven live, `qoder-effort` and `qoder-verify` |
52
52
  | the gate's section: the four numbers, one card per model, the pair of a row, the draft and the write | `client.gate.js` — `GateSection`, `SCALARS`, `opsOf`, `writeSettings`, `rowCard`, and the `picker` pull its section is seated on | the batch, then `node scripts/page-form.mjs gate` and its three neighbours — `gate-write`, `gate-refuse`, `gate-conflict`, named in §Check; the gate's step opens a row and prints the pair and the list it opens onto |
53
53
  | the foot widget: its ring, its summary line, its popover | `client.credits.js` — `CreditsSeat`, `pictureOf`, `summaryOf`, `Ring` | the batch; its words and its place are held there, and its real size in the foot is a look no check reads |
@@ -93,7 +93,7 @@ What that does not cover, named rather than left silent: the platform's own Mode
93
93
 
94
94
  **The four pages swept at once (2026-10-09, read by `node scripts/page-css-read.mjs`, `node scripts/page-form.mjs widths|roles`, and the batch).** One table, every row a measured number: the distances in use are **1×3 · 2×6 · 4×15 · 6×17 · 8×32 · 9×2 · 10×20 · 12×20 · 14×8 · 16×3 · 18×1 · 20×1 · 22×2 · 24×6 · 28×4 · 32×2 · 48×2** over 6 chunks / 280 rules / 181 distance declarations (144 numbers standing in a rule, 1 through a variable), and the reader prints **no RED** — every number is one the page already draws with. **Fixed sizes and reserved heights:** the reader holds **2 bounds on a height** — `client.mcp.js`'s `textarea.coreness-mcp-input{min-height:64px}` (a text box's floor, so a pasted key is visible) and `client.picker.js`'s `.coreness-pickerList{max-height:240px}` (the drop-down's ceiling, so a long list scrolls inside its own sheet) — plus the **one reserved box** the page keeps by the user's own decision, the answer strip (`flex:0 0 160px` in each of the three section-card heads; a **row's** strip carries no `flex-basis` at all). The buttons stand at `height:36px` in the three section chunks; the **field box is 32** and the Rate rows' buttons take `32` by the height experiment the user asked for (`client.gate.js`, one scoped line, revertible by deleting it). **Two divergences that are decisions, not debt:** the panel's rail carries the page's only `18` (`client.js` `.coreness-nav{gap:18px}`) — the panel is a copy of the host's own navigation and keeps the host's own spacing, and the user keeps the panel as it stands; and the reader counts a `9` twice which no grep of the six chunks places — the instrument prints no RED, so it is a value of its own scale, and its line belongs to the instrument's author rather than to a guess here. **The type** is read by `roles`: the eight roles agree across the four pages, and the **four that differ are the named ones** in the section below (the product's panel title, our chip against the platform's blue tag, our status word against the platform's tag, and the card title of the product's own panel); the hint line agrees with the host's own rule exactly (`12px/18px`, weight 400, `rgb(129,133,140)`).
95
95
 
96
- **The page's type is read, not assumed, and three of its readings differ from the host's on purpose.** The step `node scripts/page-form.mjs roles` prints every role of each section beside the same role on the host's own pages and lists the ones that differ; the list is short by decision and the decisions are named here, so a later session reads them as choices rather than as debt. The panel's own headline — the product's `Coreness` at 20/28 500 — is a **panel** title and not a card's, and the user keeps it as it stands; the panel itself carries neither padding nor radius for the same reason, **a panel is not a card**, and the shell's own panel is what it copies. Our chip reads 11/16 400 in the tertiary grey where the host's own status tag reads 10/10 500 in the platform's blue: the blue tag is a **state of the platform**, not a label of ours, and taking it as the model would make every label on the page shout; and the product's own version pill — the `v0.1.0` in the panel's head — carries **no border** where the three chips of the sections carry a hairline, by the same reading: a version pill is drawn like the host's own tag and not like a value chip, and the user agreed to leave it so. And the page reserves nothing, by the user's own rule: "У нас есть тексты, между текстами просто интервалы, между карточками просто интервалы, и мы вообще в принципе ничего не резервируем, как правило… Никаких фикс-размеров нет." So a room that held a height for a state, a word or a peer that had not arrived yet is a defect and not a habit: a room takes the height of what it says, and the block around it grows — the Rate card lost its state room that way, and the Rate row's head lost its two reserved lines the same way. The **one** box that keeps a size is the answer's strip (`flex:0 0 160px`, drawn always, empty without a word): the user asked for that one himself so that an answer moves nothing, and it is named here as the exception a later session must not "unreserve" in the name of this rule. The Qoder **Connection** card is an exception to the form-pair rule and stands as it is: its actions are `Save token`, `Clear token` and `Check token`, its own three, and it carries **no** `Cancel`/`Apply` pair — the user's own words, "у неё просто свои специфические кнопки", after the lead offered to unify it. A later session that reads one page and one convention must not "unify" it: the rule "Cancel on the left, Apply on the right" belongs to the cards that submit a **form** — the MCP editor, Qoder's models card, the Rate card — and a card whose presses act on one value of their own keeps its own words. **A pair belongs to the card it stands in, and that is written down because it is not obvious:** the section's pair writes the section's own numbers, and a row's editor carries its own three actions — `Remove`, `Cancel`, `Apply` — on a line of their own at the bottom right under its fields, where `Remove` stands only for a row that already exists (a row just added has nothing to go back to, and its `Cancel` takes it out). The user's words: "кнопки «Cancel» и «Apply» принимаются к той карточке, в которой они находятся. Это лучше сразу записать и сделать, чтобы мы не ломали логику, которая должна быть по умолчанию, потому что она неочевидна." And two blocks left the Rate section by his word ("про rate limiter в самом низу какой-то мусор… это всё мусор"), and what a reader loses with them is named here rather than left to be discovered: the per-provider sum of ceilings (what one key faces, which this page alone showed, the platform's own pages showing no such sum) and the list of rows the gate would not use — the one place such a row was named **before** a press. That second loss is covered elsewhere: the refusal is the platform's own, it comes back on `Apply` in the card's own strip, and a row the schema will not take is refused at the write, which is where the reader meets it. The row's own counter line (`used of ceiling tokens · nothing waiting · not used: …`) left with them, and its numbers stand where they are edited: the gate's fields carry what it runs with, and the live spend is what the platform's own gate route answers. And where the host draws no field label at all (its own Settings page prints `field label: nothing stands there`), the rule that decides is this page's own: **a small line that names is 500, a small line that explains is 400, and the two share one size and one colour** — the field's name against the sentence under it, so a reader can tell "this is the field" from "this is about the field" without a size change, which is what the user asked for in the Rate card.
96
+ **The page's type is read, not assumed, and three of its readings differ from the host's on purpose.** The step `node scripts/page-form.mjs roles` prints every role of each section beside the same role on the host's own pages and lists the ones that differ; the list is short by decision and the decisions are named here, so a later session reads them as choices rather than as debt. The panel's own headline — the product's `Coreness` at 20/28 500 — is a **panel** title and not a card's, and the user keeps it as it stands; the panel itself carries neither padding nor radius for the same reason, **a panel is not a card**, and the shell's own panel is what it copies. Our chip reads **12/18 400** in the tertiary grey (`coreness-tag`, the element a `v0.1.0`-shaped value stands in): the page's small role, and the pair a label takes everywhere since the sizes were merged, with the weight and the tertiary grey a label keeps. The host's own status tag is **measurably** another thing — `10px/10px` at weight 500 in `rgb(65, 118, 230)`, padding `0/7/0/7`, on the element `_tag_brmue_4 fO69Vq_statusTag` and the word `Experimental` — and both readings come from the same step, `node scripts/page-form.mjs roles`. They are two surfaces and not a contradiction: that tag lives on the host's **own** pages (the plugin manager, `Settings → Models`), while "nothing below 12 px" was read in the shell and its sidebar. One detail worth keeping from the same reading: on the host **one element answers two roles** — its chip and its status word are one element. Our decision stands on the measurement: a blue tag is a **state of the platform**, not a label of ours, and taking it as the model would make every label on the page shout; and the product's own version pill — the `v0.1.0` in the panel's head — carries **no border** where the three chips of the sections carry a hairline, by the same reading: a version pill is drawn like the host's own tag and not like a value chip, and the user agreed to leave it so. And the page reserves nothing, by the user's own rule: "У нас есть тексты, между текстами просто интервалы, между карточками просто интервалы, и мы вообще в принципе ничего не резервируем, как правило… Никаких фикс-размеров нет." So a room that held a height for a state, a word or a peer that had not arrived yet is a defect and not a habit: a room takes the height of what it says, and the block around it grows — the Rate card lost its state room that way, and the Rate row's head lost its two reserved lines the same way. The **one** box that keeps a size is the answer's strip (`flex:0 0 160px`, drawn always, empty without a word): the user asked for that one himself so that an answer moves nothing, and it is named here as the exception a later session must not "unreserve" in the name of this rule. The Qoder **Connection** card is an exception to the form-pair rule and stands as it is: its actions are `Save token`, `Clear token` and `Check token`, its own three, and it carries **no** `Cancel`/`Apply` pair — the user's own words, "у неё просто свои специфические кнопки", after the lead offered to unify it. A later session that reads one page and one convention must not "unify" it: the rule "Cancel on the left, Apply on the right" belongs to the cards that submit a **form** — the MCP editor, Qoder's models card, the Rate card — and a card whose presses act on one value of their own keeps its own words. **A pair belongs to the card it stands in, and that is written down because it is not obvious:** the section's pair writes the section's own numbers, and a row's editor carries its own three actions — `Remove`, `Cancel`, `Apply` — on a line of their own at the bottom right under its fields, where `Remove` stands only for a row that already exists (a row just added has nothing to go back to, and its `Cancel` takes it out). The user's words: "кнопки «Cancel» и «Apply» принимаются к той карточке, в которой они находятся. Это лучше сразу записать и сделать, чтобы мы не ломали логику, которая должна быть по умолчанию, потому что она неочевидна." And two blocks left the Rate section by his word ("про rate limiter в самом низу какой-то мусор… это всё мусор"), and what a reader loses with them is named here rather than left to be discovered: the per-provider sum of ceilings (what one key faces, which this page alone showed, the platform's own pages showing no such sum) and the list of rows the gate would not use — the one place such a row was named **before** a press. That second loss is covered elsewhere: the refusal is the platform's own, it comes back on `Apply` in the card's own strip, and a row the schema will not take is refused at the write, which is where the reader meets it. The row's own counter line (`used of ceiling tokens · nothing waiting · not used: …`) left with them, and its numbers stand where they are edited: the gate's fields carry what it runs with, and the live spend is what the platform's own gate route answers. And where the host draws no field label at all (its own Settings page prints `field label: nothing stands there`), the rule that decides is this page's own: **a small line that names is 500, a small line that explains is 400, and the two share one size and one colour** — the field's name against the sentence under it, so a reader can tell "this is the field" from "this is about the field" without a size change, which is what the user asked for in the Rate card.
97
97
 
98
98
  ## Boundaries
99
99
 
@@ -86,31 +86,31 @@ window.__ModuleLoader__.load({
86
86
  The class names are ours; every colour is either a `--dsw-alias-*` token or the donor's own
87
87
  literal, kept where the host's token has no counterpart. */
88
88
  const CSS = `
89
- .coreness-credits-seat{box-sizing:border-box;flex:0 0 auto;width:100%;min-width:0;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:1px solid transparent;border-radius:10px;align-items:center;gap:8px;margin:0 0 4px;padding:8px;display:flex}
89
+ .coreness-credits-seat{box-sizing:border-box;flex:0 0 auto;width:100%;min-width:0;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:1px solid transparent;border-radius:var(--dsw-radius-md);align-items:center;gap:8px;margin:0 0 4px;padding:8px;display:flex}
90
90
  .coreness-credits-seat:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,#0000000f);border-color:var(--dsw-alias-border-l2,#0000001f)}
91
- .coreness-credits-seat:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}
92
- .coreness-credits-seatName{flex:none;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}
93
- .coreness-credits-seatNote{flex:0 1 auto;margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
91
+ .coreness-credits-seat:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#0f1115);outline-offset:1px}
92
+ .coreness-credits-seatName{flex:none;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
93
+ .coreness-credits-seatNote{flex:0 1 auto;margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums;white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
94
94
  .coreness-credits-seatRow{flex-direction:column;gap:4px;min-width:0;display:flex}
95
95
  .coreness-credits-rowHead{align-items:baseline;gap:8px;min-width:0;display:flex}
96
- .coreness-credits-rowLabel{flex:1;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
97
- .coreness-credits-rowAmount{flex:none;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;white-space:nowrap}
98
- .coreness-credits-rowNote{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;white-space:nowrap}
96
+ .coreness-credits-rowLabel{flex:1;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
97
+ .coreness-credits-rowAmount{flex:none;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums;white-space:nowrap}
98
+ .coreness-credits-rowNote{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}
99
99
  .coreness-credits-bar{display:block;background:var(--dsw-alias-bg-layer-2,#00000014);border-radius:999px;height:5px;overflow:hidden}
100
- .coreness-credits-barFill{display:block;background:var(--dsw-alias-brand-primary,#4d6bfe);border-radius:999px;height:100%}
101
- .coreness-credits-glyph{flex:none;justify-content:center;align-items:center;display:inline-flex;color:var(--dsw-alias-brand-primary,#4d6bfe)}
102
- .coreness-credits-rail{box-sizing:border-box;width:36px;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:1px solid transparent;border-radius:8px;flex:none;justify-content:center;align-items:center;margin:0 0 4px;padding:0;display:inline-flex}
100
+ .coreness-credits-barFill{display:block;background:var(--dsw-alias-brand-primary,#0f1115);border-radius:999px;height:100%}
101
+ .coreness-credits-glyph{flex:none;justify-content:center;align-items:center;display:inline-flex;color:var(--dsw-alias-brand-primary,#0f1115)}
102
+ .coreness-credits-rail{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:1px solid transparent;border-radius:var(--dsw-radius-sm);flex:none;justify-content:center;align-items:center;margin:0 0 4px;padding:0;display:inline-flex}
103
103
  .coreness-credits-rail:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,#0000000f)}
104
- .coreness-credits-rail:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}
105
- .coreness-credits-card{background:var(--dsw-alias-bg-layer-3,#fff);border:.5px solid var(--dsw-alias-border-l2,#0000001f);border-radius:12px;box-shadow:0 12px 32px #0000002e;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;font-size:12px;gap:10px;line-height:1.5;max-width:320px;min-width:260px;padding:12px;position:fixed;z-index:1000}
104
+ .coreness-credits-rail:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#0f1115);outline-offset:1px}
105
+ .coreness-credits-card{background:var(--dsw-alias-bg-layer-3,#fff);border:.5px solid var(--dsw-alias-border-l2,#0000001f);border-radius:var(--dsw-radius-md);box-shadow:var(--coreness-elevation);color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;font-size:12px;gap:10px;line-height:1.5;max-width:320px;min-width:260px;padding:12px;position:fixed;z-index:1000}
106
106
  .coreness-credits-card .coreness-credits-bar{height:8px}
107
107
  .coreness-credits-cardHead{align-items:center;gap:8px;display:flex}
108
- .coreness-credits-cardTitle{flex:1;font-size:13px;font-weight:600}
109
- .coreness-credits-blockTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600}
108
+ .coreness-credits-cardTitle{flex:1;font-size:14px;font-weight:600;line-height:22px}
109
+ .coreness-credits-blockTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:18px}
110
110
  .coreness-credits-cardLabel{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:18px}
111
- .coreness-credits-icon{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;line-height:0;font:inherit;color:var(--dsw-alias-label-tertiary);background:0 0;border:0;border-radius:6px;cursor:pointer;text-decoration:none}
111
+ .coreness-credits-icon{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;line-height:0;font:inherit;color:var(--dsw-alias-label-tertiary);background:0 0;border:0;border-radius:var(--dsw-radius-sm);cursor:pointer;text-decoration:none}
112
112
  .coreness-credits-icon:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,#0000000f)}
113
- .coreness-credits-icon:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}
113
+ .coreness-credits-icon:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#0f1115);outline-offset:1px}
114
114
  .coreness-credits-iconWrap{display:inline-flex;line-height:0}
115
115
  /* The turn is not switched off under prefers-reduced-motion: it is the only sign that a reading is
116
116
  running, and a system with animations off would take that sign away in silence. */
@@ -121,8 +121,8 @@ window.__ModuleLoader__.load({
121
121
  .coreness-credits-rewards{display:flex;flex-direction:column;gap:6px}
122
122
  .coreness-credits-reward{align-items:baseline;display:flex;gap:8px;min-width:0}
123
123
  .coreness-credits-rewardText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
124
- .coreness-credits-rewardMark{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
125
- .coreness-credits-rewardMarkOn{flex:none;color:var(--dsw-alias-brand-primary,#4d6bfe);font-size:11px;line-height:16px}
124
+ .coreness-credits-rewardMark{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
125
+ .coreness-credits-rewardMarkOn{flex:none;color:var(--dsw-alias-brand-primary,#0f1115);font-size:12px;line-height:18px}
126
126
  `;
127
127
 
128
128
  /** The widget's words, filled in `apply`. */
@@ -86,7 +86,11 @@ window.__ModuleLoader__.load({
86
86
  title: 'Rate limiter',
87
87
  caption: 'A ceiling in tokens per minute, attached to a model: the gate holds a request the window cannot take and lets it go marked forced when the wait bound is spent.',
88
88
  'state.loading': 'Reading the gate…',
89
- 'state.standing': 'The window lives in this process and in memory alone: a restart forgets the spend, and a key shared with other machines is spent faster than this window believes.',
89
+ /* The whole sentence, in the user's own choice: the two facts that cost money and the one that saves a surprise,
90
+ and no mechanics. "Of this run alone" says whose count it is without the word "process", which read as the
91
+ page's. What left — that the window is per provider and model, and that it lives in memory and nowhere else —
92
+ belongs in the plugin's own README, where mechanics are read. */
93
+ 'state.standing': 'Of this run alone: a reload keeps it, a restart wipes it, spend elsewhere is not counted.',
90
94
  'state.error': 'The gate could not be read',
91
95
  'state.unwritable': 'The row is not configurable from here: {fault}',
92
96
  /* The answer to a press is one of three words, the same on every page of this product: `Saved` for a change
@@ -97,13 +101,7 @@ window.__ModuleLoader__.load({
97
101
  reason a reader sees on the strip (its `title`). */
98
102
  'state.written': 'Saved',
99
103
  'state.refused': 'Not saved',
100
- 'state.nothing': 'Nothing changed',
101
104
  'reason.conflict': 'The row changed elsewhere: the draft was not written, and the values below are the row as it now stands.',
102
- /* The answer to a `Remove` of a row the file does not carry: the row leaves the draft, the file is not touched
103
- because a write happens only where there is something to write, and the sentence **names the row** it was
104
- about — the row's own card is gone with the row, so without the pair the reader could not tell which Row the
105
- word is for. Silent it was, and silence is what read as the old `Cancel`. */
106
- 'reason.removed': 'The row {pair} left the draft. Because the file does not carry it, this press wrote nothing: the file still stands as it was.',
107
105
  'fault.write': 'The write did not go through',
108
106
  'card.limit': 'The limit',
109
107
  'card.limitEffective.dry': ', watching only',
@@ -124,7 +122,6 @@ window.__ModuleLoader__.load({
124
122
  'field.dry': 'Watch only',
125
123
  'field.dry.hint': 'Every request is counted and nothing is held, so the wait each one would have taken stands in its ring.',
126
124
  'field.ceiling': 'Tokens per window',
127
- 'field.rowRatio': 'Caution for this row',
128
125
  'field.provider': 'Provider',
129
126
  'field.model': 'Model',
130
127
  /* Who stands behind a number: the profile's own patch, or the schema's default. The words are short
@@ -172,34 +169,30 @@ window.__ModuleLoader__.load({
172
169
  .coreness-gate-answer-error{color:var(--dsw-alias-state-error-primary)}
173
170
  .coreness-gate-answer-warn{color:var(--dsw-alias-state-warn-label)}
174
171
  .coreness-gate-headButton{box-sizing:border-box;border:none;background:0 0;color:inherit;font:inherit;text-align:left;padding:0;cursor:pointer;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto;display:flex}
175
- .coreness-gate-headButton:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none;border-radius:var(--dsw-radius-md)}
172
+ .coreness-gate-headButton:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none;border-radius:var(--dsw-radius-md)}
176
173
  .coreness-gate-name{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
177
174
  .coreness-gate-subtitle{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
178
175
  .coreness-gate-marker{box-sizing:border-box;border:none;background:0 0;color:var(--dsw-alias-label-secondary);flex:none;width:24px;height:24px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
179
176
  .coreness-gate-marker svg{transition:transform .15s ease}
180
177
  .coreness-gate-marker[data-open='true'] svg{transform:rotate(180deg)}
181
178
  .coreness-gate-body{flex-direction:column;gap:8px;min-width:0;display:flex}
182
- /* A hint line, and the host's own rule for one: label-tertiary at 12px/18px (a:dsh-client-ui-settings-models,
183
- its advancedHint rule; the plugin manager's guideHint, registryHint, templateHint). It stood at 13/20,
184
- which is the host's own *status* rule (a:dsh-client-ui-plugin-manager: status, failure p, empty) and not
185
- the line a reader compares against the hints of the sections beside this one. */
179
+ /* A hint line: the page's small role, one pair for it everywhere — 12/18 in label-tertiary. */
186
180
  .coreness-gate-dim{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}
187
- .coreness-gate-line{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
181
+ .coreness-gate-line{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
188
182
  .coreness-gate-fields{flex-direction:column;gap:8px;display:flex}
189
- .coreness-gate-pair{flex-direction:row;flex-wrap:wrap;gap:8px;display:flex}
190
- .coreness-gate-pair>.coreness-gate-field{min-width:132px;flex:1 1 132px}
183
+ .coreness-gate-pair{flex-direction:column;gap:6px;display:flex}
191
184
  .coreness-gate-row .coreness-gate-body{flex-direction:row;flex-wrap:wrap;align-items:flex-start}
192
185
  .coreness-gate-row .coreness-gate-body>.coreness-gate-pair{display:contents}
193
186
  .coreness-gate-field{flex-direction:column;gap:6px;min-width:180px;flex:1 1 0;display:flex}
194
187
  .coreness-gate-fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:flex}
195
- .coreness-gate-input{box-sizing:border-box;border:none;background:0 0;color:var(--dsw-alias-label-primary);flex:1 1 auto;min-width:0;height:32px;font:inherit;padding:0 10px;font-size:14px;line-height:22px}
188
+ .coreness-gate-input{box-sizing:border-box;border:none;background:0 0;color:var(--dsw-alias-label-primary);flex:1 1 auto;min-width:0;font:inherit;padding:6px 10px;font-size:14px;line-height:22px}
196
189
  /* The box is the wrapper now, so the ring belongs to the wrapper: the input inside it has no border to colour. */
197
- .coreness-gate-unitBox{box-sizing:border-box;display:inline-flex;align-items:center;width:100%;height:32px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1)}
190
+ .coreness-gate-unitBox{box-sizing:border-box;display:inline-flex;align-items:center;width:100%;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1)}
198
191
  .coreness-gate-unitBox:focus-within{border-color:var(--dsw-alias-state-business-primary)}
199
192
  .coreness-gate-unitBox>.coreness-gate-input:focus{outline:none}
200
193
  .coreness-gate-unit{flex:none;padding-right:10px;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}
201
194
  .coreness-gate-input:disabled{opacity:.6;cursor:default}
202
- .coreness-gate-check{align-items:center;gap:8px;display:inline-flex;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}
195
+ .coreness-gate-check{align-items:center;gap:8px;display:inline-flex;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}
203
196
  .coreness-gate-check input{margin:0}
204
197
  /* A hint line: the host's own rule for one is label-tertiary at 12px/18px (a:dsh-client-ui-settings-models,
205
198
  its advancedHint rule; the plugin manager's guideHint, registryHint, templateHint). This one carried
@@ -222,14 +215,14 @@ window.__ModuleLoader__.load({
222
215
  is not touched, which is why this is an experiment rather than a uniform rule. */
223
216
  .coreness-gate-foot{justify-content:flex-end;align-items:center;gap:8px;flex-wrap:wrap;display:flex}
224
217
  .coreness-gate-actions{align-items:center;gap:8px;display:inline-flex}
225
- .coreness-gate-button{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:32px;font:inherit;cursor:pointer;border:.5px solid var(--dsw-alias-border-l3);background:0 0;color:var(--dsw-alias-label-primary);justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}
218
+ .coreness-gate-button{box-sizing:border-box;border-radius:var(--dsw-radius-md);font:inherit;cursor:pointer;border:.5px solid var(--dsw-alias-border-l3);background:0 0;color:var(--dsw-alias-label-primary);justify-content:center;align-items:center;gap:4px;padding:6px 14px;font-size:14px;line-height:22px;display:inline-flex}
226
219
  .coreness-gate-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
227
220
  .coreness-gate-button[data-tone='primary']{border:none;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}
228
221
  .coreness-gate-button[data-tone='primary']:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
229
222
  .coreness-gate-button[data-tone='danger']{border:none;color:var(--dsw-alias-state-error-primary);background:0 0}
230
223
  .coreness-gate-button:disabled{opacity:.4;cursor:default}
231
- .coreness-gate-button:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none}
232
- .coreness-gate-add{box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);height:32px;flex:1 1 0;min-width:180px;gap:6px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
224
+ .coreness-gate-button:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
225
+ .coreness-gate-add{box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);flex:1 1 0;min-width:180px;gap:6px;padding:6px 14px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
233
226
  .coreness-gate-addRow{margin-top:12px;display:flex}
234
227
  .coreness-gate-add:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
235
228
  @media (prefers-reduced-motion:reduce){.coreness-gate *{transition:none}}
@@ -247,7 +240,12 @@ window.__ModuleLoader__.load({
247
240
  * keeps the honest skeleton, because a skeleton is the truth when nothing is known. It holds a read
248
241
  * picture and never the visit's own state — the draft, the open row, the news of a press.
249
242
  */
250
- let lastRead = null;
243
+ /* The holder's name is the section's own; nothing else on the page writes under it. */
244
+ const HELD = '__corenessGateLastRead';
245
+ const lastRead = {
246
+ get: () => (typeof window === 'undefined' ? null : window[HELD] ?? null),
247
+ set: (picture) => { if (typeof window !== 'undefined') window[HELD] = picture; },
248
+ };
251
249
 
252
250
  /**
253
251
  * The section's sheet, held for as long as the section stands: put in here, put back if it is taken.
@@ -392,7 +390,9 @@ window.__ModuleLoader__.load({
392
390
  function rowsOps(draft, read) {
393
391
  /* The rows are compared field by field and not as two JSON strings: the same row built twice can
394
392
  carry its keys in another order, and a press that changed nothing must not write the list again. */
395
- const flat = (rows) => rows.map((one) => [one.provider, one.model, one.tokensPerMinute, one.thresholdRatio ?? null, one.dryRun ?? null]);
393
+ /* A row is compared by what a row may now name: the pair, the ceiling, the dry-run flag. The caution is the
394
+ section's own value and no longer a row's, so it takes no part in deciding whether a write is needed. */
395
+ const flat = (rows) => rows.map((one) => [one.provider, one.model, one.tokensPerMinute, one.dryRun ?? null]);
396
396
  return JSON.stringify(flat(draft.rows)) === JSON.stringify(flat(read.rows))
397
397
  ? []
398
398
  : [rowsOp(draft.rows.map((one) => {
@@ -401,7 +401,6 @@ window.__ModuleLoader__.load({
401
401
  numbers are left out when they are not chosen, so a change of the section's own number still
402
402
  reaches the row — the schema declares them optional for exactly that reason. */
403
403
  const row = { provider: one.provider, model: one.model, tokensPerMinute: one.tokensPerMinute };
404
- if (one.thresholdRatio !== null && one.thresholdRatio !== undefined && one.thresholdRatio !== '') row.thresholdRatio = one.thresholdRatio;
405
404
  if (one.dryRun !== null && one.dryRun !== undefined) row.dryRun = one.dryRun === true;
406
405
  return row;
407
406
  }))];
@@ -438,11 +437,14 @@ window.__ModuleLoader__.load({
438
437
  * room, not a missing one.
439
438
  */
440
439
  function noteOf(view) {
441
- if (view.status === 'loading') return { tone: null, text: t('state.loading') };
440
+ /* A read in flight says nothing: the load is background here, as on the other pages of this page, and a word
441
+ that only reports it would either reserve a line forever or move the cards once for no news. */
442
+ if (view.status === 'loading') return { tone: null, text: '' };
442
443
  if (view.status === 'failed') return { tone: 'error', text: `${t('state.error')} — ${view.fault}` };
443
444
  if (view.settings !== null && view.settings.ok === false) return { tone: 'warn', text: t('state.unwritable', { fault: view.settings.fault }) };
444
- const rows = view.picture?.rows ?? [];
445
- return { tone: null, text: rows.length === 0 ? '' : t('state.standing') };
445
+ /* The sentence about the window is no longer this room's: it left for the caption, which is drawn in every
446
+ state. The room keeps the read's own words — a read in flight, a failed read, a row nobody may write. */
447
+ return { tone: null, text: '' };
446
448
  }
447
449
 
448
450
  /** The chevron of a disclosure, as the host draws it. */
@@ -670,19 +672,6 @@ window.__ModuleLoader__.load({
670
672
  onPatch: (value) => act.patchRow(index, { tokensPerMinute: value }),
671
673
  }),
672
674
  ]),
673
- h('div', { key: 'optional', className: 'coreness-gate-pair' }, [
674
- numberField({
675
- id: `coreness-gate-row${index}-ratio`,
676
- key: 'rowRatio',
677
- word: t('field.rowRatio'),
678
- name: `${t('field.rowRatio')} (${pairOf(held)})`,
679
- value: held.thresholdRatio,
680
- disabled: busy,
681
- unit: null,
682
- hint: null,
683
- onPatch: (value) => act.patchRow(index, { thresholdRatio: value }),
684
- }),
685
- ]),
686
675
  h('div', { key: 'foot', className: 'coreness-gate-rowFoot' }, [
687
676
  h('button', {
688
677
  key: 'remove',
@@ -735,7 +724,9 @@ window.__ModuleLoader__.load({
735
724
  return h('div', { className: 'coreness-gate' }, [
736
725
  h('div', { key: 'head', className: 'coreness-gate-head' }, [
737
726
  h('h1', { key: 'title', className: 'coreness-gate-title' }, t('title')),
738
- h('p', { key: 'caption', className: 'coreness-gate-caption' }, t('caption')),
727
+ /* The caption carries the surface's own sentence as well: a property of the section stands in every state, so
728
+ it is drawn always — and a line that is always drawn moves nothing when a press answers. */
729
+ h('p', { key: 'caption', className: 'coreness-gate-caption' }, [t('caption'), ' ', t('state.standing')]),
739
730
  ]),
740
731
  /* The one room the section's own sentence stands in, in every state, so a refusal or a conflict
741
732
  arriving never moves the cards under it. */
@@ -752,21 +743,21 @@ window.__ModuleLoader__.load({
752
743
  live,
753
744
  })),
754
745
  ]),
755
- /* The control stands even when no row is named: an empty gate with no way to name the first model
756
- is a section that can only be written by hand. It waits for the catalogue, which is the door the
757
- pairs come through. */
758
- view.status !== 'ready'
759
- ? null
760
- : h('div', { key: 'add', className: 'coreness-gate-addRow' }, [
761
- h('button', {
762
- key: 'add',
763
- type: 'button',
764
- className: 'coreness-gate-add',
765
- disabled: busy || view.catalog === null,
766
- onClick: () => acting.addRow(),
767
- }, view.catalog === null ? t('add.reading') : t('control.add')),
768
- view.catalog !== null && pairsOf(view.catalog).length === 0 ? h('span', { key: 'none', className: 'coreness-gate-dim' }, t('add.none')) : null,
769
- ]),
746
+ /* The control stands in **every** state, the loading one included: it waits for the catalogue by being
747
+ disabled and saying so, not by being absent. The first frame therefore holds the same tile the ready frame
748
+ holds, which is what keeps the card and the section from moving when the read lands — measured on the
749
+ section: the frame without the tile read 514 tall with the first card's top at 154, the frame with it 540
750
+ and 136, on the first visit and on a return alike. */
751
+ h('div', { key: 'add', className: 'coreness-gate-addRow' }, [
752
+ h('button', {
753
+ key: 'add',
754
+ type: 'button',
755
+ className: 'coreness-gate-add',
756
+ disabled: busy || view.catalog === null,
757
+ onClick: () => acting.addRow(),
758
+ }, view.catalog === null ? t('add.reading') : t('control.add')),
759
+ view.catalog !== null && pairsOf(view.catalog).length === 0 ? h('span', { key: 'none', className: 'coreness-gate-dim' }, t('add.none')) : null,
760
+ ]),
770
761
  /* The per-provider sum and the list of rows the gate would not use stood here and left by the user's word:
771
762
  "про rate limiter в самом низу какой-то мусор… это всё мусор". What a reader loses with them is named
772
763
  where the decision is recorded (the page's README): the sum of the ceilings a key faces, and the one place
@@ -788,6 +779,8 @@ window.__ModuleLoader__.load({
788
779
  plugin over the module table it already has and takes the package's style tags with it, so the mount
789
780
  is the one place a sheet can be put back. */
790
781
  useStyles();
782
+ /* What was read in this session, if anything: the frame the mount paints before its own read answers. */
783
+ const held = lastRead.get();
791
784
  const [state, setState] = React.useState(() => ({
792
785
  view: {
793
786
  status: 'loading',
@@ -803,14 +796,14 @@ window.__ModuleLoader__.load({
803
796
  totals: totalsOf(null),
804
797
  },
805
798
  /* The picture and nothing else: what the file said, not what this visit did with it. */
806
- ...(lastRead === null ? {} : {
807
- status: lastRead.picture.status,
808
- picture: lastRead.picture.picture,
809
- settings: lastRead.picture.settings,
810
- read: lastRead.picture.read,
811
- catalog: lastRead.picture.catalog,
812
- totals: lastRead.picture.totals,
813
- fault: lastRead.picture.fault,
799
+ ...(held === null ? {} : {
800
+ status: held.picture.status,
801
+ picture: held.picture.picture,
802
+ settings: held.picture.settings,
803
+ read: held.picture.read,
804
+ catalog: held.picture.catalog,
805
+ totals: held.picture.totals,
806
+ fault: held.picture.fault,
814
807
  }),
815
808
  }));
816
809
  const view = state.view;
@@ -820,7 +813,7 @@ window.__ModuleLoader__.load({
820
813
  what the reader comes back for. Only a read that answered is remembered — a skeleton is not
821
814
  knowledge, and a read that failed keeps what stands and names the reason with its own branch. */
822
815
  React.useEffect(() => {
823
- if (state.view.status === 'ready') lastRead = { picture: state.view, at: Date.now() };
816
+ if (state.view.status === 'ready') lastRead.set({ picture: state.view, at: Date.now() });
824
817
  }, [state]);
825
818
 
826
819
  /**
@@ -918,7 +911,9 @@ window.__ModuleLoader__.load({
918
911
  const pair = pairOf(draft.rows[index] ?? {});
919
912
  const ops = rowsOps(left, view.read);
920
913
  if (ops.length === 0) {
921
- setState((now) => ({ view: { ...now.view, draft: left, note: { tone: 'done', text: t('state.nothing'), title: t('reason.removed', { pair }), at: null } } }));
914
+ /* The press removed the only card that could carry this word, and the section's card is not its place:
915
+ it answers presses made in it — so the row leaves in silence, and the leaving is the answer. */
916
+ setState((now) => ({ view: { ...now.view, draft: left, note: null } }));
922
917
  return;
923
918
  }
924
919
  setState((now) => ({ view: { ...now.view, draft: left, busy: true, note: null } }));
@@ -928,11 +923,13 @@ window.__ModuleLoader__.load({
928
923
  its own word and its reason. */
929
924
  setState((now) => ({ view: { ...now.view, busy: false, draft: null, note: null } }));
930
925
  await read();
931
- setState((now) => ({ view: { ...now.view, note: { tone: 'error', text: t('state.refused'), title: written.reason ?? written.fault, at: null } } }));
926
+ setState((now) => ({ view: { ...now.view, note: { tone: 'error', text: t('state.refused'), title: written.reason ?? written.fault, at: index } } }));
932
927
  return;
933
928
  }
934
929
  await read();
935
- setState((now) => ({ view: { ...now.view, busy: false, draft: null, note: { tone: 'done', text: t('state.written'), title: null, at: null } } }));
930
+ /* The row is gone from the record and from the page: the word has no card to stand in, so it stands
931
+ nowhere. What wrote is the row's own leaving, which the reader sees. */
932
+ setState((now) => ({ view: { ...now.view, busy: false, draft: null, note: null } }));
936
933
  },
937
934
  apply: async (place) => {
938
935
  const settings = view.settings;
@@ -63,7 +63,10 @@ window.__ModuleLoader__.load({
63
63
  const CHUNKS = ['./client.mcp.js', './client.provider.js', './client.credits.js', './client.gate.js'];
64
64
  /** The locale namespace the visible text is taken from. */
65
65
  const NS = 'coreness';
66
- const STYLE_ID = 'coreness-page-style';
66
+ /* Two names the page used are not resolved by the running product, measured live: `--dsw-focus-ring-color` and
67
+ `--dsw-font-mono`. The platform's own CSS writes both the same way, with the same fallbacks, so this is not a
68
+ copy error — but the fallback is what draws, so the dead wrapper is gone and only the real value is named. */
69
+ const STYLE_ID = 'coreness-page-style';
67
70
  /** The module id the sheet is marked with, as the host marks its own (`data-plugin-css`). */
68
71
  const STYLE_TAG = '@coreness/dsh/client';
69
72
  /** The product's own section, drawn here instead of registered into the page's own seat. */
@@ -102,14 +105,18 @@ window.__ModuleLoader__.load({
102
105
  line carries the tabular figures the host puts on the version tag and on its other count line
103
106
  but not on this one (`:1701`). */
104
107
  const CSS = `
108
+ /* The page's own elevation, named once. The platform's --dsw-elevation-prominent is three layers with a hairline
109
+ ring; this page's surfaces were one soft shadow, and that is a different look, chosen on purpose — so the page names
110
+ its own instead of borrowing the platform's. The MCP dialog keeps the platform's token: it stood on it before. */
111
+ :root{--coreness-elevation:0 12px 32px #0000002e}
105
112
  .coreness-page{display:flex;height:100%;box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary)}
106
113
  .coreness-nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;padding:22px 12px 0;display:flex}
107
114
  .coreness-navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}
108
115
  .coreness-navList{flex-direction:column;gap:4px;display:flex;overflow-y:auto}
109
- .coreness-navCell{box-sizing:border-box;border-radius:var(--dsw-radius-md);cursor:pointer;height:40px;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;align-items:center;gap:8px;padding:9px 16px 9px 12px;font-family:inherit;font-size:14px;font-weight:400;line-height:22px;display:flex}
116
+ .coreness-navCell{box-sizing:border-box;border-radius:var(--dsw-radius-md);cursor:pointer;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;align-items:center;gap:8px;padding:9px 16px 9px 12px;font-family:inherit;font-size:14px;font-weight:400;line-height:22px;display:flex}
110
117
  .coreness-navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}
111
118
  .coreness-navCell[aria-current='true']{background:var(--dsw-specific-sidebar-nav-item-active)}
112
- .coreness-navCell:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
119
+ .coreness-navCell:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
113
120
  .coreness-pane{box-sizing:border-box;flex-direction:column;flex:1;align-items:center;gap:32px;min-width:0;padding:0 clamp(24px,4vw,48px) 48px;display:flex;overflow:auto;scrollbar-gutter:stable}
114
121
  /* One named departure from the host's own page frame, which is this rule taken over whole
115
122
  (a:dsh-client-ui-plugin-manager, its .page rule and its .page>* rule): the gutter of the scrollbar is
@@ -136,7 +143,7 @@ window.__ModuleLoader__.load({
136
143
  .coreness-card{flex-direction:column;gap:8px;min-width:0;display:flex}
137
144
  .coreness-titleRow{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;display:flex}
138
145
  .coreness-title{margin:0;font-size:20px;font-weight:500;line-height:28px}
139
- .coreness-tag{display:inline-flex;align-items:center;border-radius:999px;corner-shape:round;padding:1px 6px;font-size:11px;line-height:16px;font-weight:400;white-space:nowrap;color:var(--dsw-alias-label-tertiary)}
146
+ .coreness-tag{display:inline-flex;align-items:center;border-radius:var(--dsw-radius-xs);corner-shape:round;padding:1px 6px;font-size:12px;line-height:18px;font-weight:400;white-space:nowrap;color:var(--dsw-alias-label-tertiary)}
140
147
  /* A tone names the chip's own background. The colour of its words belongs to the chip's own class beside it,
141
148
  and this rule used to set it too — with an attribute selector (0,2,0) it won over the class (0,1,0), so the
142
149
  product's chip read in the secondary grey while the three chips in the other sections read in the tertiary
@@ -144,7 +151,7 @@ window.__ModuleLoader__.load({
144
151
  .coreness-tag[data-tone='neutral']{background:var(--dsw-alias-bg-module-platform)}
145
152
  .coreness-version{font-variant-numeric:tabular-nums;flex:none}
146
153
  .coreness-name{color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;margin:0;font-size:14px;line-height:22px}
147
- .coreness-name code{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace)}
154
+ .coreness-name code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
148
155
  .coreness-desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:14px;line-height:22px}
149
156
  /* The block under the product's own card stands at the same distance as a card inside a section: 12, the number
150
157
  the whole page names for "a card to what follows it". It was 32 — the pane's own distance between the page's
@@ -157,7 +164,7 @@ window.__ModuleLoader__.load({
157
164
  .coreness-sectionHead{align-items:baseline;gap:10px;display:flex}
158
165
  .coreness-sectionTitle{margin:0;font-size:16px;font-weight:500;line-height:24px}
159
166
  .coreness-sectionCount{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
160
- .coreness-status,.coreness-failure p{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}
167
+ .coreness-status,.coreness-failure p{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}
161
168
  .coreness-failure{color:var(--dsw-alias-state-error-primary);flex-direction:column;gap:4px;display:flex}
162
169
  .coreness-withDot{align-items:center;gap:6px;display:inline-flex}
163
170
  .coreness-dot{position:relative;display:inline-block;flex:none;width:10px;height:10px}
@@ -206,18 +206,15 @@ window.__ModuleLoader__.load({
206
206
  const CSS = `
207
207
  .coreness-mcp{flex-direction:column;min-width:0;padding-top:28px;display:flex}
208
208
  .coreness-mcp-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}
209
- /* The section's own state sentence: the host's rule for one, word for word
210
- (a:dsh-client-ui-plugin-manager: status, failure p, empty — label-tertiary at 13px/20px). */
209
+ /* The section's own state sentence: the page's small role, 12/18 in label-tertiary, as everywhere else. */
211
210
  .coreness-mcp-status{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
212
- /* A hint line: the host's own rule for one is label-tertiary at 12px/18px (a:dsh-client-ui-settings-models,
213
- its advancedHint rule; the plugin manager's guideHint, registryHint, templateHint). It stood at 13/20
214
- here, which is the status rule above and not the hint rule. */
211
+ /* A hint line: the page's small role, 12/18 in label-tertiary — the same pair the status line above takes. */
215
212
  .coreness-mcp-dim{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
216
213
  /* The word that names the list, in the weight that names rather than explains: the page's own rule for a small line
217
214
  (naming is 500, explaining is 400), and the reason the label is set apart from the names it introduces. */
218
215
  .coreness-mcp-toolsLabel{font-weight:500}
219
216
  .coreness-mcp-failed{color:var(--dsw-alias-state-error-primary);flex-direction:column;gap:4px;display:flex}
220
- .coreness-mcp-failed p{margin:0;font-size:13px;line-height:20px}
217
+ .coreness-mcp-failed p{margin:0;font-size:14px;line-height:22px}
221
218
  .coreness-mcp-warn{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}
222
219
  .coreness-mcp-error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}
223
220
  .coreness-mcp-done{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}
@@ -227,15 +224,15 @@ window.__ModuleLoader__.load({
227
224
  .coreness-mcp-gone{color:var(--dsw-alias-label-tertiary);margin:0;padding:0 2px;font-size:12px;line-height:18px}
228
225
  .coreness-mcp-gone[data-tone='done']{color:var(--dsw-alias-state-success-primary)}
229
226
  .coreness-mcp-gone[data-tone='fault']{color:var(--dsw-alias-state-error-primary)}
230
- .coreness-mcp-trouble{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-state-warn-label);overflow-wrap:anywhere}
227
+ .coreness-mcp-trouble{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-state-warn-label);overflow-wrap:anywhere}
231
228
  .coreness-mcp-trouble[data-level='error']{color:var(--dsw-alias-state-error-primary)}
232
- .coreness-mcp-trouble code{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12px}
229
+ .coreness-mcp-trouble code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
233
230
  .coreness-mcp-rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}
234
231
  .coreness-mcp-row{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);flex-direction:column;gap:12px;padding:12px 14px;display:flex}
235
232
  .coreness-mcp-head{align-items:center;gap:10px;flex-wrap:nowrap;display:flex}
236
233
  /* The right side of the head: the reserved strip over the controls, both flush to the card's right edge, the column itself pinned to the head's top so the answer stands on the name's line. The numbers are the head's own: 18 (the strip) + 2 (this gap) + 28 (the controls) = 48, and the head keeps its 60 px from the left side. */
237
234
  .coreness-mcp-name{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
238
- .coreness-mcp-tag{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);color:var(--dsw-alias-label-tertiary);flex:none;padding:1px 6px;font-size:11px;line-height:16px}
235
+ .coreness-mcp-tag{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);color:var(--dsw-alias-label-tertiary);flex:none;padding:1px 6px;font-size:12px;line-height:18px}
239
236
  .coreness-mcp-actions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}
240
237
  /* The strip an answer to a press lives in: the free place in the head, on the right, reserved before the
241
238
  answer arrives so that nothing else in the head moves when it comes or goes. It takes the room that is
@@ -255,20 +252,24 @@ window.__ModuleLoader__.load({
255
252
  .coreness-mcp-answer.coreness-mcp-done{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
256
253
  .coreness-mcp-answer.coreness-mcp-error{color:var(--dsw-alias-state-error-primary,var(--dsw-alias-label-primary))}
257
254
  .coreness-mcp-body{flex-direction:column;gap:8px;min-width:0;display:flex}
258
- .coreness-mcp-line{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
259
- .coreness-mcp-line code{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12px}
255
+ .coreness-mcp-line{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
256
+ .coreness-mcp-line code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
260
257
  .coreness-mcp-pairs{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}
261
258
  .coreness-mcp-pair{align-items:center;gap:8px;display:flex}
262
259
  .coreness-mcp-pairName{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
263
260
  .coreness-mcp-pairValue{color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
264
261
  .coreness-mcp-dot[data-tone='on']{background:var(--dsw-alias-state-success-primary)}
265
262
  .coreness-mcp-dot[data-tone='off']{background:var(--dsw-alias-state-error-primary)}
266
- .coreness-mcp-button{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:32px;font:inherit;cursor:pointer;border:.5px solid var(--dsw-alias-border-l3);background:0 0;color:var(--dsw-alias-label-primary);justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}
263
+ .coreness-mcp-button{box-sizing:border-box;border-radius:var(--dsw-radius-md);font:inherit;cursor:pointer;border:.5px solid var(--dsw-alias-border-l3);background:0 0;color:var(--dsw-alias-label-primary);justify-content:center;align-items:center;gap:4px;padding:6px 14px;font-size:14px;line-height:22px;display:inline-flex}
267
264
  .coreness-mcp-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
268
265
  .coreness-mcp-button[data-tone='primary']{border:none;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}
269
266
  .coreness-mcp-button[data-tone='primary']:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
270
267
  .coreness-mcp-button[data-tone='danger']{border:none;color:var(--dsw-alias-state-error-primary);background:0 0}
271
268
  .coreness-mcp-button[data-tone='danger']:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
269
+ /* The small control's role, named as the platform names its own: .sm{height:28px;font-size:12px;line-height:18px}
270
+ (a:dsh-client-ui-primitives/lib/Button.module.css). It is written as a height and not as a padding because the page's
271
+ own scale of distances has no 5 — 18 + 5 + 5 is the platform's arithmetic, and the platform states its result, not
272
+ the sum. Any control of this page that is small takes this role. */
272
273
  .coreness-mcp-actions .coreness-mcp-button{border-radius:var(--dsw-radius-sm);height:28px;padding:0 10px;font-size:12px;line-height:18px}
273
274
  .coreness-mcp-button:disabled{opacity:.4;cursor:default}
274
275
  /* The ring is for the controls that carry no border of their own — a button, the dashed "add" box. A text field
@@ -276,8 +277,8 @@ window.__ModuleLoader__.load({
276
277
  platform does and what Qoder's field does. With the field in this list it wore **both** — a 2 px shadow ring
277
278
  over a coloured border — and the user read it as "too thick a blue band, twice the platform's, while Qoder's
278
279
  looks right" (measured: ours a 2 px box-shadow plus a .5 px border; Qoder's the .5 px border alone). */
279
- .coreness-mcp-button:focus-visible,.coreness-mcp-add:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none}
280
- .coreness-mcp-add{box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);height:32px;flex:1 1 0;min-width:180px;gap:6px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
280
+ .coreness-mcp-button:focus-visible,.coreness-mcp-add:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
281
+ .coreness-mcp-add{box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);flex:1 1 0;min-width:180px;gap:6px;padding:6px 14px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
281
282
  /* The tile's row carries the distance to the card above it: the user chose 16 over 12 for this tile (the list's
282
283
  own card-to-card distance stays 8, which is the list's gap and not this row's margin). */
283
284
  .coreness-mcp-addRow{margin-top:12px;display:flex}
@@ -288,7 +289,7 @@ window.__ModuleLoader__.load({
288
289
  .coreness-mcp-editorNote{flex:none;text-align:right;font-size:12px;line-height:18px}
289
290
  .coreness-mcp-field{flex-direction:column;gap:6px;display:flex}
290
291
  .coreness-mcp-fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}
291
- .coreness-mcp-input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:0 10px;font-size:14px;line-height:22px}
292
+ .coreness-mcp-input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:6px 10px;font-size:14px;line-height:22px}
292
293
  .coreness-mcp-input:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}
293
294
  .coreness-mcp-input::placeholder{color:var(--dsw-alias-label-dimmed)}
294
295
  .coreness-mcp-input:disabled{opacity:.6;cursor:default}
@@ -313,7 +314,7 @@ textarea.coreness-mcp-input{height:auto;min-height:64px;padding:6px 10px;resize:
313
314
  beside it, the marker at the side, and the body only while it is open — the donor's own Card. */
314
315
  .coreness-mcp-card{flex-direction:column;display:flex}
315
316
  .coreness-mcp-headButton{box-sizing:border-box;border:none;background:0 0;color:inherit;font:inherit;text-align:left;padding:0;cursor:pointer;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto;display:flex}
316
- .coreness-mcp-headButton:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none;border-radius:var(--dsw-radius-md)}
317
+ .coreness-mcp-headButton:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none;border-radius:var(--dsw-radius-md)}
317
318
  /* The subtitle carries the row's target and its state, so it is the one line of this card a long
318
319
  sentence could grow — and the card with it. Two lines are the reserved room, the same reservation the
319
320
  Qoder section's state area keeps; the rest of the sentence scrolls inside it and the words stay whole
@@ -338,14 +339,14 @@ textarea.coreness-mcp-input{height:auto;min-height:64px;padding:6px 10px;resize:
338
339
  .coreness-mcp-marker svg{transition:transform .15s ease}
339
340
  .coreness-mcp-marker[data-open='true'] svg{transform:rotate(180deg)}
340
341
  .coreness-mcp-menu{position:relative;display:inline-flex}
341
- .coreness-mcp-menuList{position:absolute;z-index:30;top:calc(100% + 4px);right:0;min-width:140px;background:var(--dsw-alias-bg-layer-3,#fff);border:.5px solid var(--dsw-alias-border-l2,#0000001f);border-radius:12px;box-shadow:0 12px 32px #0000002e;padding:4px;flex-direction:column;display:flex}
342
- .coreness-mcp-menuItem{box-sizing:border-box;border:none;border-radius:var(--dsw-radius-md);background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;padding:6px 10px;cursor:pointer;text-align:left}
342
+ .coreness-mcp-menuList{position:absolute;z-index:30;top:calc(100% + 4px);right:0;min-width:140px;background:var(--dsw-alias-bg-layer-3,#fff);border:.5px solid var(--dsw-alias-border-l2,#0000001f);border-radius:var(--dsw-radius-md);box-shadow:var(--coreness-elevation);padding:4px;flex-direction:column;display:flex}
343
+ .coreness-mcp-menuItem{box-sizing:border-box;border:none;border-radius:var(--dsw-radius-md);background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;padding:6px 10px;cursor:pointer;text-align:left}
343
344
  .coreness-mcp-menuItem:hover:not(:disabled),.coreness-mcp-menuItem:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}
344
345
  .coreness-mcp-menuItem:focus-visible{outline:none}
345
346
  .coreness-mcp-menuItem[data-tone='danger']{color:var(--dsw-alias-state-error-primary)}
346
347
  .coreness-mcp-menuItem:disabled{opacity:.4;cursor:default}
347
348
  .coreness-mcp-toolList{flex-direction:column;gap:2px;margin:0;padding:0;list-style:none;display:flex}
348
- .coreness-mcp-toolItem{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
349
+ .coreness-mcp-toolItem{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
349
350
  @media (prefers-reduced-motion:reduce){.coreness-mcp *{transition:none}}
350
351
  `;
351
352
 
@@ -176,13 +176,13 @@ window.__ModuleLoader__.load({
176
176
  takes the place the platform's `select` would have taken in the field it stands in. */
177
177
  const CSS = `
178
178
  .coreness-picker{position:relative;display:block}
179
- .coreness-pickerTrigger{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:0 8px 0 10px;font-size:14px;line-height:22px;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;display:flex;text-align:left}
180
- .coreness-pickerTrigger:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none}
179
+ .coreness-pickerTrigger{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:6px 8px 6px 10px;font-size:14px;line-height:22px;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;display:flex;text-align:left}
180
+ .coreness-pickerTrigger:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
181
181
  .coreness-pickerTrigger:disabled{opacity:.6;cursor:default}
182
182
  .coreness-pickerTrigger[aria-invalid='true']{border-color:var(--dsw-alias-state-error-primary)}
183
183
  .coreness-pickerValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
184
184
  .coreness-pickerArrow{color:var(--dsw-alias-label-secondary);flex:none;display:inline-flex}
185
- .coreness-pickerList{position:absolute;z-index:30;top:calc(100% + 4px);left:0;right:0;max-height:240px;overflow-y:auto;background:var(--dsw-alias-bg-layer-3,#fff);border:.5px solid var(--dsw-alias-border-l2,#0000001f);border-radius:12px;box-shadow:0 12px 32px #0000002e;padding:4px;flex-direction:column;display:flex}
185
+ .coreness-pickerList{position:absolute;z-index:30;top:calc(100% + 4px);left:0;right:0;max-height:240px;overflow-y:auto;background:var(--dsw-alias-bg-layer-3,#fff);border:.5px solid var(--dsw-alias-border-l2,#0000001f);border-radius:var(--dsw-radius-md);box-shadow:var(--coreness-elevation);padding:4px;flex-direction:column;display:flex}
186
186
  .coreness-pickerItem{box-sizing:border-box;border:none;border-radius:var(--dsw-radius-md);background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;padding:6px 8px;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;display:flex;text-align:left}
187
187
  .coreness-pickerItem:hover,.coreness-pickerItem[data-active='true']{background:var(--dsw-alias-interactive-bg-hover)}
188
188
  .coreness-pickerWords{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -252,13 +252,10 @@ window.__ModuleLoader__.load({
252
252
  .coreness-qoder{flex-direction:column;min-width:0;padding-top:28px;display:flex}
253
253
  .coreness-qoder-title{margin:0;font-size:16px;font-weight:500;line-height:24px;color:var(--dsw-alias-label-primary)}
254
254
  .coreness-qoder-intro{color:var(--dsw-alias-label-tertiary);margin:8px 0 0;font-size:14px;line-height:22px}
255
- /* A hint line: the small explanation under a control, and it is the host's own rule for one
256
- (a:dsh-client-ui-settings-models, its advancedHint rule: label-tertiary at 12px/18px, and the same shape
257
- in the plugin manager's guideHint, registryHint and templateHint). It stood at 13/20 here, which is the
258
- host's *status* rule (a:dsh-client-ui-plugin-manager: status, failure p, empty) and not its hint rule. */
255
+ /* A hint line: the page's small role, one pair for it everywhere — 12/18 in label-tertiary. */
259
256
  .coreness-qoder-dim{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
260
257
  .coreness-qoder-failed{color:var(--dsw-alias-state-error-primary);flex-direction:column;gap:4px;display:flex}
261
- .coreness-qoder-failed p{margin:0;font-size:13px;line-height:20px}
258
+ .coreness-qoder-failed p{margin:0;font-size:14px;line-height:22px}
262
259
  .coreness-qoder-card{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);flex-direction:column;gap:12px;margin-top:12px;padding:12px 14px;display:flex}
263
260
  .coreness-qoder-card+.coreness-qoder-card{margin-top:8px}
264
261
  .coreness-qoder-head{align-items:center;gap:10px;flex-wrap:wrap;display:flex}
@@ -295,22 +292,22 @@ window.__ModuleLoader__.load({
295
292
  in the title, and a state — a verdict, Connected: …, nabled — never comes here: a state has its own
296
293
  room and its own genre. */
297
294
  .coreness-qoder-answer{flex:0 0 160px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;font-size:12px;line-height:18px}
298
- .coreness-qoder-tag{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);color:var(--dsw-alias-label-tertiary);flex:none;padding:1px 6px;font-size:11px;line-height:16px}
299
- .coreness-qoder-line{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
295
+ .coreness-qoder-tag{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);color:var(--dsw-alias-label-tertiary);flex:none;padding:1px 6px;font-size:12px;line-height:18px}
296
+ .coreness-qoder-line{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
300
297
  /* In a card's header the line that names the node keeps its own room and no more: a state beside it may
301
298
  not squeeze it into a second line, which would grow the header — and the card — by that line. */
302
299
  .coreness-qoder-card > .coreness-qoder-head > .coreness-qoder-line{flex:0 0 auto;max-width:42%}
303
- .coreness-qoder-line code{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:12px}
300
+ .coreness-qoder-line code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
304
301
  .coreness-qoder-dot{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}
305
302
  .coreness-qoder-dot[data-tone='on']{background:var(--dsw-alias-state-success-primary)}
306
303
  .coreness-qoder-dot[data-tone='off']{background:var(--dsw-alias-state-error-primary)}
307
304
  .coreness-qoder-field{flex-direction:column;gap:6px;display:flex}
308
305
  .coreness-qoder-fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}
309
- .coreness-qoder-input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:0 10px;font-size:14px;line-height:22px}
306
+ .coreness-qoder-input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:6px 10px;font-size:14px;line-height:22px}
310
307
  .coreness-qoder-input:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}
311
308
  .coreness-qoder-input::placeholder{color:var(--dsw-alias-label-dimmed)}
312
309
  .coreness-qoder-input:disabled{opacity:.6;cursor:default}
313
- .coreness-qoder-button{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:32px;font:inherit;cursor:pointer;border:.5px solid var(--dsw-alias-border-l3);background:0 0;color:var(--dsw-alias-label-primary);justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}
310
+ .coreness-qoder-button{box-sizing:border-box;border-radius:var(--dsw-radius-md);font:inherit;cursor:pointer;border:.5px solid var(--dsw-alias-border-l3);background:0 0;color:var(--dsw-alias-label-primary);justify-content:center;align-items:center;gap:4px;padding:6px 14px;font-size:14px;line-height:22px;display:inline-flex}
314
311
  .coreness-qoder-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
315
312
  .coreness-qoder-button[data-tone='primary']{border:none;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}
316
313
  .coreness-qoder-button[data-tone='primary']:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
@@ -328,9 +325,9 @@ window.__ModuleLoader__.load({
328
325
  .coreness-qoder-model{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-lg);flex-direction:column;gap:10px;padding:12px 14px;display:flex}
329
326
  .coreness-qoder-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px}
330
327
  .coreness-qoder-inputs{flex-wrap:wrap;align-items:center;gap:12px;display:flex}
331
- .coreness-qoder-check{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;font-size:13px;line-height:20px;display:inline-flex}
328
+ .coreness-qoder-check{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;font-size:14px;line-height:22px;display:inline-flex}
332
329
  .coreness-qoder-check input{margin:0}
333
- .coreness-qoder-add{box-sizing:border-box;border:.5px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);height:32px;flex:1 1 0;gap:6px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
330
+ .coreness-qoder-add{box-sizing:border-box;border:.5px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);flex:1 1 0;gap:6px;padding:6px 14px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}
334
331
  .coreness-qoder-add:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
335
332
  .coreness-qoder-add:disabled{opacity:.4;cursor:default}
336
333
  .coreness-qoder-foot{justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;display:flex}
@@ -350,7 +347,9 @@ window.__ModuleLoader__.load({
350
347
  round trip, and an animation would owe this sheet a stilling rule that the prefers-reduced-motion block
351
348
  above does not give it. */
352
349
  .coreness-qoder-skeleton{background:var(--dsw-alias-bg-skeleton,var(--dsw-alias-interactive-bg-hover));border-color:transparent;border-radius:var(--dsw-radius-xs);color:transparent}
353
- .coreness-qoder-skeleton.coreness-qoder-line{height:20px;width:220px}
350
+ /* The height of the line this stands in for, and nothing else: .coreness-qoder-line is 14/22, so the swap from the
351
+ skeleton to the words moves nothing. A width is its own business — it says how much text is coming. */
352
+ .coreness-qoder-skeleton.coreness-qoder-line{height:22px;width:220px}
354
353
  .coreness-qoder-skeleton.coreness-qoder-button{width:120px}
355
354
  @media (prefers-reduced-motion:reduce){.coreness-qoder *{transition:none}}
356
355
  `;
@@ -35,6 +35,10 @@ A request built by the agent loop arrives deep-frozen and is only read (`a:dsh-a
35
35
  - **The window is a rolling one**, `windowMs` long, per pair, in process memory: entries carry their own moment and charge and leave the window as they age. Sessions, subagents, compaction and titles share it, because what a ceiling belongs to is the key or the account and not a session.
36
36
  - **Not counted: the route's own control calls.** The node sweep, the catalogue read, the token exchange and the credits read go to the vendor through the Qoder plugin's transport (`plugins/provider/qoder/in.js`) and never pass the model service, as no adapter's discovery call does. No request-per-minute ceiling of a vendor's is covered by this gate on any road.
37
37
 
38
+ ### Where the window lives
39
+
40
+ The window is a private field of the gate instance — one map keyed by the pair, `keyOf(provider, model)`, rather than a counter for the plugin — and an entry appears in it only where `GateLedger` admitted a request of its own: a pair no row names, and a request cancelled while it waited, leave the window as they found it. An entry carries its own moment and charge and leaves as it ages (`#prune`, `#timeUntilFree`), which is what the rolling window above is made of. Nothing of it reaches the disk — the row's numbers are the profile patch's, written through the platform's own settings service, while the spend is the instance's alone — so a restart re-reads the file and begins with an empty window, and the host half outliving the page is what keeps the count across a reload. The vendor's own counting is read nowhere here, so spend under the same key, from another machine or another person, is invisible to this window.
41
+
38
42
  ## At the ceiling
39
43
 
40
44
  - **A request the window has room for is admitted at once.** The ceiling is a budget, so a request that fits spends it rather than queueing behind one that does not; what waits is what the window cannot hold.