feat: add Dioxus and GPUI UI prototypes for framework comparison

Dioxus (ui-dioxus/): 2,169 lines, WebView mode (same wry as Tauri),
  Catppuccin theme, 12 components, agor-core integration, compiles clean.
  Evolution path — keeps xterm.js, gradual migration from Tauri.

GPUI (ui-gpui/): 2,490 lines, GPU-accelerated rendering, alacritty_terminal
  for native terminal, 17 files, Catppuccin palette, demo data.
  Revolution path — pure Rust UI, 120fps target, no WebView.

Both are standalone (not in workspace), share agor-core backend.
Created for side-by-side comparison to inform framework decision.
This commit is contained in:
Hibryda 2026-03-19 06:05:58 +01:00
parent 90c7315336
commit f3d2ca78ba
34 changed files with 17467 additions and 0 deletions

View file

@ -0,0 +1,85 @@
/// CommandPalette — Ctrl+K overlay with search and command list.
///
/// Mirrors the Svelte app's CommandPalette.svelte: Spotlight-style overlay
/// with search input and filtered command list.
use dioxus::prelude::*;
use crate::state::{palette_commands, PaletteCommand};
#[component]
pub fn CommandPalette(
visible: Signal<bool>,
) -> Element {
let mut search = use_signal(|| String::new());
let commands = palette_commands();
// Filter commands by search text
let search_text = search.read().to_lowercase();
let filtered: Vec<&PaletteCommand> = if search_text.is_empty() {
commands.iter().collect()
} else {
commands
.iter()
.filter(|c| c.label.to_lowercase().contains(&search_text))
.collect()
};
let close = move |_| {
visible.set(false);
search.set(String::new());
};
let on_key = move |e: KeyboardEvent| {
if e.key() == Key::Escape {
visible.set(false);
search.set(String::new());
}
};
if !*visible.read() {
return rsx! {};
}
rsx! {
div {
class: "palette-overlay",
onclick: close,
onkeydown: on_key,
div {
class: "palette-box",
// Stop click propagation so clicking inside doesn't close
onclick: move |e| e.stop_propagation(),
input {
class: "palette-input",
r#type: "text",
placeholder: "Type a command...",
value: "{search}",
oninput: move |e| search.set(e.value()),
autofocus: true,
}
div { class: "palette-results",
for cmd in filtered.iter() {
div { class: "palette-item",
span { class: "palette-item-icon", "{cmd.icon}" }
span { class: "palette-item-label", "{cmd.label}" }
if let Some(ref shortcut) = cmd.shortcut {
span { class: "palette-item-shortcut", "{shortcut}" }
}
}
}
if filtered.is_empty() {
div {
style: "padding: 1rem; text-align: center; color: var(--ctp-overlay0); font-size: 0.8125rem;",
"No matching commands"
}
}
}
}
}
}
}