fraim 2.0.363-next.0 → 2.0.363
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/dist/src/cli/utils/managed-agent-install.js +1 -1
- package/dist/src/cli/utils/persist-shell-path.js +36 -8
- package/dist/src/first-run/server.js +9 -0
- package/dist/src/first-run/session-service.js +38 -17
- package/package.json +1 -1
- package/public/first-run/index.html +3 -1
- package/public/first-run/script.js +59 -5
- package/public/first-run/styles.css +4 -0
|
@@ -186,7 +186,7 @@ async function installManagedAgent(option, systemPath, deps) {
|
|
|
186
186
|
if (!(await verifyPrefix(prefix))) {
|
|
187
187
|
throw new Error(option.label + ' managed installed target failed verification. Standard install: ' + (standardInstallError || 'not requested'));
|
|
188
188
|
}
|
|
189
|
-
(0, persist_shell_path_1.persistShellPath)();
|
|
189
|
+
await (0, persist_shell_path_1.persistShellPath)({ signal: option.signal });
|
|
190
190
|
(0, managed_agent_paths_1.invalidateAgentCommandRecovery)();
|
|
191
191
|
return { outcome: 'managed' };
|
|
192
192
|
}
|
|
@@ -8,9 +8,13 @@ exports.persistShellPath = persistShellPath;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const
|
|
11
|
+
const background_command_1 = require("./background-command");
|
|
12
12
|
const script_sync_utils_1 = require("./script-sync-utils");
|
|
13
13
|
const managed_agent_paths_1 = require("./managed-agent-paths");
|
|
14
|
+
// Unconfirmed cleanup means the old writer may still be running. Both setup
|
|
15
|
+
// and managed installs must stop retrying until this process is restarted.
|
|
16
|
+
let cleanupFailure;
|
|
17
|
+
let registrationQueue = Promise.resolve();
|
|
14
18
|
// Issue #1285: pure command-string builder extracted from persistShellPath()
|
|
15
19
|
// so the generated PowerShell command's directory order (the current
|
|
16
20
|
// versioned dir added before the legacy flat dir, per `bins`' own order —
|
|
@@ -24,14 +28,25 @@ function buildPersistShellPathWindowsCommand(bins) {
|
|
|
24
28
|
`$ErrorActionPreference = 'Stop'`,
|
|
25
29
|
...assignments,
|
|
26
30
|
`$cur = [Environment]::GetEnvironmentVariable('PATH', 'User')`,
|
|
31
|
+
`$originalPath = $cur`,
|
|
27
32
|
`$parts = @($cur -split ';' | Where-Object { $_ })`,
|
|
28
33
|
...removals,
|
|
29
34
|
...additions,
|
|
30
35
|
`$cur = ($parts | Select-Object -Unique) -join ';'`,
|
|
31
|
-
`[Environment]::SetEnvironmentVariable('PATH', $cur, 'User')`,
|
|
36
|
+
`if ($cur -cne $originalPath) { [Environment]::SetEnvironmentVariable('PATH', $cur, 'User') }`,
|
|
32
37
|
].join('; ');
|
|
33
38
|
}
|
|
34
|
-
function persistShellPath() {
|
|
39
|
+
function persistShellPath(options = {}) {
|
|
40
|
+
// Configuration and separate agent installs share the same User PATH. Queue
|
|
41
|
+
// each caller with its own cancellation signal rather than overlapping writes.
|
|
42
|
+
const operation = registrationQueue.then(() => registerShellPath(options));
|
|
43
|
+
registrationQueue = operation.catch(() => undefined);
|
|
44
|
+
return operation;
|
|
45
|
+
}
|
|
46
|
+
async function registerShellPath(options) {
|
|
47
|
+
if (cleanupFailure)
|
|
48
|
+
throw cleanupFailure;
|
|
49
|
+
options.signal?.throwIfAborted();
|
|
35
50
|
const marker = '# FRAIM managed binaries';
|
|
36
51
|
const legacyExportLine = 'export PATH=”$HOME/.fraim/node/bin:$PATH”';
|
|
37
52
|
// The stable CLI shim must outrank old prefix installs already on inherited PATH.
|
|
@@ -51,23 +66,34 @@ function persistShellPath() {
|
|
|
51
66
|
const stanza = `\n${marker}\n${exportLine}\n`;
|
|
52
67
|
if (process.platform === 'win32') {
|
|
53
68
|
const psCmd = buildPersistShellPathWindowsCommand(bins);
|
|
54
|
-
// Issue #1692: a sandboxed test run
|
|
55
|
-
// success, fraim row success all reach this function) must never write
|
|
69
|
+
// Issue #1692: a sandboxed test run must never write
|
|
56
70
|
// its temp fake-home bin dirs into the real, registry-backed Windows
|
|
57
71
|
// User PATH. FRAIM_TEST_SANDBOX is already the canonical "this process
|
|
58
72
|
// is a sandboxed test run" signal set by every test runner (see
|
|
59
73
|
// getUserFraimDirPath() in project-fraim-paths.ts) rather than a
|
|
60
74
|
// dedicated flag for this call site.
|
|
61
75
|
if (process.env.FRAIM_TEST_SANDBOX !== '1') {
|
|
62
|
-
const result = (0,
|
|
76
|
+
const result = await (0, background_command_1.runBackgroundCommand)('powershell', ['-NoProfile', '-NonInteractive', '-Command', psCmd], {
|
|
77
|
+
signal: options.signal, timeoutMs: 10000,
|
|
78
|
+
});
|
|
63
79
|
if (result.error || result.status !== 0) {
|
|
64
|
-
|
|
80
|
+
const details = result.error?.message || result.stderr.trim() || `PowerShell exited with status ${result.status}`;
|
|
81
|
+
if (result.outcome === 'cleanup-failed') {
|
|
82
|
+
cleanupFailure = Object.assign(new Error(`Could not stop Windows PATH registration: ${details}. Close the failed PowerShell process and restart FRAIM before trying again.`), {
|
|
83
|
+
code: result.outcome,
|
|
84
|
+
});
|
|
85
|
+
throw cleanupFailure;
|
|
86
|
+
}
|
|
87
|
+
throw Object.assign(new Error(`Could not register managed binaries on the User PATH: ${details}`), {
|
|
88
|
+
code: result.outcome,
|
|
89
|
+
});
|
|
65
90
|
}
|
|
66
91
|
}
|
|
67
92
|
// Fall through to write POSIX profile files for Git Bash (VS Code
|
|
68
93
|
// integrated terminal, Claude Code, etc.) which do not inherit the
|
|
69
94
|
// Windows user PATH written above.
|
|
70
95
|
}
|
|
96
|
+
options.signal?.throwIfAborted();
|
|
71
97
|
// Test runners isolate FRAIM_USER_DIR but may leave os.homedir() unchanged.
|
|
72
98
|
const sandboxDir = process.env.FRAIM_USER_DIR ? path_1.default.resolve(process.env.FRAIM_USER_DIR) : null;
|
|
73
99
|
const profileHome = process.env.FRAIM_TEST_SANDBOX === '1'
|
|
@@ -107,7 +133,9 @@ function persistShellPath() {
|
|
|
107
133
|
if (content.includes(marker)) {
|
|
108
134
|
if (!content.includes(exportLine)) {
|
|
109
135
|
// Replace stale runtime directories or an older shim ordering.
|
|
110
|
-
|
|
136
|
+
const updated = content.replace(/export PATH=[\u201D"][^\u201D"]*\.fraim[^\u201D"]*[\u201D"]/, exportLine);
|
|
137
|
+
if (updated !== content)
|
|
138
|
+
fs_1.default.writeFileSync(profile, updated, 'utf8');
|
|
111
139
|
}
|
|
112
140
|
continue;
|
|
113
141
|
}
|
|
@@ -141,6 +141,15 @@ class FirstRunServer {
|
|
|
141
141
|
res.json({ status: 'ok', service: 'fraim-first-run' });
|
|
142
142
|
});
|
|
143
143
|
this.registerRoutes();
|
|
144
|
+
const apiErrorHandler = (error, req, res, next) => {
|
|
145
|
+
if (!req.path.startsWith('/api/first-run/') || res.headersSent)
|
|
146
|
+
return next(error);
|
|
147
|
+
const status = error?.type === 'entity.parse.failed' ? 400 : 500;
|
|
148
|
+
res.status(status).json({ error: status === 400
|
|
149
|
+
? 'Invalid JSON request.'
|
|
150
|
+
: error instanceof Error ? error.message : 'First-run request failed.' });
|
|
151
|
+
};
|
|
152
|
+
this.app.use(apiErrorHandler);
|
|
144
153
|
}
|
|
145
154
|
async start(port) {
|
|
146
155
|
this.sessionService.resumePendingOperations();
|
|
@@ -168,6 +168,7 @@ class FirstRunSessionService {
|
|
|
168
168
|
this.operationAbort = new AbortController(); }
|
|
169
169
|
cancelPendingOperations() { this.operationAbort.abort(); }
|
|
170
170
|
constructor(options) {
|
|
171
|
+
this.rowOperations = new Map();
|
|
171
172
|
this.operationAbort = new AbortController();
|
|
172
173
|
// Issue #646: the key may be absent when first-run is launched by the macOS
|
|
173
174
|
// installer (.dmg) instead of `fraim first-run --key=…`. In that case the
|
|
@@ -175,6 +176,7 @@ class FirstRunSessionService {
|
|
|
175
176
|
this.key = options.key || '';
|
|
176
177
|
this.headless = options.headless === true;
|
|
177
178
|
this.embeddedDesktop = options.embeddedDesktop === true;
|
|
179
|
+
this.registerShellPath = options.registerShellPath ?? persist_shell_path_1.persistShellPath;
|
|
178
180
|
this.fakeMode = getFakeStateMode();
|
|
179
181
|
this.fakeStderr =
|
|
180
182
|
process.env.FRAIM_FIRST_RUN_FAKE_STDERR ??
|
|
@@ -182,6 +184,10 @@ class FirstRunSessionService {
|
|
|
182
184
|
this.requestToken = crypto_1.default.randomUUID();
|
|
183
185
|
this.state = options.resume ? (0, install_state_1.loadFirstRunState)() || (0, install_state_1.createInitialFirstRunState)(options.key) : (0, install_state_1.createInitialFirstRunState)(options.key);
|
|
184
186
|
this.state.rows = normalizeRows(this.state.rows);
|
|
187
|
+
const interrupted = this.getRow('fraim');
|
|
188
|
+
if (interrupted.status === 'in-progress') {
|
|
189
|
+
this.setRowError(interrupted, 'Setting up FRAIM on this machine', 'Setup was interrupted before it completed. Try again.', [{ id: 'retry', label: 'Retry', variant: 'primary' }]);
|
|
190
|
+
}
|
|
185
191
|
if (options.projectRoot) {
|
|
186
192
|
this.state.workspacePath = path_1.default.resolve(options.projectRoot);
|
|
187
193
|
(0, install_state_1.saveFirstRunState)(this.state);
|
|
@@ -261,7 +267,10 @@ class FirstRunSessionService {
|
|
|
261
267
|
gitRow.verb = 'optional — only needed for code delivery workflows';
|
|
262
268
|
}
|
|
263
269
|
const fraimRow = this.getRow('fraim');
|
|
264
|
-
if (
|
|
270
|
+
if (fraimRow.status === 'error' || fraimRow.status === 'in-progress') {
|
|
271
|
+
// A prior config is not evidence that the current setup attempt completed.
|
|
272
|
+
}
|
|
273
|
+
else if (this.embeddedDesktop || commandVersion('npx') !== null) {
|
|
265
274
|
// Only mark ok if setup has previously completed (config.json is written after autoConfigureMCP).
|
|
266
275
|
// Without this, the wizard skips runFraimRow entirely and autoConfigureMCP never runs for IDEs.
|
|
267
276
|
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
@@ -273,7 +282,6 @@ class FirstRunSessionService {
|
|
|
273
282
|
fraimRow.status = 'pending';
|
|
274
283
|
fraimRow.verb = "we'll configure your IDEs";
|
|
275
284
|
}
|
|
276
|
-
(0, persist_shell_path_1.persistShellPath)();
|
|
277
285
|
}
|
|
278
286
|
else {
|
|
279
287
|
fraimRow.status = 'pending';
|
|
@@ -473,6 +481,19 @@ class FirstRunSessionService {
|
|
|
473
481
|
* order, until every row is `ok` or surfaces an error.
|
|
474
482
|
*/
|
|
475
483
|
async runRow(rowId, request = {}) {
|
|
484
|
+
const existing = this.rowOperations.get(rowId);
|
|
485
|
+
if (existing)
|
|
486
|
+
return existing;
|
|
487
|
+
const operation = this.executeRow(rowId, request);
|
|
488
|
+
this.rowOperations.set(rowId, operation);
|
|
489
|
+
try {
|
|
490
|
+
return await operation;
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
this.rowOperations.delete(rowId);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
async executeRow(rowId, request) {
|
|
476
497
|
const row = this.getRow(rowId);
|
|
477
498
|
if (row.status === 'ok') {
|
|
478
499
|
return this.respond(`${row.label} already ready.`, true);
|
|
@@ -523,7 +544,6 @@ class FirstRunSessionService {
|
|
|
523
544
|
this.persist();
|
|
524
545
|
return this.respond('FRAIM installer repair is required.', false);
|
|
525
546
|
}
|
|
526
|
-
(0, persist_shell_path_1.persistShellPath)();
|
|
527
547
|
row.status = 'ok';
|
|
528
548
|
row.verb = 'Node and FRAIM launchers ready';
|
|
529
549
|
this.persist();
|
|
@@ -533,7 +553,6 @@ class FirstRunSessionService {
|
|
|
533
553
|
// If node is missing here something is severely wrong; fall through to error.
|
|
534
554
|
const ver = commandVersion('node');
|
|
535
555
|
if (ver) {
|
|
536
|
-
(0, persist_shell_path_1.persistShellPath)();
|
|
537
556
|
row.status = 'ok';
|
|
538
557
|
row.verb = `${ver} installed`;
|
|
539
558
|
this.persist();
|
|
@@ -581,22 +600,21 @@ class FirstRunSessionService {
|
|
|
581
600
|
this.persist();
|
|
582
601
|
return this.respond('Fake-mode fraim ok.', true);
|
|
583
602
|
}
|
|
584
|
-
// FRAIM_SKIP_SYNC signals an isolated/test environment. Skip all external
|
|
585
|
-
// writes (MCP config files, global config.json, slash commands) so tests
|
|
586
|
-
// that auto-trigger this path cannot corrupt the real user config.
|
|
587
|
-
if (process.env.FRAIM_SKIP_SYNC) {
|
|
588
|
-
row.status = 'ok';
|
|
589
|
-
row.verb = 'Ready.';
|
|
590
|
-
this.persist();
|
|
591
|
-
return this.respond('FRAIM configured.', true);
|
|
592
|
-
}
|
|
593
603
|
try {
|
|
594
|
-
if (this.embeddedDesktop && (!fs_1.default.existsSync((0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)()) || !fs_1.default.existsSync((0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)()))) {
|
|
604
|
+
if (!process.env.FRAIM_SKIP_SYNC && this.embeddedDesktop && (!fs_1.default.existsSync((0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)()) || !fs_1.default.existsSync((0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)()))) {
|
|
595
605
|
throw new Error('Stable FRAIM launchers are missing. Re-run the native installer in repair mode.');
|
|
596
606
|
}
|
|
597
|
-
if (!this.embeddedDesktop)
|
|
607
|
+
if (!process.env.FRAIM_SKIP_SYNC && !this.embeddedDesktop)
|
|
598
608
|
(0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
|
|
599
|
-
(
|
|
609
|
+
await this.registerShellPath({ signal: this.operationAbort.signal });
|
|
610
|
+
// Tests isolate PATH with FRAIM_TEST_SANDBOX; this flag skips remote sync
|
|
611
|
+
// and IDE/global configuration while retaining the setup lifecycle.
|
|
612
|
+
if (process.env.FRAIM_SKIP_SYNC) {
|
|
613
|
+
row.status = 'ok';
|
|
614
|
+
row.verb = 'Ready.';
|
|
615
|
+
this.persist();
|
|
616
|
+
return this.respond('FRAIM configured.', true);
|
|
617
|
+
}
|
|
600
618
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
601
619
|
if (detectedIDEs.length > 0) {
|
|
602
620
|
await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, detectedIDEs.map((ide) => ide.name));
|
|
@@ -835,6 +853,9 @@ class FirstRunSessionService {
|
|
|
835
853
|
};
|
|
836
854
|
}
|
|
837
855
|
async finishAgentRecruiting() {
|
|
856
|
+
const existing = this.rowOperations.get('fraim');
|
|
857
|
+
if (existing)
|
|
858
|
+
return existing;
|
|
838
859
|
this.updateAgentSummaryRow();
|
|
839
860
|
const readyCount = this.state.setupResult?.detectedSurfaceCount || 0;
|
|
840
861
|
if (readyCount < 1) {
|
|
@@ -846,7 +867,7 @@ class FirstRunSessionService {
|
|
|
846
867
|
fraimRow.verb = 'configuring FRAIM for local AI agents';
|
|
847
868
|
this.persist();
|
|
848
869
|
appendInstallLog(`agent-recruiting-complete ready=${readyCount}`);
|
|
849
|
-
return this.
|
|
870
|
+
return this.runRow('fraim');
|
|
850
871
|
}
|
|
851
872
|
openTerminalWithCommand(command) {
|
|
852
873
|
if (process.platform === 'win32') {
|
package/package.json
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
</header>
|
|
16
16
|
|
|
17
17
|
<section class="card">
|
|
18
|
-
<
|
|
18
|
+
<div id="checklist" data-testid="setup-checklist">
|
|
19
|
+
<ul class="checklist" aria-label="FRAIM setup checklist">
|
|
19
20
|
<!-- Skeleton rows shown until the async session-load completes.
|
|
20
21
|
Replaced wholesale by script.js once /api/first-run/session returns. -->
|
|
21
22
|
<li class="row skeleton-row" data-row-id="node" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">Node.js</span><span class="verb" data-testid="row-verb">checking...</span></li>
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
<li class="row skeleton-row" data-row-id="agent" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">Locally installed AI agents</span><span class="verb" data-testid="row-verb">checking...</span></li>
|
|
24
25
|
<li class="row skeleton-row" data-row-id="fraim" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">FRAIM</span><span class="verb" data-testid="row-verb">checking...</span></li>
|
|
25
26
|
</ul>
|
|
27
|
+
</div>
|
|
26
28
|
<div class="actions">
|
|
27
29
|
<button id="primary-button" class="primary-button" data-testid="primary-button">Set up FRAIM</button>
|
|
28
30
|
</div>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const state = {
|
|
10
10
|
session: null,
|
|
11
|
+
sessionLoadInFlight: false,
|
|
11
12
|
activeStep: 'prereqs',
|
|
12
13
|
configureInFlight: false,
|
|
13
14
|
configureError: null,
|
|
@@ -45,8 +46,19 @@
|
|
|
45
46
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
46
47
|
});
|
|
47
48
|
if (response.status === 204) return null;
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
let json;
|
|
50
|
+
try {
|
|
51
|
+
json = await response.json();
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error(response.ok
|
|
54
|
+
? 'FRAIM returned an invalid response. Please try again.'
|
|
55
|
+
: `Request failed (status ${response.status}). Please try again.`);
|
|
56
|
+
}
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(json && typeof json.error === 'string' && json.error.trim()
|
|
59
|
+
? json.error
|
|
60
|
+
: `Request failed (status ${response.status}). Please try again.`);
|
|
61
|
+
}
|
|
50
62
|
return json;
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -632,6 +644,46 @@
|
|
|
632
644
|
}
|
|
633
645
|
}
|
|
634
646
|
|
|
647
|
+
function renderSessionLoadFailure(message) {
|
|
648
|
+
CHECKLIST_EL.className = 'setup-shell setup-shell--single';
|
|
649
|
+
CHECKLIST_EL.innerHTML = '';
|
|
650
|
+
PRIMARY_BUTTON.style.display = 'none';
|
|
651
|
+
setHeader('Set up FRAIM', 'Retry to check your machine and continue setup.');
|
|
652
|
+
setStatus('');
|
|
653
|
+
|
|
654
|
+
const pane = document.createElement('div');
|
|
655
|
+
pane.className = 'setup-pane';
|
|
656
|
+
const heading = document.createElement('h2');
|
|
657
|
+
heading.textContent = 'Could not load setup';
|
|
658
|
+
const error = document.createElement('div');
|
|
659
|
+
error.className = 'ready-strip ready-strip--error';
|
|
660
|
+
error.setAttribute('role', 'alert');
|
|
661
|
+
error.textContent = message;
|
|
662
|
+
const retry = button('Retry', 'primary');
|
|
663
|
+
retry.addEventListener('click', () => startSessionLoad(retry));
|
|
664
|
+
pane.append(heading, error, retry);
|
|
665
|
+
CHECKLIST_EL.appendChild(pane);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function startSessionLoad(retryButton) {
|
|
669
|
+
if (state.sessionLoadInFlight) return;
|
|
670
|
+
state.sessionLoadInFlight = true;
|
|
671
|
+
PRIMARY_BUTTON.disabled = true;
|
|
672
|
+
if (retryButton) {
|
|
673
|
+
retryButton.disabled = true;
|
|
674
|
+
retryButton.textContent = 'Retrying…';
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
await loadSession();
|
|
678
|
+
setStatus('');
|
|
679
|
+
} catch (err) {
|
|
680
|
+
renderSessionLoadFailure(err.message || 'Could not load setup. Please try again.');
|
|
681
|
+
} finally {
|
|
682
|
+
state.sessionLoadInFlight = false;
|
|
683
|
+
PRIMARY_BUTTON.disabled = false;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
635
687
|
// Issue #646: when first-run was launched without a key (the no-terminal macOS
|
|
636
688
|
// installer path), gate the whole wizard behind a paste-your-key step.
|
|
637
689
|
function renderKeyEntry() {
|
|
@@ -717,11 +769,13 @@
|
|
|
717
769
|
}
|
|
718
770
|
|
|
719
771
|
PRIMARY_BUTTON.addEventListener('click', () => {
|
|
772
|
+
if (!state.session) {
|
|
773
|
+
startSessionLoad();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
720
776
|
state.activeStep = chooseActiveStep();
|
|
721
777
|
render();
|
|
722
778
|
});
|
|
723
779
|
|
|
724
|
-
|
|
725
|
-
setStatus(err.message || 'Could not load first-run.', 'error');
|
|
726
|
-
});
|
|
780
|
+
startSessionLoad();
|
|
727
781
|
}());
|
|
@@ -430,6 +430,9 @@ body {
|
|
|
430
430
|
grid-template-columns: minmax(190px, 0.85fr) minmax(0, 2fr);
|
|
431
431
|
gap: 20px;
|
|
432
432
|
}
|
|
433
|
+
.setup-shell--single {
|
|
434
|
+
grid-template-columns: minmax(0, 1fr);
|
|
435
|
+
}
|
|
433
436
|
.setup-steps {
|
|
434
437
|
display: flex;
|
|
435
438
|
flex-direction: column;
|
|
@@ -619,6 +622,7 @@ body {
|
|
|
619
622
|
color: var(--muted);
|
|
620
623
|
}
|
|
621
624
|
.ready-strip--error {
|
|
625
|
+
overflow-wrap: anywhere;
|
|
622
626
|
background: #fff1f2;
|
|
623
627
|
border-color: #fb7185;
|
|
624
628
|
color: #9f1239;
|