@bhooai/nexus-admin 2.0.11 → 2.0.12

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/App.tsx +210 -210
package/src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import logoUrl from './assets/bhooai-nexus-logo.svg';
3
3
  import { Icon, isIconName, type IconName } from './icons.js';
4
4
  import { SafeGotoDialog, SafeGotoLink } from '@bhooai/nexus-safe-goto';
@@ -25,9 +25,9 @@ type UiMode = 'workspace' | 'compact' | 'focus';
25
25
  type ToggleStyle = 'chip' | 'neon' | 'icon' | 'orb' | 'track';
26
26
 
27
27
  const TOGGLE_STYLES: Array<{ id: ToggleStyle; label: string; hint: string }> = [
28
- { id: 'chip', label: 'Chip ON/OFF', hint: 'Default · glass chip' },
29
- { id: 'neon', label: 'Neon glass', hint: 'Blue→violet border' },
30
- { id: 'icon', label: 'Icon knob', hint: 'Power · bolt glow' },
28
+ { id: 'chip', label: 'Chip ON/OFF', hint: 'Default · glass chip' },
29
+ { id: 'neon', label: 'Neon glass', hint: 'Blueviolet border' },
30
+ { id: 'icon', label: 'Icon knob', hint: 'Power · bolt glow' },
31
31
  { id: 'orb', label: 'Orb dot', hint: 'Pulse halo' },
32
32
  { id: 'track', label: 'Gradient-track', hint: 'Sliding trail' },
33
33
  ];
@@ -165,7 +165,7 @@ export function App() {
165
165
  }, []);
166
166
 
167
167
  if (boot === 'loading') {
168
- return <div className="admin-boot bg-mesh"><span className="admin-overline">Loading…</span></div>;
168
+ return <div className="admin-boot bg-mesh"><span className="admin-overline">Loading…</span></div>;
169
169
  }
170
170
  if (!authed) return <Login onAuthed={(u) => { setUser(u); window.localStorage.setItem(USER_KEY, JSON.stringify(u)); setAuthed(true); }} />;
171
171
  const onUserChange = (u: AdminUser) => { setUser(u); try { window.localStorage.setItem(USER_KEY, JSON.stringify(u)); } catch { /* ignore */ } };
