clearotron 0.3.2-beta.8 → 0.3.2-beta.9
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/.env.example +34 -3
- package/CONTRIBUTING.md +8 -8
- package/INSTALL.md +6 -6
- package/SECURITY.md +3 -3
- package/bin/brandowner.mjs +3 -3
- package/bin/framework-preflight.mjs +1 -1
- package/bin/start.mjs +18 -4
- package/build-info.json +2 -2
- package/docs/DELIVERY.md +2 -1
- package/docs/INTAKE.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/architecture/03-run-lifecycle.md +6 -6
- package/docs/architecture/04-configuration-reference.md +6 -3
- package/docs/architecture/05-config-governance.md +6 -1
- package/docs/architecture/05-customer-profiles.md +2 -2
- package/docs/architecture/06-operations-runbook.md +3 -3
- package/docs/architecture/08-development-guide.md +6 -6
- package/docs/configuration.md +5 -5
- package/docs/decisions/0003-credential-model.md +1 -1
- package/docs/writing-standard.md +4 -0
- package/driver/CHANGELOG.md +48 -0
- package/driver/README.md +3 -3
- package/driver/binding-layers.mjs +1 -1
- package/driver/citation-census.json +3 -3
- package/driver/{prelim-variants-record.mjs → clearance-variants-record.mjs} +24 -24
- package/driver/common-law-receipts.mjs +2 -2
- package/driver/company-bundle.mjs +3 -3
- package/driver/compose-read.mjs +8 -14
- package/driver/consumption-ledger.mjs +2 -2
- package/driver/contract-arm2-baseline.json +2 -3
- package/driver/contract-dictation-registry.mjs +19 -19
- package/driver/contract-e3-backlog.mjs +19 -19
- package/driver/contract-e3-baseline.json +14 -14
- package/driver/contract-vocabulary.mjs +24 -17
- package/driver/deliver-trigger.sh +16 -16
- package/driver/demo-container.mjs +3 -3
- package/driver/dev-portal.mjs +3 -3
- package/driver/disposition-call.mjs +1 -1
- package/driver/doubt-ledger.mjs +2 -2
- package/driver/drainer-identity.mjs +34 -8
- package/driver/driver.config.mjs +95 -45
- package/driver/engine/mcp/README.md +1 -1
- package/driver/engine/mcp/dispositions-server.mjs +3 -3
- package/driver/engine/mcp/gather-config.mjs +9 -9
- package/driver/engine/mcp/perplexity-server.mjs +2 -2
- package/driver/engine/mcp/recording-server.mjs +5 -5
- package/driver/enqueue-schema.mjs +6 -2
- package/driver/findings-model.mjs +5 -2
- package/driver/flag-snapshot.mjs +6 -3
- package/driver/form-neighbourhood.mjs +54 -7
- package/driver/framework.mjs +4 -4
- package/driver/gateway.mjs +12 -6
- package/driver/jx-lanes.mjs +2 -2
- package/driver/jx-units.mjs +1 -1
- package/driver/jx.mjs +30 -2
- package/driver/knockout-review-record.mjs +56 -4
- package/driver/known-conflicts.mjs +1 -1
- package/driver/named-band.mjs +1 -33
- package/driver/ordinary-words.mjs +51 -0
- package/driver/outbox-backoff.mjs +31 -16
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +2 -2
- package/driver/phase0.mjs +3 -3
- package/driver/pipeline-knockout.mjs +5 -5
- package/driver/pipeline.mjs +206 -68
- package/driver/placement-form.mjs +77 -1
- package/driver/placement-model.mjs +1 -1
- package/driver/portal-report.mjs +92 -5
- package/driver/portal-service.mjs +34 -8
- package/driver/portal-upstream.mjs +1 -1
- package/driver/preserve-merge.mjs +3 -3
- package/driver/product-rows.mjs +2 -2
- package/driver/products.mjs +1 -1
- package/driver/profiles/README.md +3 -3
- package/driver/profiles/demo-brand-owner.json +2 -2
- package/driver/profiles.mjs +55 -17
- package/driver/progress.mjs +18 -8
- package/driver/provider-usage.mjs +8 -8
- package/driver/publish/index.mjs +110 -5
- package/driver/publish/knockout.mjs +29 -4
- package/driver/publish/pool-admin.mjs +1 -1
- package/driver/publish/publish-inputs.mjs +18 -2
- package/driver/publish/render-knockout.mjs +115 -24
- package/driver/publish/render.mjs +153 -34
- package/driver/publish/search-depth.mjs +133 -4
- package/driver/publish/templates/report.css +60 -3
- package/driver/publish/xlsx.mjs +8 -1
- package/driver/queue-order.mjs +2 -2
- package/driver/recording-agreement.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/register-count.mjs +50 -5
- package/driver/register-coverage.mjs +67 -0
- package/driver/register-grant-vocabulary.mjs +1 -1
- package/driver/register-plan.mjs +19 -2
- package/driver/registry-fidelity.mjs +3 -3
- package/driver/repair-composers.mjs +1 -1
- package/driver/repair-contract.mjs +1 -1
- package/driver/replay-archive.mjs +6 -6
- package/driver/report-overview-record.mjs +2 -2
- package/driver/run-requirements.mjs +3 -3
- package/driver/runner.mjs +2 -2
- package/driver/scope-facts.mjs +20 -5
- package/driver/scope-ledger.mjs +5 -5
- package/driver/search-policy.mjs +22 -12
- package/driver/skills/README.md +15 -15
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/case-law-citation/SKILL.md +4 -4
- package/driver/skills/case-law-citation/sources/eurlex.md +1 -1
- package/driver/skills/{prelim-common-law → clearance-common-law}/SKILL.md +22 -22
- package/driver/skills/{prelim-common-law → clearance-common-law}/perplexity-prompts.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/SKILL.md +10 -10
- package/driver/skills/{prelim-register → clearance-register}/digest.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/providers/README.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/providers/clarivate.md +37 -35
- package/driver/skills/{prelim-register → clearance-register}/providers/corsearch.md +20 -11
- package/driver/skills/{prelim-register → clearance-register}/providers/signa.md +5 -5
- package/driver/skills/{prelim-register → clearance-register}/register-recipes.md +3 -3
- package/driver/skills/{prelim-register → clearance-register}/status-rules.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/stealth-filer-indicators.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/unit.md +2 -2
- package/driver/skills/{prelim-search → clearance-search}/SKILL.md +31 -31
- package/driver/skills/{prelim-search → clearance-search}/delivery-contract.md +1 -1
- package/driver/skills/{prelim-search → clearance-search}/phase2-execution.md +18 -18
- package/driver/skills/{prelim-search → clearance-search}/synthesis-rules.md +7 -7
- package/driver/skills/{prelim-variants → clearance-variants}/SKILL.md +18 -18
- package/driver/skills/{prelim-variants → clearance-variants}/transliteration-scripts.md +5 -5
- package/driver/skills/frame-diff/SKILL.md +1 -1
- package/driver/skills/knockout-assess/SKILL.md +10 -7
- package/driver/skills/matter-frame/SKILL.md +3 -3
- package/driver/skills/narrative-refutation/SKILL.md +9 -9
- package/driver/skills/placement-inquiry/SKILL.md +5 -5
- package/driver/stage-context.mjs +1 -1
- package/driver/stages-knockout.mjs +4 -4
- package/driver/stages.mjs +53 -53
- package/driver/status-snapshot.mjs +2 -2
- package/driver/suite-census.json +218 -92
- package/driver/surface-exit-verdict.mjs +58 -0
- package/driver/systemd/README.md +2 -2
- package/driver/systemd/clearotron-worker.service +1 -1
- package/driver/terminal-clamp.mjs +2 -0
- package/driver/usage-ledger.mjs +1 -1
- package/driver/variant-manifest-model.mjs +4 -4
- package/driver/verify-knockout.mjs +27 -0
- package/driver/verify.mjs +67 -6
- package/driver/whatif-queue.mjs +1 -1
- package/driver/wordlists/en.txt +63906 -0
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/README.md +1 -1
- package/mcp-server/lib/README.md +1 -1
- package/mcp-server/lib/options.mjs +8 -7
- package/mcp-server/lib/plan.mjs +18 -2
- package/mcp-server/lib/runs.mjs +1 -1
- package/mcp-server/lib/usage.mjs +3 -3
- package/mcp-server/lib/whatif.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +3 -0
- package/package.json +12 -11
- package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
- package/portal-ui/dist/assets/{index-6jzO9HiX.js → index-EVaSo5-g.js} +1459 -482
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/README.md +1 -1
- package/providers/_shared/enumerate.mjs +6 -6
- package/providers/_shared/execute-plan.mjs +3 -3
- package/providers/_shared/ledger.mjs +119 -5
- package/providers/_shared/provider-text.mjs +2 -2
- package/providers/_shared/screen.mjs +2 -2
- package/providers/_shared/script-form.mjs +3 -3
- package/providers/_shared/territory-codes.mjs +23 -3
- package/providers/clarivate/README.md +1 -1
- package/providers/clarivate/src/capabilities.js +12 -12
- package/providers/clarivate/src/core.js +37 -43
- package/providers/corsearch/README.md +1 -1
- package/providers/corsearch/src/capabilities.js +5 -5
- package/providers/corsearch/src/core.js +3 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +1 -1
- package/providers/signa/README.md +1 -1
- package/providers/signa/src/capabilities.js +42 -49
- package/providers/signa/src/core.js +106 -29
- package/providers/uspto-local/src/sync.js +1 -1
- package/scripts/README.md +1 -0
- package/scripts/ask-ai-render-check.mjs +127 -1
- package/scripts/authority-boundary-probe.mjs +4 -4
- package/scripts/backfill-started-at.mjs +2 -2
- package/scripts/census-merge-driver.mjs +33 -2
- package/scripts/citation-anchor-report.mjs +181 -0
- package/scripts/dead-names.mjs +1 -1
- package/scripts/deprecate-below.mjs +66 -8
- package/scripts/drain-preflight.mjs +1 -1
- package/scripts/e2e.mjs +174 -0
- package/scripts/env-audit.mjs +27 -0
- package/scripts/env-classify.mjs +20 -2
- package/scripts/freeze-example-run.mjs +20 -10
- package/scripts/live-surface-check.mjs +124 -41
- package/scripts/markdown-link-check.mjs +1 -1
- package/scripts/merge-shape-check.mjs +242 -0
- package/scripts/mint-names-in-force.mjs +5 -5
- package/scripts/mint-offered-territories.mjs +72 -0
- package/scripts/mint-public-residue.mjs +2 -2
- package/scripts/mint-reference-strip-backlog.mjs +2 -2
- package/scripts/mint-suite-census.mjs +75 -2
- package/scripts/mint-writing-standard-backlog.mjs +2 -2
- package/scripts/purge-runs.mjs +7 -7
- package/scripts/reconcile-runs.mjs +2 -2
- package/scripts/release-approve-parked.mjs +20 -2
- package/scripts/release-await-cut.mjs +120 -1
- package/scripts/release-note-required.mjs +76 -8
- package/scripts/report-header-render-check.mjs +164 -0
- package/shared/brand.mjs +27 -0
- package/shared/connect-clients.mjs +39 -11
- package/shared/env-aliases.mjs +1 -1
- package/shared/identifier-scan.mjs +65 -9
- package/shared/identifier-sentinels.mjs +22 -0
- package/shared/names-in-force.mjs +3 -1
- package/shared/offered-territories.json +738 -0
- package/shared/pre-rename-spellings.mjs +53 -0
- package/shared/reference-guard-classes.mjs +40 -2
- package/shared/stdio-connect.mjs +39 -4
- package/shared/tree-commit.mjs +48 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/euipo.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/free-tier.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/uspto-local.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/field-doctrine-pharma.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/firm-wide-reasoning.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/report-prose.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/template-formatting.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/email/generic.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/search-request-form.html +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples.md +0 -0
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/README.md
CHANGED
|
@@ -73,7 +73,7 @@ whether the stage makes **billed** external calls, **what downstream is NOT reco
|
|
|
73
73
|
report downstream are **not** recomputed (a finished-report answer would need a full cascade, which is out of
|
|
74
74
|
scope for v1 — see Future).
|
|
75
75
|
|
|
76
|
-
`what_if_run` takes the `confirmationToken` and re-runs the one stage in `_experiments/…` under a `
|
|
76
|
+
`what_if_run` takes the `confirmationToken` and re-runs the one stage in `_experiments/…` under a `clearance-exp-…`
|
|
77
77
|
session key (canonical bytes untouched, its billed calls keyed off-run), then diffs it against canonical.
|
|
78
78
|
**Live (undelivered) runs only.** Express the change with `instructions` (e.g. *"treat ACME's mark as
|
|
79
79
|
expired"*) and/or `model`.
|
package/mcp-server/lib/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Change behaviour in these files, never in a tool handler.
|
|
|
20
20
|
- `ops.mjs` — the write verbs: `start_run`, `stop_run`, `feed_context`, plus the delivery seam
|
|
21
21
|
(`list_outbox_events`, `get_delivery_packet`, `ack_event`, `mark_sent`). Writes in an agent's own queue and run
|
|
22
22
|
dirs *and* clears `.pending` markers in the one shared outbox (`CLEAROTRON_OUTBOX_DIR`, default
|
|
23
|
-
`<workspaceRoot>/
|
|
23
|
+
`<workspaceRoot>/clearance-outbox`), which is why `ack_event` is account-gated; shells nothing. **`start_run`
|
|
24
24
|
enqueues a job that spends real money.**
|
|
25
25
|
- `whatif.mjs` — the approval-gated counterfactual. `whatIfPlan()` never spends and returns the cost prior, what
|
|
26
26
|
downstream will *not* be recomputed, a completeness verdict and a `confirmationToken`; `whatIfRun(token)` re-runs
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// It is a DESCRIPTION and never an order: plan_run is still the free preview of a specific request, and
|
|
35
35
|
// start_run is still the only thing that spends.
|
|
36
36
|
|
|
37
|
-
import { ORDERABLE_PRODUCTS, PRODUCT_POLICIES, productAvailability, gateCause, UNAVAILABLE_NOTE,
|
|
37
|
+
import { ORDERABLE_PRODUCTS, PRODUCT_POLICIES, productAvailability, gateCause, UNAVAILABLE_NOTE, loadRecipes } from "../../driver/search-policy.mjs";
|
|
38
38
|
import { productRow } from "../../driver/product-rows.mjs";
|
|
39
39
|
import { PRODUCTS, productName } from "../../driver/products.mjs";
|
|
40
40
|
import { readFlagSnapshot, builtFor, registerCanCountFor, registerTerritoriesFor } from "../../driver/flag-snapshot.mjs";
|
|
@@ -152,12 +152,13 @@ function products(built, registerCanCount = null, registerTerritories = undefine
|
|
|
152
152
|
// The CAUSE never travels; only its sentence does. This is the structural reason no CLEAROTRON_* name
|
|
153
153
|
// can reach a client's assistant from here.
|
|
154
154
|
unavailableNote: cause ? UNAVAILABLE_NOTE[cause] : null,
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// the
|
|
160
|
-
|
|
155
|
+
// NO COVERAGE SENTENCE ON A PRODUCT ROW, AND THIS DOOR GOES WITH THE OTHER ONE. The rule that put
|
|
156
|
+
// a disclosure here was "two doors, one answer: an assistant driving this must not be able to tell
|
|
157
|
+
// a client something the screen does not say." That rule is why this line is now null rather than
|
|
158
|
+
// why it exists — the owner took the paragraph off the screen on 2026-09-18, and an assistant that
|
|
159
|
+
// kept reading it out would be the same defect through the other door. The reason it went is in
|
|
160
|
+
// portal-service.mjs beside its twin: it promises to defer a territory the engine now refuses.
|
|
161
|
+
coverageNote: null,
|
|
161
162
|
};
|
|
162
163
|
});
|
|
163
164
|
}
|
package/mcp-server/lib/plan.mjs
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { validateJob, wantsPortalRoute, PORTAL_ROUTE_UNAVAILABLE } from "../../driver/enqueue-schema.mjs";
|
|
28
28
|
import { resolveEffectiveProfile, recipeProseGuard } from "../../driver/profiles.mjs";
|
|
29
|
+
import { demoRunAgreement } from "../../driver/demo-run-agreement.mjs"; // the claim's own demo decision, consulted not copied
|
|
29
30
|
import { gateCause, UNAVAILABLE_NOTE, coverageDisclosure, policyFor, loadRecipes, countJobMarks } from "../../driver/search-policy.mjs";
|
|
30
31
|
import { productRow, baseTurnaroundFor } from "../../driver/product-rows.mjs";
|
|
31
32
|
import { resolveRequest } from "../../driver/resolve-request.mjs";
|
|
@@ -199,7 +200,14 @@ export function planRun(args = {}, { scope, now = Date.now() } = {}) {
|
|
|
199
200
|
let profile = null, projectKey = null, origins = null;
|
|
200
201
|
// Profile resolution reads the roster off disk; infra trouble must degrade the PREVIEW, never throw —
|
|
201
202
|
// the same fail-open posture validateJob takes for the roster checks.
|
|
202
|
-
|
|
203
|
+
// …EXCEPT the one refusal the claim itself makes on this read. With a company file unreadable, an order that
|
|
204
|
+
// names no company cannot be told apart from an order for that company, and the claim refuses it rather
|
|
205
|
+
// than rate it under `generic`. A preview that swallowed that answered wouldRun:true about a job the queue
|
|
206
|
+
// will refuse, so it travels as a blocker in the claim's own words. Only an unkeyed order reaches it, and
|
|
207
|
+
// only a staff session sends one unkeyed: a session scoped to accounts has its key stamped above.
|
|
208
|
+
let rosterRefusal = null;
|
|
209
|
+
try { ({ profile, projectKey, origins } = resolveEffectiveProfile(job)); }
|
|
210
|
+
catch (e) { if (e?.code === "profile_roster_incomplete") rosterRefusal = String(e.message); /* anything else: previewed without it */ }
|
|
203
211
|
|
|
204
212
|
let resolved = null, eff = null, unavailable = null, snapshotForCoverage = null;
|
|
205
213
|
try {
|
|
@@ -315,6 +323,14 @@ export function planRun(args = {}, { scope, now = Date.now() } = {}) {
|
|
|
315
323
|
// plan_run itself invites. Read off `job` (post-buildJob, post-validateJob) with the wall's own
|
|
316
324
|
// predicate and the wall's own sentence, so the two doors cannot drift apart.
|
|
317
325
|
const routeBlocker = wantsPortalRoute(job) ? PORTAL_ROUTE_UNAVAILABLE : null;
|
|
326
|
+
// THE DEMO-DATA AGREEMENT, asked as the claim asks it. A demo account with an ordinary order, or a real
|
|
327
|
+
// account with a demo order, is refused at the claim in `demoRunAgreement`'s own words; a preview that did
|
|
328
|
+
// not ask answered wouldRun:true about an order the queue refuses. Same function, same inputs, same
|
|
329
|
+
// precedence as the wall: only once the request resolves, never ahead of a clarify.
|
|
330
|
+
const demoAdmission = profile && !resolved?.clarify
|
|
331
|
+
? demoRunAgreement({ demoRun: job?.demoRun === true, demoData: profile?.demoData === true, who: job?.account ?? job?.profileKey ?? "this account" })
|
|
332
|
+
: null;
|
|
333
|
+
const demoBlocker = demoAdmission && !demoAdmission.ok ? demoAdmission.reject : null;
|
|
318
334
|
// DEDUPED, and this door is the only one that needs it. The other four SHORT-CIRCUIT — they refuse on
|
|
319
335
|
// validateJob and never reach the resolved-product fold — while a preview's whole job is to list every
|
|
320
336
|
// blocker at once, so it sees both. Some sentences are legitimately produced twice for one request: a
|
|
@@ -322,7 +338,7 @@ export function planRun(args = {}, { scope, now = Date.now() } = {}) {
|
|
|
322
338
|
// the fold against what it RESOLVES TO, and when the two are the same product they are the same
|
|
323
339
|
// sentence. Read aloud to a requester, "this request names 2 countries" twice is a defect; deduping
|
|
324
340
|
// where they are joined keeps every source honest and says it once.
|
|
325
|
-
const blocking = [...new Set([...(v.classify === "reject" || !v.ok ? v.errors : []), ...gates.errors, ...(unavailable ? [unavailable] : []), ...(quotaBlocker ? [quotaBlocker] : []), ...(routeBlocker ? [routeBlocker] : [])])];
|
|
341
|
+
const blocking = [...new Set([...(rosterRefusal ? [rosterRefusal] : []), ...(v.classify === "reject" || !v.ok ? v.errors : []), ...gates.errors, ...(unavailable ? [unavailable] : []), ...(quotaBlocker ? [quotaBlocker] : []), ...(routeBlocker ? [routeBlocker] : []), ...(demoBlocker ? [demoBlocker] : [])])];
|
|
326
342
|
|
|
327
343
|
// ONE lookup, and the fields below come off it — the same shape portal-service's plan door was fixed
|
|
328
344
|
// into. productRow computes a turnaround on every call; asking twice for one answer is how two answers
|
package/mcp-server/lib/runs.mjs
CHANGED
|
@@ -25,7 +25,7 @@ function findStatusFiles(root, depth, acc) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// Every workspace-<agent>/studio/
|
|
28
|
+
// Every workspace-<agent>/studio/clearance-search root under the live workspace root.
|
|
29
29
|
export function studioRoots() {
|
|
30
30
|
const out = [];
|
|
31
31
|
let names = [];
|
package/mcp-server/lib/usage.mjs
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import { tallyRegisterCalls, DEFAULT_LEDGER_PATH } from "./driver.mjs";
|
|
12
12
|
|
|
13
|
-
// The driver tags every billed call's gateway session-key `
|
|
14
|
-
// named "trademark-artifacts", but the RUNS were produced by the prelim-driver, so the prefix stays `
|
|
15
|
-
export function runPrefix(run) { return `
|
|
13
|
+
// The driver tags every billed call's gateway session-key `clearance-<slug>-<codename>-<stage>…`. Our server is
|
|
14
|
+
// named "trademark-artifacts", but the RUNS were produced by the prelim-driver, so the prefix stays `clearance-`.
|
|
15
|
+
export function runPrefix(run) { return `clearance-${run.slug}-${run.codename}-`; }
|
|
16
16
|
|
|
17
17
|
// — the compared key set is DERIVED from the two tallies, not restated here.
|
|
18
18
|
//
|
|
@@ -54,7 +54,7 @@ export const WHAT_IF_NOTE =
|
|
|
54
54
|
|
|
55
55
|
// Stages that hit billed external APIs (so the cost gate is honest about more than tokens).
|
|
56
56
|
const BILLED_EXTERNAL = new Set(["common-law", "register-unit", "case-law"]);
|
|
57
|
-
const MAYBE_EXTERNAL = new Set(["
|
|
57
|
+
const MAYBE_EXTERNAL = new Set(["clearance-variants"]); // may make a few Perplexity famous-mark calls
|
|
58
58
|
|
|
59
59
|
function encodeToken(op) { return Buffer.from(JSON.stringify(op)).toString("base64url"); }
|
|
60
60
|
function decodeToken(token) { try { return JSON.parse(Buffer.from(String(token), "base64url").toString("utf8")); } catch { return null; } }
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
3
|
+
"version": "0.3.2-beta.9",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|
package/mcp-server/server.mjs
CHANGED
|
@@ -316,6 +316,9 @@ const tools = {
|
|
|
316
316
|
// Staff-only tool (TOOL_SCOPES), so the reason may name the file: no client door sees this reply.
|
|
317
317
|
...(storeError ? { storeUnreadable: `The company store could not be re-read (${storeError}). This is the list as `
|
|
318
318
|
+ "last read, so a company added or changed since may be missing until that file is fixed." } : {}),
|
|
319
|
+
// A company whose own file would not load is left off `clients` and named here with the reason, so a
|
|
320
|
+
// missing company reads as a fault to fix rather than as a company that does not exist.
|
|
321
|
+
...(roster?.unreadable?.length ? { unreadable: roster.unreadable.map((u) => ({ key: u.key, reason: u.reason })) } : {}),
|
|
319
322
|
};
|
|
320
323
|
},
|
|
321
324
|
async describe_options(args, extra) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clearotron",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.2-beta.
|
|
4
|
+
"version": "0.3.2-beta.9",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -107,21 +107,22 @@
|
|
|
107
107
|
"!driver/profiles/projects/*/",
|
|
108
108
|
"driver/profiles/projects/demo-brand-owner/",
|
|
109
109
|
"!driver/recipes/*/",
|
|
110
|
-
"!driver/skills/
|
|
111
|
-
"driver/skills/
|
|
112
|
-
"driver/skills/
|
|
113
|
-
"driver/skills/
|
|
114
|
-
"driver/skills/
|
|
115
|
-
"driver/skills/
|
|
116
|
-
"driver/skills/
|
|
117
|
-
"!driver/skills/
|
|
118
|
-
"driver/skills/
|
|
119
|
-
"driver/skills/
|
|
110
|
+
"!driver/skills/clearance-search/risk-framework-*",
|
|
111
|
+
"driver/skills/clearance-search/risk-framework.md",
|
|
112
|
+
"driver/skills/clearance-search/risk-framework.manifest.json",
|
|
113
|
+
"driver/skills/clearance-search/risk-framework-demo.md",
|
|
114
|
+
"driver/skills/clearance-search/risk-framework-demo.manifest.json",
|
|
115
|
+
"driver/skills/clearance-search/risk-framework-triage.md",
|
|
116
|
+
"driver/skills/clearance-search/risk-framework-triage.manifest.json",
|
|
117
|
+
"!driver/skills/clearance-search/worked-examples-*",
|
|
118
|
+
"driver/skills/clearance-search/worked-examples.md",
|
|
119
|
+
"driver/skills/clearance-search/worked-examples-demo.md",
|
|
120
120
|
"!scripts/home-render-check.mjs",
|
|
121
121
|
"!scripts/ai-page-render-check.mjs",
|
|
122
122
|
"!scripts/composer-render-check.mjs",
|
|
123
123
|
"!scripts/clearances-render-check.mjs",
|
|
124
124
|
"!scripts/portal-lifecycle-check.mjs",
|
|
125
|
+
"!scripts/portal-strings.mjs",
|
|
125
126
|
"!scripts/test-account-names.mjs",
|
|
126
127
|
"!scripts/no-test-account-reaches-the-package.mjs",
|
|
127
128
|
"!**/test/",
|
|
@@ -254,6 +254,32 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
|
|
|
254
254
|
border-bottom: 1px solid var(--border-hairline);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/* THE HEADER'S SECOND ROW: which read you are in, then where you are inside it.
|
|
258
|
+
The Reads strip used to sit BELOW this box and the report's own section breadcrumb inside the frame,
|
|
259
|
+
so neither stayed on screen — the frame is sized to its content, which leaves a `position:sticky` bar
|
|
260
|
+
in there pinned to nothing. Both are rows of the pinned header now (owner, 2026-09-18).
|
|
261
|
+
It scrolls sideways rather than wrapping to a third line: a header that grows taller with the number of
|
|
262
|
+
sections is a header that eats the document it is heading. */
|
|
263
|
+
.report-nav {
|
|
264
|
+
display: flex; align-items: center; gap: 10px; margin-top: 9px;
|
|
265
|
+
overflow-x: auto; scrollbar-width: none;
|
|
266
|
+
}
|
|
267
|
+
.report-nav::-webkit-scrollbar { display: none; }
|
|
268
|
+
.report-nav-sep { flex: none; width: 1px; align-self: stretch; background: var(--border-hairline); }
|
|
269
|
+
.report-sections { display: flex; align-items: center; gap: 2px; }
|
|
270
|
+
.report-section {
|
|
271
|
+
flex: none; display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
|
|
272
|
+
font: inherit; font-size: 12.5px; font-weight: 600; letter-spacing: .01em;
|
|
273
|
+
padding: 5px 10px; border: 1px solid transparent; border-radius: 999px;
|
|
274
|
+
background: transparent; color: var(--text-muted); cursor: pointer;
|
|
275
|
+
}
|
|
276
|
+
.report-section:hover, .report-section:focus-visible { color: var(--text-strong); background: var(--surface-sunken); }
|
|
277
|
+
.report-section-dot {
|
|
278
|
+
width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; box-sizing: border-box;
|
|
279
|
+
}
|
|
280
|
+
.report-section[aria-current='true'] { color: var(--text-strong); background: var(--surface-sunken); }
|
|
281
|
+
.report-section[aria-current='true'] .report-section-dot { border-color: var(--accent); border-width: 3px; }
|
|
282
|
+
|
|
257
283
|
/* The breadcrumb above it: one step back as a quiet button, then where the reader is. Compound selectors
|
|
258
284
|
throughout this block, because the button and row rules they adjust are declared later in the file. */
|
|
259
285
|
.report-crumbs { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
|
@@ -271,6 +297,36 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
|
|
|
271
297
|
.btn-ghost.ask-ai-btn.is-quiet { border-color: transparent; color: var(--text-muted); }
|
|
272
298
|
.btn-ghost.ask-ai-btn.is-quiet:hover { border-color: var(--border-hairline); color: var(--text-strong); }
|
|
273
299
|
.ask-ai-float { position: absolute; right: 0; top: calc(100% + 6px); z-index: 50; }
|
|
300
|
+
|
|
301
|
+
/* THE PANEL BELONGS TO THE BUTTON THAT OPENED IT, and a reader has to be able to see that it does. It was
|
|
302
|
+
anchored already — the rule above hangs it off `.ask-ai`, which is positioned — but it is 360px wide and
|
|
303
|
+
right-aligned, so it reaches back under the header's other chrome and reads as something floating over
|
|
304
|
+
the page rather than as this button's panel (owner, on test, 2026-09-18). Two triangles rather than one:
|
|
305
|
+
the outer carries the panel's own hairline and the inner covers it in the panel's surface, so the caret
|
|
306
|
+
is the panel's edge continuing rather than a shape drawn near it. Both grounds, from the same tokens. */
|
|
307
|
+
.ask-ai-float::before,
|
|
308
|
+
.ask-ai-float::after {
|
|
309
|
+
content: '';
|
|
310
|
+
position: absolute;
|
|
311
|
+
right: 16px;
|
|
312
|
+
width: 0;
|
|
313
|
+
height: 0;
|
|
314
|
+
border-left: 7px solid transparent;
|
|
315
|
+
border-right: 7px solid transparent;
|
|
316
|
+
}
|
|
317
|
+
.ask-ai-float::before { top: -7px; border-bottom: 7px solid var(--border-strong); }
|
|
318
|
+
.ask-ai-float::after { top: -6px; border-bottom: 7px solid var(--surface-float); }
|
|
319
|
+
|
|
320
|
+
/* AND THE BUTTON SAYS IT IS THE OPEN ONE. `aria-expanded` is already on it for a reader using a screen
|
|
321
|
+
reader; this is the same fact for the reader looking at it, drawn the way every other open-or-current
|
|
322
|
+
control in this file is (accent wash, accent border). One class stronger than the quiet variant, whose
|
|
323
|
+
transparent border would otherwise win. */
|
|
324
|
+
.btn-ghost.ask-ai-btn[aria-expanded='true'],
|
|
325
|
+
.btn-ghost.ask-ai-btn.is-quiet[aria-expanded='true'] {
|
|
326
|
+
background: var(--accent-wash);
|
|
327
|
+
border-color: var(--accent);
|
|
328
|
+
color: var(--text-accent);
|
|
329
|
+
}
|
|
274
330
|
.ask-ai-panel { width: 360px; max-width: calc(100vw - 32px); padding: 12px; display: grid; gap: 10px; text-align: left; }
|
|
275
331
|
.ask-ai-head { font-size: 12.5px; line-height: 1.45; color: var(--text-muted); }
|
|
276
332
|
.ask-ai-when { white-space: nowrap; }
|
|
@@ -313,6 +369,11 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
|
|
|
313
369
|
padding: 0 10px;
|
|
314
370
|
}
|
|
315
371
|
|
|
372
|
+
/* The board's own box for Connect your AI: between the list that scrolls and the foot. */
|
|
373
|
+
.sidebar-pinned {
|
|
374
|
+
padding: 0 10px 2px;
|
|
375
|
+
}
|
|
376
|
+
|
|
316
377
|
.sidebar-foot {
|
|
317
378
|
padding: 10px;
|
|
318
379
|
border-top: 1px solid var(--border-hairline);
|
|
@@ -462,6 +523,65 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
|
|
|
462
523
|
overflow-x: auto;
|
|
463
524
|
}
|
|
464
525
|
|
|
526
|
+
/* A TABLE THAT CONTINUES PAST THIS WRAPPER'S RIGHT EDGE SHOWS THAT IT DOES. The note further down makes
|
|
527
|
+
half the argument — a table a reader drags sideways is readable, one printed through itself is not —
|
|
528
|
+
and this is the other half: a reader has to be able to see that it drags. Ruled 2026-09-17, a
|
|
529
|
+
scrollbar, always visible, no text.
|
|
530
|
+
|
|
531
|
+
THE TWO WAYS OF ASKING FOR ONE CANNOT SIT TOGETHER, and nothing about reading this file would tell
|
|
532
|
+
you. Putting the standard `scrollbar-width` on the rule above, beside these pseudo-elements, makes the
|
|
533
|
+
engine take the standard path and IGNORE them — and on the engine these pages render in, that path
|
|
534
|
+
draws an overlay bar with no layout height at all. Measured on a delivered report at 390px, reading
|
|
535
|
+
the height the bar takes out of the panel:
|
|
536
|
+
|
|
537
|
+
both together 0px
|
|
538
|
+
pseudo-elements alone 9px
|
|
539
|
+
standard alone 0px
|
|
540
|
+
|
|
541
|
+
So the standard properties sit behind a support query that the engines carrying the pseudo-elements
|
|
542
|
+
never enter, and each engine gets exactly one instruction. Merging these two blocks back together is
|
|
543
|
+
the tidy-up that deletes the scrollbar with every check still green, which is why this is written here
|
|
544
|
+
and pinned by an arm rather than left to be re-derived.
|
|
545
|
+
|
|
546
|
+
`--border-strong` is the line the table already sits inside, and it has a real value on both grounds. */
|
|
547
|
+
.table-wrap::-webkit-scrollbar { height: 9px; -webkit-appearance: none; }
|
|
548
|
+
.table-wrap::-webkit-scrollbar-track { background: transparent; }
|
|
549
|
+
.table-wrap::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 5px; }
|
|
550
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
551
|
+
.table-wrap { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/* THE BAR, PINNED to the bottom of the screen while its table is in view (components/PinnedScrollbar.tsx).
|
|
555
|
+
Drawn, not native: a phone's own bar is an overlay that takes no room and shows only while moving. The
|
|
556
|
+
track and thumb take the colours the desktop bar above uses, on the page's own surface so rows do not
|
|
557
|
+
show through, and sticky, so the bar settles under the table at the table's foot. */
|
|
558
|
+
.pinned-bar {
|
|
559
|
+
position: sticky;
|
|
560
|
+
bottom: 0;
|
|
561
|
+
z-index: 2;
|
|
562
|
+
padding: 4px 0;
|
|
563
|
+
background: var(--surface-page);
|
|
564
|
+
}
|
|
565
|
+
.pinned-track {
|
|
566
|
+
position: relative;
|
|
567
|
+
height: 9px;
|
|
568
|
+
border-radius: 5px;
|
|
569
|
+
touch-action: none;
|
|
570
|
+
}
|
|
571
|
+
.table-wrap.has-pinned-bar::-webkit-scrollbar { height: 0; }
|
|
572
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
573
|
+
.table-wrap.has-pinned-bar { scrollbar-width: none; }
|
|
574
|
+
}
|
|
575
|
+
.pinned-thumb {
|
|
576
|
+
position: absolute;
|
|
577
|
+
top: 0;
|
|
578
|
+
height: 100%;
|
|
579
|
+
min-width: 24px;
|
|
580
|
+
border-radius: 5px;
|
|
581
|
+
background: var(--border-strong);
|
|
582
|
+
touch-action: none;
|
|
583
|
+
}
|
|
584
|
+
|
|
465
585
|
table.data {
|
|
466
586
|
width: 100%;
|
|
467
587
|
min-width: 760px;
|
|
@@ -724,6 +844,29 @@ table.data td.nest-2 {
|
|
|
724
844
|
panel opened from a row near the bottom of the list was cut off at the table's edge. */
|
|
725
845
|
overflow: visible;
|
|
726
846
|
}
|
|
847
|
+
|
|
848
|
+
/* ── AND BELOW THE BREAKPOINT IT SCROLLS AFTER ALL, AND CONTAINS WHAT IT SCROLLS ────────────────────
|
|
849
|
+
*
|
|
850
|
+
* The rule above is right at desktop widths and wrong below 560px, where `table.data.fixed` stops
|
|
851
|
+
* dividing and becomes an ordinary content-sized table with its 860px floor restored. The note on that
|
|
852
|
+
* breakpoint says "its wrapper, which has always had `overflow-x: auto`, does the scrolling" — true of
|
|
853
|
+
* `.table-wrap`, and NOT true of this wrapper, which turns it off two rules later for the Ask AI panel.
|
|
854
|
+
* Two rules describing the same box disagreed, the later one won, and the table pushed the page sideways
|
|
855
|
+
* instead. Measured at 400px: table 860px, page scrolling by 396px, every column still present.
|
|
856
|
+
*
|
|
857
|
+
* `position: relative` IS LOAD-BEARING and does not look it. Scrolling alone leaves the page scrolling,
|
|
858
|
+
* because the sort button's visually-hidden label (", sorted descending") is absolutely positioned and
|
|
859
|
+
* this wrapper was not a containing block — so it resolved against the body, landed at x=568 with the
|
|
860
|
+
* header cell it belongs to, and extended the document on its own. One pixel wide. Both halves were
|
|
861
|
+
* planted at 400, 500 and 560: remove either and the page scrolls again.
|
|
862
|
+
*
|
|
863
|
+
* WHAT THIS COSTS, said plainly: a scroll container clips both axes, so below 560px an Ask AI panel
|
|
864
|
+
* opened from a row near the bottom is clipped again — the thing the rule above exists to prevent. That
|
|
865
|
+
* is the trade the phone width forces, and the page scrolling sideways is the worse of the two for a
|
|
866
|
+
* client reading on a phone. Above 560px nothing changes and the panel is unclipped as before. */
|
|
867
|
+
@media (max-width: 560px) {
|
|
868
|
+
.names-wrap { overflow-x: auto; position: relative; }
|
|
869
|
+
}
|
|
727
870
|
/* Eight pixels either side instead of twelve, on every column but the two narrow ones at the left: the
|
|
728
871
|
actions column takes a quarter of the table, and the text columns keep their words on one line by
|
|
729
872
|
giving up padding rather than width. The date and the actions give up a little more. */
|
|
@@ -750,6 +893,37 @@ table.data td.nest-2 {
|
|
|
750
893
|
.status-count {
|
|
751
894
|
white-space: nowrap;
|
|
752
895
|
}
|
|
896
|
+
/* One line wherever a risk word is drawn — in the stylesheet rather than inline, so the table below can
|
|
897
|
+
let it wrap. */
|
|
898
|
+
.risk-dot {
|
|
899
|
+
white-space: nowrap;
|
|
900
|
+
}
|
|
901
|
+
/* A risk word wraps the same way, and for the same reason: "No rated conflicts" in a column of fixed
|
|
902
|
+
width ran on under the date beside it instead of taking a second line. */
|
|
903
|
+
.names-wrap table.data.fixed td .risk-dot {
|
|
904
|
+
white-space: normal;
|
|
905
|
+
align-items: flex-start;
|
|
906
|
+
}
|
|
907
|
+
.names-wrap table.data.fixed td .risk-dot .dot {
|
|
908
|
+
flex: none;
|
|
909
|
+
margin-top: 0.42em;
|
|
910
|
+
}
|
|
911
|
+
/* AND NEVER INSIDE A WORD. At 700px the column holds 84px and "Manageable" is 85: beside its dot it broke
|
|
912
|
+
into "Managea" over "ble". The cell gives up padding the way the date's does, the dot and the word may
|
|
913
|
+
part so a word too long to sit beside its dot takes the next line whole, and the word itself breaks only
|
|
914
|
+
when it cannot fit even on a line of its own — `break-word`, which unlike `anywhere` does not shrink the
|
|
915
|
+
word's minimum width, so the parting happens first. */
|
|
916
|
+
.names-wrap table.data.fixed td:has(.risk-dot) {
|
|
917
|
+
padding-left: 6px;
|
|
918
|
+
padding-right: 6px;
|
|
919
|
+
}
|
|
920
|
+
.names-wrap table.data.fixed td .risk-dot {
|
|
921
|
+
flex-wrap: wrap;
|
|
922
|
+
row-gap: 2px;
|
|
923
|
+
}
|
|
924
|
+
.names-wrap table.data.fixed td .risk-dot > span:last-child {
|
|
925
|
+
overflow-wrap: break-word;
|
|
926
|
+
}
|
|
753
927
|
/* A date is one word to a reader. A line may break before it, never at its hyphens: "2026-09-" over "02"
|
|
754
928
|
read as a different date on the line below. */
|
|
755
929
|
.names-wrap table.data.fixed td .sub.report-line {
|
|
@@ -766,9 +940,15 @@ table.data td.nest-2 {
|
|
|
766
940
|
and red in CI on the same commit. 13.5em is the local measurement plus about a fifth, which is
|
|
767
941
|
more than the spread between the font stacks the two see. */
|
|
768
942
|
--row-open-w: 13.5em;
|
|
769
|
-
|
|
943
|
+
/* Ask AI measured 89px at this size, so 6.5em (81px) ran the button into the menu beside it. */
|
|
944
|
+
--row-ask-w: 7.25em;
|
|
770
945
|
display: grid;
|
|
771
|
-
|
|
946
|
+
/* MINMAX, BECAUSE THE TRACK IS A CEILING HERE AND NOT A FLOOR. A bare `var(--row-open-w)` is a fixed
|
|
947
|
+
13.5em track that cannot shrink, so in a narrow container the row's controls were wider than the
|
|
948
|
+
column they were given: measured at 700px, the "more" button sat at x=697 with the wrapper's right
|
|
949
|
+
edge at 682, and the cell overflowed the table and then the page by 25px. The wide form below
|
|
950
|
+
restores the fixed track, where it fits and the one-width rule matters. */
|
|
951
|
+
grid-template-columns: minmax(0, var(--row-open-w)) auto;
|
|
772
952
|
grid-template-areas: 'open more' 'ask more';
|
|
773
953
|
justify-content: start;
|
|
774
954
|
align-items: start;
|
|
@@ -789,7 +969,15 @@ table.data td.nest-2 {
|
|
|
789
969
|
.row-actions-ask:empty {
|
|
790
970
|
display: none;
|
|
791
971
|
}
|
|
792
|
-
|
|
972
|
+
/* SIDE BY SIDE ONLY WHERE THE CELL HOLDS IT. This asked the whole table's width, and the actions column is
|
|
973
|
+
a fifth of it: at 1440px the table was 1060px, the query said "wide", and three fixed tracks needing
|
|
974
|
+
about 300px were laid into a cell of about 200 — every row's menu past the table's edge, the page
|
|
975
|
+
scrolling sideways. The cell itself is the container now, so the wide form appears exactly when its
|
|
976
|
+
tracks fit and the stacked form, which shrinks, holds every other width. */
|
|
977
|
+
.row-actions-box {
|
|
978
|
+
container: rowactions / inline-size;
|
|
979
|
+
}
|
|
980
|
+
@container rowactions (min-width: 300px) {
|
|
793
981
|
.row-actions {
|
|
794
982
|
grid-template-columns: var(--row-open-w) var(--row-ask-w) auto;
|
|
795
983
|
grid-template-areas: 'open ask more';
|
|
@@ -812,6 +1000,22 @@ button.row-open {
|
|
|
812
1000
|
white-space: nowrap;
|
|
813
1001
|
cursor: pointer;
|
|
814
1002
|
}
|
|
1003
|
+
/* AND ITS LABEL WRAPS, in the narrow form only. Ruled 2026-09-18: the row controls collapse at this
|
|
1004
|
+
width.
|
|
1005
|
+
THIS LINE DOES NOT RED WHEN IT IS REMOVED, and it is kept anyway — said out loud because the next
|
|
1006
|
+
person to plant it will find the same thing. With `nowrap` and the shrunken track, the longest label
|
|
1007
|
+
measures 121px inside a 120px button on this box: it does not overflow here, so the check passes, and
|
|
1008
|
+
it is one pixel from clipping. The comment on `--row-open-w` above records this exact failure
|
|
1009
|
+
happening — green on a developer machine, red on the CI runner, whose default font is wider, on the
|
|
1010
|
+
same commit. Wrapping removes the dependency on which font measured it rather than betting a pixel
|
|
1011
|
+
on it. Nothing in this check can see the difference, which is why this note is here instead. */
|
|
1012
|
+
@container (max-width: 999px) {
|
|
1013
|
+
button.row-open {
|
|
1014
|
+
min-width: 0;
|
|
1015
|
+
white-space: normal;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
815
1019
|
button.row-open:hover {
|
|
816
1020
|
border-color: var(--text-muted);
|
|
817
1021
|
}
|