Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
241 lines
7.2 KiB
TypeScript
241 lines
7.2 KiB
TypeScript
import { StrictMode, Suspense, lazy } from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import '@fontsource/mona-sans/400.css';
|
|
import '@fontsource/mona-sans/500.css';
|
|
import '@fontsource/mona-sans/600.css';
|
|
import '@fontsource/mona-sans/700.css';
|
|
import '@fontsource/space-grotesk/400.css';
|
|
import '@fontsource/space-grotesk/500.css';
|
|
import '@fontsource/space-grotesk/600.css';
|
|
import '@fontsource/space-grotesk/700.css';
|
|
import '@fontsource/jetbrains-mono/400.css';
|
|
import '@fontsource/jetbrains-mono/500.css';
|
|
import '@fontsource/jetbrains-mono/600.css';
|
|
import App from './App';
|
|
import { AppLockGate } from './components/AppLockGate';
|
|
|
|
const LazySettingsPage = lazy(() => import('./components/SettingsPage'));
|
|
const LazyTrayPanel = lazy(() => import('./components/TrayPanel'));
|
|
const LazyTerminalPopupPage = lazy(() => import('./components/TerminalPopupPage'));
|
|
|
|
function SettingsWindowFallback() {
|
|
return (
|
|
<div
|
|
style={{
|
|
height: '100vh',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
background: 'hsl(var(--background))',
|
|
color: 'hsl(var(--foreground))',
|
|
fontFamily: 'var(--font-sans)',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
flexShrink: 0,
|
|
borderBottom: '1px solid hsl(var(--border))',
|
|
padding: '20px 16px 12px',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
width: 96,
|
|
height: 22,
|
|
borderRadius: 6,
|
|
background: 'hsl(var(--muted) / 0.5)',
|
|
}}
|
|
/>
|
|
<div
|
|
style={{
|
|
marginTop: 8,
|
|
width: 150,
|
|
height: 14,
|
|
borderRadius: 5,
|
|
background: 'hsl(var(--muted) / 0.38)',
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', flex: 1, minHeight: 0 }}>
|
|
<div
|
|
style={{
|
|
width: 224,
|
|
flexShrink: 0,
|
|
borderRight: '1px solid hsl(var(--border))',
|
|
padding: 12,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 8,
|
|
}}
|
|
>
|
|
{Array.from({ length: 7 }).map((_, index) => (
|
|
<div
|
|
key={index}
|
|
style={{
|
|
height: 36,
|
|
borderRadius: 8,
|
|
background: index === 0 ? 'hsl(var(--card))' : 'hsl(var(--muted) / 0.45)',
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div style={{ flex: 1, padding: 20, display: 'flex', flexDirection: 'column', gap: 14 }}>
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
|
<div
|
|
key={index}
|
|
style={{
|
|
height: index === 0 ? 54 : 76,
|
|
borderRadius: 12,
|
|
background: 'hsl(var(--muted) / 0.38)',
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TerminalPopupWindowFallback() {
|
|
return (
|
|
<div
|
|
style={{
|
|
width: '100vw',
|
|
height: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
background: '#0b1015',
|
|
color: '#d7e0ea',
|
|
}}
|
|
>
|
|
<svg width="28" height="28" viewBox="0 0 28 28" aria-label="Loading" style={{ opacity: 0.8 }}>
|
|
<circle cx="14" cy="14" r="11" fill="none" stroke="currentColor" strokeWidth="2" opacity="0.18" />
|
|
<path d="M25 14a11 11 0 0 0-11-11" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="2">
|
|
<animateTransform
|
|
attributeName="transform"
|
|
dur="0.75s"
|
|
from="0 14 14"
|
|
repeatCount="indefinite"
|
|
to="360 14 14"
|
|
type="rotate"
|
|
/>
|
|
</path>
|
|
</svg>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const rootElement = document.getElementById('root');
|
|
if (!rootElement) {
|
|
throw new Error("Could not find root element to mount to");
|
|
}
|
|
|
|
// Simple hash-based routing for separate windows
|
|
const getRoute = () => {
|
|
const hash = window.location.hash;
|
|
if (hash === '#/settings' || hash.startsWith('#/settings')) {
|
|
return 'settings';
|
|
}
|
|
if (hash === '#/tray' || hash.startsWith('#/tray')) {
|
|
return 'tray';
|
|
}
|
|
if (hash === '#/terminal-popup' || hash.startsWith('#/terminal-popup')) {
|
|
return 'terminal-popup';
|
|
}
|
|
if (hash === '#/session-window' || hash.startsWith('#/session-window')) {
|
|
return 'main';
|
|
}
|
|
return 'main';
|
|
};
|
|
|
|
const root = ReactDOM.createRoot(rootElement);
|
|
|
|
const syncTrayWindowClass = (route: string) => {
|
|
const rootEl = document.documentElement;
|
|
if (route === 'tray') {
|
|
rootEl.classList.add('tray-window');
|
|
document.getElementById('splash')?.remove();
|
|
} else {
|
|
rootEl.classList.remove('tray-window');
|
|
}
|
|
};
|
|
|
|
const renderApp = () => {
|
|
const route = getRoute();
|
|
const isPeerSessionWindow = window.location.hash.startsWith('#/session-window');
|
|
// Peer session windows must not drive the main window's settings IPC sync
|
|
// and must not re-apply OS-level system settings effects (tray, global
|
|
// shortcuts, …) — they follow the main window through the chrome stores.
|
|
const settingsOptions = isPeerSessionWindow
|
|
? { enableSettingsSync: false, enableSystemEffects: false }
|
|
: undefined;
|
|
|
|
syncTrayWindowClass(route);
|
|
if (route === 'settings') {
|
|
root.render(
|
|
<StrictMode>
|
|
<AppLockGate settingsOptions={settingsOptions}>
|
|
{({ settings, appLock }) => (
|
|
<Suspense fallback={<SettingsWindowFallback />}>
|
|
<LazySettingsPage settings={settings} appLock={appLock} />
|
|
</Suspense>
|
|
)}
|
|
</AppLockGate>
|
|
</StrictMode>
|
|
);
|
|
} else if (route === 'tray') {
|
|
root.render(
|
|
<StrictMode>
|
|
<AppLockGate settingsOptions={settingsOptions}>
|
|
{({ settings }) => (
|
|
<Suspense fallback={<div style={{ minHeight: 48, background: 'hsl(var(--background))' }} />}>
|
|
<LazyTrayPanel settings={settings} />
|
|
</Suspense>
|
|
)}
|
|
</AppLockGate>
|
|
</StrictMode>
|
|
);
|
|
} else if (route === 'terminal-popup') {
|
|
// forceRenderChildren: main sends terminalPopupConfig immediately after
|
|
// loadURL; the page must mount (and register onTerminalPopupConfig) without
|
|
// waiting for async app-lock init. notifyRendererReady stays false — this
|
|
// route is not on the deep-link readiness path.
|
|
root.render(
|
|
<StrictMode>
|
|
<AppLockGate
|
|
notifyRendererReady={false}
|
|
forceRenderChildren
|
|
settingsOptions={settingsOptions}
|
|
>
|
|
{({ settings, appLock }) => (
|
|
<Suspense fallback={<TerminalPopupWindowFallback />}>
|
|
{/* forceRenderChildren mounts the page so config IPC registers while
|
|
locked; defer starting the terminal until unlock (Codex P2). */}
|
|
<LazyTerminalPopupPage
|
|
settings={settings}
|
|
allowTerminalStart={appLock.initialized && !appLock.locked}
|
|
/>
|
|
</Suspense>
|
|
)}
|
|
</AppLockGate>
|
|
</StrictMode>
|
|
);
|
|
} else {
|
|
root.render(
|
|
<StrictMode>
|
|
<AppLockGate settingsOptions={settingsOptions}>
|
|
{({ settings, appLock }) => <App settings={settings} appLock={appLock} />}
|
|
</AppLockGate>
|
|
</StrictMode>
|
|
);
|
|
}
|
|
};
|
|
|
|
// Initial render
|
|
renderApp();
|
|
|
|
// Listen for hash changes
|
|
window.addEventListener('hashchange', renderApp);
|