clearotron 0.3.0-beta.3 → 0.3.0-beta.5
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/INSTALL.md +15 -6
- package/bin/connect.mjs +68 -25
- package/bin/disconnect.mjs +16 -11
- package/bin/onboard.mjs +42 -10
- package/bin/start.mjs +56 -16
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +35 -10
- package/driver/engine/openai-agent.mjs +52 -2
- package/driver/package.json +1 -1
- package/driver/portal-upstream.mjs +9 -0
- package/driver/suite-census.json +35 -11
- package/mcp-server/CHANGELOG.md +12 -2
- package/mcp-server/lib/options.mjs +4 -1
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-CsCuPshD.css → index-Cv-E_agg.css} +199 -86
- package/portal-ui/dist/assets/{index-C1gpdkyH.js → index-DBUVdQT-.js} +506 -375
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/engine-probe.mjs +2 -2
- package/shared/connect-clients.mjs +282 -321
- package/shared/names-in-force.mjs +1 -0
- package/shared/stdio-connect.mjs +63 -0
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
// CLEAROTRON_CODEX_SANDBOX_BYPASS=1 swaps that `--sandbox workspace-write` for
|
|
23
23
|
// `--dangerously-bypass-approvals-and-sandbox` — see buildCodexArgs below for why.
|
|
24
24
|
|
|
25
|
-
import { mkdtempSync, writeFileSync, copyFileSync, existsSync, rmSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
25
|
+
import { mkdtempSync, writeFileSync, copyFileSync, existsSync, rmSync, readFileSync, readdirSync, statSync, symlinkSync, lstatSync, realpathSync } from "node:fs";
|
|
26
|
+
import { writeSecretFile } from "../../shared/secret-file.mjs"; // the rotated login goes back the way every credential is written
|
|
26
27
|
import { tmpdir, homedir } from "node:os";
|
|
27
28
|
import { join } from "node:path";
|
|
28
29
|
import { runStreamingChild, absolutizeSkillRefs, WRITE_DISCIPLINE, buildEnvelope, resolveSpawnCwd } from "./common.mjs";
|
|
@@ -174,6 +175,49 @@ export function spawnEnv(base = process.env, codexHome) {
|
|
|
174
175
|
|
|
175
176
|
const authFilePath = (env = process.env) => env.CLEAROTRON_OPENAI_AUTH_FILE || join(homedir(), ".codex", "auth.json");
|
|
176
177
|
|
|
178
|
+
// ── THE LOGIN IS THE MASTER'S, NOT A COPY'S ─────────────────────────────────────────────────────────
|
|
179
|
+
//
|
|
180
|
+
// codex refreshes its access token inside a turn and rotates the refresh token as it does, and the
|
|
181
|
+
// provider accepts each refresh token once. The stage home used to be seeded with a COPY of the master
|
|
182
|
+
// login and deleted when its ladder settled, so the rotated token was deleted with it. The master kept a
|
|
183
|
+
// token the provider had already consumed, and every later stage on every run failed its refresh with a
|
|
184
|
+
// 401 until a person signed in again. The engine's only report was an exit code.
|
|
185
|
+
//
|
|
186
|
+
// So the stage home LINKS to the master: a refresh codex writes in place lands where the next stage
|
|
187
|
+
// reads, including a ladder running beside this one. If codex replaces the link with a file of its own,
|
|
188
|
+
// `returnAuth` writes that file back.
|
|
189
|
+
|
|
190
|
+
/** Point `<codexHome>/auth.json` at the master login. Returns what the master held, for `returnAuth`. */
|
|
191
|
+
function seedAuth(masterPath, codexHome) {
|
|
192
|
+
const seat = join(codexHome, "auth.json");
|
|
193
|
+
// A LINK LEFT BY THIS LADDER'S PREVIOUS TURN is removed first: copying the master onto a link to
|
|
194
|
+
// itself would truncate the one file the login lives in.
|
|
195
|
+
rmSync(seat, { force: true });
|
|
196
|
+
symlinkSync(masterPath, seat);
|
|
197
|
+
return readFileSync(masterPath, "utf8");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* After a turn: a login codex rewrote as its own file goes back to the master.
|
|
202
|
+
*
|
|
203
|
+
* ONLY WHILE THE MASTER STILL HOLDS WHAT THIS TURN WAS SEEDED WITH. A master that moved meanwhile was
|
|
204
|
+
* rotated by another ladder, and the provider accepts one refresh per token, so of two rotations from
|
|
205
|
+
* one seed only one can be live. Writing ours over theirs could put back the spent one.
|
|
206
|
+
*/
|
|
207
|
+
export function returnAuth(masterPath, codexHome, seeded) { // @internal
|
|
208
|
+
const seat = join(codexHome, "auth.json");
|
|
209
|
+
let st;
|
|
210
|
+
try { st = lstatSync(seat); } catch { return false; }
|
|
211
|
+
if (st.isSymbolicLink() || !st.isFile()) return false; // written through the link, or not there
|
|
212
|
+
const now = readFileSync(seat, "utf8");
|
|
213
|
+
if (now === seeded) return false;
|
|
214
|
+
let master;
|
|
215
|
+
try { master = readFileSync(masterPath, "utf8"); } catch { return false; }
|
|
216
|
+
if (master !== seeded) return false;
|
|
217
|
+
writeSecretFile(masterPath, now);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
177
221
|
// Build the codex argv. Prompt rides STDIN via the `-` placeholder (no MAX_ARG_STRLEN ceiling on a 150KB
|
|
178
222
|
// register prompt). Global flags precede the `resume` subcommand (flag-corpus ordering). config.toml
|
|
179
223
|
// (mcp_servers + developer_instructions) rides CODEX_HOME, not argv.
|
|
@@ -530,6 +574,7 @@ export const openaiAgentEngine = {
|
|
|
530
574
|
const ownHome = !providedHome;
|
|
531
575
|
try { codexHome = providedHome ?? mkdtempSync(join(tmpdir(), "codex-home-")); }
|
|
532
576
|
catch (e) { return errResult(t0, e, resumeRef); }
|
|
577
|
+
let authMaster = null, seeded = null;
|
|
533
578
|
try {
|
|
534
579
|
// Auth toggle (may throw loud on api-key-without-key — a config error, not a retryable failure).
|
|
535
580
|
const { env, mode } = spawnEnv(process.env, codexHome);
|
|
@@ -537,7 +582,9 @@ export const openaiAgentEngine = {
|
|
|
537
582
|
const af = authFilePath(process.env);
|
|
538
583
|
if (!existsSync(af))
|
|
539
584
|
throw new Error(`CLEAROTRON_AI_BILLING=subscription but no auth.json at ${af} — run \`codex login\` (or set CLEAROTRON_OPENAI_AUTH_FILE — NOT an aliased name, and deliberately left as it is), or use CLEAROTRON_AI_BILLING=api-key + CODEX_API_KEY.`);
|
|
540
|
-
|
|
585
|
+
// The REAL path, so a master that is itself a link is written where its login actually lives.
|
|
586
|
+
authMaster = realpathSync(af);
|
|
587
|
+
seeded = seedAuth(authMaster, codexHome);
|
|
541
588
|
}
|
|
542
589
|
// Per-run config.toml: the gather MCP servers (translated from the claude-shaped mcpConfig, so
|
|
543
590
|
// gather-config.mjs is untouched) + developer_instructions = WRITE_DISCIPLINE (codex's
|
|
@@ -574,6 +621,9 @@ export const openaiAgentEngine = {
|
|
|
574
621
|
if (!ev.model) ev.model = readServedModel(codexHome, spawnedAtMs);
|
|
575
622
|
return settleTuple({ r, ev, resumeRef });
|
|
576
623
|
} finally {
|
|
624
|
+
// BEFORE the home can be deleted, here or by the ladder that owns it. A failed write-back leaves the
|
|
625
|
+
// turn's result as it was; the next turn is seeded from the master either way.
|
|
626
|
+
try { if (authMaster) returnAuth(authMaster, codexHome, seeded); } catch { /* the master is unchanged */ }
|
|
577
627
|
try { if (ownHome && codexHome) rmSync(codexHome, { recursive: true, force: true }); } catch { /* best-effort cleanup */ }
|
|
578
628
|
}
|
|
579
629
|
},
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.0-beta.
|
|
5
|
+
"version": "0.3.0-beta.5",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
|
|
8
8
|
"engines": {
|
|
@@ -420,6 +420,11 @@ export function makeUpstream({ callUpstream, callRecipes = null, recipesOff = nu
|
|
|
420
420
|
const draft = {};
|
|
421
421
|
for (const k of CREATABLE_FIELDS) if (body[k] !== undefined) draft[k] = body[k];
|
|
422
422
|
const r = await call("POST", "/profiles", draft, principal);
|
|
423
|
+
// A STORE THAT CANNOT RECORD THE COMPANY is answered with its path and the git commands that fix it.
|
|
424
|
+
// That sentence is for whoever runs the installation. A manager who holds an organisation cannot act
|
|
425
|
+
// on it and must not be shown the server's paths, so they are told what to do instead.
|
|
426
|
+
if (r.status === 409 && String(r.json?.code ?? "").startsWith("store_") && !seesEverything(principal))
|
|
427
|
+
return { status: 409, json: { code: r.json.code, error: STORE_REFUSAL_FOR_MANAGERS } };
|
|
423
428
|
if (r.status !== 201 || org == null || !fileCompany) return r;
|
|
424
429
|
const key = r.json?.key ?? r.json?.profile?.key ?? draft.key;
|
|
425
430
|
try { await fileCompany({ tenant: org, account: key }); }
|
|
@@ -431,6 +436,10 @@ export function makeUpstream({ callUpstream, callRecipes = null, recipesOff = nu
|
|
|
431
436
|
};
|
|
432
437
|
}
|
|
433
438
|
|
|
439
|
+
/** The store refusal as a manager who does not run the installation reads it: no path, no command. */
|
|
440
|
+
export const STORE_REFUSAL_FOR_MANAGERS = "No company was created: this installation cannot save new companies yet, "
|
|
441
|
+
+ "and nothing was changed. Ask whoever runs it to fix the company store.";
|
|
442
|
+
|
|
434
443
|
/**
|
|
435
444
|
* What a browser may state when creating a company.
|
|
436
445
|
*
|
package/driver/suite-census.json
CHANGED
|
@@ -135,6 +135,12 @@
|
|
|
135
135
|
"skips": 0,
|
|
136
136
|
"todos": 0
|
|
137
137
|
},
|
|
138
|
+
"a-codex-login-survives-its-own-refresh.test.mjs": {
|
|
139
|
+
"tests": 3,
|
|
140
|
+
"asserts": 14,
|
|
141
|
+
"skips": 0,
|
|
142
|
+
"todos": 0
|
|
143
|
+
},
|
|
138
144
|
"a-company-can-be-given-a-framework-after-it-exists.test.mjs": {
|
|
139
145
|
"tests": 8,
|
|
140
146
|
"asserts": 19,
|
|
@@ -381,6 +387,12 @@
|
|
|
381
387
|
"skips": 0,
|
|
382
388
|
"todos": 0
|
|
383
389
|
},
|
|
390
|
+
"a-local-install-hands-its-stores-to-every-reader.test.mjs": {
|
|
391
|
+
"tests": 1,
|
|
392
|
+
"asserts": 10,
|
|
393
|
+
"skips": 0,
|
|
394
|
+
"todos": 0
|
|
395
|
+
},
|
|
384
396
|
"a-mark-name-cannot-lie-about-how-it-reads.test.mjs": {
|
|
385
397
|
"tests": 9,
|
|
386
398
|
"asserts": 37,
|
|
@@ -747,6 +759,12 @@
|
|
|
747
759
|
"skips": 0,
|
|
748
760
|
"todos": 0
|
|
749
761
|
},
|
|
762
|
+
"a-store-refusal-reaches-the-person-who-can-fix-it.test.mjs": {
|
|
763
|
+
"tests": 3,
|
|
764
|
+
"asserts": 11,
|
|
765
|
+
"skips": 0,
|
|
766
|
+
"todos": 0
|
|
767
|
+
},
|
|
750
768
|
"a-stored-territory-that-does-nothing-is-named.test.mjs": {
|
|
751
769
|
"tests": 6,
|
|
752
770
|
"asserts": 12,
|
|
@@ -1366,8 +1384,8 @@
|
|
|
1366
1384
|
"todos": 0
|
|
1367
1385
|
},
|
|
1368
1386
|
"connect-clients-are-data.test.mjs": {
|
|
1369
|
-
"tests":
|
|
1370
|
-
"asserts":
|
|
1387
|
+
"tests": 15,
|
|
1388
|
+
"asserts": 75,
|
|
1371
1389
|
"skips": 0,
|
|
1372
1390
|
"todos": 0
|
|
1373
1391
|
},
|
|
@@ -1397,7 +1415,7 @@
|
|
|
1397
1415
|
},
|
|
1398
1416
|
"connector-annotations-and-pack.test.mjs": {
|
|
1399
1417
|
"tests": 7,
|
|
1400
|
-
"asserts":
|
|
1418
|
+
"asserts": 12,
|
|
1401
1419
|
"skips": 0,
|
|
1402
1420
|
"todos": 0
|
|
1403
1421
|
},
|
|
@@ -1804,8 +1822,8 @@
|
|
|
1804
1822
|
"todos": 0
|
|
1805
1823
|
},
|
|
1806
1824
|
"doctor-reads-the-units-not-the-shell.test.mjs": {
|
|
1807
|
-
"tests":
|
|
1808
|
-
"asserts":
|
|
1825
|
+
"tests": 32,
|
|
1826
|
+
"asserts": 93,
|
|
1809
1827
|
"skips": 0,
|
|
1810
1828
|
"todos": 0
|
|
1811
1829
|
},
|
|
@@ -4725,6 +4743,12 @@
|
|
|
4725
4743
|
"skips": 0,
|
|
4726
4744
|
"todos": 0
|
|
4727
4745
|
},
|
|
4746
|
+
"the-proving-question-names-no-engines-model.test.mjs": {
|
|
4747
|
+
"tests": 2,
|
|
4748
|
+
"asserts": 5,
|
|
4749
|
+
"skips": 0,
|
|
4750
|
+
"todos": 0
|
|
4751
|
+
},
|
|
4728
4752
|
"the-public-residue-is-a-floor.test.mjs": {
|
|
4729
4753
|
"tests": 15,
|
|
4730
4754
|
"asserts": 59,
|
|
@@ -5404,7 +5428,7 @@
|
|
|
5404
5428
|
},
|
|
5405
5429
|
"describe-options.test.mjs": {
|
|
5406
5430
|
"tests": 23,
|
|
5407
|
-
"asserts":
|
|
5431
|
+
"asserts": 99,
|
|
5408
5432
|
"skips": 0,
|
|
5409
5433
|
"todos": 0
|
|
5410
5434
|
},
|
|
@@ -5594,8 +5618,8 @@
|
|
|
5594
5618
|
"ext": "ts",
|
|
5595
5619
|
"perFile": {
|
|
5596
5620
|
"api.test.ts": {
|
|
5597
|
-
"tests":
|
|
5598
|
-
"asserts":
|
|
5621
|
+
"tests": 44,
|
|
5622
|
+
"asserts": 176,
|
|
5599
5623
|
"skips": 0,
|
|
5600
5624
|
"todos": 0
|
|
5601
5625
|
},
|
|
@@ -5774,8 +5798,8 @@
|
|
|
5774
5798
|
"todos": 0
|
|
5775
5799
|
},
|
|
5776
5800
|
"newCompanyReachableFromProfile.test.ts": {
|
|
5777
|
-
"tests":
|
|
5778
|
-
"asserts":
|
|
5801
|
+
"tests": 5,
|
|
5802
|
+
"asserts": 13,
|
|
5779
5803
|
"skips": 0,
|
|
5780
5804
|
"todos": 0
|
|
5781
5805
|
},
|
|
@@ -5913,7 +5937,7 @@
|
|
|
5913
5937
|
},
|
|
5914
5938
|
"useYourAI.test.ts": {
|
|
5915
5939
|
"tests": 13,
|
|
5916
|
-
"asserts":
|
|
5940
|
+
"asserts": 41,
|
|
5917
5941
|
"skips": 0,
|
|
5918
5942
|
"todos": 0
|
|
5919
5943
|
},
|
package/mcp-server/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# trademark-artifacts-mcp
|
|
2
2
|
|
|
3
|
+
## 0.3.0-beta.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e7cd9e1: Fixed: On an install run with `clearotron start`, a connected assistant now lists the same saved searches as the portal, including Generic's. `clearotron doctor` no longer says working saved searches are off, and it names one place profiles come from.
|
|
8
|
+
|
|
9
|
+
## 0.3.0-beta.4
|
|
10
|
+
|
|
11
|
+
No changes in this release.
|
|
12
|
+
|
|
3
13
|
## 0.3.0-beta.3
|
|
4
14
|
|
|
5
15
|
No changes in this release.
|
|
@@ -14,11 +24,11 @@ No changes in this release.
|
|
|
14
24
|
|
|
15
25
|
### Patch Changes
|
|
16
26
|
|
|
17
|
-
- b8ec213: Fixed:
|
|
27
|
+
- b8ec213: Fixed: A company you create in the browser now appears in your assistant's list of companies straight away.
|
|
18
28
|
|
|
19
29
|
It used to appear only after the service restarted, although a search could already be started for it. Its projects were missing from the list in the same way, and so was its account in the search options.
|
|
20
30
|
|
|
21
|
-
The
|
|
31
|
+
The check comparing the running service with the configuration store no longer calls a company the portal's key does not cover a disagreement. It names that gap on a line of its own, with the command that widens the key.
|
|
22
32
|
|
|
23
33
|
## 0.2.4
|
|
24
34
|
|
|
@@ -193,7 +193,10 @@ function accountFor(key, { scope, now }) {
|
|
|
193
193
|
profile = loadProfiles().get(key) ?? null;
|
|
194
194
|
if (!profile) { fresh = loadProfiles({ force: true }); profile = fresh.get(key) ?? null; }
|
|
195
195
|
} catch { return null; } // unreadable roster ⇒ say nothing
|
|
196
|
-
|
|
196
|
+
// GENERIC IS ANSWERED LIKE ANY COMPANY (owner ruling, 2026-09-11). It owns saved searches in the portal,
|
|
197
|
+
// and a fresh install holds Generic alone, so treating it as nobody's account told the assistant on
|
|
198
|
+
// every such install that none existed.
|
|
199
|
+
if (!profile) return null;
|
|
197
200
|
|
|
198
201
|
let projects = [];
|
|
199
202
|
try {
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.5",
|
|
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/package.json
CHANGED
|
@@ -2205,127 +2205,249 @@ input.filter {
|
|
|
2205
2205
|
}
|
|
2206
2206
|
|
|
2207
2207
|
|
|
2208
|
-
|
|
2208
|
+
/* ── WHAT YOU CAN DO ─────────────────────────────────────────────────────────────────────────────── */
|
|
2209
|
+
.ai-can { margin-top: 20px; max-width: 860px; }
|
|
2209
2210
|
|
|
2210
|
-
|
|
2211
|
-
.ai-where { margin: 0 0 14px; max-width: var(--screen-measure); }
|
|
2211
|
+
.ai-screen .ai-can h2 { font-size: 14.5px; margin: 0 0 3px; }
|
|
2212
2212
|
|
|
2213
|
-
.ai-
|
|
2213
|
+
.ai-can-list {
|
|
2214
|
+
list-style: none;
|
|
2215
|
+
margin: 8px 0 0;
|
|
2216
|
+
padding: 0;
|
|
2217
|
+
display: grid;
|
|
2218
|
+
grid-template-columns: 1fr 1fr;
|
|
2219
|
+
gap: 8px 28px;
|
|
2220
|
+
}
|
|
2214
2221
|
|
|
2215
|
-
|
|
2222
|
+
@media (max-width: 700px) { .ai-can-list { grid-template-columns: 1fr; } }
|
|
2216
2223
|
|
|
2217
|
-
.ai-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2224
|
+
.ai-can-list li { display: flex; gap: 9px; font-size: 14px; line-height: 1.5; color: var(--text-body); }
|
|
2225
|
+
|
|
2226
|
+
.ai-can-list li svg { margin-top: 3px; color: var(--accent-quiet); }
|
|
2227
|
+
|
|
2228
|
+
.ai-can-foot {
|
|
2229
|
+
margin: 13px 0 0;
|
|
2230
|
+
padding-top: 11px;
|
|
2231
|
+
border-top: 1px solid var(--border-hairline);
|
|
2232
|
+
font-size: 13.5px;
|
|
2223
2233
|
color: var(--text-muted);
|
|
2224
|
-
cursor: pointer;
|
|
2225
2234
|
}
|
|
2226
2235
|
|
|
2227
|
-
.ai-
|
|
2228
|
-
|
|
2229
|
-
.ai-seg-btn[data-on] { background: var(--surface-sunken); color: var(--text-strong); }
|
|
2236
|
+
.ai-can-foot strong { color: var(--text-strong); }
|
|
2230
2237
|
|
|
2231
|
-
.ai-
|
|
2238
|
+
.ai-connect { margin-top: 34px; }
|
|
2232
2239
|
|
|
2233
|
-
.ai-
|
|
2240
|
+
.ai-screen .ai-connect-head { margin: 0 0 12px; }
|
|
2234
2241
|
|
|
2235
|
-
|
|
2242
|
+
/* ── THE WHERE-CHOICE ─────────────────────────────────────────────────────────────────────────────
|
|
2243
|
+
ONE control, the same size before and after a pick. Selection is shown the way every other choice in
|
|
2244
|
+
the portal shows it — accent wash, accent border, filled radio — and never a second colour. */
|
|
2245
|
+
.where-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
2236
2246
|
|
|
2237
|
-
|
|
2238
|
-
.ai-dests {
|
|
2239
|
-
display: flex;
|
|
2240
|
-
flex-direction: column;
|
|
2241
|
-
gap: 8px;
|
|
2242
|
-
max-width: var(--screen-measure);
|
|
2243
|
-
}
|
|
2247
|
+
@media (max-width: 640px) { .where-grid { grid-template-columns: 1fr; } }
|
|
2244
2248
|
|
|
2245
|
-
|
|
2246
|
-
heights"; more importantly a row that resizes when it is pressed moves its neighbours, which is the
|
|
2247
|
-
reflow this rebuild exists to end. */
|
|
2248
|
-
.ai-dest {
|
|
2249
|
+
.where-card {
|
|
2249
2250
|
display: flex;
|
|
2250
2251
|
align-items: center;
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
padding: 8px 14px;
|
|
2256
|
-
border: 1px solid var(--border-hairline);
|
|
2257
|
-
border-radius: 8px;
|
|
2252
|
+
gap: 12px;
|
|
2253
|
+
padding: 18px;
|
|
2254
|
+
border: 1px solid var(--border-strong);
|
|
2255
|
+
border-radius: 12px;
|
|
2258
2256
|
background: var(--surface-raised);
|
|
2259
2257
|
font: inherit;
|
|
2258
|
+
font-size: 16px;
|
|
2259
|
+
font-weight: 700;
|
|
2260
|
+
line-height: 1.35;
|
|
2261
|
+
color: var(--text-strong);
|
|
2260
2262
|
text-align: left;
|
|
2261
2263
|
cursor: pointer;
|
|
2262
2264
|
}
|
|
2263
2265
|
|
|
2264
|
-
.
|
|
2266
|
+
.where-card:hover { border-color: var(--accent-ink); }
|
|
2265
2267
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2268
|
+
.where-card[aria-pressed='true'],
|
|
2269
|
+
.where-card[aria-pressed='true']:hover { border-color: var(--accent); background: var(--accent-wash); }
|
|
2270
|
+
|
|
2271
|
+
.ai-none { margin: 22px 0 0; max-width: 860px; border-left-color: var(--tone-medium); }
|
|
2272
|
+
|
|
2273
|
+
.ai-none p { margin: 6px 0 0; color: var(--text-muted); font-size: 13.5px; line-height: 1.55; }
|
|
2274
|
+
|
|
2275
|
+
/* ── THE APP LIST AND THE PANEL BESIDE IT ─────────────────────────────────────────────────────── */
|
|
2276
|
+
.ai-connect-grid {
|
|
2277
|
+
display: grid;
|
|
2278
|
+
grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
|
|
2279
|
+
gap: 20px;
|
|
2280
|
+
margin-top: 22px;
|
|
2281
|
+
align-items: start;
|
|
2272
2282
|
}
|
|
2273
2283
|
|
|
2274
|
-
|
|
2284
|
+
@media (max-width: 860px) { .ai-connect-grid { grid-template-columns: 1fr; } }
|
|
2285
|
+
|
|
2286
|
+
.ai-apps { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
|
|
2287
|
+
|
|
2288
|
+
/* The New Clearance picker's radio-row, centred because these rows carry a name and at most one line. */
|
|
2289
|
+
.ai-app { width: 100%; align-items: center; font: inherit; }
|
|
2275
2290
|
|
|
2276
|
-
.ai-
|
|
2291
|
+
.ai-app-label { min-width: 0; }
|
|
2277
2292
|
|
|
2278
|
-
.ai-
|
|
2293
|
+
.ai-app-name { display: block; font-size: 14px; font-weight: 700; color: var(--text-strong); }
|
|
2279
2294
|
|
|
2280
|
-
.ai-
|
|
2295
|
+
.ai-app-sub { display: block; margin-top: 1px; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
|
|
2296
|
+
|
|
2297
|
+
.ai-app-go { margin-left: auto; display: inline-flex; color: var(--text-accent); opacity: 0; }
|
|
2298
|
+
|
|
2299
|
+
.pick-row-on .ai-app-go { opacity: 1; }
|
|
2300
|
+
|
|
2301
|
+
/* ── THE RESERVED SLOT ──────────────────────────────────────────────────────────────────────────────
|
|
2302
|
+
A MINIMUM HEIGHT WHETHER OR NOT ANYTHING IS IN IT, and the page raises it to the tallest panel this
|
|
2303
|
+
card can show — so neither picking an app nor pressing Copy moves anything around it. */
|
|
2304
|
+
.ai-slot { position: relative; min-height: 180px; }
|
|
2305
|
+
|
|
2306
|
+
/* The hidden copy of every panel the slot's height is taken from. Laid out at the slot's own width, never
|
|
2307
|
+
seen, never reached by a pointer or a keyboard. */
|
|
2308
|
+
.ai-probe {
|
|
2309
|
+
position: absolute;
|
|
2310
|
+
top: 0;
|
|
2311
|
+
left: 0;
|
|
2312
|
+
right: 0;
|
|
2313
|
+
display: grid;
|
|
2314
|
+
align-items: start;
|
|
2315
|
+
visibility: hidden;
|
|
2316
|
+
pointer-events: none;
|
|
2317
|
+
}
|
|
2281
2318
|
|
|
2282
|
-
/*
|
|
2283
|
-
|
|
2284
|
-
.ai-dest-say { display: grid; font-size: 13px; }
|
|
2319
|
+
/* Stacked in one cell, so the copy is as tall as its tallest panel and never taller than the slot. */
|
|
2320
|
+
.ai-probe > * { grid-area: 1 / 1; }
|
|
2285
2321
|
|
|
2286
|
-
.
|
|
2322
|
+
.steps-panel {
|
|
2323
|
+
border: 1px solid var(--border-strong);
|
|
2324
|
+
border-left: 3px solid var(--accent);
|
|
2325
|
+
border-radius: 12px;
|
|
2326
|
+
background: var(--surface-raised);
|
|
2327
|
+
padding: 18px 20px 20px;
|
|
2328
|
+
}
|
|
2287
2329
|
|
|
2288
|
-
.ai-
|
|
2330
|
+
.ai-slot > .steps-panel { min-height: inherit; }
|
|
2289
2331
|
|
|
2290
|
-
.
|
|
2332
|
+
.steps-panel-empty {
|
|
2333
|
+
border-left-color: var(--border-strong);
|
|
2334
|
+
display: flex;
|
|
2335
|
+
align-items: center;
|
|
2336
|
+
justify-content: center;
|
|
2337
|
+
}
|
|
2291
2338
|
|
|
2292
|
-
.
|
|
2339
|
+
.steps-empty { display: flex; align-items: center; gap: 9px; color: var(--text-faint); font-size: 14px; }
|
|
2293
2340
|
|
|
2294
|
-
.ai-
|
|
2341
|
+
.ai-screen .steps-name { margin: 3px 0 0; font-size: 18px; font-weight: 700; color: var(--text-strong); }
|
|
2295
2342
|
|
|
2296
|
-
/* ── THE
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2343
|
+
/* ── THE NUMBERED STEPS ─────────────────────────────────────────────────────────────────────────── */
|
|
2344
|
+
ol.steps {
|
|
2345
|
+
list-style: none;
|
|
2346
|
+
counter-reset: step;
|
|
2300
2347
|
margin: 16px 0 0;
|
|
2301
|
-
|
|
2302
|
-
|
|
2348
|
+
padding: 0;
|
|
2349
|
+
display: flex;
|
|
2350
|
+
flex-direction: column;
|
|
2351
|
+
gap: 16px;
|
|
2303
2352
|
}
|
|
2304
2353
|
|
|
2305
|
-
.
|
|
2354
|
+
ol.steps > li {
|
|
2355
|
+
counter-increment: step;
|
|
2356
|
+
display: grid;
|
|
2357
|
+
grid-template-columns: 24px minmax(0, 1fr);
|
|
2358
|
+
gap: 11px;
|
|
2359
|
+
}
|
|
2306
2360
|
|
|
2307
|
-
.
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2361
|
+
ol.steps > li::before {
|
|
2362
|
+
content: counter(step);
|
|
2363
|
+
width: 23px;
|
|
2364
|
+
height: 23px;
|
|
2365
|
+
margin-top: -1px;
|
|
2366
|
+
display: flex;
|
|
2367
|
+
align-items: center;
|
|
2368
|
+
justify-content: center;
|
|
2369
|
+
border-radius: 50%;
|
|
2311
2370
|
background: var(--surface-sunken);
|
|
2371
|
+
border: 1px solid var(--border-hairline);
|
|
2372
|
+
font-family: var(--font-mono);
|
|
2373
|
+
font-size: 11px;
|
|
2374
|
+
font-weight: 600;
|
|
2375
|
+
color: var(--text-muted);
|
|
2312
2376
|
}
|
|
2313
2377
|
|
|
2314
|
-
.
|
|
2378
|
+
.step-text { font-size: 14px; line-height: 1.55; color: var(--text-body); }
|
|
2379
|
+
|
|
2380
|
+
.step-text b { color: var(--text-strong); }
|
|
2381
|
+
|
|
2382
|
+
.step-hint { margin-top: 4px; font-size: 12.5px; line-height: 1.5; color: var(--text-muted); }
|
|
2315
2383
|
|
|
2316
|
-
.ai-
|
|
2384
|
+
.ai-code {
|
|
2385
|
+
padding: 1px 5px;
|
|
2386
|
+
border: 1px solid var(--border-hairline);
|
|
2387
|
+
border-radius: 4px;
|
|
2388
|
+
background: var(--surface-sunken);
|
|
2389
|
+
font-family: var(--font-mono);
|
|
2390
|
+
font-size: 12px;
|
|
2391
|
+
color: var(--text-strong);
|
|
2392
|
+
overflow-wrap: anywhere;
|
|
2393
|
+
}
|
|
2317
2394
|
|
|
2318
|
-
.
|
|
2395
|
+
.codeblock {
|
|
2396
|
+
margin-top: 8px;
|
|
2397
|
+
display: flex;
|
|
2398
|
+
align-items: flex-start;
|
|
2399
|
+
gap: 10px;
|
|
2400
|
+
padding: 10px 10px 10px 13px;
|
|
2401
|
+
border: 1px solid var(--border-hairline);
|
|
2402
|
+
border-radius: 10px;
|
|
2403
|
+
background: var(--surface-sunken);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
.codeblock pre {
|
|
2407
|
+
flex: 1;
|
|
2408
|
+
min-width: 0;
|
|
2319
2409
|
margin: 0;
|
|
2320
|
-
padding-
|
|
2321
|
-
|
|
2322
|
-
|
|
2410
|
+
padding-top: 3px;
|
|
2411
|
+
overflow-x: auto;
|
|
2412
|
+
font-family: var(--font-mono);
|
|
2413
|
+
font-size: 12px;
|
|
2414
|
+
line-height: 1.6;
|
|
2415
|
+
color: var(--text-strong);
|
|
2323
2416
|
}
|
|
2324
2417
|
|
|
2325
|
-
.
|
|
2418
|
+
.codeblock .btn-ghost { padding: 6px 12px; white-space: nowrap; }
|
|
2419
|
+
|
|
2420
|
+
.secret-btn { margin-top: 8px; }
|
|
2421
|
+
|
|
2422
|
+
/* THE PRESSED CONTROL IS THE CONFIRMATION: solid accent on the button pressed, never a muted caption. */
|
|
2423
|
+
.is-copied,
|
|
2424
|
+
.is-copied:hover,
|
|
2425
|
+
.btn-primary.is-copied:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); color: var(--text-on-accent); }
|
|
2426
|
+
|
|
2427
|
+
.landed {
|
|
2428
|
+
margin-top: 10px;
|
|
2429
|
+
padding: 10px 13px;
|
|
2430
|
+
border: 1px solid var(--border-hairline);
|
|
2431
|
+
border-radius: 10px;
|
|
2432
|
+
background: var(--surface-sunken);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
.landed-label {
|
|
2436
|
+
font-size: 10px;
|
|
2437
|
+
font-weight: 700;
|
|
2438
|
+
letter-spacing: 0.13em;
|
|
2439
|
+
text-transform: uppercase;
|
|
2440
|
+
color: var(--text-faint);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
.landed .mono { font-size: 12px; color: var(--text-strong); overflow-wrap: anywhere; }
|
|
2444
|
+
|
|
2445
|
+
.landed .mono + .mono { margin-top: 2px; }
|
|
2326
2446
|
|
|
2327
2447
|
.ai-said { margin: 0 0 6px; color: var(--text-muted); }
|
|
2328
2448
|
|
|
2449
|
+
.ai-failed { margin: 10px 0 0; }
|
|
2450
|
+
|
|
2329
2451
|
.ai-pre {
|
|
2330
2452
|
margin: 0;
|
|
2331
2453
|
padding: 10px;
|
|
@@ -2335,21 +2457,12 @@ input.filter {
|
|
|
2335
2457
|
font-size: 13px;
|
|
2336
2458
|
}
|
|
2337
2459
|
|
|
2338
|
-
/* The help a person needs,
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
.ai-help > summary {
|
|
2342
|
-
cursor: pointer;
|
|
2343
|
-
color: var(--text-muted);
|
|
2344
|
-
font-size: 13px;
|
|
2345
|
-
font-style: italic;
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
.ai-help-body { margin: 10px 0 0; color: var(--text-muted); line-height: 1.65; }
|
|
2460
|
+
/* The help a person needs, as a quiet notice. The link opens the document where a reader's own AI can
|
|
2461
|
+
read it too. */
|
|
2462
|
+
.ai-help { margin-top: 30px; max-width: 860px; }
|
|
2349
2463
|
|
|
2350
|
-
.ai-help
|
|
2464
|
+
.ai-help p { margin: 6px 0 0; color: var(--text-muted); font-size: 13.5px; line-height: 1.55; }
|
|
2351
2465
|
|
|
2352
|
-
.ai-help-doc { font-size: 13px; }
|
|
2353
2466
|
|
|
2354
2467
|
/* ── Give someone access: the points on the tree a new person can be given ─────────────────────────────
|
|
2355
2468
|
One row per point. A BUTTON, because choosing a point is an action and a keyboard has to reach it; the
|