Rename binary to agent-orchestrator, add splash screen, fix white flash

- Rename Cargo package from bterminal to agent-orchestrator so WM_CLASS
  matches desktop entry and taskbar groups correctly
- Update lib name (agent_orchestrator_lib) and telemetry service name
- Add Pandora's Box splash screen with progress steps during startup
- Prevent white window flash with inline CSS and Tauri backgroundColor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DexterFromLab 2026-03-12 12:09:18 +01:00
parent 79e13649a1
commit adde8462ef
9 changed files with 213 additions and 49 deletions

62
v2/Cargo.lock generated
View file

@ -8,6 +8,37 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "agent-orchestrator"
version = "0.1.0"
dependencies = [
"bterminal-core",
"dirs 5.0.1",
"futures-util",
"keyring",
"log",
"notify",
"notify-rust",
"opentelemetry",
"opentelemetry-otlp",
"opentelemetry_sdk",
"rfd",
"rusqlite",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-updater",
"tempfile",
"tokio",
"tokio-tungstenite",
"tracing",
"tracing-opentelemetry",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "ahash"
version = "0.8.12"
@ -363,37 +394,6 @@ dependencies = [
"alloc-stdlib",
]
[[package]]
name = "bterminal"
version = "0.1.0"
dependencies = [
"bterminal-core",
"dirs 5.0.1",
"futures-util",
"keyring",
"log",
"notify",
"notify-rust",
"opentelemetry",
"opentelemetry-otlp",
"opentelemetry_sdk",
"rfd",
"rusqlite",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-updater",
"tempfile",
"tokio",
"tokio-tungstenite",
"tracing",
"tracing-opentelemetry",
"tracing-subscriber",
"uuid",
]
[[package]]
name = "bterminal-core"
version = "0.1.0"

View file

@ -4,6 +4,10 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Agent Orchestrator</title>
<style>
html, body { margin: 0; padding: 0; background: #1e1e2e; height: 100%; overflow: hidden; }
#app { height: 100%; }
</style>
</head>
<body>
<div id="app"></div>

View file

@ -1,5 +1,5 @@
[package]
name = "bterminal"
name = "agent-orchestrator"
version = "0.1.0"
description = "Multi-session Claude agent dashboard"
authors = ["DexterFromLab"]
@ -10,7 +10,7 @@ rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "bterminal_lib"
name = "agent_orchestrator_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]

View file

@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
bterminal_lib::run();
agent_orchestrator_lib::run();
}

View file

@ -28,7 +28,7 @@ impl Drop for TelemetryGuard {
/// Call once at app startup, before any tracing macros fire.
pub fn init() -> TelemetryGuard {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("bterminal=info,bterminal_lib=info,bterminal_core=info"));
.unwrap_or_else(|_| EnvFilter::new("agent_orchestrator=info,agent_orchestrator_lib=info,bterminal_core=info"));
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(true)
@ -42,7 +42,7 @@ pub fn init() -> TelemetryGuard {
match build_otlp_provider(&endpoint) {
Ok(provider) => {
let otel_layer = tracing_opentelemetry::layer()
.with_tracer(provider.tracer("bterminal"));
.with_tracer(provider.tracer("agent-orchestrator"));
tracing_subscriber::registry()
.with(filter)
@ -90,7 +90,7 @@ fn build_otlp_provider(endpoint: &str) -> Result<SdkTracerProvider, Box<dyn std:
let resource = Resource::builder()
.with_attributes([
KeyValue::new("service.name", "bterminal"),
KeyValue::new("service.name", "agent-orchestrator"),
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
])
.build();

View file

@ -17,7 +17,8 @@
"height": 1080,
"resizable": true,
"fullscreen": false,
"decorations": true
"decorations": true,
"backgroundColor": [30, 30, 46, 255]
}
],
"security": {

View file

@ -31,6 +31,7 @@
// Shared
import StatusBar from './lib/components/StatusBar/StatusBar.svelte';
import ToastContainer from './lib/components/Notifications/ToastContainer.svelte';
import SplashScreen from './lib/components/SplashScreen.svelte';
// Detached mode (preserved from v2)
import TerminalPane from './lib/components/Terminal/TerminalPane.svelte';
@ -44,6 +45,19 @@
let drawerOpen = $state(false);
let loaded = $state(false);
// Splash screen loading steps
let splashSteps = $state([
{ label: 'Initializing theme...', done: false },
{ label: 'Registering providers...', done: false },
{ label: 'Starting agent dispatcher...', done: false },
{ label: 'Connecting sidecar...', done: false },
{ label: 'Loading workspace...', done: false },
]);
function markStep(idx: number) {
splashSteps[idx] = { ...splashSteps[idx], done: true };
}
let activeTab = $derived(getActiveTab());
let panelContentEl: HTMLElement | undefined = $state();
let panelWidth = $state<string | undefined>(undefined);
@ -77,26 +91,42 @@
}
onMount(() => {
// Step 0: Theme
initTheme();
getSetting('project_max_aspect').then(v => {
if (v) document.documentElement.style.setProperty('--project-max-aspect', v);
});
markStep(0);
// Step 1: Providers
registerProvider(CLAUDE_PROVIDER);
registerProvider(CODEX_PROVIDER);
registerProvider(OLLAMA_PROVIDER);
const memora = new MemoraAdapter();
registerMemoryAdapter(memora);
memora.checkAvailability();
markStep(1);
// Step 2: Agent dispatcher
startAgentDispatcher();
startHealthTick();
markStep(2);
// Disable wake scheduler in test mode to prevent timer interference
invoke<boolean>('is_test_mode').then(isTest => {
if (isTest) disableWakeScheduler();
});
// Step 3: Sidecar (small delay to let sidecar report ready)
setTimeout(() => markStep(3), 300);
if (!detached) {
loadWorkspace().then(() => { loaded = true; });
// Step 4: Workspace
loadWorkspace().then(() => {
markStep(4);
// Brief pause to show completed state before transition
setTimeout(() => { loaded = true; }, 400);
});
}
/** Check if event target is an editable element (input, textarea, contenteditable) */
@ -299,7 +329,7 @@
<CommandPalette open={paletteOpen} onclose={() => paletteOpen = false} />
<SearchOverlay open={searchOpen} onclose={() => searchOpen = false} />
{:else}
<div class="loading">Loading workspace...</div>
<SplashScreen steps={splashSteps} />
{/if}
<ToastContainer />
@ -380,13 +410,4 @@
flex-direction: column;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: var(--ctp-overlay0);
font-size: 0.9rem;
background: var(--ctp-base);
}
</style>

BIN
v2/src/assets/splash.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View file

@ -0,0 +1,138 @@
<script lang="ts">
import splashImg from '../../assets/splash.jpg';
interface Props {
steps: { label: string; done: boolean }[];
version?: string;
}
let { steps, version = 'v3' }: Props = $props();
let doneCount = $derived(steps.filter(s => s.done).length);
let progress = $derived(steps.length > 0 ? doneCount / steps.length : 0);
let currentStep = $derived(steps.find(s => !s.done)?.label ?? 'Ready');
</script>
<div class="splash">
<img src={splashImg} alt="" class="splash-bg" />
<div class="splash-overlay"></div>
<div class="splash-content">
<div class="splash-title">
<h1>Agent Orchestrator</h1>
<span class="splash-version">{version}</span>
<span class="splash-codename">Pandora's Box</span>
</div>
<div class="splash-progress">
<div class="progress-bar">
<div class="progress-fill" style:width="{progress * 100}%"></div>
</div>
<div class="progress-label">{currentStep}</div>
</div>
</div>
</div>
<style>
.splash {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: #1e1e2e;
z-index: 9999;
}
.splash-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.4;
filter: blur(2px);
}
.splash-overlay {
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(30, 30, 46, 0.3) 0%,
rgba(30, 30, 46, 0.6) 50%,
rgba(30, 30, 46, 0.95) 100%
);
}
.splash-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
width: min(480px, 90vw);
}
.splash-title {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
.splash-title h1 {
font-size: 1.8rem;
font-weight: 700;
color: #cdd6f4;
margin: 0;
letter-spacing: 0.02em;
text-shadow: 0 2px 12px rgba(203, 166, 247, 0.3);
}
.splash-version {
font-size: 0.75rem;
color: #a6adc8;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.splash-codename {
font-size: 0.7rem;
color: #cba6f7;
font-style: italic;
opacity: 0.8;
}
.splash-progress {
width: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.progress-bar {
width: 100%;
height: 3px;
background: rgba(108, 112, 134, 0.3);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #cba6f7, #89b4fa);
border-radius: 2px;
transition: width 0.3s ease;
}
.progress-label {
font-size: 0.7rem;
color: #6c7086;
text-align: center;
min-height: 1em;
}
</style>