cli-jaw 2.17.67 → 2.17.68

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.
@@ -6,10 +6,10 @@
6
6
  <link rel="icon" type="image/png" href="/dist/assets/icon-192-CADbZUeU.png">
7
7
  <title>Jaw Manager</title>
8
8
  <script src="/manager/theme-boot.js"></script>
9
- <script type="module" crossorigin src="/dist/assets/manager-BXf6oL68.js"></script>
9
+ <script type="module" crossorigin src="/dist/assets/manager-Dhz8mX5E.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/dist/assets/vendor-render-DSSkXCir.css">
11
11
  <link rel="stylesheet" crossorigin href="/dist/assets/settings-embedding-CUQ8Is3y.css">
12
- <link rel="stylesheet" crossorigin href="/dist/assets/manager-CEtdsk3Q.css">
12
+ <link rel="stylesheet" crossorigin href="/dist/assets/manager-B9imHVqw.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="manager-root"></div>
@@ -6,6 +6,8 @@ import type {
6
6
  } from '../types';
7
7
  import { ProcessControlPanel } from './ProcessControlPanel';
8
8
  import { InstanceLogsPanel } from './InstanceLogsPanel';
9
+ import { formatUptime } from '../instance-label';
10
+ import './instance-overview.css';
9
11
 
10
12
  type InstanceDetailPanelProps = {
11
13
  instance: DashboardInstance | null;
@@ -16,22 +18,73 @@ type InstanceDetailPanelProps = {
16
18
 
17
19
  export function InstanceDetailPanel(props: InstanceDetailPanelProps) {
18
20
  const instance = props.instance;
21
+ const checkedAt = instance ? new Date(instance.lastCheckedAt) : null;
22
+ const hasCheckedAt = checkedAt != null && Number.isFinite(checkedAt.getTime());
19
23
 
20
24
  return (
21
25
  <section className="detail-panel" aria-label="Selected instance detail">
22
26
  {props.activeTab === 'overview' && (
23
- <>
24
- <div className="overview-grid">
25
- <div><span>Status</span><strong>{instance?.status || 'n/a'}</strong></div>
26
- <div><span>CLI</span><strong>{instance?.currentCli || 'n/a'}</strong></div>
27
- <div><span>Model</span><strong>{instance?.currentModel || 'n/a'}</strong></div>
28
- <div><span>Owner</span><strong>{instance?.lifecycle?.owner || 'n/a'}</strong></div>
29
- <div><span>Version</span><strong>{instance?.version || 'n/a'}</strong></div>
30
- <div><span>Group</span><strong>{instance?.group || 'ungrouped'}</strong></div>
31
- <div><span>Reason</span><strong>{instance?.lifecycle?.reason || instance?.healthReason || 'ok'}</strong></div>
32
- </div>
27
+ <div className="instance-overview">
28
+ {instance ? (
29
+ <section className="overview-instance" aria-label="Instance overview">
30
+ <header className="overview-status-line">
31
+ <div className="overview-status-heading">
32
+ <span className={`overview-status is-${instance.status}`}>
33
+ <span aria-hidden="true" />
34
+ {instance.status}
35
+ </span>
36
+ <span className="overview-port">:{instance.port}</span>
37
+ </div>
38
+ <span className="overview-checked">
39
+ {hasCheckedAt ? <>Checked <time dateTime={checkedAt.toISOString()} title={checkedAt.toLocaleString()}>{checkedAt.toLocaleTimeString()}</time></> : 'Check time unavailable'}
40
+ </span>
41
+ </header>
42
+ {!instance.ok && <p className="overview-unavailable">This instance is not responding. Check its logs or use the instance menu to manage it.</p>}
43
+ <div className="overview-sections">
44
+ <section aria-label="Runtime">
45
+ <h3>Runtime</h3>
46
+ <dl className="overview-properties">
47
+ <div><dt>CLI</dt><dd>{instance.currentCli || 'Not reported'}</dd></div>
48
+ <div><dt>Model</dt><dd className="overview-mono">{instance.currentModel || 'Not reported'}</dd></div>
49
+ <div><dt>Version</dt><dd>{instance.version || 'Not reported'}</dd></div>
50
+ <div><dt>Uptime</dt><dd>{instance.ok && instance.uptime != null ? formatUptime(instance.uptime) : 'Not available'}</dd></div>
51
+ </dl>
52
+ </section>
53
+ <section aria-label="Connection">
54
+ <h3>Connection</h3>
55
+ <dl className="overview-properties">
56
+ <div><dt>Address</dt><dd className="overview-mono">{instance.url}</dd></div>
57
+ <div><dt>Managed by</dt><dd>{instance.lifecycle?.owner || 'Unknown'}</dd></div>
58
+ <div><dt>Process ID</dt><dd className="overview-mono">{instance.lifecycle?.pid ?? 'Not reported'}</dd></div>
59
+ <div><dt>Group</dt><dd>{instance.group || 'Ungrouped'}</dd></div>
60
+ </dl>
61
+ </section>
62
+ </div>
63
+ <section className="overview-project" aria-label="Project directories">
64
+ <h3>Project</h3>
65
+ {instance.projectDirs?.length ? (
66
+ <ul>{instance.projectDirs.map(dir => <li key={dir} className="overview-mono">{dir}</li>)}</ul>
67
+ ) : <p>{instance.ok && instance.projectDirs != null
68
+ ? 'No project selected. Choose a folder from the instance header.'
69
+ : 'Project information unavailable.'}</p>}
70
+ </section>
71
+ <details className="overview-diagnostics">
72
+ <summary>Health &amp; ownership details</summary>
73
+ <dl className="overview-properties">
74
+ <div><dt>Health reason</dt><dd>{instance.healthReason || 'Not reported'}</dd></div>
75
+ <div><dt>Ownership reason</dt><dd>{instance.lifecycle?.reason || 'Not reported'}</dd></div>
76
+ <div><dt>Service mode</dt><dd>{instance.serviceMode}</dd></div>
77
+ </dl>
78
+ </details>
79
+ </section>
80
+ ) : (
81
+ <div className="overview-no-selection">
82
+ <h3>No instance selected</h3>
83
+ <p>Select an instance in the sidebar to see its runtime and connection details.</p>
84
+ </div>
85
+ )}
33
86
  <ProcessControlPanel />
34
- </>
87
+ </div>
35
88
  )}
36
89
 
37
90
  {props.activeTab === 'logs' && instance && (
@@ -1,87 +1,120 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import {
3
3
  adoptManagedProcesses,
4
4
  fetchProcessControlState,
5
5
  stopManagedProcesses,
6
6
  } from '../api';
7
7
  import type { DashboardProcessControlState } from '../types';
8
+ import './process-control.css';
9
+
10
+ type ProcessOperation = 'refresh' | 'recover' | 'stop';
8
11
 
9
12
  export function ProcessControlPanel() {
10
13
  const [state, setState] = useState<DashboardProcessControlState | null>(null);
11
- const [busy, setBusy] = useState<string | null>(null);
14
+ const [busy, setBusy] = useState<ProcessOperation | null>('refresh');
12
15
  const [message, setMessage] = useState<string | null>(null);
13
- const managedCount = state?.managed.length || 0;
16
+ const [error, setError] = useState<string | null>(null);
17
+ const mounted = useRef(false);
18
+ const activeRequest = useRef<object | null>(null);
19
+ const managedCount = state?.managed.length ?? 0;
14
20
 
15
- useEffect(() => {
16
- void refresh();
17
- }, []);
18
-
19
- async function refresh(): Promise<void> {
20
- try {
21
- setState(await fetchProcessControlState());
22
- } catch (error) {
23
- setMessage((error as Error).message);
24
- }
25
- }
26
-
27
- async function run(label: string, fn: () => Promise<DashboardProcessControlState>): Promise<void> {
28
- setBusy(label);
21
+ const run = useCallback(async (
22
+ operation: ProcessOperation,
23
+ request: () => Promise<DashboardProcessControlState>,
24
+ ): Promise<void> => {
25
+ if (!mounted.current || activeRequest.current) return;
26
+ const token = {};
27
+ activeRequest.current = token;
28
+ setBusy(operation);
29
29
  setMessage(null);
30
+ setError(null);
30
31
  try {
31
- const next = await fn();
32
+ const next = await request();
33
+ if (!mounted.current || activeRequest.current !== token) return;
32
34
  setState(next);
33
- setMessage(`${label} complete.`);
34
- } catch (error) {
35
- setMessage((error as Error).message);
35
+ setMessage(operation === 'stop'
36
+ ? 'Stop request complete. The list shows remaining managed servers.'
37
+ : operation === 'recover' ? 'Recovery complete. Managed process records refreshed.' : null);
38
+ } catch (cause) {
39
+ if (!mounted.current || activeRequest.current !== token) return;
40
+ setError(cause instanceof Error ? cause.message : 'Unable to update managed processes. Try refreshing.');
36
41
  } finally {
37
- setBusy(null);
42
+ if (mounted.current && activeRequest.current === token) {
43
+ activeRequest.current = null;
44
+ setBusy(null);
45
+ }
38
46
  }
39
- }
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ mounted.current = true;
51
+ void run('refresh', fetchProcessControlState);
52
+ return () => {
53
+ mounted.current = false;
54
+ activeRequest.current = null;
55
+ };
56
+ }, [run]);
40
57
 
41
58
  function handleStopAll(): void {
42
- if (managedCount === 0) return;
43
- if (!window.confirm(`Stop ${managedCount} dashboard-managed server${managedCount === 1 ? '' : 's'}?`)) return;
44
- void run('Stop all managed', stopManagedProcesses);
59
+ if (activeRequest.current || managedCount === 0 || error) return;
60
+ if (!window.confirm(`Stop ${managedCount} dashboard-managed server${managedCount === 1 ? '' : 's'} across all instances?`)) return;
61
+ void run('stop', stopManagedProcesses);
45
62
  }
46
63
 
47
64
  return (
48
- <section className="process-control-panel" aria-label="Process control">
49
- <div className="process-control-header">
50
- <div>
51
- <span>Process control</span>
52
- <strong>{managedCount} managed</strong>
65
+ <section className="process-control-panel" aria-label="Managed processes — all instances">
66
+ <header className="process-control-header">
67
+ <div className="process-control-heading">
68
+ <h3>Managed processes</h3>
69
+ <p>All instances in this manager</p>
53
70
  </div>
54
- <button type="button" onClick={refresh} disabled={busy != null}>Refresh</button>
55
- </div>
56
- <div className="process-control-actions">
57
- <button type="button" onClick={handleStopAll} disabled={busy != null || managedCount === 0}>
58
- Stop all managed
59
- </button>
60
- <button
61
- type="button"
62
- onClick={() => void run('Adopt/recover managed', adoptManagedProcesses)}
63
- disabled={busy != null}
64
- >
65
- Adopt/recover
66
- </button>
67
- <button type="button" disabled title={state?.unsupported.reason || 'Planned'}>
68
- Force release port
71
+ <button type="button" onClick={() => void run('refresh', fetchProcessControlState)} disabled={busy !== null}>
72
+ {busy === 'refresh' ? 'Refreshing…' : 'Refresh'}
69
73
  </button>
74
+ </header>
75
+ <div className="process-control-content" aria-busy={busy !== null}>
76
+ <p className="process-control-count">
77
+ {state ? `${managedCount} managed server${managedCount === 1 ? '' : 's'}` : 'Process count unavailable'}
78
+ {error && state && <span> · Last loaded list</span>}
79
+ </p>
80
+ {state && managedCount > 0 && (
81
+ <table className="process-control-table">
82
+ <caption className="process-control-sr-only">Dashboard-managed servers across all instances</caption>
83
+ <thead><tr><th scope="col">Port</th><th scope="col">PID</th><th scope="col">Ownership proof</th></tr></thead>
84
+ <tbody>
85
+ {state.managed.map(entry => (
86
+ <tr key={entry.port}>
87
+ <th scope="row">:{entry.port}</th>
88
+ <td>{entry.pid ?? 'Unavailable'}</td>
89
+ <td>{entry.proof === 'child' ? 'Child process' : 'Registry record'}</td>
90
+ </tr>
91
+ ))}
92
+ </tbody>
93
+ </table>
94
+ )}
95
+ {state && managedCount === 0 && (
96
+ <p className="process-control-empty">No managed servers. Start an instance or recover existing records below.</p>
97
+ )}
70
98
  </div>
71
- <p className="process-control-hint">
72
- Stop all affects only dashboard-managed servers. Adopt/recover reconnects known managed process records without stopping external instances.
73
- </p>
74
- <div className="process-control-list">
75
- {state?.managed.map(entry => (
76
- <div key={entry.port} className="process-control-row">
77
- <span>:{entry.port}</span>
78
- <strong>{entry.pid ? `pid ${entry.pid}` : 'pid n/a'}</strong>
79
- <em>{entry.proof}</em>
80
- </div>
81
- ))}
82
- {managedCount === 0 && <p>No dashboard-managed servers are registered.</p>}
99
+ <div className="process-control-feedback" role="status" aria-live="polite">
100
+ {busy ? (busy === 'refresh' ? 'Loading managed processes…' : busy === 'recover' ? 'Recovering managed processes…' : 'Stopping managed servers…') : message}
83
101
  </div>
84
- {message && <p className="process-control-message">{message}</p>}
102
+ {error && <p className="process-control-error" role="alert">{error} Use Refresh to check the current state.</p>}
103
+ <details className="process-control-maintenance">
104
+ <summary>Recovery &amp; stop all</summary>
105
+ <div className="process-control-maintenance-body">
106
+ <p>These actions apply to all dashboard-managed servers. External instances are not stopped.</p>
107
+ <div className="process-control-actions">
108
+ <button type="button" onClick={() => void run('recover', adoptManagedProcesses)} disabled={busy !== null}>
109
+ {busy === 'recover' ? 'Recovering…' : 'Adopt/recover'}
110
+ </button>
111
+ <button type="button" className="process-control-stop" onClick={handleStopAll} disabled={busy !== null || managedCount === 0 || error !== null}>
112
+ {busy === 'stop' ? 'Stopping…' : 'Stop all managed'}
113
+ </button>
114
+ </div>
115
+ <p className="process-control-hint">Adopt/recover reconnects known managed process records.</p>
116
+ </div>
117
+ </details>
85
118
  </section>
86
119
  );
87
120
  }
@@ -0,0 +1,97 @@
1
+ .workbench-overview > .workbench-header {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ height: auto;
5
+ min-height: var(--workspace-topbar-height);
6
+ padding: 12px;
7
+ }
8
+ .workbench-overview .workbench-header .detail-header {
9
+ display: flex;
10
+ flex: 1 1 520px;
11
+ min-width: 0;
12
+ align-items: center;
13
+ flex-wrap: wrap;
14
+ border: 0;
15
+ padding: 0;
16
+ }
17
+ .workbench-overview .workbench-header .detail-header > div:first-child { flex: 1 1 180px; }
18
+ .workbench-overview .workbench-header .detail-header-actions { flex-wrap: wrap; }
19
+ .workbench-overview .workbench-mode-bar { margin-left: auto; }
20
+ .instance-overview {
21
+ width: 100%;
22
+ min-height: 0;
23
+ overflow: auto;
24
+ padding: clamp(16px, 3vw, 32px);
25
+ container-type: inline-size;
26
+ }
27
+ .instance-overview > * {
28
+ max-width: 1040px;
29
+ margin-inline: auto;
30
+ }
31
+ .overview-instance { margin-bottom: 28px; }
32
+ .overview-status-line,
33
+ .overview-status-heading { display: flex; align-items: center; gap: 14px; }
34
+ .overview-status-line {
35
+ justify-content: space-between;
36
+ flex-wrap: wrap;
37
+ padding-bottom: 20px;
38
+ border-bottom: 1px solid var(--border-subtle);
39
+ }
40
+ .overview-status {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 8px;
44
+ font-size: 16px;
45
+ font-weight: 600;
46
+ text-transform: capitalize;
47
+ }
48
+ .overview-status > span {
49
+ width: 8px;
50
+ height: 8px;
51
+ border-radius: 50%;
52
+ background: var(--status-idle);
53
+ }
54
+ .overview-status.is-online > span { background: var(--status-success); }
55
+ .overview-status.is-error > span { background: var(--status-error); }
56
+ .overview-status.is-timeout > span { background: var(--status-warn); }
57
+ .overview-port { color: var(--text-secondary); font-family: var(--font-mono); font-size: 13px; }
58
+ .overview-checked { color: var(--text-secondary); font-size: 12px; }
59
+ .overview-unavailable { color: var(--text-secondary); font-size: 13px; line-height: 1.6; }
60
+ .overview-sections { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 40px; }
61
+ .instance-overview h3 { margin: 0 0 14px; font-size: 13px; font-weight: 600; }
62
+ .overview-sections > section { min-width: 0; padding-block: 22px; }
63
+ .overview-properties { margin: 0; display: grid; gap: 13px; }
64
+ .overview-properties > div { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 12px; font-size: 13px; line-height: 1.5; }
65
+ .overview-properties dt { color: var(--text-secondary); }
66
+ .overview-properties dd { margin: 0; overflow-wrap: anywhere; }
67
+ .overview-mono { font-family: var(--font-mono); font-size: 12px; }
68
+ .overview-project { padding-block: 18px; border-top: 1px solid var(--border-subtle); }
69
+ .overview-project ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }
70
+ .overview-project li { overflow-wrap: anywhere; line-height: 1.6; }
71
+ .overview-project p,
72
+ .overview-no-selection p { margin: 0; color: var(--text-secondary); font-size: 13px; line-height: 1.6; }
73
+ .overview-diagnostics { border-top: 1px solid var(--border-subtle); }
74
+ .overview-diagnostics summary { padding-block: 14px; color: var(--text-secondary); cursor: pointer; font-size: 12px; }
75
+ .overview-diagnostics summary:hover { color: var(--text-primary); }
76
+ .overview-diagnostics summary:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
77
+ .overview-diagnostics .overview-properties { padding-block: 4px 16px; }
78
+ .overview-diagnostics .overview-properties > div { grid-template-columns: 128px minmax(0, 1fr); }
79
+ .overview-no-selection { padding-block: 20px 32px; }
80
+ @container (max-width: 640px) {
81
+ .overview-sections { grid-template-columns: minmax(0, 1fr); gap: 0; }
82
+ .overview-sections > section + section { border-top: 1px solid var(--border-subtle); }
83
+ }
84
+ @container (max-width: 360px) {
85
+ .overview-properties > div { grid-template-columns: 80px minmax(0, 1fr); gap: 8px; }
86
+ .overview-diagnostics .overview-properties > div { grid-template-columns: minmax(0, 1fr); gap: 4px; }
87
+ }
88
+ @media (max-width: 480px) {
89
+ .workbench-overview .workbench-header .detail-header { align-items: stretch; flex-direction: column; }
90
+ .workbench-overview .workbench-header .detail-header > div:first-child { flex-basis: auto; }
91
+ .workbench-overview .workbench-mode-bar { margin-left: 0; }
92
+ .workbench-overview .workbench-header .detail-header-actions { justify-content: flex-start; }
93
+ .workbench-overview .workbench-header .detail-header button,
94
+ .workbench-overview .workbench-header .detail-header .open-link,
95
+ .workbench-overview .workbench-mode-tabs button { min-height: 44px; height: auto; }
96
+ .overview-diagnostics summary { min-height: 44px; }
97
+ }
@@ -0,0 +1,128 @@
1
+ .process-control-panel {
2
+ min-width: 0;
3
+ border: 1px solid var(--border-subtle);
4
+ border-radius: 8px;
5
+ background: var(--canvas-mid);
6
+ color: var(--text-primary);
7
+ overflow: hidden;
8
+ }
9
+
10
+ .process-control-header {
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: space-between;
14
+ gap: 16px;
15
+ padding: 16px 18px 12px;
16
+ }
17
+
18
+ .process-control-panel .process-control-heading h3 {
19
+ margin: 0;
20
+ font-size: 14px;
21
+ font-weight: 600;
22
+ line-height: 1.4;
23
+ }
24
+
25
+ .process-control-heading p,
26
+ .process-control-count,
27
+ .process-control-empty,
28
+ .process-control-maintenance-body p {
29
+ margin: 5px 0 0;
30
+ color: var(--text-secondary);
31
+ font-size: 12px;
32
+ line-height: 1.5;
33
+ }
34
+
35
+ .process-control-header button,
36
+ .process-control-actions button {
37
+ min-height: 32px;
38
+ padding: 5px 10px;
39
+ font: inherit;
40
+ font-size: 12px;
41
+ white-space: nowrap;
42
+ }
43
+
44
+ .process-control-content { padding: 0 18px 14px; }
45
+ .process-control-count { margin: 0 0 10px; }
46
+ .process-control-count span { font-size: 11px; }
47
+
48
+ .process-control-table {
49
+ width: 100%;
50
+ border-collapse: collapse;
51
+ font-size: 12px;
52
+ text-align: left;
53
+ }
54
+
55
+ .process-control-table th,
56
+ .process-control-table td {
57
+ padding: 9px 10px;
58
+ border-bottom: 1px solid var(--border-subtle);
59
+ overflow-wrap: anywhere;
60
+ }
61
+
62
+ .process-control-table thead th {
63
+ padding-top: 0;
64
+ color: var(--text-secondary);
65
+ font-size: 11px;
66
+ font-weight: 500;
67
+ }
68
+
69
+ .process-control-table tbody th,
70
+ .process-control-table tbody td:nth-child(2) {
71
+ font-variant-numeric: tabular-nums;
72
+ }
73
+
74
+ .process-control-table tbody th { font-weight: 600; }
75
+ .process-control-table tbody td { color: var(--text-secondary); }
76
+ .process-control-table tr:last-child > * { border-bottom: 0; }
77
+ .process-control-table thead tr > * { border-bottom: 1px solid var(--border-subtle); }
78
+ .process-control-table tr > :first-child { padding-left: 0; }
79
+ .process-control-table tr > :last-child { padding-right: 0; }
80
+
81
+ .process-control-feedback:not(:empty),
82
+ .process-control-error {
83
+ margin: 0;
84
+ padding: 0 18px 14px;
85
+ color: var(--text-secondary);
86
+ font-size: 12px;
87
+ line-height: 1.5;
88
+ overflow-wrap: anywhere;
89
+ }
90
+
91
+ .process-control-error { color: var(--status-error); }
92
+ .process-control-maintenance { border-top: 1px solid var(--border-subtle); }
93
+ .process-control-maintenance summary {
94
+ padding: 12px 18px;
95
+ cursor: pointer;
96
+ color: var(--text-secondary);
97
+ font-size: 12px;
98
+ font-weight: 500;
99
+ }
100
+ .process-control-maintenance summary:hover { color: var(--text-primary); }
101
+ .process-control-maintenance-body { padding: 0 18px 16px; }
102
+ .process-control-maintenance-body p { margin-top: 0; }
103
+ .process-control-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 8px; }
104
+ .process-control-actions .process-control-stop { color: var(--status-error); }
105
+ .process-control-panel button:disabled { cursor: not-allowed; opacity: 0.5; }
106
+ .process-control-panel button:focus-visible,
107
+ .process-control-maintenance summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
108
+
109
+ .process-control-sr-only {
110
+ position: absolute;
111
+ width: 1px;
112
+ height: 1px;
113
+ padding: 0;
114
+ margin: -1px;
115
+ overflow: hidden;
116
+ clip-path: inset(50%);
117
+ white-space: nowrap;
118
+ }
119
+
120
+ @media (max-width: 480px) {
121
+ .process-control-header button, .process-control-actions button, .process-control-maintenance summary { min-height: 44px; }
122
+ .process-control-header { gap: 10px; padding: 14px 12px 10px; }
123
+ .process-control-content { padding: 0 12px 12px; }
124
+ .process-control-feedback:not(:empty), .process-control-error { padding-inline: 12px; }
125
+ .process-control-maintenance summary { padding: 12px; }
126
+ .process-control-maintenance-body { padding: 0 12px 14px; }
127
+ .process-control-table th, .process-control-table td { padding-inline: 6px; }
128
+ }
@@ -885,29 +885,12 @@ button.instance-group-header[aria-expanded="false"] .instance-group-chevron {
885
885
  overflow: auto;
886
886
  padding: 12px;
887
887
  }
888
- .overview-grid {
889
- display: grid;
890
- grid-template-columns: repeat(3, minmax(0, 1fr));
891
- gap: 10px;
892
- }
893
- .overview-grid > div, .metric, .detail-empty {
888
+ .metric, .detail-empty {
894
889
  border: 1px solid var(--border-subtle);
895
890
  background: var(--canvas-mid);
896
891
  border-radius: 8px;
897
892
  padding: 11px;
898
893
  }
899
- .overview-grid span {
900
- display: block;
901
- color: var(--text-secondary);
902
- font-size: 11px;
903
- }
904
- .overview-grid strong {
905
- display: block;
906
- margin-top: 6px;
907
- overflow-wrap: anywhere;
908
- font-size: 14px;
909
- font-weight: 500;
910
- }
911
894
  /* ─── Logs Panel ──────────────────────────────── */
912
895
  .logs-panel {
913
896
  display: flex;
@@ -984,64 +967,6 @@ button.instance-group-header[aria-expanded="false"] .instance-group-chevron {
984
967
  min-width: 0;
985
968
  }
986
969
 
987
- .process-control-panel {
988
- margin-top: 12px;
989
- border: 1px solid var(--border-subtle);
990
- background: var(--canvas-mid);
991
- border-radius: 8px;
992
- padding: 11px;
993
- }
994
- .process-control-header,
995
- .process-control-actions,
996
- .process-control-row {
997
- display: flex;
998
- align-items: center;
999
- gap: 8px;
1000
- }
1001
- .process-control-header {
1002
- justify-content: space-between;
1003
- }
1004
- .process-control-header span,
1005
- .process-control-list p,
1006
- .process-control-message {
1007
- color: var(--text-secondary);
1008
- font-size: 11px;
1009
- }
1010
- .process-control-header strong {
1011
- display: block;
1012
- margin-top: 3px;
1013
- font-size: 14px;
1014
- }
1015
- .process-control-actions {
1016
- flex-wrap: wrap;
1017
- margin-top: 10px;
1018
- }
1019
- .process-control-actions button,
1020
- .process-control-header button {
1021
- min-height: 28px;
1022
- padding: 5px 9px;
1023
- font-size: 12px;
1024
- }
1025
- .process-control-list {
1026
- display: grid;
1027
- gap: 6px;
1028
- margin-top: 10px;
1029
- }
1030
- .process-control-row {
1031
- min-height: 28px;
1032
- border: 1px solid var(--border-subtle);
1033
- border-radius: 6px;
1034
- padding: 5px 7px;
1035
- }
1036
- .process-control-row span {
1037
- font-weight: 700;
1038
- }
1039
- .process-control-row strong,
1040
- .process-control-row em {
1041
- color: var(--text-secondary);
1042
- font-size: 11px;
1043
- font-style: normal;
1044
- }
1045
970
  .preview-panel {
1046
971
  position: relative;
1047
972
  min-height: 100%;
@@ -150,7 +150,6 @@
150
150
  .dashboard-shell.manager-shell:not(.is-sidebar-collapsed) .manager-workspace.is-side-panel-open {
151
151
  --right-panel-width: 50vw;
152
152
  }
153
- .overview-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
154
153
  }
155
154
  @media (max-width: 1023px) {
156
155
  .dashboard-shell.manager-shell .manager-workspace,
@@ -285,7 +284,6 @@
285
284
  overflow-x: auto;
286
285
  }
287
286
  .workbench-header .workbench-mode-tabs button { flex: 1 0 auto; }
288
- .overview-grid { grid-template-columns: 1fr; }
289
287
  .workbench-preview .preview-panel {
290
288
  gap: 8px;
291
289
  padding: 8px 10px;