@@ -214,7 +214,7 @@ function ConfirmDialog(props: {
214
214
  <div className="confirm-dialog-head">
215
215
  <span className="confirm-dialog-mark">{props.danger ? '!' : <Icon name="info" size={15} />}</span>
216
216
  <strong>{props.title}</strong>
217
- <button className="confirm-dialog-close" onClick={props.onCancel} aria-label="Cancel">×</button>
217
+ <button className="confirm-dialog-close" onClick={props.onCancel} aria-label="Cancel">×</button>
218
218
  </div>
219
219
  <p className="confirm-dialog-message">{props.message}</p>
220
220
  <div className="confirm-dialog-actions">
@@ -225,7 +225,7 @@ function ConfirmDialog(props: {
225
225
  disabled={props.busy}
226
226
  autoFocus
227
227
  >
228
- {props.busy ? 'Working…' : (props.confirmLabel ?? 'Confirm')}
228
+ {props.busy ? 'Working' : (props.confirmLabel ?? 'Confirm')}
229
229
  </button>
230
230
  </div>
231
231
  </div>
@@ -286,7 +286,7 @@ function Login({ onAuthed }: { onAuthed: (user: AdminUser) => void }) {
286
286
  </div>
287
287
  )}
288
288
  <button className="glass-btn-primary w-full disabled:opacity-60 disabled:cursor-not-allowed" type="submit" disabled={busy}>
289
- {busy ? 'Please wait…' : mode === 'login' ? 'Sign in' : 'Register & sign in'}
289
+ {busy ? 'Please wait' : mode === 'login' ? 'Sign in' : 'Register & sign in'}
290
290
  </button>
291
291
  <button type="button" className="w-full cursor-pointer text-sm text-slate-400 transition-colors hover:text-slate-200" onClick={() => setMode(mode === 'login' ? 'register' : 'login')}>
292
292
  {mode === 'login' ? 'Need an account? Register' : 'Already have one? Sign in'}
@@ -517,12 +517,12 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
517
517
  <strong>NEXUS</strong>
518
518
  <span>Admin workspace</span>
519
519
  </div>
520
- <button className="admin-close-mobile" onClick={() => setMobileNav(false)} aria-label="Close navigation">×</button>
520
+ <button className="admin-close-mobile" onClick={() => setMobileNav(false)} aria-label="Close navigation">×</button>
521
521
  </div>
522
522
  <div className="admin-project-card">
523
523
  <span className="admin-overline">ACTIVE PROJECT</span>
524
- <strong>{displayProject || 'Loading…'}</strong>
525
- {projectDb && <em className="admin-project-db">db · {projectDb}</em>}
524
+ <strong>{displayProject || 'Loading'}</strong>
525
+ {projectDb && <em className="admin-project-db">db · {projectDb}</em>}
526
526
  <span><i className="status-dot good" /> Development</span>
527
527
  </div>
528
528
  <div className="admin-nav-label">Workspace</div>
@@ -546,7 +546,7 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
546
546
  <div className="admin-main-shell">
547
547
  <header className={`admin-topbar${mobileSearch ? ' is-mobile-search' : ''}`}>
548
548
  <button className="admin-mobile-menu" onClick={() => setMobileNav(true)} aria-label="Open navigation"><Icon name="menu" size={16} /></button>
549
- <div className="admin-window-tab"><span className="window-tab-mark">â—ˆ</span><strong>{title}</strong></div>
549
+ <div className="admin-window-tab"><span className="window-tab-mark">◈</span><strong>{title}</strong></div>
550
550
  <div className="admin-search" ref={searchRef}>
551
551
  <span className="admin-search-icon"><Icon name="search" size={14} /></span>
552
552
  <input
@@ -570,11 +570,11 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
570
570
  }}
571
571
  placeholder="Search workspace"
572
572
  />
573
- <kbd>⌘ K</kbd>
573
+ <kbd>⌘ K</kbd>
574
574
  {searchOpen && search.length > 0 && (
575
575
  <div className="admin-search-results">
576
576
  {searchResults.length === 0 ? (
577
- <div className="admin-search-empty">No matches for “{search}”</div>
577
+ <div className="admin-search-empty">No matches for {search}”</div>
578
578
  ) : (
579
579
  searchResults.map((n) => (
580
580
  <button key={n.id} className={tab === n.id ? 'is-active' : ''} onMouseDown={(e) => { e.preventDefault(); setTab(n.id); setSearch(''); setSearchOpen(false); setMobileSearch(false); }}>
@@ -630,7 +630,7 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
630
630
  </div>
631
631
  </div>
632
632
  </header>
633
- <div className="admin-breadcrumb"><strong>{displayProject || 'Project'}</strong><b>›</b><span>{title}</span><button>⋯</button></div>
633
+ <div className="admin-breadcrumb"><strong>{displayProject || 'Project'}</strong><b>›</b><span>{title}</span><button>⋯</button></div>
634
634
  <div className="admin-content-layout">
635
635
  <main className="admin-content">
636
636
  {tab === 'overview' && <Overview onNavigate={setTab} />}
@@ -648,12 +648,12 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
648
648
  {tab === 'theme' && <ThemeCentre theme={theme} onThemeChange={(value) => { setTheme(value); window.localStorage.setItem('nexus-admin-theme', value); }} uiMode={uiMode} onUiModeChange={(value) => { setUiMode(value); window.localStorage.setItem('nexus-admin-ui', value); }} custom={customTheme} onCustom={updateCustomTheme} particles={particles} onParticles={updateParticles} />}
649
649
  </main>
650
650
  <aside className="admin-inspector">
651
- <div className="inspector-heading"><span>WORKSPACE INSPECTOR</span><button aria-label="Close inspector">×</button></div>
651
+ <div className="inspector-heading"><span>WORKSPACE INSPECTOR</span><button aria-label="Close inspector">×</button></div>
652
652
  <div className="inspector-preview"><div className="preview-orbit"><span /><i /><b /></div><strong>Project Nexus</strong><span>Full-stack application</span></div>
653
653
  <div className="inspector-tabs"><b>Summary</b><span>Activity</span></div>
654
654
  <div className="inspector-card"><span className="admin-overline">CURRENT SURFACE</span><strong>{title}</strong><p>{nav.find((item) => item.id === tab)?.desc ?? 'Manage the running Nexus project from this workspace.'}</p></div>
655
- <div className="inspector-card inspector-list"><span className="admin-overline">QUICK LINKS</span><button onClick={() => setTab('config')}>Configuration <b>→</b></button><button onClick={() => setTab('env')}>Environment <b>→</b></button><button onClick={() => setTab('logs')}>Logs <b>→</b></button></div>
656
- <button className="glass-chip-btn-primary w-full justify-between" onClick={() => setTab('overview')}><Icon name="activity" size={13} /> Open project overview <span>→</span></button>
655
+ <div className="inspector-card inspector-list"><span className="admin-overline">QUICK LINKS</span><button onClick={() => setTab('config')}>Configuration <b>→</b></button><button onClick={() => setTab('env')}>Environment <b>→</b></button><button onClick={() => setTab('logs')}>Logs <b>→</b></button></div>
656
+ <button className="glass-chip-btn-primary w-full justify-between" onClick={() => setTab('overview')}><Icon name="activity" size={13} /> Open project overview <span>→</span></button>
657
657
  <div className="admin-side-widget">
658
658
  <div className="admin-widget-heading"><span>RUNTIME LOAD</span><b>LIVE</b></div>
659
659
  <div className="load-line"><span>CPU</span><i><b style={{ width: '32%' }} /></i><strong>32%</strong></div>
@@ -689,7 +689,7 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
689
689
  />
690
690
  </div>
691
691
  )}
692
- <footer className="admin-dock"><span><b className="dock-logo">◈</b> BhooAI Nexus</span><span>Connected to <strong>localhost:4000</strong></span><span className="dock-right"><i className="status-dot good" /> Secure admin session · {title}</span></footer>
692
+ <footer className="admin-dock"><span><b className="dock-logo">◈</b> BhooAI Nexus</span><span>Connected to <strong>localhost:4000</strong></span><span className="dock-right"><i className="status-dot good" /> Secure admin session · {title}</span></footer>
693
693
  </div>
694
694
  <SafeGotoDialog />
695
695
  <SettingsDialog
@@ -711,7 +711,7 @@ function Dashboard({ user, onUserChange, onLogout }: { user: AdminUser | null; o
711
711
  }
712
712
 
713
713
  function formatUptime(seconds?: number): string {
714
- if (!seconds || seconds <= 0) return '—';
714
+ if (!seconds || seconds <= 0) return '';
715
715
  const d = Math.floor(seconds / 86400);
716
716
  const h = Math.floor((seconds % 86400) / 3600);
717
717
  const m = Math.floor((seconds % 3600) / 60);
@@ -849,7 +849,7 @@ function SettingsDialog({ open, onClose, user, onUserChange, projectName, projec
849
849
  <span className="admin-overline">ACCOUNT & WORKSPACE</span>
850
850
  <h3 id="settings-title">Settings</h3>
851
851
  </div>
852
- <button type="button" onClick={onClose} className="glass-chip-btn" aria-label="Close settings">✕</button>
852
+ <button type="button" onClick={onClose} className="glass-chip-btn" aria-label="Close settings">✕</button>
853
853
  </div>
854
854
 
855
855
  <div className="settings-body">
@@ -868,7 +868,7 @@ function SettingsDialog({ open, onClose, user, onUserChange, projectName, projec
868
868
  </div>
869
869
  {statusLine(profileMsg)}
870
870
  <div className="settings-actions">
871
- <button type="button" onClick={saveProfile} disabled={profileBusy} className="glass-chip-btn-primary">{profileBusy ? 'Saving…' : 'Save profile'}</button>
871
+ <button type="button" onClick={saveProfile} disabled={profileBusy} className="glass-chip-btn-primary">{profileBusy ? 'Saving' : 'Save profile'}</button>
872
872
  </div>
873
873
  </section>
874
874
 
@@ -893,20 +893,20 @@ function SettingsDialog({ open, onClose, user, onUserChange, projectName, projec
893
893
  </div>
894
894
  {statusLine(pwMsg)}
895
895
  <div className="settings-actions">
896
- <button type="button" onClick={savePassword} disabled={pwBusy} className="glass-chip-btn-primary">{pwBusy ? 'Updating…' : 'Change password'}</button>
896
+ <button type="button" onClick={savePassword} disabled={pwBusy} className="glass-chip-btn-primary">{pwBusy ? 'Updating' : 'Change password'}</button>
897
897
  </div>
898
898
  </section>
899
899
 
900
900
  <section className="settings-section">
901
901
  <h4>About</h4>
902
902
  <div className="settings-facts">
903
- <div><span>Package</span><b>{projectName || '—'}</b></div>
904
- <div><span>Version</span><b>{about?.version ?? (projectVersion || '—')}</b></div>
905
- <div><span>Database</span><b>{projectDb || '—'}</b></div>
906
- <div><span>Path</span><b>{about?.path ?? '—'}</b></div>
907
- <div><span>Node.js</span><b>{about?.node ?? '—'}</b></div>
903
+ <div><span>Package</span><b>{projectName || ''}</b></div>
904
+ <div><span>Version</span><b>{about?.version ?? (projectVersion || '')}</b></div>
905
+ <div><span>Database</span><b>{projectDb || ''}</b></div>
906
+ <div><span>Path</span><b>{about?.path ?? ''}</b></div>
907
+ <div><span>Node.js</span><b>{about?.node ?? ''}</b></div>
908
908
  <div><span>Uptime</span><b>{formatUptime(about?.uptime)}</b></div>
909
- <div><span>PID</span><b>{about?.pid ?? '—'}</b></div>
909
+ <div><span>PID</span><b>{about?.pid ?? ''}</b></div>
910
910
  </div>
911
911
  {isAdmin && (
912
912
  <div className="settings-package">
@@ -922,7 +922,7 @@ function SettingsDialog({ open, onClose, user, onUserChange, projectName, projec
922
922
  </div>
923
923
  {statusLine(pkgMsg)}
924
924
  <div className="settings-actions">
925
- <button type="button" onClick={savePackage} disabled={pkgBusy} className="glass-chip-btn-primary">{pkgBusy ? 'Updating…' : 'Update package'}</button>
925
+ <button type="button" onClick={savePackage} disabled={pkgBusy} className="glass-chip-btn-primary">{pkgBusy ? 'Updating' : 'Update package'}</button>
926
926
  </div>
927
927
  </div>
928
928
  )}
@@ -953,7 +953,7 @@ function AppearancePanel({
953
953
  <button key={id} onClick={() => onThemeChange(id)} className={theme === id ? 'is-selected' : ''}>
954
954
  <span className={`theme-preview ${id}`} />
955
955
  <span><b>{label}</b><small>{detail}</small></span>
956
- {theme === id && <i>✓</i>}
956
+ {theme === id && <i>✓</i>}
957
957
  </button>
958
958
  ))}
959
959
  </div>
@@ -1016,18 +1016,18 @@ function RailPanel({
1016
1016
  {open === 'search' && (
1017
1017
  <>
1018
1018
  <div className="rail-panel-head"><span className="admin-overline">SMART SEARCH</span></div>
1019
- <p className="rail-panel-text">Press <kbd>⌘ K</kbd> (or <kbd>Ctrl K</kbd>) to jump to any surface from the top search bar — or click below to focus it now.</p>
1019
+ <p className="rail-panel-text">Press <kbd>⌘ K</kbd> (or <kbd>Ctrl K</kbd>) to jump to any surface from the top search bar or click below to focus it now.</p>
1020
1020
  <button type="button" className="glass-chip-btn-primary w-full justify-center" onClick={onSearch}><Icon name="search" size={12} /> Focus search</button>
1021
1021
  <div className="rail-divider" />
1022
- <span className="rail-panel-hint">Try: roles, mongo, palette, stripe, ollama, payments…</span>
1022
+ <span className="rail-panel-hint">Try: roles, mongo, palette, stripe, ollama, payments…</span>
1023
1023
  </>
1024
1024
  )}
1025
1025
 
1026
1026
  {open === 'notes' && (
1027
1027
  <>
1028
- <div className="rail-panel-head"><span className="admin-overline">AI NOTES</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1029
- <p className="rail-panel-text">Jot quick notes — saved locally in your browser.</p>
1030
- <textarea className="rail-notes" value={notes} onChange={(e) => onNotes(e.target.value)} placeholder="Type a note…" spellCheck={false} />
1028
+ <div className="rail-panel-head"><span className="admin-overline">AI NOTES</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1029
+ <p className="rail-panel-text">Jot quick notes saved locally in your browser.</p>
1030
+ <textarea className="rail-notes" value={notes} onChange={(e) => onNotes(e.target.value)} placeholder="Type a note" spellCheck={false} />
1031
1031
  <div className="rail-row">
1032
1032
  <button type="button" className="glass-chip-btn" onClick={() => onNotes('')}>Clear</button>
1033
1033
  <span className="rail-panel-hint">{notes.length} chars</span>
@@ -1037,7 +1037,7 @@ function RailPanel({
1037
1037
 
1038
1038
  {open === 'activity' && (
1039
1039
  <>
1040
- <div className="rail-panel-head"><span className="admin-overline">SCREEN RECALL</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1040
+ <div className="rail-panel-head"><span className="admin-overline">SCREEN RECALL</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1041
1041
  <p className="rail-panel-text">Your recent admin activity.</p>
1042
1042
  {activity.length === 0 ? (
1043
1043
  <p className="rail-empty">No activity yet.</p>
@@ -1054,14 +1054,14 @@ function RailPanel({
1054
1054
 
1055
1055
  {open === 'appearance' && (
1056
1056
  <>
1057
- <div className="rail-panel-head"><span className="admin-overline">APPEARANCE</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1057
+ <div className="rail-panel-head"><span className="admin-overline">APPEARANCE</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1058
1058
  <span className="rail-panel-hint">Choose your surface</span>
1059
1059
  <div className="rail-appearance">
1060
1060
  {([['aurora', 'Aurora', 'Electric blue'], ['midnight', 'Midnight', 'Deep navy'], ['violet', 'Violet', 'Indigo glow']] as const).map(([id, label, detail]) => (
1061
1061
  <button key={id} onClick={() => onThemeChange(id)} className={theme === id ? 'is-selected' : ''}>
1062
1062
  <span className={`theme-preview ${id}`} />
1063
1063
  <span className="rail-appearance-label"><b>{label}</b><small>{detail}</small></span>
1064
- {theme === id && <i>✓</i>}
1064
+ {theme === id && <i>✓</i>}
1065
1065
  </button>
1066
1066
  ))}
1067
1067
  </div>
@@ -1071,7 +1071,7 @@ function RailPanel({
1071
1071
  {([['workspace', 'Workspace', 'Inspector + tools'], ['compact', 'Compact', 'More content'], ['focus', 'Focus', 'Canvas first']] as const).map(([id, label, detail]) => (
1072
1072
  <button key={id} onClick={() => onUiModeChange(id)} className={uiMode === id ? 'is-selected' : ''}>
1073
1073
  <span className="rail-appearance-label"><b>{label}</b><small>{detail}</small></span>
1074
- {uiMode === id && <i>✓</i>}
1074
+ {uiMode === id && <i>✓</i>}
1075
1075
  </button>
1076
1076
  ))}
1077
1077
  </div>
@@ -1080,13 +1080,13 @@ function RailPanel({
1080
1080
 
1081
1081
  {open === 'launcher' && (
1082
1082
  <>
1083
- <div className="rail-panel-head"><span className="admin-overline">ADD TOOL</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1083
+ <div className="rail-panel-head"><span className="admin-overline">ADD TOOL</span><button type="button" className="rail-x" onClick={onClose} aria-label="Close">×</button></div>
1084
1084
  <p className="rail-panel-text">Pin your most-used surfaces to the tool rail. Click to toggle.</p>
1085
1085
  <ul className="rail-list rail-list--nav">
1086
1086
  {nav.map((n) => (
1087
1087
  <li key={n.id}>
1088
1088
  <button type="button" className={favorites.includes(n.id) ? 'is-on' : ''} onClick={() => onToggleFavorite(n.id)}>
1089
- <span className="rail-fav-icon"><Icon name={n.icon} size={14} /></span><span className="rail-fav-label">{n.label}</span><span className="rail-fav-check">{favorites.includes(n.id) ? '✓' : '+'}</span>
1089
+ <span className="rail-fav-icon"><Icon name={n.icon} size={14} /></span><span className="rail-fav-label">{n.label}</span><span className="rail-fav-check">{favorites.includes(n.id) ? '' : '+'}</span>
1090
1090
  </button>
1091
1091
  </li>
1092
1092
  ))}
@@ -1131,18 +1131,18 @@ function NotifyPanel({ onClose }: { onClose: () => void }) {
1131
1131
  <span className="admin-overline">NOTIFICATIONS</span>
1132
1132
  <strong>{hasAny ? `${alerts.length + (notify?.length ?? 0)} notification${alerts.length + (notify?.length ?? 0) === 1 ? '' : 's'}` : 'All clear'}</strong>
1133
1133
  </div>
1134
- <button type="button" className="notify-refresh" aria-label="Refresh notifications" onClick={() => void loadNotify()} disabled={notifyBusy}>{notifyBusy ? '…' : <Icon name="refresh" size={13} />}</button>
1135
- <button type="button" className="rail-x" aria-label="Close notifications" onClick={onClose}>×</button>
1134
+ <button type="button" className="notify-refresh" aria-label="Refresh notifications" onClick={() => void loadNotify()} disabled={notifyBusy}>{notifyBusy ? '' : <Icon name="refresh" size={13} />}</button>
1135
+ <button type="button" className="rail-x" aria-label="Close notifications" onClick={onClose}>×</button>
1136
1136
  </div>
1137
1137
 
1138
1138
  <div className="notify-body">
1139
1139
  {hasAny ? (
1140
1140
  <>
1141
1141
  <div className="notify-summary">
1142
- {errs > 0 && <span className="is-err">● {errs} error{errs === 1 ? '' : 's'}</span>}
1143
- {warns > 0 && <span className="is-warn">● {warns} warning{warns === 1 ? '' : 's'}</span>}
1144
- {oks > 0 && <span className="is-ok">● {oks} notice{oks === 1 ? '' : 's'}</span>}
1145
- {hasLogs && <span className="is-log">● {notify!.length} log line{(notify?.length ?? 0) === 1 ? '' : 's'}</span>}
1142
+ {errs > 0 && <span className="is-err">● {errs} error{errs === 1 ? '' : 's'}</span>}
1143
+ {warns > 0 && <span className="is-warn">● {warns} warning{warns === 1 ? '' : 's'}</span>}
1144
+ {oks > 0 && <span className="is-ok">● {oks} notice{oks === 1 ? '' : 's'}</span>}
1145
+ {hasLogs && <span className="is-log">● {notify!.length} log line{(notify?.length ?? 0) === 1 ? '' : 's'}</span>}
1146
1146
  </div>
1147
1147
 
1148
1148
  {hasAlerts && (
@@ -1158,9 +1158,9 @@ function NotifyPanel({ onClose }: { onClose: () => void }) {
1158
1158
  <span className="notify-alert-dot" />
1159
1159
  <span className="notify-alert-body">
1160
1160
  <b>{a.message}</b>
1161
- <small>{a.source} · {a.at}</small>
1161
+ <small>{a.source} · {a.at}</small>
1162
1162
  </span>
1163
- <button type="button" className="notify-alert-x" aria-label="Dismiss alert" onClick={() => dismissAlert(a.id)}>×</button>
1163
+ <button type="button" className="notify-alert-x" aria-label="Dismiss alert" onClick={() => dismissAlert(a.id)}>×</button>
1164
1164
  </li>
1165
1165
  ))}
1166
1166
  </ul>
@@ -1171,13 +1171,13 @@ function NotifyPanel({ onClose }: { onClose: () => void }) {
1171
1171
  <>
1172
1172
  <div className="notify-section-title">
1173
1173
  <span>SYSTEM ERRORS</span>
1174
- <span className="notify-section-meta">supervisor · error level</span>
1174
+ <span className="notify-section-meta">supervisor · error level</span>
1175
1175
  </div>
1176
1176
  <ul className="notify-logs">
1177
1177
  {notify!.slice(0, 8).map((l, i) => (
1178
1178
  <li key={i}>
1179
1179
  <b>{l.service}</b><span>{new Date(l.ts).toLocaleTimeString()}</span>
1180
- <p>{l.line.length > 96 ? `${l.line.slice(0, 96)}…` : l.line}</p>
1180
+ <p>{l.line.length > 96 ? `${l.line.slice(0, 96)}…` : l.line}</p>
1181
1181
  </li>
1182
1182
  ))}
1183
1183
  </ul>
@@ -1185,7 +1185,7 @@ function NotifyPanel({ onClose }: { onClose: () => void }) {
1185
1185
  )}
1186
1186
  </>
1187
1187
  ) : loading ? (
1188
- <div className="notify-empty"><span className="notify-empty-icon"><Icon name="bell" size={18} /></span><strong>Loading notifications…</strong></div>
1188
+ <div className="notify-empty"><span className="notify-empty-icon"><Icon name="bell" size={18} /></span><strong>Loading notifications…</strong></div>
1189
1189
  ) : (
1190
1190
  <div className="notify-empty"><span className="notify-empty-icon"><Icon name="bell" size={18} /></span><strong>All clear</strong><p>No errors or alerts right now.</p></div>
1191
1191
  )}
@@ -1281,7 +1281,7 @@ function ThemeCentre({
1281
1281
  <button type="button" className="workspace-action" onClick={resetAll}><Icon name="refresh" size={12} /> Reset all</button>
1282
1282
  </div>
1283
1283
  </PageHead>
1284
- <PageHero kicker="SURFACE STUDIO" title={<>Make it <em>yours.</em></>} desc="Curated themes, your own palette, particle fields and surface tuning — applied live to this workspace and remembered locally in your browser." glyph="palette" art="palette" />
1284
+ <PageHero kicker="SURFACE STUDIO" title={<>Make it <em>yours.</em></>} desc="Curated themes, your own palette, particle fields and surface tuning applied live to this workspace and remembered locally in your browser." glyph="palette" art="palette" />
1285
1285
  <section className="glass-card space-y-4">
1286
1286
  <div className="flex items-center justify-between">
1287
1287
  <div><span className="admin-overline">CURATED THEMES</span><h3 className="glass-h3">Start from a surface</h3></div>
@@ -1295,7 +1295,7 @@ function ThemeCentre({
1295
1295
  <button key={id} type="button" className={`theme-card${isActive ? ' is-active' : ''}`} onClick={() => { onThemeChange(id); onCustom({ ...preset, active: false }); }}>
1296
1296
  <span className={`theme-preview ${id}`} />
1297
1297
  <span className="theme-card-body"><b>{label}</b><small>{detail}</small></span>
1298
- {isActive && <i className="theme-card-check">✓</i>}
1298
+ {isActive && <i className="theme-card-check">✓</i>}
1299
1299
  </button>
1300
1300
  );
1301
1301
  })}
@@ -1311,11 +1311,11 @@ function ThemeCentre({
1311
1311
  <div><span className="admin-overline">CUSTOM THEME</span><h3 className="glass-h3">Your own palette</h3></div>
1312
1312
  <div className="flex items-center gap-2">
1313
1313
  <button type="button" className="glass-chip-btn" onClick={() => { const p = randomAccentPalette(); onCustom({ ...p, active: !custom.active ? true : custom.active }); }}><Icon name="sparkles" size={12} /> Randomize</button>
1314
- <button type="button" className={`custom-toggle${custom.active ? ' is-active' : ''}`} onClick={() => onCustom({ active: !custom.active })}>{custom.active ? 'Applied · live' : 'Apply custom'}</button>
1314
+ <button type="button" className={`custom-toggle${custom.active ? ' is-active' : ''}`} onClick={() => onCustom({ active: !custom.active })}>{custom.active ? 'Applied · live' : 'Apply custom'}</button>
1315
1315
  <button type="button" className="glass-chip-btn" onClick={() => onCustom({ ...THEME_PRESETS[theme], active: true })}>Seed from {theme}</button>
1316
1316
  </div>
1317
1317
  </div>
1318
- <p className="text-sm text-slate-400">Tune each color below — the workspace re-themes instantly while custom is applied. Stored locally in your browser.</p>
1318
+ <p className="text-sm text-slate-400">Tune each color below the workspace re-themes instantly while custom is applied. Stored locally in your browser.</p>
1319
1319
  <div className="custom-grid">
1320
1320
  {colors.map((c) => (
1321
1321
  <label key={c.key} className="color-row">
@@ -1355,7 +1355,7 @@ function ThemeCentre({
1355
1355
  >
1356
1356
  <span className={`ai-toggle tg-${t.id} is-on toggle-style-preview`}><span className="ai-toggle-knob" /></span>
1357
1357
  <span className="toggle-style-meta"><b>{t.label}</b><small>{t.hint}</small></span>
1358
- {custom.toggle === t.id && <i className="toggle-style-check">✓</i>}
1358
+ {custom.toggle === t.id && <i className="toggle-style-check">✓</i>}
1359
1359
  </button>
1360
1360
  ))}
1361
1361
  </div>
@@ -1371,13 +1371,13 @@ function ThemeCentre({
1371
1371
  </div>
1372
1372
  <p className="text-sm text-slate-400">Control how many particles drift behind the workspace, how fast they rise, and build a custom color list for them.</p>
1373
1373
  {slider('Particle amount', 'Count of dots in the field', particles.amount, 0, 48, 1, String(particles.amount), (v) => onParticles({ amount: v }))}
1374
- {slider('Particle speed', 'Rise speed multiplier', particles.speed, 0.2, 3, 0.05, `${particles.speed.toFixed(2)}×`, (v) => onParticles({ speed: v }))}
1374
+ {slider('Particle speed', 'Rise speed multiplier', particles.speed, 0.2, 3, 0.05, `${particles.speed.toFixed(2)}×`, (v) => onParticles({ speed: v }))}
1375
1375
  <div className="palette-list">
1376
1376
  {particles.colors.map((c, i) => (
1377
1377
  <div className="palette-chip" key={i}>
1378
1378
  <span className="color-swatch"><input type="color" value={c} onChange={(e) => setParticleColor(i, e.target.value)} /></span>
1379
1379
  <input className="glass-input color-hex" value={c} onChange={(e) => { const v = e.target.value.trim(); if (/^#[0-9a-fA-F]{3,8}$/.test(v)) setParticleColor(i, v); }} />
1380
- <button type="button" className="row-delete" onClick={() => removeParticleColor(i)} aria-label="Remove color" disabled={particles.colors.length <= 1}>×</button>
1380
+ <button type="button" className="row-delete" onClick={() => removeParticleColor(i)} aria-label="Remove color" disabled={particles.colors.length <= 1}>×</button>
1381
1381
  </div>
1382
1382
  ))}
1383
1383
  <button type="button" className="add-color" onClick={addParticleColor}>+ Add color</button>
@@ -1388,19 +1388,19 @@ function ThemeCentre({
1388
1388
  }
1389
1389
 
1390
1390
  const CATEGORY_LABEL: Record<string, { label: string; hint: string }> = {
1391
- refused: { label: 'Connection refused', hint: 'the target address is reachable but nothing is listening — is the service running?' },
1392
- timeout: { label: 'Timed out', hint: 'the target did not respond in time — it may be down or filtering traffic.' },
1391
+ refused: { label: 'Connection refused', hint: 'the target address is reachable but nothing is listening is the service running?' },
1392
+ timeout: { label: 'Timed out', hint: 'the target did not respond in time it may be down or filtering traffic.' },
1393
1393
  dns: { label: 'DNS lookup failed', hint: 'the host could not be resolved.' },
1394
1394
  ssl: { label: 'TLS handshake failed', hint: 'the target answered but the TLS handshake failed.' },
1395
1395
  http: { label: 'HTTP error status', hint: 'the endpoint responded with an error status code.' },
1396
- engine_offline: { label: 'Diagnostics engine offline', hint: 'the Python engine is not running — start it with "python main.py" or "nexus dev".' },
1396
+ engine_offline: { label: 'Diagnostics engine offline', hint: 'the Python engine is not running start it with "python main.py" or "nexus dev".' },
1397
1397
  };
1398
1398
 
1399
1399
  function friendlyPreflightError(error?: string, category?: string): string {
1400
1400
  if (!error) return 'unreachable';
1401
1401
  const meta = category ? CATEGORY_LABEL[category] : undefined;
1402
1402
  if (!meta || error.toLowerCase().startsWith(meta.label.toLowerCase())) return error;
1403
- return `${meta.label} — ${error}`;
1403
+ return `${meta.label} ${error}`;
1404
1404
  }
1405
1405
 
1406
1406
  function preflightAddress(c: PreflightCheck): string {
@@ -1453,42 +1453,42 @@ function Overview({ onNavigate }: { onNavigate: (tab: Tab) => void }) {
1453
1453
  };
1454
1454
  return (
1455
1455
  <div className="admin-view-stack">
1456
- <PageHead title="Project Overview"><button onClick={() => onNavigate('processes')} className="workspace-action">Open services <span>→</span></button></PageHead>
1456
+ <PageHead title="Project Overview"><button onClick={() => onNavigate('processes')} className="workspace-action">Open services <span>→</span></button></PageHead>
1457
1457
  <div className="overview-hero"><div><span className="admin-overline">GOOD MORNING, ADMIN</span><h1>Your project is <em>in orbit.</em></h1><p>One calm surface for the services, configuration, data, and AI that power your Nexus application.</p></div><div className="hero-orbit-art"><span /><i /><b /><strong>NX</strong></div></div>
1458
- <div className="overview-stat-grid"><div className="overview-stat"><span>ACTIVE SERVICES</span><strong>{running}<small>/{services.length || 4}</small></strong><b className="stat-good">● running now</b></div><div className="overview-stat"><span>HTTP REQUESTS</span><strong>{requestCount || '—'}</strong><b>since boot</b></div><div className="overview-stat"><span>UPTIME</span><strong>{metrics ? `${Math.round((metrics.uptime ?? 0) / 60)}m` : '—'}</strong><b>backend process</b></div><div className="overview-stat"><span>ENVIRONMENT</span><strong>DEV</strong><b className="stat-violet">local workspace</b></div></div>
1458
+ <div className="overview-stat-grid"><div className="overview-stat"><span>ACTIVE SERVICES</span><strong>{running}<small>/{services.length || 4}</small></strong><b className="stat-good">● running now</b></div><div className="overview-stat"><span>HTTP REQUESTS</span><strong>{requestCount || ''}</strong><b>since boot</b></div><div className="overview-stat"><span>UPTIME</span><strong>{metrics ? `${Math.round((metrics.uptime ?? 0) / 60)}m` : ''}</strong><b>backend process</b></div><div className="overview-stat"><span>ENVIRONMENT</span><strong>DEV</strong><b className="stat-violet">local workspace</b></div></div>
1459
1459
  <section className="workspace-panel live-traffic-panel"><div className="panel-title-row"><div><span className="admin-overline">HTTP REQUESTS</span><h3>Live traffic</h3></div><span className="live-badge"><i className="status-dot good" /> LIVE</span></div><TrafficChart samples={traffic} total={requestCount} last={traffic.length ? traffic[traffic.length - 1] : 0} onNavigate={onNavigate} /></section>
1460
- <div className="overview-columns"><section className="workspace-panel"><div className="panel-title-row"><div><span className="admin-overline">PROJECT SURFACES</span><h3>Everything in one place</h3></div><button onClick={() => onNavigate('config')} className="text-action">View config →</button></div><div className="surface-grid"><button className="surface-card acc-blue" onClick={() => onNavigate('config')}><span className="surface-icon"><Icon name="braces" size={20} /></span><strong>Runtime config</strong><small>nexus.runtime.json overrides</small><i className="surface-go">→</i></button><button className="surface-card acc-violet" onClick={() => onNavigate('env')}><span className="surface-icon"><Icon name="key" size={20} /></span><strong>Environment</strong><small>Masked secrets editor</small><i className="surface-go">→</i></button><button className="surface-card acc-pink" onClick={() => onNavigate('databases')}><span className="surface-icon"><Icon name="database" size={20} /></span><strong>Data layer</strong><small>Mongo databases &amp; collections</small><i className="surface-go">→</i></button><button className="surface-card acc-blue" onClick={() => onNavigate('theme')}><span className="surface-icon"><Icon name="sparkles" size={20} /></span><strong>AI theme</strong><small>Generate schemas from English</small><i className="surface-go">→</i></button><button className="surface-card acc-violet" onClick={() => onNavigate('users')}><span className="surface-icon"><Icon name="users" size={20} /></span><strong>Users &amp; roles</strong><small>Accounts, roles &amp; grants</small><i className="surface-go">→</i></button><button className="surface-card acc-pink" onClick={() => onNavigate('payments')}><span className="surface-icon"><Icon name="dollar" size={20} /></span><strong>Payments</strong><small>Orders, transactions &amp; providers</small><i className="surface-go">→</i></button><button className="surface-card acc-blue" onClick={() => onNavigate('ai')}><span className="surface-icon"><Icon name="bot" size={20} /></span><strong>AI agents</strong><small>Providers &amp; chat playground</small><i className="surface-go">→</i></button></div></section><section className="workspace-panel pulse-panel"><div className="panel-title-row"><div><span className="admin-overline">SYSTEM PULSE</span><h3>Services are moving</h3></div><span className="live-badge"><i className="status-dot good" /> LIVE</span></div><div className="pulse-bars">{[34,55,42,78,62,88,52,72,48,66,84,58].map((height, index) => <i key={index} style={{ height: `${height}%` }} />)}</div><p>Live supervisor status refreshes every five seconds.</p><button onClick={() => onNavigate('monitoring')} className="text-action">Open monitoring →</button></section></div>
1460
+ <div className="overview-columns"><section className="workspace-panel"><div className="panel-title-row"><div><span className="admin-overline">PROJECT SURFACES</span><h3>Everything in one place</h3></div><button onClick={() => onNavigate('config')} className="text-action">View config →</button></div><div className="surface-grid"><button className="surface-card acc-blue" onClick={() => onNavigate('config')}><span className="surface-icon"><Icon name="braces" size={20} /></span><strong>Runtime config</strong><small>nexus.runtime.json overrides</small><i className="surface-go">→</i></button><button className="surface-card acc-violet" onClick={() => onNavigate('env')}><span className="surface-icon"><Icon name="key" size={20} /></span><strong>Environment</strong><small>Masked secrets editor</small><i className="surface-go">→</i></button><button className="surface-card acc-pink" onClick={() => onNavigate('databases')}><span className="surface-icon"><Icon name="database" size={20} /></span><strong>Data layer</strong><small>Mongo databases &amp; collections</small><i className="surface-go">→</i></button><button className="surface-card acc-blue" onClick={() => onNavigate('theme')}><span className="surface-icon"><Icon name="sparkles" size={20} /></span><strong>AI theme</strong><small>Generate schemas from English</small><i className="surface-go">→</i></button><button className="surface-card acc-violet" onClick={() => onNavigate('users')}><span className="surface-icon"><Icon name="users" size={20} /></span><strong>Users &amp; roles</strong><small>Accounts, roles &amp; grants</small><i className="surface-go">→</i></button><button className="surface-card acc-pink" onClick={() => onNavigate('payments')}><span className="surface-icon"><Icon name="dollar" size={20} /></span><strong>Payments</strong><small>Orders, transactions &amp; providers</small><i className="surface-go">→</i></button><button className="surface-card acc-blue" onClick={() => onNavigate('ai')}><span className="surface-icon"><Icon name="bot" size={20} /></span><strong>AI agents</strong><small>Providers &amp; chat playground</small><i className="surface-go">→</i></button></div></section><section className="workspace-panel pulse-panel"><div className="panel-title-row"><div><span className="admin-overline">SYSTEM PULSE</span><h3>Services are moving</h3></div><span className="live-badge"><i className="status-dot good" /> LIVE</span></div><div className="pulse-bars">{[34,55,42,78,62,88,52,72,48,66,84,58].map((height, index) => <i key={index} style={{ height: `${height}%` }} />)}</div><p>Live supervisor status refreshes every five seconds.</p><button onClick={() => onNavigate('monitoring')} className="text-action">Open monitoring →</button></section></div>
1461
1461
  <section className="workspace-panel">
1462
1462
  <div className="panel-title-row">
1463
1463
  <div><span className="admin-overline">PREFLIGHT DIAGNOSTICS</span><h3>Dependencies are being probed</h3></div>
1464
- <button onClick={runChecks} disabled={preflightBusy} className="glass-chip-btn">{preflightBusy ? 'probing…' : <><Icon name="refresh" size={12} /> run checks</>}</button>
1464
+ <button onClick={runChecks} disabled={preflightBusy} className="glass-chip-btn">{preflightBusy ? 'probing' : <><Icon name="refresh" size={12} /> run checks</>}</button>
1465
1465
  </div>
1466
1466
  <p className="preflight-note">The Python server pings the backend API, AI server, GraphQL endpoint and data-store ports, and reports latency per target.</p>
1467
1467
  {preflightErr && (
1468
1468
  <div role="alert" className="mb-3 rounded-lg border border-rose-500/30 bg-rose-500/10 px-3 py-2 text-sm text-rose-400">
1469
- Preflight failed — {preflightErr}
1469
+ Preflight failed {preflightErr}
1470
1470
  </div>
1471
1471
  )}
1472
1472
  {preflight ? (
1473
1473
  <>
1474
1474
  <div className="preflight-summary">
1475
- <span className="preflight-count ok">● {preflight.passed} ok</span>
1476
- <span className="preflight-count warn">◐ {preflight.warnings} slow</span>
1477
- <span className="preflight-count bad">● {preflight.failed} failed</span>
1478
- <span className="preflight-meta">{preflight.durationMs}ms · {preflight.ranAt}</span>
1475
+ <span className="preflight-count ok">● {preflight.passed} ok</span>
1476
+ <span className="preflight-count warn">◐ {preflight.warnings} slow</span>
1477
+ <span className="preflight-count bad">● {preflight.failed} failed</span>
1478
+ <span className="preflight-meta">{preflight.durationMs}ms · {preflight.ranAt}</span>
1479
1479
  </div>
1480
1480
  {preflight.engineOk === false || (preflight.checks.length > 0 && preflight.failed === preflight.checks.length) ? (
1481
1481
  <div className="preflight-guidance">
1482
1482
  <strong>Nothing responded.</strong>
1483
- <span>The stack may not be running. Start everything with <code>nexus dev</code>, or Python-only with <code>python main.py</code>. Then press “run checks” again.</span>
1484
- <button onClick={() => onNavigate('processes')} className="text-action">Open services →</button>
1483
+ <span>The stack may not be running. Start everything with <code>nexus dev</code>, or Python-only with <code>python main.py</code>. Then press run checks again.</span>
1484
+ <button onClick={() => onNavigate('processes')} className="text-action">Open services →</button>
1485
1485
  </div>
1486
1486
  ) : null}
1487
1487
  </>
1488
1488
  ) : preflightBusy ? (
1489
- <p className="preflight-empty">Probing dependencies…</p>
1489
+ <p className="preflight-empty">Probing dependencies…</p>
1490
1490
  ) : (
1491
- <p className="preflight-empty">No checks run yet — press “run checks”.</p>
1491
+ <p className="preflight-empty">No checks run yet press run checks”.</p>
1492
1492
  )}
1493
1493
  {preflight && (
1494
1494
  <div className="preflight-list">
@@ -1535,7 +1535,7 @@ function TrafficChart({ samples, total, last, onNavigate }: { samples: number[];
1535
1535
  <path d={line} fill="none" stroke="var(--admin-blue, #54b7ff)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
1536
1536
  <circle cx={lastPt[0]} cy={lastPt[1]} r="3.5" fill="var(--admin-blue, #54b7ff)" />
1537
1537
  </svg>
1538
- <div className="traffic-meta"><span>{last} req / 5s</span><span>{total} since boot</span><button onClick={() => onNavigate('monitoring')} className="text-action">Open monitoring →</button></div>
1538
+ <div className="traffic-meta"><span>{last} req / 5s</span><span>{total} since boot</span><button onClick={() => onNavigate('monitoring')} className="text-action">Open monitoring →</button></div>
1539
1539
  </div>
1540
1540
  );
1541
1541
  }
@@ -1562,7 +1562,7 @@ function Processes() {
1562
1562
  <PageHead title="Processes">
1563
1563
  <button onClick={refresh} className="glass-chip-btn"><Icon name="refresh" size={12} /> refresh</button>
1564
1564
  </PageHead>
1565
- <PageHero kicker="SERVICE CONTROL PLANE" title={<>Your services, <em>in check.</em></>} desc="Every backend, frontend and AI process the supervisor is tending — live status, uptime, PID and one-click control." glyph="server" art="bars" />
1565
+ <PageHero kicker="SERVICE CONTROL PLANE" title={<>Your services, <em>in check.</em></>} desc="Every backend, frontend and AI process the supervisor is tending live status, uptime, PID and one-click control." glyph="server" art="bars" />
1566
1566
  <div className="glass-panel overflow-hidden p-2">
1567
1567
  <table className="glass-table">
1568
1568
  <thead><tr className="text-slate-400"><th>Service</th><th className="text-center">Status</th><th className="text-center">PID</th><th className="text-center">Uptime</th><th className="text-center">Actions</th></tr></thead>
@@ -1576,8 +1576,8 @@ function Processes() {
1576
1576
  {s.status}
1577
1577
  </span>
1578
1578
  </td>
1579
- <td className="text-center text-slate-400">{s.pid ?? '—'}</td>
1580
- <td className="text-center text-slate-400">{s.startedAt ? `${Math.round((Date.now() - s.startedAt) / 1000)}s` : '—'}</td>
1579
+ <td className="text-center text-slate-400">{s.pid ?? ''}</td>
1580
+ <td className="text-center text-slate-400">{s.startedAt ? `${Math.round((Date.now() - s.startedAt) / 1000)}s` : ''}</td>
1581
1581
  <td className="text-center">
1582
1582
  <span className="inline-flex gap-1.5">
1583
1583
  <button onClick={() => controlService('start', s.name)} className="glass-chip-btn">start</button>
@@ -1595,7 +1595,7 @@ function Processes() {
1595
1595
  {sel && (
1596
1596
  <div className="glass-card space-y-2">
1597
1597
  <h3 className="glass-h3">{sel} logs</h3>
1598
- <pre className="glass-code max-h-80 overflow-auto p-3">{((logs[sel] ?? []).join('\n')) || '— empty —'}</pre>
1598
+ <pre className="glass-code max-h-80 overflow-auto p-3">{((logs[sel] ?? []).join('\n')) || ' empty '}</pre>
1599
1599
  </div>
1600
1600
  )}
1601
1601
  </div>
@@ -1708,7 +1708,7 @@ function Logs({ onNavigate }: { onNavigate: (tab: Tab) => void }) {
1708
1708
  if (aiSummaryBusy) return;
1709
1709
  if (!ordered.length) {
1710
1710
  setAiSummary('');
1711
- setAiSummaryErr('No log lines to summarize — widen the filters first.');
1711
+ setAiSummaryErr('No log lines to summarize widen the filters first.');
1712
1712
  setAiSummaryOpen(true);
1713
1713
  return;
1714
1714
  }
@@ -1773,16 +1773,16 @@ function Logs({ onNavigate }: { onNavigate: (tab: Tab) => void }) {
1773
1773
  <PageHead title="Logs">
1774
1774
  <div className="flex items-center gap-2">
1775
1775
  <button aria-label="Refresh logs" onClick={() => void refresh()} className="glass-chip-btn"><Icon name="refresh" size={12} /> refresh</button>
1776
- <button aria-pressed={live} onClick={() => setLive(!live)} className={`glass-chip-btn log-live-btn${live ? ' is-live' : ''}`}>{live ? '● live' : '○ paused'}</button>
1776
+ <button aria-pressed={live} onClick={() => setLive(!live)} className={`glass-chip-btn log-live-btn${live ? ' is-live' : ''}`}>{live ? ' live' : ' paused'}</button>
1777
1777
  <button aria-label="Copy filtered logs" onClick={() => void copyAll()} className="glass-chip-btn">{copied ? 'Copied' : <><Icon name="copy" size={12} /> copy</>}</button>
1778
- <button aria-label="Summarize logs with AI" onClick={() => void runAiSummary()} disabled={aiSummaryBusy} className="glass-chip-btn log-ai-btn"><Icon name="sparkles" size={12} /> {aiSummaryBusy ? 'summarizing…' : 'ai summary'}</button>
1778
+ <button aria-label="Summarize logs with AI" onClick={() => void runAiSummary()} disabled={aiSummaryBusy} className="glass-chip-btn log-ai-btn"><Icon name="sparkles" size={12} /> {aiSummaryBusy ? 'summarizing' : 'ai summary'}</button>
1779
1779
  <button aria-label="Clear all logs" onClick={async () => { try { await clearLogs(); await refresh(); } catch { /* supervisor may not be reachable */ } }} className="glass-chip-btn-danger"><Icon name="trash" size={12} /> clear</button>
1780
1780
  </div>
1781
1781
  </PageHead>
1782
- <PageHero kicker="ERROR CONSOLE" title={<>Every failure, <em>in one stream.</em></>} desc="Aggregated stdout, stderr and supervisor events from every service — live as the supervisor records them, filterable and searchable." glyph="terminal" art="wave" />
1782
+ <PageHero kicker="ERROR CONSOLE" title={<>Every failure, <em>in one stream.</em></>} desc="Aggregated stdout, stderr and supervisor events from every service live as the supervisor records them, filterable and searchable." glyph="terminal" art="wave" />
1783
1783
  {err && (
1784
1784
  <div role="alert" className="rounded-lg border border-rose-500/30 bg-rose-500/10 px-3 py-2 text-sm text-rose-400">
1785
- Logs unavailable — {err}. Is <code className="font-mono">nexus dev</code> running?
1785
+ Logs unavailable {err}. Is <code className="font-mono">nexus dev</code> running?
1786
1786
  </div>
1787
1787
  )}
1788
1788
  <div className="glass-card log-card">
@@ -1800,19 +1800,19 @@ function Logs({ onNavigate }: { onNavigate: (tab: Tab) => void }) {
1800
1800
  </button>
1801
1801
  ))}
1802
1802
  </div>
1803
- <button aria-pressed={criticalFirst} onClick={() => { setCriticalFirst(!criticalFirst); setSticky(true); }} className={criticalFirst ? 'log-crit-btn is-active' : 'log-crit-btn'}>⤓ critical first</button>
1803
+ <button aria-pressed={criticalFirst} onClick={() => { setCriticalFirst(!criticalFirst); setSticky(true); }} className={criticalFirst ? 'log-crit-btn is-active' : 'log-crit-btn'}>⤓ critical first</button>
1804
1804
  <div className="log-toolbar-right">
1805
1805
  <select value={service} onChange={(e) => { setService(e.target.value); setSticky(true); }} aria-label="Filter by service" className="glass-input log-service-input">
1806
1806
  <option value="all" className="bg-slate-900">All services</option>
1807
1807
  {services.map((s) => <option key={s} value={s} className="bg-slate-900">{s}</option>)}
1808
1808
  </select>
1809
- <input value={q} onChange={(e) => { setQ(e.target.value); setSticky(true); }} placeholder="Filter lines…" aria-label="Filter log lines" className="glass-input log-search-input" />
1809
+ <input value={q} onChange={(e) => { setQ(e.target.value); setSticky(true); }} placeholder="Filter lines" aria-label="Filter log lines" className="glass-input log-search-input" />
1810
1810
  </div>
1811
1811
  </div>
1812
1812
  {aiSummaryOpen && (
1813
1813
  <div className="log-ai-panel">
1814
1814
  <div className="log-ai-head">
1815
- <span className="log-ai-title"><Icon name="sparkles" size={12} /> AI summary{aiModel ? ` · ${aiModel}` : ''}</span>
1815
+ <span className="log-ai-title"><Icon name="sparkles" size={12} /> AI summary{aiModel ? ` · ${aiModel}` : ''}</span>
1816
1816
  <div className="log-ai-actions">
1817
1817
  {aiSummaryBusy && <button onClick={stopAiSummary} className="glass-chip-btn-danger"><Icon name="square" size={10} /> stop</button>}
1818
1818
  {!aiSummaryBusy && aiSummary && <button onClick={() => void copyAiSummary()} className="glass-chip-btn"><Icon name="copy" size={12} /> copy</button>}
@@ -1823,11 +1823,11 @@ function Logs({ onNavigate }: { onNavigate: (tab: Tab) => void }) {
1823
1823
  <div role="alert" className="log-ai-err">
1824
1824
  AI summary failed: {aiSummaryErr}
1825
1825
  {aiSummaryErr.includes('AI provider') && (
1826
- <button onClick={() => onNavigate('ai')} className="glass-chip-btn log-ai-open">Open AI Agents →</button>
1826
+ <button onClick={() => onNavigate('ai')} className="glass-chip-btn log-ai-open">Open AI Agents →</button>
1827
1827
  )}
1828
1828
  </div>
1829
1829
  )}
1830
- {aiSummaryBusy && !aiSummary && <div className="log-ai-note">Analyzing {ordered.length} lines…</div>}
1830
+ {aiSummaryBusy && !aiSummary && <div className="log-ai-note">Analyzing {ordered.length} lines…</div>}
1831
1831
  {aiSummary && <pre className="log-ai-body">{aiSummary}</pre>}
1832
1832
  </div>
1833
1833
  )}
@@ -1858,8 +1858,8 @@ function Logs({ onNavigate }: { onNavigate: (tab: Tab) => void }) {
1858
1858
  )}
1859
1859
  </div>
1860
1860
  <div className="log-footer">
1861
- <span className="log-foot-left">Aggregated across services · last {entries.length} lines · {live ? <span className="log-foot-left"><span className="log-live-dot" /> polling every 1.5s</span> : 'paused'}</span>
1862
- {!sticky && <button onClick={() => setSticky(true)} className="glass-chip-btn">{criticalFirst ? 'â–² jump to top' : 'â–¼ jump to latest'}</button>}
1861
+ <span className="log-foot-left">Aggregated across services · last {entries.length} lines · {live ? <span className="log-foot-left"><span className="log-live-dot" /> polling every 1.5s</span> : 'paused'}</span>
1862
+ {!sticky && <button onClick={() => setSticky(true)} className="glass-chip-btn">{criticalFirst ? ' jump to top' : ' jump to latest'}</button>}
1863
1863
  </div>
1864
1864
  </div>
1865
1865
  </div>
@@ -1942,16 +1942,16 @@ function LintChecks({ run, title, overline }: { run: () => Promise<LintReport>;
1942
1942
  <section className="workspace-panel lint-panel">
1943
1943
  <div className="panel-title-row">
1944
1944
  <div><span className="admin-overline">{overline}</span><h3>{title}</h3></div>
1945
- <button onClick={doRun} disabled={busy} className="glass-chip-btn">{busy ? 'linting…' : <><Icon name="refresh" size={12} /> run lint</>}</button>
1945
+ <button onClick={doRun} disabled={busy} className="glass-chip-btn">{busy ? 'linting' : <><Icon name="refresh" size={12} /> run lint</>}</button>
1946
1946
  </div>
1947
1947
  {err && <p className="preflight-empty">{err}</p>}
1948
1948
  {report ? (
1949
1949
  <>
1950
1950
  <div className="preflight-summary">
1951
- <span className="preflight-count bad">● {report.summary.error} error{report.summary.error === 1 ? '' : 's'}</span>
1952
- <span className="preflight-count warn">◐ {report.summary.warning} warning{report.summary.warning === 1 ? '' : 's'}</span>
1953
- <span className="preflight-count inf">◌ {report.summary.info} info</span>
1954
- {report.summary.ok > 0 && <span className="preflight-count ok">✓ {report.summary.ok} ok</span>}
1951
+ <span className="preflight-count bad">● {report.summary.error} error{report.summary.error === 1 ? '' : 's'}</span>
1952
+ <span className="preflight-count warn">◐ {report.summary.warning} warning{report.summary.warning === 1 ? '' : 's'}</span>
1953
+ <span className="preflight-count inf">◌ {report.summary.info} info</span>
1954
+ {report.summary.ok > 0 && <span className="preflight-count ok">✓ {report.summary.ok} ok</span>}
1955
1955
  <span className="preflight-meta">{report.ranAt}</span>
1956
1956
  </div>
1957
1957
  {report.engineOk === false && (
@@ -1961,7 +1961,7 @@ function LintChecks({ run, title, overline }: { run: () => Promise<LintReport>;
1961
1961
  </div>
1962
1962
  )}
1963
1963
  </>
1964
- ) : busy ? <p className="preflight-empty">Linting…</p> : <p className="preflight-empty">Run the linter to validate.</p>}
1964
+ ) : busy ? <p className="preflight-empty">Linting…</p> : <p className="preflight-empty">Run the linter to validate.</p>}
1965
1965
  {report && (
1966
1966
  <div className="preflight-list">
1967
1967
  {report.checks.map((c) => (
@@ -2041,7 +2041,7 @@ function AiSettings() {
2041
2041
  ))}
2042
2042
  </div>
2043
2043
  ) : (
2044
- <p className="text-xs text-slate-500">Loading settings…</p>
2044
+ <p className="text-xs text-slate-500">Loading settings…</p>
2045
2045
  )}
2046
2046
  </section>
2047
2047
 
@@ -2066,7 +2066,7 @@ function AiSettings() {
2066
2066
  ))}
2067
2067
  </>
2068
2068
  ) : (
2069
- <p className="col-span-3 text-xs text-slate-500">No status yet{aiErr ? '' : ' — run a check'}.</p>
2069
+ <p className="col-span-3 text-xs text-slate-500">No status yet{aiErr ? '' : ' run a check'}.</p>
2070
2070
  )}
2071
2071
  </div>
2072
2072
  {aiStatus && <p className="text-xs text-slate-500">"auto" currently resolves to <span className="font-mono text-slate-300">{aiStatus.autoResolvesTo}</span> on the AI server.</p>}
@@ -2135,7 +2135,7 @@ function isLocalProvider(baseUrl: string): boolean {
2135
2135
  } catch { return false; }
2136
2136
  }
2137
2137
 
2138
- /** Preset for the "Add provider" dialog — popular providers with autofill data
2138
+ /** Preset for the "Add provider" dialog popular providers with autofill data
2139
2139
  * and a list of well-known models (the user can still edit everything after). */
2140
2140
  interface ProviderPreset { id: string; label: string; baseUrl: string; defaultModel: string; models: string[]; }
2141
2141
  const POPULAR_PROVIDERS: ProviderPreset[] = [
@@ -2298,7 +2298,7 @@ function AiProviders({ toggle }: { toggle: ToggleStyle }) {
2298
2298
  try {
2299
2299
  const result = await testAiProvider(p.id);
2300
2300
  setTestResults((prev) => ({ ...prev, [p.id]: result }));
2301
- if (result.ok) setMsg(`${p.label}: reachable — ${result.modelCount ?? 0} model(s).`);
2301
+ if (result.ok) setMsg(`${p.label}: reachable ${result.modelCount ?? 0} model(s).`);
2302
2302
  else setMsg(`${p.label}: ${result.error ?? 'unreachable'}.`);
2303
2303
  } catch (e: any) {
2304
2304
  setTestResults((prev) => ({ ...prev, [p.id]: { ok: false, provider: p.id, error: String(e?.message ?? e), checkedAt: new Date().toISOString() } }));
@@ -2321,13 +2321,13 @@ function AiProviders({ toggle }: { toggle: ToggleStyle }) {
2321
2321
 
2322
2322
  const groups: Array<{ id: string; label: string; icon: string; items: AiProviderView[] }> = [];
2323
2323
  if (filter === 'all' || filter === 'enabled') {
2324
- if (enabled.length) groups.push({ id: 'enabled', label: 'Enabled', icon: '●', items: enabled });
2324
+ if (enabled.length) groups.push({ id: 'enabled', label: 'Enabled', icon: '', items: enabled });
2325
2325
  }
2326
2326
  if (filter === 'all' || filter === 'disabled') {
2327
- if (disabled.length) groups.push({ id: 'disabled', label: 'Disabled', icon: 'â—‹', items: disabled });
2327
+ if (disabled.length) groups.push({ id: 'disabled', label: 'Disabled', icon: '', items: disabled });
2328
2328
  }
2329
2329
  if (filter === 'all' || filter === 'custom') {
2330
- if (custom.length) groups.push({ id: 'custom', label: 'Custom', icon: '✚', items: custom });
2330
+ if (custom.length) groups.push({ id: 'custom', label: 'Custom', icon: '', items: custom });
2331
2331
  }
2332
2332
 
2333
2333
  const filterPills: Array<[ProviderFilter, string, number]> = [
@@ -2356,7 +2356,7 @@ function AiProviders({ toggle }: { toggle: ToggleStyle }) {
2356
2356
  <span className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-500"><Icon name="search" size={13} /></span>
2357
2357
  <input
2358
2358
  className="glass-input !py-1.5 text-sm w-full sm:w-64 pl-8"
2359
- placeholder="Search providers…"
2359
+ placeholder="Search providers"
2360
2360
  type="search"
2361
2361
  autoComplete="off"
2362
2362
  autoCapitalize="none"
@@ -2511,7 +2511,7 @@ function ProviderCard({ provider, toggle, anim, busy, isCustom, testing, testRes
2511
2511
  onClick={onToggle}
2512
2512
  disabled={busy}
2513
2513
  className={`ai-toggle tg-${toggle} ${provider.enabled ? 'is-on' : ''}`}
2514
- title={provider.enabled ? 'Enabled — click to disable' : 'Disabled — click to enable'}
2514
+ title={provider.enabled ? 'Enabled click to disable' : 'Disabled click to enable'}
2515
2515
  >
2516
2516
  <span className="ai-toggle-knob" />
2517
2517
  </button>
@@ -2520,7 +2520,7 @@ function ProviderCard({ provider, toggle, anim, busy, isCustom, testing, testRes
2520
2520
  {/* Status badges */}
2521
2521
  <div className="flex flex-wrap items-center gap-1.5">
2522
2522
  {provider.hasApiKey
2523
- ? <span className="provider-badge-ok">✓ key</span>
2523
+ ? <span className="provider-badge-ok">✓ key</span>
2524
2524
  : <span className="provider-badge-warn">no key</span>}
2525
2525
  {provider.enabled
2526
2526
  ? <span className="provider-badge-ok">enabled</span>
@@ -2556,7 +2556,7 @@ function ProviderCard({ provider, toggle, anim, busy, isCustom, testing, testRes
2556
2556
  </>
2557
2557
  ) : (
2558
2558
  <>
2559
- <code className="text-xs text-slate-300 font-mono truncate flex-1">{provider.defaultModel || '—'}</code>
2559
+ <code className="text-xs text-slate-300 font-mono truncate flex-1">{provider.defaultModel || ''}</code>
2560
2560
  <button type="button" onClick={() => setEditingModel(true)} disabled={busy} className="glass-chip-btn shrink-0 text-[10px]" title="Edit model"><Icon name="pencil" size={11} /></button>
2561
2561
  </>
2562
2562
  )}
@@ -2567,7 +2567,7 @@ function ProviderCard({ provider, toggle, anim, busy, isCustom, testing, testRes
2567
2567
  value=""
2568
2568
  onChange={(e) => { if (e.target.value) setModelInput(e.target.value); }}
2569
2569
  >
2570
- <option value="" className="bg-slate-900">…or pick a popular model</option>
2570
+ <option value="" className="bg-slate-900">…or pick a popular model</option>
2571
2571
  {PRESET_BY_ID[provider.id]!.models.map((m) => (
2572
2572
  <option key={m} value={m} className="bg-slate-900">{m}</option>
2573
2573
  ))}
@@ -2593,18 +2593,18 @@ function ProviderCard({ provider, toggle, anim, busy, isCustom, testing, testRes
2593
2593
  {/* Test row */}
2594
2594
  <div className="flex items-center gap-2 pt-0.5 border-t border-white/[0.06]">
2595
2595
  <button type="button" onClick={onTest} disabled={busy || testing} className="glass-chip-btn shrink-0 text-[10px]">
2596
- {testing ? 'testing…' : <><Icon name="zap" size={11} /> test</>}
2596
+ {testing ? 'testing' : <><Icon name="zap" size={11} /> test</>}
2597
2597
  </button>
2598
2598
  {testResult && (
2599
2599
  testResult.ok ? (
2600
2600
  <span className="text-[10px] text-emerald-300 truncate">
2601
- ✓ {testResult.modelCount ?? 0} model{testResult.modelCount === 1 ? '' : 's'}
2601
+ {testResult.modelCount ?? 0} model{testResult.modelCount === 1 ? '' : 's'}
2602
2602
  {testResult.models && testResult.models.length > 0 && (
2603
- <span className="text-slate-500"> — {testResult.models.slice(0, 3).join(', ')}{testResult.models.length > 3 ? '…' : ''}</span>
2603
+ <span className="text-slate-500"> {testResult.models.slice(0, 3).join(', ')}{testResult.models.length > 3 ? '' : ''}</span>
2604
2604
  )}
2605
2605
  </span>
2606
2606
  ) : (
2607
- <span className="text-[10px] text-rose-400 truncate" title={testResult.error}>✗ {testResult.error ?? 'unreachable'}</span>
2607
+ <span className="text-[10px] text-rose-400 truncate" title={testResult.error}>✗ {testResult.error ?? 'unreachable'}</span>
2608
2608
  )
2609
2609
  )}
2610
2610
  </div>
@@ -2637,7 +2637,7 @@ function ProviderKeyDialog({ provider, value, onChange, onSave, onCancel, busy }
2637
2637
  <h3 id="provider-key-title" className="mt-1 text-base font-semibold text-slate-100">{provider.label}</h3>
2638
2638
  <code className="text-xs text-slate-500">{provider.id}</code>
2639
2639
  </div>
2640
- <button type="button" onClick={onCancel} disabled={busy} className="glass-chip-btn text-xs" aria-label="Close API key dialog">✕</button>
2640
+ <button type="button" onClick={onCancel} disabled={busy} className="glass-chip-btn text-xs" aria-label="Close API key dialog">✕</button>
2641
2641
  </div>
2642
2642
 
2643
2643
  <div className="rounded-xl border border-sky-400/20 bg-black/20 px-3 py-2 text-xs text-slate-400">
@@ -2653,7 +2653,7 @@ function ProviderKeyDialog({ provider, value, onChange, onSave, onCancel, busy }
2653
2653
  name="ai-provider-api-key"
2654
2654
  autoComplete="new-password"
2655
2655
  autoFocus
2656
- placeholder={provider.hasApiKey ? '•••••••• (enter new to replace)' : 'paste API key'}
2656
+ placeholder={provider.hasApiKey ? '•••••••• (enter new to replace)' : 'paste API key'}
2657
2657
  value={value}
2658
2658
  onChange={(event) => onChange(event.target.value)}
2659
2659
  onKeyDown={(event) => {
@@ -2668,7 +2668,7 @@ function ProviderKeyDialog({ provider, value, onChange, onSave, onCancel, busy }
2668
2668
  <div className="flex justify-end gap-2 border-t border-sky-400/20 pt-3">
2669
2669
  <button type="button" onClick={onCancel} disabled={busy} className="glass-chip-btn">Cancel</button>
2670
2670
  <button type="button" onClick={onSave} disabled={busy || !value.trim()} className="glass-btn-primary !py-1.5 text-sm">
2671
- {busy ? 'Saving…' : 'Save API key'}
2671
+ {busy ? 'Saving' : 'Save API key'}
2672
2672
  </button>
2673
2673
  </div>
2674
2674
  </div>
@@ -2697,7 +2697,7 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2697
2697
 
2698
2698
  const applyPreset = (id: string) => {
2699
2699
  setPresetId(id);
2700
- if (!id) return; // "— select —"
2700
+ if (!id) return; // " select "
2701
2701
  if (id === 'other') {
2702
2702
  // Clear fields for a fully custom provider; user fills everything.
2703
2703
  onChange({ id: '', label: '', baseUrl: '', apiKey: value.apiKey, defaultModel: '' });
@@ -2720,11 +2720,11 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2720
2720
  <div className="provider-modal space-y-4" onClick={(e) => e.stopPropagation()}>
2721
2721
  <div className="flex items-center justify-between">
2722
2722
  <h3 className="glass-h3">Add custom provider</h3>
2723
- <button onClick={onCancel} className="glass-chip-btn text-xs">✕</button>
2723
+ <button onClick={onCancel} className="glass-chip-btn text-xs">✕</button>
2724
2724
  </div>
2725
2725
 
2726
2726
  <div className="space-y-3">
2727
- {/* Preset selector — pick a popular provider or "Other" */}
2727
+ {/* Preset selector pick a popular provider or "Other" */}
2728
2728
  <label className="block">
2729
2729
  <span className="admin-overline">Choose a provider</span>
2730
2730
  <select
@@ -2732,13 +2732,13 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2732
2732
  value={presetId}
2733
2733
  onChange={(e) => applyPreset(e.target.value)}
2734
2734
  >
2735
- <option value="" className="bg-slate-900">— select —</option>
2735
+ <option value="" className="bg-slate-900">— select —</option>
2736
2736
  {POPULAR_PROVIDERS.map((p) => (
2737
2737
  <option key={p.id} value={p.id} className="bg-slate-900">{p.label}</option>
2738
2738
  ))}
2739
2739
  <option value="other" className="bg-slate-900">Other (custom)</option>
2740
2740
  </select>
2741
- <span className="text-[10px] text-slate-500">Selecting a provider autofills the fields below — all editable.</span>
2741
+ <span className="text-[10px] text-slate-500">Selecting a provider autofills the fields below all editable.</span>
2742
2742
  </label>
2743
2743
 
2744
2744
  <label className="block">
@@ -2749,7 +2749,7 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2749
2749
  value={value.id}
2750
2750
  onChange={(e) => onChange({ ...value, id: e.target.value })}
2751
2751
  />
2752
- {missing.id && <span className="text-[10px] text-amber-400">required — lowercase, no spaces</span>}
2752
+ {missing.id && <span className="text-[10px] text-amber-400">required lowercase, no spaces</span>}
2753
2753
  </label>
2754
2754
 
2755
2755
  <label className="block">
@@ -2785,7 +2785,7 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2785
2785
  />
2786
2786
  </label>
2787
2787
 
2788
- {/* Default model — text input + a "pick from popular" select */}
2788
+ {/* Default model text input + a "pick from popular" select */}
2789
2789
  <label className="block">
2790
2790
  <span className="admin-overline">Default model <span className="text-slate-500">(optional)</span></span>
2791
2791
  <input
@@ -2797,13 +2797,13 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2797
2797
  </label>
2798
2798
  {preset && preset.models.length > 0 && (
2799
2799
  <label className="block">
2800
- <span className="admin-overline">…or pick a model</span>
2800
+ <span className="admin-overline">…or pick a model</span>
2801
2801
  <select
2802
2802
  className="glass-input mt-1 text-sm font-mono"
2803
2803
  value=""
2804
2804
  onChange={(e) => { if (e.target.value) onChange({ ...value, defaultModel: e.target.value }); }}
2805
2805
  >
2806
- <option value="" className="bg-slate-900">{preset.models.length} popular model{preset.models.length === 1 ? '' : 's'} — click to use</option>
2806
+ <option value="" className="bg-slate-900">{preset.models.length} popular model{preset.models.length === 1 ? '' : 's'} click to use</option>
2807
2807
  {preset.models.map((m) => (
2808
2808
  <option key={m} value={m} className="bg-slate-900">{m}</option>
2809
2809
  ))}
@@ -2815,7 +2815,7 @@ function AddProviderDialog({ value, onChange, onSubmit, onCancel, busy }: {
2815
2815
  <div className="flex justify-end gap-2 pt-2 border-t border-sky-400/20">
2816
2816
  <button onClick={onCancel} disabled={busy} className="glass-chip-btn">Cancel</button>
2817
2817
  <button onClick={onSubmit} disabled={busy || missing.id || missing.label || missing.baseUrl} className="glass-btn-primary !py-1.5 text-sm">
2818
- {busy ? 'Adding…' : '+ Add provider'}
2818
+ {busy ? 'Adding' : '+ Add provider'}
2819
2819
  </button>
2820
2820
  </div>
2821
2821
  </div>
@@ -2853,7 +2853,7 @@ function AiChatPlayground() {
2853
2853
  // Load its models once the provider state lands.
2854
2854
  setTimeout(() => { void loadModelsRef.current(firstEnabled.id); }, 0);
2855
2855
  } else {
2856
- // No enabled provider — fall back to the AI server's own detection.
2856
+ // No enabled provider fall back to the AI server's own detection.
2857
2857
  void checkAiRef.current();
2858
2858
  }
2859
2859
  }).catch(() => { void checkAiRef.current(); });
@@ -2964,7 +2964,7 @@ function AiChatPlayground() {
2964
2964
  <div className="flex flex-wrap items-center gap-3">
2965
2965
  <label className="text-xs text-slate-400">Provider
2966
2966
  <select className="glass-input w-44 !py-1.5 text-sm" value={provider} onChange={(e) => loadModels(e.target.value)}>
2967
- {!provider && <option value="" className="bg-slate-900">— select —</option>}
2967
+ {!provider && <option value="" className="bg-slate-900">— select —</option>}
2968
2968
  {providerOptions.map((p) => <option key={p.id} value={p.id} className="bg-slate-900">{p.label}</option>)}
2969
2969
  </select>
2970
2970
  </label>
@@ -2976,7 +2976,7 @@ function AiChatPlayground() {
2976
2976
  value=""
2977
2977
  onChange={(e) => { const v = e.target.value; if (v) setModel(v); }}
2978
2978
  disabled={!modelOptions.length}
2979
- title={modelOptions.length ? `Available models for "${loadedFor || provider}"` : 'No models — click refresh models'}
2979
+ title={modelOptions.length ? `Available models for "${loadedFor || provider}"` : 'No models click refresh models'}
2980
2980
  >
2981
2981
  <option value="" className="bg-slate-900">{modelOptions.length ? `${modelOptions.length} available for "${loadedFor || provider}"` : 'no models loaded'}</option>
2982
2982
  {modelOptions.map((m) => <option key={m} value={m} className="bg-slate-900">{m}</option>)}
@@ -2990,7 +2990,7 @@ function AiChatPlayground() {
2990
2990
  {/* System prompt */}
2991
2991
  <div>
2992
2992
  <label className="text-xs text-slate-400">System prompt</label>
2993
- <textarea className="glass-code mt-1 h-16 w-full p-2 text-sm" value={systemPrompt} onChange={(e) => setSystemPrompt(e.target.value)} placeholder="Set the assistant's behaviour…" />
2993
+ <textarea className="glass-code mt-1 h-16 w-full p-2 text-sm" value={systemPrompt} onChange={(e) => setSystemPrompt(e.target.value)} placeholder="Set the assistant's behaviour" />
2994
2994
  </div>
2995
2995
 
2996
2996
  {/* Chat messages */}
@@ -3007,8 +3007,8 @@ function AiChatPlayground() {
3007
3007
  {streaming && (
3008
3008
  <div className="flex justify-start">
3009
3009
  <div className="max-w-[80%] rounded-xl bg-emerald-500/15 px-3 py-2 text-slate-100">
3010
- <span className="mb-1 block text-[10px] uppercase tracking-wider opacity-60">assistant <span className="animate-pulse">…</span></span>
3011
- <span className="whitespace-pre-wrap">{streamText}<span className="animate-pulse">â–‹</span></span>
3010
+ <span className="mb-1 block text-[10px] uppercase tracking-wider opacity-60">assistant <span className="animate-pulse">…</span></span>
3011
+ <span className="whitespace-pre-wrap">{streamText}<span className="animate-pulse">▋</span></span>
3012
3012
  </div>
3013
3013
  </div>
3014
3014
  )}
@@ -3016,8 +3016,8 @@ function AiChatPlayground() {
3016
3016
 
3017
3017
  {/* Input */}
3018
3018
  <div className="flex gap-2">
3019
- <textarea className="glass-code flex-1 h-12 p-2 text-sm" value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={sendOnEnter} placeholder="Type a message… (Enter to send, Shift+Enter for newline)" disabled={streaming} />
3020
- <button onClick={send} disabled={streaming || !input.trim()} className="glass-btn-primary shrink-0">{streaming ? '…' : 'Send'}</button>
3019
+ <textarea className="glass-code flex-1 h-12 p-2 text-sm" value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={sendOnEnter} placeholder="Type a message (Enter to send, Shift+Enter for newline)" disabled={streaming} />
3020
+ <button onClick={send} disabled={streaming || !input.trim()} className="glass-btn-primary shrink-0">{streaming ? '' : 'Send'}</button>
3021
3021
  </div>
3022
3022
  </section>
3023
3023
  </div>
@@ -3101,8 +3101,8 @@ function Config({ onNavigate }: { onNavigate?: (tab: Tab) => void }) {
3101
3101
  <PageHead title="Configuration"><div className="heading-actions"><span className="restart-badge">RESTART REQUIRED AFTER SAVE</span><button onClick={load} className="workspace-action"><Icon name="refresh" size={12} /> Reload</button></div></PageHead>
3102
3102
  <div className="config-intro"><div><span className="admin-overline">PROJECT CONTROL PLANE</span><h1>Configure your <em>runtime.</em></h1><p>Use key/value rows for safe JSON overrides, or switch to source mode for the typed project configuration.</p></div><div className="hero-art hero-art--cubes"><strong>{'{ }'}</strong><span className="ha-a" /><span className="ha-b" /><span className="ha-c" /></div></div>
3103
3103
  <div className="workspace-tabs">{([['runtime', 'Runtime JSON'], ['source', 'Source file'], ['effective', 'Effective view']] as const).map(([id, label]) => <button key={id} onClick={() => setMode(id)} className={mode === id ? 'is-active' : ''}>{label}</button>)}</div>
3104
- {mode === 'runtime' && <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">NEXUS CONFIG · KEY/VALUE</span><h3>Configuration overrides</h3></div><button onClick={addKey} className="workspace-action">+ Add key</button></div><p className="panel-description">Existing configuration is loaded into the rows below. Changes are written to nexus.runtime.json and validated against the Nexus schema before saving. Secrets are redacted and managed in the Environment surface.</p><div className="key-add-bar">
3105
- <input value={newKey} onChange={(event) => setNewKey(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addKey(); }} placeholder="key path — e.g. server.port" />
3104
+ {mode === 'runtime' && <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">NEXUS CONFIG · KEY/VALUE</span><h3>Configuration overrides</h3></div><button onClick={addKey} className="workspace-action">+ Add key</button></div><p className="panel-description">Existing configuration is loaded into the rows below. Changes are written to nexus.runtime.json and validated against the Nexus schema before saving. Secrets are redacted and managed in the Environment surface.</p><div className="key-add-bar">
3105
+ <input value={newKey} onChange={(event) => setNewKey(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addKey(); }} placeholder="key path e.g. server.port" />
3106
3106
  <select value={newType} onChange={(event) => { setNewType(event.target.value as ConfigValueType); if (event.target.value === 'null') setNewValue(''); }}><option value="string">string</option><option value="number">number</option><option value="boolean">boolean</option><option value="null">null</option><option value="object">object</option><option value="array">array</option></select>
3107
3107
  <input value={newValue} disabled={newType === 'null'} onChange={(event) => setNewValue(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addKey(); }} placeholder={newType === 'object' || newType === 'array' ? '{ }' : 'value'} />
3108
3108
  </div><div className="key-value-table">
@@ -3115,16 +3115,16 @@ function Config({ onNavigate }: { onNavigate?: (tab: Tab) => void }) {
3115
3115
  <input value={entry.key} onChange={(event) => setEntries(entries.map((item, itemIndex) => itemIndex === index ? { ...item, key: event.target.value } : item))} placeholder="server.port" />
3116
3116
  <select value={entry.type} onChange={(event) => setEntries(entries.map((item, itemIndex) => itemIndex === index ? { ...item, type: event.target.value as ConfigValueType, value: event.target.value === 'null' ? '' : item.value } : item))}><option value="string">string</option><option value="number">number</option><option value="boolean">boolean</option><option value="null">null</option><option value="object">object</option><option value="array">array</option></select>
3117
3117
  <input value={entry.value} disabled={entry.type === 'null'} onChange={(event) => setEntries(entries.map((item, itemIndex) => itemIndex === index ? { ...item, value: event.target.value } : item))} placeholder={entry.type === 'object' || entry.type === 'array' ? '{ }' : 'value'} />
3118
- <button className="row-delete" onClick={() => setEntries(entries.filter((_, itemIndex) => itemIndex !== index))} aria-label={`Delete ${entry.key || 'entry'}`}>×</button>
3118
+ <button className="row-delete" onClick={() => setEntries(entries.filter((_, itemIndex) => itemIndex !== index))} aria-label={`Delete ${entry.key || 'entry'}`}>×</button>
3119
3119
  </div>
3120
3120
  ))}
3121
3121
  </div>
3122
3122
  ))}
3123
3123
  {!entries.length && <div className="key-value-empty">No configuration keys loaded.</div>}
3124
- </div><div className="config-footer"><span>{entries.length} override{entries.length === 1 ? '' : 's'} · validated on save</span><button onClick={saveRuntime} disabled={busy} className="glass-chip-btn-primary shrink-0">{busy ? 'Saving…' : 'Save runtime JSON'} <span>→</span></button></div></section>}
3125
- {mode === 'source' && <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">HUMAN-EDITED SOURCE</span><h3>{filePath ? filePath.split(/[\\/]/).pop() : 'nexus.config.ts'}</h3></div><button onClick={saveFile} disabled={busy} className="glass-chip-btn-primary shrink-0">{busy ? 'Saving…' : 'Save source'} <span>→</span></button></div><textarea className="source-editor" value={fileText} onChange={(event) => setFileText(event.target.value)} spellCheck={false} /></section>}
3126
- {mode === 'effective' && <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">READ-ONLY · SECRETS REDACTED</span><h3>Effective merged config</h3></div></div><pre className="source-editor effective-editor">{cfg ? JSON.stringify(cfg.config, null, 2) : 'Loading configuration…'}</pre></section>}
3127
- <LintChecks run={runLintConfig} title="Configuration checks" overline="VALIDATOR · nexus.runtime.json" />
3124
+ </div><div className="config-footer"><span>{entries.length} override{entries.length === 1 ? '' : 's'} · validated on save</span><button onClick={saveRuntime} disabled={busy} className="glass-chip-btn-primary shrink-0">{busy ? 'Saving' : 'Save runtime JSON'} <span>→</span></button></div></section>}
3125
+ {mode === 'source' && <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">HUMAN-EDITED SOURCE</span><h3>{filePath ? filePath.split(/[\\/]/).pop() : 'nexus.config.ts'}</h3></div><button onClick={saveFile} disabled={busy} className="glass-chip-btn-primary shrink-0">{busy ? 'Saving' : 'Save source'} <span>→</span></button></div><textarea className="source-editor" value={fileText} onChange={(event) => setFileText(event.target.value)} spellCheck={false} /></section>}
3126
+ {mode === 'effective' && <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">READ-ONLY · SECRETS REDACTED</span><h3>Effective merged config</h3></div></div><pre className="source-editor effective-editor">{cfg ? JSON.stringify(cfg.config, null, 2) : 'Loading configuration'}</pre></section>}
3127
+ <LintChecks run={runLintConfig} title="Configuration checks" overline="VALIDATOR · nexus.runtime.json" />
3128
3128
  <ConfirmDialog
3129
3129
  open={restartDialog}
3130
3130
  title="Restart required"
@@ -3159,10 +3159,10 @@ function Environment({ onNavigate }: { onNavigate?: (tab: Tab) => void }) {
3159
3159
  return (
3160
3160
  <div className="admin-view-stack">
3161
3161
  <PageHead title="Environment"><div className="heading-actions"><span className="secret-badge">SECRETS MASKED</span><button onClick={() => load()} className="workspace-action"><Icon name="refresh" size={12} /> Reload</button></div></PageHead>
3162
- <div className="config-intro env-intro"><div><span className="admin-overline">LOCAL ENVIRONMENT · {data?.path ?? '.env'}</span><h1>Your environment, <em>under control.</em></h1><p>Manage project variables as key/value pairs. Secret values stay on disk and are never returned to the browser.</p></div><div className="env-lock-art"><i className="env-lock-glyph"><Icon name="lock" size={26} /></i><span>PRIVATE</span></div></div>
3163
- <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">{file.toUpperCase()} FILE · PRESERVES COMMENTS</span><h3>Environment variables</h3></div><button onClick={addVariable} className="workspace-action">+ Add variable</button></div><div className="env-add-bar">
3162
+ <div className="config-intro env-intro"><div><span className="admin-overline">LOCAL ENVIRONMENT · {data?.path ?? '.env'}</span><h1>Your environment, <em>under control.</em></h1><p>Manage project variables as key/value pairs. Secret values stay on disk and are never returned to the browser.</p></div><div className="env-lock-art"><i className="env-lock-glyph"><Icon name="lock" size={26} /></i><span>PRIVATE</span></div></div>
3163
+ <section className="workspace-panel config-panel"><div className="panel-title-row"><div><span className="admin-overline">{file.toUpperCase()} FILE · PRESERVES COMMENTS</span><h3>Environment variables</h3></div><button onClick={addVariable} className="workspace-action">+ Add variable</button></div><div className="env-add-bar">
3164
3164
  <input value={newKey} onChange={(event) => setNewKey(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addVariable(); }} placeholder="NEXUS_SERVER_PORT" />
3165
- <div className="env-value-wrap"><input value={newValue} onChange={(event) => setNewValue(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addVariable(); }} placeholder="value" /><span className="secret-mark">{newKey && /SECRET|PASSWORD|PASS|TOKEN|PRIVATE|API_KEY|CLIENT_SECRET|ACCESS_KEY|KEY_SECRET/i.test(newKey) ? '●' : '○'}</span></div>
3165
+ <div className="env-value-wrap"><input value={newValue} onChange={(event) => setNewValue(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addVariable(); }} placeholder="value" /><span className="secret-mark">{newKey && /SECRET|PASSWORD|PASS|TOKEN|PRIVATE|API_KEY|CLIENT_SECRET|ACCESS_KEY|KEY_SECRET/i.test(newKey) ? '' : ''}</span></div>
3166
3166
  </div><div className="key-value-table env-table">{groupEntries(entries).map(({ group, items }) => (
3167
3167
  <div className="key-group" key={group}>
3168
3168
  <div className="key-group-head"><span className="key-group-name">{group}</span><small>{items.length} variable{items.length === 1 ? '' : 's'}</small></div>
@@ -3170,13 +3170,13 @@ function Environment({ onNavigate }: { onNavigate?: (tab: Tab) => void }) {
3170
3170
  {items.map(({ index, entry }) => (
3171
3171
  <div className="key-value-row" key={`${entry.key}-${index}`}>
3172
3172
  <input value={entry.key} onChange={(event) => setEntries(entries.map((item, itemIndex) => itemIndex === index ? { ...item, key: event.target.value, secret: /SECRET|PASSWORD|PASS|TOKEN|PRIVATE|API_KEY|CLIENT_SECRET|ACCESS_KEY|KEY_SECRET/i.test(event.target.value) } : item))} placeholder="NEXUS_SERVER_PORT" />
3173
- <div className="env-value-wrap"><input type={entry.secret ? 'password' : 'text'} value={entry.value ?? ''} placeholder={entry.secret ? '•••••••• · unchanged' : 'value'} onChange={(event) => setEntries(entries.map((item, itemIndex) => itemIndex === index ? { ...item, value: event.target.value } : item))} /><span className={entry.secret ? 'secret-mark is-secret' : 'secret-mark'}>{entry.secret ? '●' : '○'}</span><button className="row-delete" onClick={() => setDeleteTarget(entry.key)} aria-label={`Delete ${entry.key || 'entry'}`} title={`Delete ${entry.key || 'entry'}`}>×</button></div>
3173
+ <div className="env-value-wrap"><input type={entry.secret ? 'password' : 'text'} value={entry.value ?? ''} placeholder={entry.secret ? '•••••••• · unchanged' : 'value'} onChange={(event) => setEntries(entries.map((item, itemIndex) => itemIndex === index ? { ...item, value: event.target.value } : item))} /><span className={entry.secret ? 'secret-mark is-secret' : 'secret-mark'}>{entry.secret ? '' : ''}</span><button className="row-delete" onClick={() => setDeleteTarget(entry.key)} aria-label={`Delete ${entry.key || 'entry'}`} title={`Delete ${entry.key || 'entry'}`}>×</button></div>
3174
3174
  </div>
3175
3175
  ))}
3176
3176
  </div>
3177
3177
  ))}
3178
- {!entries.length && <div className="key-value-empty">No environment variables found.</div>}</div><div className="config-footer"><span>{entries.length} variable{entries.length === 1 ? '' : 's'} · masked secrets are preserved when unchanged</span><button onClick={save} disabled={busy} className="glass-chip-btn-primary shrink-0">{busy ? 'Saving…' : `Save ${file}`} <span>→</span></button></div></section>
3179
- <LintChecks run={() => runLintEnv(file)} title="Environment checks" overline={`VALIDATOR · ${file}`} />
3178
+ {!entries.length && <div className="key-value-empty">No environment variables found.</div>}</div><div className="config-footer"><span>{entries.length} variable{entries.length === 1 ? '' : 's'} · masked secrets are preserved when unchanged</span><button onClick={save} disabled={busy} className="glass-chip-btn-primary shrink-0">{busy ? 'Saving' : `Save ${file}`} <span>→</span></button></div></section>
3179
+ <LintChecks run={() => runLintEnv(file)} title="Environment checks" overline={`VALIDATOR · ${file}`} />
3180
3180
  <ConfirmDialog
3181
3181
  open={restartDialog}
3182
3182
  title="Restart required"
@@ -3200,17 +3200,17 @@ function Environment({ onNavigate }: { onNavigate?: (tab: Tab) => void }) {
3200
3200
  function Plugins() {
3201
3201
  const [data, setData] = useState<{ pages: any[]; slots: Record<string, any[]> } | null>(null);
3202
3202
  useEffect(() => { getPlugins().then(setData).catch(() => {}); }, []);
3203
- if (!data) return <p className="text-slate-400">Loading…</p>;
3203
+ if (!data) return <p className="text-slate-400">Loading…</p>;
3204
3204
  return (
3205
3205
  <div className="space-y-6">
3206
3206
  <PageHead title="Plugins" />
3207
- <PageHero kicker="EXTENSION GALLERY" title={<>Extend Nexus, <em>effortlessly.</em></>} desc="Every admin page, slot and surface that plugin modules register — a living index of what's available to your build." glyph="puzzle" art="grid" />
3207
+ <PageHero kicker="EXTENSION GALLERY" title={<>Extend Nexus, <em>effortlessly.</em></>} desc="Every admin page, slot and surface that plugin modules register a living index of what's available to your build." glyph="puzzle" art="grid" />
3208
3208
  <div className="glass-card space-y-4">
3209
3209
  <h3 className="glass-h3">Admin pages</h3>
3210
3210
  <ul className="space-y-1">
3211
3211
  {data.pages.map((p) => (
3212
3212
  <li key={p.path} className="flex items-center gap-2 font-mono text-sm text-slate-200">
3213
- <span className="chip">{p.group ?? 'Plugins'}</span> {p.title} —{' '}
3213
+ <span className="chip">{p.group ?? 'Plugins'}</span> {p.title} {' '}
3214
3214
  <a href={p.path} target="_blank" rel="noreferrer" className="text-slate-400 underline decoration-dotted underline-offset-4 transition-colors hover:text-sky-300">
3215
3215
  {p.path}
3216
3216
  </a>
@@ -3272,15 +3272,15 @@ function Users({ currentUserEmail }: { currentUserEmail?: string }) {
3272
3272
  const roleChip = (id: string) => {
3273
3273
  const def = roleDef(id);
3274
3274
  const accent = def?.accent ?? '#8ba0bd';
3275
- return <span key={id} className="role-chip" style={{ color: accent, borderColor: `${accent}66`, background: `${accent}1a` }}>{def?.icon ?? '◌'} {def?.label ?? id}</span>;
3275
+ return <span key={id} className="role-chip" style={{ color: accent, borderColor: `${accent}66`, background: `${accent}1a` }}>{def?.icon ?? ''} {def?.label ?? id}</span>;
3276
3276
  };
3277
3277
 
3278
- if (!data) return <p className="text-slate-400">Loading…</p>;
3278
+ if (!data) return <p className="text-slate-400">Loading…</p>;
3279
3279
 
3280
3280
  return (
3281
3281
  <div className="space-y-6">
3282
3282
  <PageHead title="Users & Roles" />
3283
- <PageHero kicker="ACCESS & PERMISSIONS" title={<>Your team, <em>in control.</em></>} desc="Every account that can reach this Nexus surface — with role grants and restrictions applied in one built-in permission catalog." glyph="users" art="people" />
3283
+ <PageHero kicker="ACCESS & PERMISSIONS" title={<>Your team, <em>in control.</em></>} desc="Every account that can reach this Nexus surface with role grants and restrictions applied in one built-in permission catalog." glyph="users" art="people" />
3284
3284
 
3285
3285
  <div className="glass-panel overflow-hidden p-2">
3286
3286
  <table className="glass-table">
@@ -3289,13 +3289,13 @@ function Users({ currentUserEmail }: { currentUserEmail?: string }) {
3289
3289
  {data.users.map((u) => (
3290
3290
  <tr key={String(u._id)}>
3291
3291
  <td className="text-slate-200">{u.email}{u.email === currentUserEmail && <span className="you-tag">you</span>}</td>
3292
- <td className="text-slate-300">{u.name ?? '—'}</td>
3292
+ <td className="text-slate-300">{u.name ?? ''}</td>
3293
3293
  <td>
3294
3294
  {(u.roles ?? []).map(roleChip)}
3295
- {!(u.roles ?? []).length && '—'}
3295
+ {!(u.roles ?? []).length && ''}
3296
3296
  </td>
3297
- <td className="text-center text-emerald-400">{u.emailVerified ? '✓' : <span className="text-slate-500">—</span>}</td>
3298
- <td className="text-right"><button className="glass-chip-btn" onClick={() => openEditor(u)}>â—ˆ Edit roles</button></td>
3297
+ <td className="text-center text-emerald-400">{u.emailVerified ? '' : <span className="text-slate-500">—</span>}</td>
3298
+ <td className="text-right"><button className="glass-chip-btn" onClick={() => openEditor(u)}>◈ Edit roles</button></td>
3299
3299
  </tr>
3300
3300
  ))}
3301
3301
  {!data.users.length && <tr><td colSpan={5} className="py-8 text-center text-slate-500">No users registered.</td></tr>}
@@ -3315,13 +3315,13 @@ function Users({ currentUserEmail }: { currentUserEmail?: string }) {
3315
3315
  </div>
3316
3316
  <p className="role-box-desc">{r.description}</p>
3317
3317
  <div className="role-section">
3318
- <span className="role-section-title is-can">✓ Can do</span>
3318
+ <span className="role-section-title is-can">✓ Can do</span>
3319
3319
  <ul className="role-list">
3320
3320
  {r.grants.map((g) => <li key={g.label}><b>{g.label}</b><span>{g.detail}</span></li>)}
3321
3321
  </ul>
3322
3322
  </div>
3323
3323
  <div className="role-section">
3324
- <span className="role-section-title is-no">✕ Restricted from</span>
3324
+ <span className="role-section-title is-no">✕ Restricted from</span>
3325
3325
  <ul className="role-list">
3326
3326
  {r.restricts.map((g) => <li key={g.label}><b>{g.label}</b><span>{g.detail}</span></li>)}
3327
3327
  </ul>
@@ -3334,32 +3334,32 @@ function Users({ currentUserEmail }: { currentUserEmail?: string }) {
3334
3334
  <div className="confirm-overlay" onClick={() => !busy && setEditing(null)}>
3335
3335
  <div className="confirm-dialog role-dialog" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
3336
3336
  <div className="confirm-dialog-head">
3337
- <span className="confirm-dialog-mark">â—Ž</span>
3338
- <strong>Edit roles · {editing.email}</strong>
3339
- <button className="confirm-dialog-close" onClick={() => !busy && setEditing(null)} aria-label="Close">×</button>
3337
+ <span className="confirm-dialog-mark">◎</span>
3338
+ <strong>Edit roles · {editing.email}</strong>
3339
+ <button className="confirm-dialog-close" onClick={() => !busy && setEditing(null)} aria-label="Close">×</button>
3340
3340
  </div>
3341
- <p className="confirm-dialog-message">Choose which roles this account holds — every selected role applies.</p>
3341
+ <p className="confirm-dialog-message">Choose which roles this account holds every selected role applies.</p>
3342
3342
  <div className="role-options">
3343
3343
  {roles.map((r) => {
3344
3344
  const on = draft.includes(r.id);
3345
3345
  return (
3346
3346
  <button key={r.id} type="button" className={`role-option${on ? ' is-on' : ''}`} style={{ ['--role-accent' as string]: r.accent }} onClick={() => toggleRole(r.id)}>
3347
- <span className="role-option-icon" style={{ color: r.accent }}>{on ? '✓' : r.icon}</span>
3347
+ <span className="role-option-icon" style={{ color: r.accent }}>{on ? '' : r.icon}</span>
3348
3348
  <span className="role-option-text">
3349
3349
  <b>{r.label}</b>
3350
3350
  <small>{r.description}</small>
3351
3351
  </span>
3352
- <span className="role-option-check">{on ? '✓' : ''}</span>
3352
+ <span className="role-option-check">{on ? '' : ''}</span>
3353
3353
  </button>
3354
3354
  );
3355
3355
  })}
3356
3356
  </div>
3357
3357
  {editing.email === currentUserEmail && (
3358
- <p className="role-hint">Editing your own account — removing <b>Administrator</b> from yourself is blocked server-side.</p>
3358
+ <p className="role-hint">Editing your own account removing <b>Administrator</b> from yourself is blocked server-side.</p>
3359
3359
  )}
3360
3360
  <div className="confirm-dialog-actions">
3361
3361
  <button className="glass-chip-btn" onClick={() => setEditing(null)} disabled={busy}>Cancel</button>
3362
- <button className="glass-chip-btn-danger confirm-dialog-primary" onClick={saveRoles} disabled={busy}>{busy ? 'Saving…' : 'Save roles'}</button>
3362
+ <button className="glass-chip-btn-danger confirm-dialog-primary" onClick={saveRoles} disabled={busy}>{busy ? 'Saving' : 'Save roles'}</button>
3363
3363
  </div>
3364
3364
  </div>
3365
3365
  </div>
@@ -3476,15 +3476,15 @@ function Monitoring() {
3476
3476
  const t = setInterval(() => getRequestLogs().then(setLogs).catch(() => {}), 5000);
3477
3477
  return () => clearInterval(t);
3478
3478
  }, []);
3479
- if (!data) return <p className="text-slate-400">Loading…</p>;
3479
+ if (!data) return <p className="text-slate-400">Loading…</p>;
3480
3480
  return (
3481
3481
  <div className="space-y-6">
3482
3482
  <PageHead title="Monitoring" />
3483
- <PageHero kicker="LIVE TELEMETRY" title={<>Watch the whole <em>machine.</em></>} desc="A calm, self-refreshing readout of the backend heartbeat — uptime, PID and request traffic without the noise." glyph="activity" art="coins" />
3483
+ <PageHero kicker="LIVE TELEMETRY" title={<>Watch the whole <em>machine.</em></>} desc="A calm, self-refreshing readout of the backend heartbeat uptime, PID and request traffic without the noise." glyph="activity" art="coins" />
3484
3484
  <div className="grid grid-cols-2 gap-4 lg:grid-cols-3">
3485
3485
  <div className="glass-card"><div className="glass-h3">Uptime</div><div className="mt-2 text-3xl font-bold text-slate-100">{Math.round(data.uptime ?? 0)}<span className="ml-1 text-sm font-normal text-slate-400">s</span></div></div>
3486
3486
  <div className="glass-card"><div className="glass-h3">PID</div><div className="mt-2 font-mono text-3xl font-bold text-slate-100">{data.pid}</div></div>
3487
- <div className="glass-card"><div className="glass-h3">HTTP requests</div><div className="mt-2 text-2xl font-bold text-indigo-300">{(() => { const v = data.metrics?.http_requests_total?.values; return v ? Object.values(v).reduce((a: number, b) => a + Number(b ?? 0), 0) : '—'; })()}</div></div>
3487
+ <div className="glass-card"><div className="glass-h3">HTTP requests</div><div className="mt-2 text-2xl font-bold text-indigo-300">{(() => { const v = data.metrics?.http_requests_total?.values; return v ? Object.values(v).reduce((a: number, b) => a + Number(b ?? 0), 0) : ''; })()}</div></div>
3488
3488
  </div>
3489
3489
  <div className="glass-card">
3490
3490
  <div className="flex flex-wrap items-center justify-between gap-3">
@@ -3511,8 +3511,8 @@ function Monitoring() {
3511
3511
  <td className="req-td-path" title={r.url ?? r.path}>{r.url ?? r.path}</td>
3512
3512
  <td className={`text-center req-status ${r.status >= 500 ? 'req-status-err' : r.status >= 400 ? 'req-status-warn' : ''}`}>{r.status}</td>
3513
3513
  <td className="text-right req-td-dur">{r.durationMs}ms</td>
3514
- <td className="req-td-ip">{r.ip ?? '—'}</td>
3515
- <td className="req-td-ref" title={r.referer}>{r.referer ?? '—'}</td>
3514
+ <td className="req-td-ip">{r.ip ?? ''}</td>
3515
+ <td className="req-td-ref" title={r.referer}>{r.referer ?? ''}</td>
3516
3516
  </tr>
3517
3517
  ))}
3518
3518
  </tbody>
@@ -3581,7 +3581,7 @@ function Payments({ toggle }: { toggle: ToggleStyle }) {
3581
3581
  <button onClick={() => load()} className="glass-chip-btn"><Icon name="refresh" size={12} /> refresh</button>
3582
3582
  </div>
3583
3583
  </PageHead>
3584
- <PageHero kicker="PAYMENT OPERATIONS" title={<>Money flow, <em>on the ledger.</em></>} desc="Every charge, refund and provider event from your payment stack — one surface to reconcile what went through." glyph="dollar" art="cards" />
3584
+ <PageHero kicker="PAYMENT OPERATIONS" title={<>Money flow, <em>on the ledger.</em></>} desc="Every charge, refund and provider event from your payment stack one surface to reconcile what went through." glyph="dollar" art="cards" />
3585
3585
 
3586
3586
  <div className="workspace-tabs">
3587
3587
  {([
@@ -3614,12 +3614,12 @@ function OrdersTable({ orders }: { orders: PaymentOrder[] }) {
3614
3614
  {orders.map((o, i) => (
3615
3615
  <tr key={`${o.orderId}-${i}`}>
3616
3616
  <td><span className="chip">{o.provider}</span></td>
3617
- <td className="font-mono text-slate-200">{o.orderId || '—'}</td>
3618
- <td className="font-mono text-slate-400">{o.reference || '—'}</td>
3617
+ <td className="font-mono text-slate-200">{o.orderId || ''}</td>
3618
+ <td className="font-mono text-slate-400">{o.reference || ''}</td>
3619
3619
  <td className={`text-center ${statusColor(o.status)}`}>{o.status}</td>
3620
3620
  <td className="text-right font-medium text-slate-200">{o.currency} {o.amount}</td>
3621
- <td className="font-mono text-slate-400">{o.paymentId || '—'}</td>
3622
- <td className="text-slate-400">{o.createdAt ? new Date(o.createdAt).toLocaleString() : '—'}</td>
3621
+ <td className="font-mono text-slate-400">{o.paymentId || ''}</td>
3622
+ <td className="text-slate-400">{o.createdAt ? new Date(o.createdAt).toLocaleString() : ''}</td>
3623
3623
  </tr>
3624
3624
  ))}
3625
3625
  {!orders.length && <tr><td colSpan={7} className="py-8 text-center text-slate-500">No orders recorded yet.</td></tr>}
@@ -3639,11 +3639,11 @@ function TransactionsTable({ transactions }: { transactions: PaymentTransaction[
3639
3639
  <tr key={i}>
3640
3640
  <td><span className="chip">{t.provider}</span></td>
3641
3641
  <td className="font-mono text-slate-200">{t.event}</td>
3642
- <td className={`text-center ${t.verified ? 'text-emerald-400' : 'text-rose-400'}`}>{t.verified ? '✓' : '✗'}</td>
3643
- <td className="font-mono text-slate-400">{t.orderId || '—'}</td>
3644
- <td className="font-mono text-slate-400">{t.paymentId || '—'}</td>
3645
- <td className="text-right text-slate-200">{t.currency ? `${t.currency} ${t.amount ?? ''}` : '—'}</td>
3646
- <td className="text-slate-400">{t.createdAt ? new Date(t.createdAt).toLocaleString() : '—'}</td>
3642
+ <td className={`text-center ${t.verified ? 'text-emerald-400' : 'text-rose-400'}`}>{t.verified ? '' : ''}</td>
3643
+ <td className="font-mono text-slate-400">{t.orderId || ''}</td>
3644
+ <td className="font-mono text-slate-400">{t.paymentId || ''}</td>
3645
+ <td className="text-right text-slate-200">{t.currency ? `${t.currency} ${t.amount ?? ''}` : ''}</td>
3646
+ <td className="text-slate-400">{t.createdAt ? new Date(t.createdAt).toLocaleString() : ''}</td>
3647
3647
  </tr>
3648
3648
  ))}
3649
3649
  {!transactions.length && <tr><td colSpan={7} className="py-8 text-center text-slate-500">No webhook transactions yet.</td></tr>}
@@ -3724,7 +3724,7 @@ function TestConsole({ toggle, status, onRun, onNotice, onCreated }: { toggle: T
3724
3724
  <div className="flex items-center justify-between">
3725
3725
  <h3 className="glass-h3">Provider status</h3>
3726
3726
  <button onClick={async () => { setProbeBusy(true); try { await onRun(); } finally { setProbeBusy(false); } }} className="glass-chip-btn" disabled={probeBusy}>
3727
- {probeBusy ? 'checking…' : <><Icon name="refresh" size={12} /> re-run checks</>}
3727
+ {probeBusy ? 'checking' : <><Icon name="refresh" size={12} /> re-run checks</>}
3728
3728
  </button>
3729
3729
  </div>
3730
3730
  <div className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
@@ -3737,7 +3737,7 @@ function TestConsole({ toggle, status, onRun, onNotice, onCreated }: { toggle: T
3737
3737
  onClick={() => toggleProvider(p)}
3738
3738
  disabled={!!toggleBusy || !!anim}
3739
3739
  className={`ai-toggle tg-${toggle} ${p.enabled ? 'is-on' : ''}`}
3740
- title={p.enabled ? 'Enabled — click to disable' : 'Disabled — click to enable'}
3740
+ title={p.enabled ? 'Enabled click to disable' : 'Disabled click to enable'}
3741
3741
  >
3742
3742
  <span className="ai-toggle-knob" />
3743
3743
  </button>
@@ -3769,12 +3769,12 @@ function TestConsole({ toggle, status, onRun, onNotice, onCreated }: { toggle: T
3769
3769
  <section className="glass-card space-y-3">
3770
3770
  <div>
3771
3771
  <h3 className="glass-h3">Create a test order</h3>
3772
- <p className="mt-1 text-sm text-slate-400">Creates a real order through the selected provider (hosted checkout, no charge) and records it in the orders table — verifies keys, upstream reachability and persistence.</p>
3772
+ <p className="mt-1 text-sm text-slate-400">Creates a real order through the selected provider (hosted checkout, no charge) and records it in the orders table verifies keys, upstream reachability and persistence.</p>
3773
3773
  </div>
3774
3774
  <form onSubmit={testOrder} className="flex flex-wrap items-end gap-3">
3775
3775
  <label className="text-sm text-slate-300">Provider
3776
3776
  <select value={provider} onChange={(e) => setProvider(e.target.value)} required className="glass-input mt-1 block w-44">
3777
- <option value="" disabled className="bg-slate-900">choose…</option>
3777
+ <option value="" disabled className="bg-slate-900">choose…</option>
3778
3778
  {status.map((p) => (
3779
3779
  <option key={p.name} value={p.name} className="bg-slate-900">{p.enabled ? p.name : `${p.name} (disabled)`}</option>
3780
3780
  ))}
@@ -3787,13 +3787,13 @@ function TestConsole({ toggle, status, onRun, onNotice, onCreated }: { toggle: T
3787
3787
  <input value={currency} maxLength={3} onChange={(e) => setCurrency(e.target.value.toUpperCase())} className="glass-input mt-1 block w-20" />
3788
3788
  </label>
3789
3789
  <button type="submit" disabled={busy || !provider} className="glass-btn-primary">
3790
- {busy ? 'Creating…' : 'Create test order'}
3790
+ {busy ? 'Creating' : 'Create test order'}
3791
3791
  </button>
3792
3792
  </form>
3793
3793
  {err && <p className="text-sm text-rose-400">{err}</p>}
3794
3794
  {selected && !selected.enabled && (
3795
3795
  <p className="rounded-lg border border-amber-400/30 bg-amber-400/10 px-3 py-2 text-xs text-amber-200">
3796
- “{selected.name}” is disabled in the config — it will be rejected by the backend. To enable it, set{' '}
3796
+ {selected.name} is disabled in the config it will be rejected by the backend. To enable it, set{' '}
3797
3797
  <code className="font-mono">NEXUS_PAYMENTS_{selected.name.toUpperCase()}_ENABLED=true</code> (plus its keys) in the project <code className="font-mono">.env</code>, or <code className="font-mono">payments.{selected.name}.enabled: true</code> in <code className="font-mono">nexus.config.ts</code>, then restart the backend. A provider also activates automatically once its keys are present.
3798
3798
  </p>
3799
3799
  )}
@@ -3845,7 +3845,7 @@ function PaymentKeysDialog({ provider, value, onChange, onSave, onCancel, busy,
3845
3845
  <h3 id="payment-keys-title" className="mt-1 text-base font-semibold text-slate-100">{provider.name}</h3>
3846
3846
  <code className="text-xs text-slate-500">{provider.name}</code>
3847
3847
  </div>
3848
- <button type="button" onClick={onCancel} disabled={busy} className="glass-chip-btn text-xs" aria-label="Close payment keys dialog">✕</button>
3848
+ <button type="button" onClick={onCancel} disabled={busy} className="glass-chip-btn text-xs" aria-label="Close payment keys dialog">✕</button>
3849
3849
  </div>
3850
3850
 
3851
3851
  <div className="rounded-xl border border-sky-400/20 bg-black/20 px-3 py-2 text-xs text-slate-400">
@@ -3868,7 +3868,7 @@ function PaymentKeysDialog({ provider, value, onChange, onSave, onCancel, busy,
3868
3868
  id={`payment-${f.field}`}
3869
3869
  name={`payment-${f.field}`}
3870
3870
  autoComplete="new-password"
3871
- placeholder={f.hasValue ? `•••••••• (enter new to replace)` : `paste ${f.label.toLowerCase()}`}
3871
+ placeholder={f.hasValue ? `•••••••• (enter new to replace)` : `paste ${f.label.toLowerCase()}`}
3872
3872
  value={value[f.field] ?? ''}
3873
3873
  onChange={(event) => onChange({ ...value, [f.field]: event.target.value })}
3874
3874
  onKeyDown={(event) => {
@@ -3882,7 +3882,7 @@ function PaymentKeysDialog({ provider, value, onChange, onSave, onCancel, busy,
3882
3882
  <div className="flex justify-end gap-2 border-t border-sky-400/20 pt-3">
3883
3883
  <button type="button" onClick={onCancel} disabled={busy} className="glass-chip-btn">Cancel</button>
3884
3884
  <button type="button" onClick={onSave} disabled={busy || fields.length === 0} className="glass-chip-btn-primary">
3885
- {busy ? 'Saving…' : 'Save keys'}
3885
+ {busy ? 'Saving' : 'Save keys'}
3886
3886
  </button>
3887
3887
  </div>
3888
3888
  </div>
@@ -3940,7 +3940,7 @@ function Databases() {
3940
3940
  <PageHead title="Databases">
3941
3941
  <button onClick={refresh} className="glass-chip-btn"><Icon name="refresh" size={12} /> refresh</button>
3942
3942
  </PageHead>
3943
- <PageHero kicker="DATA FOUNDATION" title={<>Your data, <em>organized.</em></>} desc="Databases and collections backing your project — inspect, create and keep the layer that stores everything sane." glyph="database" art="cubes" />
3943
+ <PageHero kicker="DATA FOUNDATION" title={<>Your data, <em>organized.</em></>} desc="Databases and collections backing your project inspect, create and keep the layer that stores everything sane." glyph="database" art="cubes" />
3944
3944
 
3945
3945
  <div className="glass-card flex gap-2">
3946
3946
  <input className="glass-input max-w-xs" placeholder="new database name" value={newDb} onChange={(e) => setNewDb(e.target.value)} />
@@ -3954,8 +3954,8 @@ function Databases() {
3954
3954
  <button onClick={() => setExpanded(expanded === db.name ? null : db.name)} className="cursor-pointer font-mono text-base font-semibold text-slate-100 transition-colors hover:text-indigo-300">
3955
3955
  <span className={`mr-1 inline-block transition-transform ${expanded === db.name ? 'rotate-90' : ''}`}><Icon name="chevronRight" size={14} /></span>{db.name}
3956
3956
  </button>
3957
- <span className="text-xs text-slate-500">{(db.sizeOnDisk / 1024).toFixed(1)} KB · {db.collections.length} collections</span>
3958
- <button onClick={() => setConfirm({ title: `Drop database “${db.name}”?`, message: <>This will permanently delete the database <strong>{db.name}</strong> and all of its collections. This cannot be undone.</>, action: async () => { if (await withOk(() => deleteDatabase(db.name))) await refresh(); } })} className="glass-chip-btn-danger ml-auto">drop</button>
3957
+ <span className="text-xs text-slate-500">{(db.sizeOnDisk / 1024).toFixed(1)} KB · {db.collections.length} collections</span>
3958
+ <button onClick={() => setConfirm({ title: `Drop database “${db.name}”?`, message: <>This will permanently delete the database <strong>{db.name}</strong> and all of its collections. This cannot be undone.</>, action: async () => { if (await withOk(() => deleteDatabase(db.name))) await refresh(); } })} className="glass-chip-btn-danger ml-auto">drop</button>
3959
3959
  </div>
3960
3960
 
3961
3961
  {expanded === db.name && (
@@ -3970,9 +3970,9 @@ function Databases() {
3970
3970
  <span className="font-mono text-slate-200">{c.name}</span>
3971
3971
  <span className="text-xs text-slate-500">{c.count} docs</span>
3972
3972
  <button onClick={() => toggleDocs(db.name, c.name)} className="glass-chip-btn ml-auto">docs</button>
3973
- <input className="glass-input w-36 !py-1 text-xs" placeholder="rename to…" value={renames[`${db.name}/${c.name}`] ?? ''} onChange={(e) => setRenames({ ...renames, [`${db.name}/${c.name}`]: e.target.value })} />
3973
+ <input className="glass-input w-36 !py-1 text-xs" placeholder="rename to" value={renames[`${db.name}/${c.name}`] ?? ''} onChange={(e) => setRenames({ ...renames, [`${db.name}/${c.name}`]: e.target.value })} />
3974
3974
  <button onClick={() => doRename(db.name, c.name)} className="glass-chip-btn">rename</button>
3975
- <button onClick={() => setConfirm({ title: `Drop collection “${c.name}”?`, message: <>This will permanently delete the collection <strong>{db.name}/{c.name}</strong> and its {c.count} document{c.count === 1 ? '' : 's'}. This cannot be undone.</>, action: async () => { if (await withOk(() => dropCollection(db.name, c.name))) await refresh(); } })} className="glass-chip-btn-danger">drop</button>
3975
+ <button onClick={() => setConfirm({ title: `Drop collection “${c.name}”?`, message: <>This will permanently delete the collection <strong>{db.name}/{c.name}</strong> and its {c.count} document{c.count === 1 ? '' : 's'}. This cannot be undone.</>, action: async () => { if (await withOk(() => dropCollection(db.name, c.name))) await refresh(); } })} className="glass-chip-btn-danger">drop</button>
3976
3976
  </div>
3977
3977
  ))}
3978
3978
  </div>
@@ -3984,8 +3984,8 @@ function Databases() {
3984
3984
  const [db, coll] = key.split('/') as [string, string];
3985
3985
  return expanded === db ? (
3986
3986
  <div key={key} className="glass-code max-h-64 overflow-auto p-3 text-xs">
3987
- <span className="text-slate-400">{db}/{coll} — {val.count} docs (showing {val.docs.length}):</span>
3988
- <pre className="mt-1 font-mono">{JSON.stringify(val.docs[0] ?? {}, null, 2)}{val.docs.length > 1 ? `\n… and ${val.docs.length - 1} more` : ''}</pre>
3987
+ <span className="text-slate-400">{db}/{coll} {val.count} docs (showing {val.docs.length}):</span>
3988
+ <pre className="mt-1 font-mono">{JSON.stringify(val.docs[0] ?? {}, null, 2)}{val.docs.length > 1 ? `\n and ${val.docs.length - 1} more` : ''}</pre>
3989
3989
  </div>
3990
3990
  ) : null;
3991
3991
  })}
@@ -4031,8 +4031,8 @@ function Schema() {
4031
4031
  const g = await generateSchema(prompt);
4032
4032
  setResult(g);
4033
4033
  setEdited(JSON.stringify(g.jsonSchema, null, 2));
4034
- setMsg(`Schema generated (${g.model}) — review and create.`);
4035
- } catch (e: any) { setMsg(`Generation failed: ${e?.message ?? e} — check the AI status below (is the AI server running on :8000?).`); }
4034
+ setMsg(`Schema generated (${g.model}) review and create.`);
4035
+ } catch (e: any) { setMsg(`Generation failed: ${e?.message ?? e} check the AI status below (is the AI server running on :8000?).`); }
4036
4036
  finally { setBusy(false); }
4037
4037
  };
4038
4038
 
@@ -4052,7 +4052,7 @@ function Schema() {
4052
4052
  <div className="space-y-6">
4053
4053
  <PageHead title="AI Schema Creation" />
4054
4054
  <PageHero kicker="AI WORKSPACE" title={<>Schemas, <em>born from language.</em></>} desc="Describe the data you need in plain English and let the model return a validated MongoDB schema you can review before it's live." glyph="sparkles" art="bolt" />
4055
- <p className="text-sm text-slate-400">Describe the data in plain English — the AI returns a MongoDB <code className="rounded bg-white/10 px-1 font-mono text-xs">$jsonSchema</code> validator plus a field list, which you review and create as a validator-protected collection.</p>
4055
+ <p className="text-sm text-slate-400">Describe the data in plain English the AI returns a MongoDB <code className="rounded bg-white/10 px-1 font-mono text-xs">$jsonSchema</code> validator plus a field list, which you review and create as a validator-protected collection.</p>
4056
4056
 
4057
4057
  <section className="glass-card space-y-3">
4058
4058
  <div className="flex items-center justify-between">
@@ -4075,7 +4075,7 @@ function Schema() {
4075
4075
  ))}
4076
4076
  </>
4077
4077
  ) : (
4078
- <p className="col-span-3 text-xs text-slate-500">No status yet{aiErr ? '' : ' — run a check'}.</p>
4078
+ <p className="col-span-3 text-xs text-slate-500">No status yet{aiErr ? '' : ' run a check'}.</p>
4079
4079
  )}
4080
4080
  </div>
4081
4081
  {aiStatus && <p className="text-xs text-slate-500">"auto" currently resolves to <span className="font-mono text-slate-300">{aiStatus.autoResolvesTo}</span> on the AI server.</p>}
@@ -4090,18 +4090,18 @@ function Schema() {
4090
4090
  <div className="flex flex-wrap items-center gap-2">
4091
4091
  <label className="mr-1.5 text-sm text-slate-400">Database</label>
4092
4092
  <select className="glass-input w-48 !py-1.5 text-sm" value={db} onChange={(e) => setDb(e.target.value)}>
4093
- <option value="" className="bg-slate-900">{databases.length ? 'pick a database…' : 'no databases yet'}</option>
4093
+ <option value="" className="bg-slate-900">{databases.length ? 'pick a database' : 'no databases yet'}</option>
4094
4094
  {databases.map((d) => <option key={d.name} value={d.name} className="bg-slate-900">{d.name}</option>)}
4095
4095
  </select>
4096
4096
  <span className="text-xs text-slate-500">Provider &amp; model auto-selected on the AI server.</span>
4097
- <button onClick={generate} disabled={busy} className="glass-btn-primary ml-auto">{busy ? 'Generating…' : 'Generate with AI'}</button>
4097
+ <button onClick={generate} disabled={busy} className="glass-btn-primary ml-auto">{busy ? 'Generating' : 'Generate with AI'}</button>
4098
4098
  </div>
4099
4099
  </div>
4100
4100
 
4101
4101
  {result && (
4102
4102
  <div className="space-y-3">
4103
4103
  <div className="glass-card space-y-3">
4104
- <h3 className="glass-h3">Generated schema — <span className="font-mono text-indigo-300">{result.collection}</span></h3>
4104
+ <h3 className="glass-h3">Generated schema <span className="font-mono text-indigo-300">{result.collection}</span></h3>
4105
4105
  <div className="max-h-48 overflow-auto rounded-xl border border-white/10">
4106
4106
  <table className="glass-table">
4107
4107
  <thead><tr><th>Field</th><th>Type</th><th className="text-center">Required</th><th className="text-center">Unique</th><th>Description</th></tr></thead>
@@ -4110,8 +4110,8 @@ function Schema() {
4110
4110
  <tr key={f.name}>
4111
4111
  <td className="font-mono text-slate-200">{f.name}</td>
4112
4112
  <td className="text-slate-300">{f.type}</td>
4113
- <td className="text-center text-emerald-400">{f.required ? '✓' : ''}</td>
4114
- <td className="text-center text-indigo-300">{f.unique ? '✓' : ''}</td>
4113
+ <td className="text-center text-emerald-400">{f.required ? '' : ''}</td>
4114
+ <td className="text-center text-indigo-300">{f.unique ? '' : ''}</td>
4115
4115
  <td className="text-slate-400">{f.description ?? ''}</td>
4116
4116
  </tr>
4117
4117
  ))}