This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
name: test
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
core:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo test -p rabby-core
|
||||
@@ -0,0 +1,4 @@
|
||||
/target/
|
||||
/src-tauri/target/
|
||||
.DS_Store
|
||||
*.log
|
||||
Generated
+4394
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
[workspace]
|
||||
members = ["rabby-core", "src-tauri"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://gitea.thetomyou.com/thetomyou/rabby"
|
||||
@@ -0,0 +1,37 @@
|
||||
# Rabby
|
||||
|
||||
Rabby is a Rust/Tauri terminal workspace inspired by [Tabby](https://github.com/Eugeny/tabby), cloned locally at `../tabby-source` for feature reference.
|
||||
|
||||
## Goal
|
||||
|
||||
Keep the same broad product shape as Tabby—terminal tabs, nested split panes, local/SSH/Telnet/Serial profiles, themes, shortcuts, quake-mode UX, session restore, and transfer/security surfaces—while replacing the Electron/Angular shell with a smaller Rust core plus Tauri desktop shell.
|
||||
|
||||
## Current implementation
|
||||
|
||||
- `rabby-core`: dependency-light Rust domain model for profiles, tabs, split panes, session restore primitives, and feature coverage tests.
|
||||
- `src-tauri`: Tauri v2 desktop backend exposing the Rust feature matrix to the UI.
|
||||
- `ui`: static responsive UI mock/prototype matching Tabby-style terminal UX without a JavaScript build chain.
|
||||
- `docs/rabby-screenshot.svg`: screenshot artifact of the UI/UX direction.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cargo test -p rabby-core
|
||||
```
|
||||
|
||||
The core crate is intentionally testable without launching the desktop shell.
|
||||
|
||||
## Tabby feature mapping
|
||||
|
||||
| Tabby feature | Rabby implementation direction |
|
||||
| --- | --- |
|
||||
| Terminal emulator | Rust core models terminal sessions; Tauri UI presents panes/tabs. |
|
||||
| SSH/Telnet/Serial | Typed connection profiles with validation and command boundaries. |
|
||||
| Split panes/tabs | Recursive pane tree with tested split bounds and leaf counting. |
|
||||
| Theming/shortcuts | Static UI theme now; config surface reserved in core. |
|
||||
| Session restore | Workspace struct stores profiles, tabs, active tab, and quake mode. |
|
||||
| Smaller footprint | No Electron/Angular runtime in this scaffold; static assets + Tauri. |
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
@@ -0,0 +1,54 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1240" height="760" viewBox="0 0 1240 760">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" x2="1"><stop stop-color="#5d5fef"/><stop offset="1" stop-color="#16c5d9"/></linearGradient>
|
||||
<filter id="shadow"><feDropShadow dx="0" dy="24" stdDeviation="30" flood-color="#000" flood-opacity=".55"/></filter>
|
||||
</defs>
|
||||
<rect width="1240" height="760" fill="#090d18"/>
|
||||
<circle cx="900" cy="80" r="280" fill="#203d7a" opacity=".35"/>
|
||||
<rect x="0" y="0" width="290" height="760" fill="#0d1324" stroke="#263149"/>
|
||||
<rect x="22" y="24" width="42" height="42" rx="12" fill="url(#g)"/>
|
||||
<text x="78" y="43" fill="#fff" font-family="Inter,Arial" font-size="18" font-weight="700">Rabby</text>
|
||||
<text x="78" y="61" fill="#8ea0c5" font-family="Inter,Arial" font-size="12">Rust + Tauri terminal</text>
|
||||
<rect x="22" y="92" width="246" height="42" rx="10" fill="url(#g)"/>
|
||||
<text x="92" y="119" fill="#fff" font-family="Inter,Arial" font-size="14" font-weight="700">+ New tab</text>
|
||||
<rect x="22" y="160" width="246" height="42" rx="10" fill="#1d2a49"/>
|
||||
<text x="38" y="186" fill="#fff" font-family="Inter,Arial" font-size="14">⌘ Local Shell</text>
|
||||
<text x="38" y="232" fill="#aebfe4" font-family="Inter,Arial" font-size="14">🔐 SSH: production</text>
|
||||
<text x="38" y="278" fill="#aebfe4" font-family="Inter,Arial" font-size="14">☎ Telnet lab</text>
|
||||
<text x="38" y="324" fill="#aebfe4" font-family="Inter,Arial" font-size="14">🔌 Serial /dev/ttyUSB0</text>
|
||||
<text x="22" y="390" fill="#6f82aa" font-family="Inter,Arial" font-size="12" font-weight="700" letter-spacing="2">FEATURES</text>
|
||||
<text x="38" y="424" fill="#aebfe4" font-family="Inter,Arial" font-size="14">• Terminal: tabs, split panes, Unicode</text>
|
||||
<text x="38" y="454" fill="#aebfe4" font-family="Inter,Arial" font-size="14">• SSH / Telnet / Serial profiles</text>
|
||||
<text x="38" y="484" fill="#aebfe4" font-family="Inter,Arial" font-size="14">• Command palette + quake mode</text>
|
||||
<text x="38" y="514" fill="#aebfe4" font-family="Inter,Arial" font-size="14">• Rust core + Tauri shell</text>
|
||||
<rect x="290" y="0" width="950" height="62" fill="#0b1020" stroke="#263149"/>
|
||||
<rect x="308" y="16" width="136" height="46" rx="12" fill="#18233d"/>
|
||||
<text x="326" y="44" fill="#fff" font-family="Inter,Arial" font-size="14">Local Shell ●</text>
|
||||
<rect x="452" y="16" width="112" height="46" rx="12" fill="#10182d"/>
|
||||
<text x="470" y="44" fill="#aebfe4" font-family="Inter,Arial" font-size="14">Prod SSH</text>
|
||||
<rect x="950" y="16" width="140" height="34" rx="10" fill="#141d33" stroke="#34425f"/>
|
||||
<text x="968" y="38" fill="#e8f0ff" font-family="Inter,Arial" font-size="13">Command palette</text>
|
||||
<rect x="1104" y="16" width="108" height="34" rx="10" fill="#141d33" stroke="#34425f"/>
|
||||
<text x="1122" y="38" fill="#e8f0ff" font-family="Inter,Arial" font-size="13">Quake mode</text>
|
||||
<rect x="308" y="80" width="606" height="640" rx="18" fill="#0c1222" stroke="#273753" filter="url(#shadow)"/>
|
||||
<rect x="308" y="80" width="606" height="42" rx="18" fill="#111a30"/>
|
||||
<text x="326" y="106" fill="#8fa4d0" font-family="Inter,Arial" font-size="13">bash — rabby-core tests</text>
|
||||
<text x="330" y="160" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">$ cargo test -p rabby-core</text>
|
||||
<text x="330" y="202" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">running 6 tests</text>
|
||||
<text x="330" y="238" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">test slug_normalizes_profile_ids ... ok</text>
|
||||
<text x="330" y="274" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">test profile_validation_rejects_empty_command ... ok</text>
|
||||
<text x="330" y="310" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">test split_panes_count_nested_leaves ... ok</text>
|
||||
<text x="330" y="346" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">test split_ratio_has_sane_bounds ... ok</text>
|
||||
<text x="330" y="382" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">test workspace_adds_tabs_for_known_profiles ... ok</text>
|
||||
<text x="330" y="418" fill="#8df7c8" font-family="Consolas,monospace" font-size="16">test feature_matrix_covers_tabby_readme_capabilities ... ok</text>
|
||||
<text x="330" y="480" fill="#dce7ff" font-family="Consolas,monospace" font-size="16">Rabby keeps Tabby-style UX while moving the app shell to Rust/Tauri.</text>
|
||||
<rect x="930" y="80" width="292" height="640" rx="18" fill="#0c1222" stroke="#273753" filter="url(#shadow)"/>
|
||||
<rect x="930" y="80" width="292" height="42" rx="18" fill="#111a30"/>
|
||||
<text x="948" y="106" fill="#8fa4d0" font-family="Inter,Arial" font-size="13">profile inspector</text>
|
||||
<rect x="948" y="146" width="256" height="112" rx="14" fill="#121d34" stroke="#283957"/>
|
||||
<text x="966" y="180" fill="#fff" font-family="Inter,Arial" font-size="16" font-weight="700">Session restore</text>
|
||||
<text x="966" y="212" fill="#9fb0d4" font-family="Inter,Arial" font-size="14">Tabs and split tree are represented in Rust.</text>
|
||||
<rect x="948" y="276" width="256" height="112" rx="14" fill="#121d34" stroke="#283957"/>
|
||||
<text x="966" y="310" fill="#fff" font-family="Inter,Arial" font-size="16" font-weight="700">Small footprint target</text>
|
||||
<text x="966" y="342" fill="#9fb0d4" font-family="Inter,Arial" font-size="14">No Electron runtime; static HTML/CSS plus Tauri.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "rabby-core"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
@@ -0,0 +1,246 @@
|
||||
//! Core feature model for Rabby, a Rust/Tauri terminal workspace inspired by Tabby.
|
||||
//!
|
||||
//! This crate is intentionally dependency-light so terminal/profile/session behavior can be
|
||||
//! unit-tested without starting the desktop shell.
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConnectionKind {
|
||||
LocalShell,
|
||||
Ssh,
|
||||
Telnet,
|
||||
Serial,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Profile {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub kind: ConnectionKind,
|
||||
pub command: String,
|
||||
pub color_scheme: String,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn new(id: &str, name: &str, kind: ConnectionKind, command: &str) -> Result<Self, String> {
|
||||
if id.trim().is_empty() {
|
||||
return Err("profile id is required".into());
|
||||
}
|
||||
if name.trim().is_empty() {
|
||||
return Err("profile name is required".into());
|
||||
}
|
||||
if command.trim().is_empty() {
|
||||
return Err("profile command is required".into());
|
||||
}
|
||||
Ok(Self {
|
||||
id: slug(id),
|
||||
name: name.trim().to_string(),
|
||||
kind,
|
||||
command: command.trim().to_string(),
|
||||
color_scheme: "Rabby Dark".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PaneNode {
|
||||
Leaf {
|
||||
profile_id: String,
|
||||
},
|
||||
Split {
|
||||
axis: SplitAxis,
|
||||
ratio_percent: u8,
|
||||
first: Box<PaneNode>,
|
||||
second: Box<PaneNode>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SplitAxis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl PaneNode {
|
||||
pub fn leaf(profile_id: impl Into<String>) -> Self {
|
||||
Self::Leaf {
|
||||
profile_id: profile_id.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(
|
||||
self,
|
||||
axis: SplitAxis,
|
||||
other: PaneNode,
|
||||
ratio_percent: u8,
|
||||
) -> Result<Self, String> {
|
||||
if !(10..=90).contains(&ratio_percent) {
|
||||
return Err("split ratio must be between 10 and 90 percent".into());
|
||||
}
|
||||
Ok(Self::Split {
|
||||
axis,
|
||||
ratio_percent,
|
||||
first: Box::new(self),
|
||||
second: Box::new(other),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn leaf_count(&self) -> usize {
|
||||
match self {
|
||||
PaneNode::Leaf { .. } => 1,
|
||||
PaneNode::Split { first, second, .. } => first.leaf_count() + second.leaf_count(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Tab {
|
||||
pub title: String,
|
||||
pub root: PaneNode,
|
||||
pub progress: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Workspace {
|
||||
pub profiles: Vec<Profile>,
|
||||
pub tabs: Vec<Tab>,
|
||||
pub active_tab: usize,
|
||||
pub quake_mode: bool,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn default_linux() -> Self {
|
||||
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_string());
|
||||
let profile = Profile::new("local", "Local Shell", ConnectionKind::LocalShell, &shell)
|
||||
.expect("static default profile is valid");
|
||||
Self {
|
||||
profiles: vec![profile.clone()],
|
||||
tabs: vec![Tab {
|
||||
title: profile.name.clone(),
|
||||
root: PaneNode::leaf(profile.id),
|
||||
progress: None,
|
||||
}],
|
||||
active_tab: 0,
|
||||
quake_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tab(&mut self, profile_id: &str) -> Result<(), String> {
|
||||
let profile = self
|
||||
.profiles
|
||||
.iter()
|
||||
.find(|p| p.id == profile_id)
|
||||
.ok_or_else(|| format!("unknown profile: {profile_id}"))?;
|
||||
self.tabs.push(Tab {
|
||||
title: profile.name.clone(),
|
||||
root: PaneNode::leaf(profile.id.clone()),
|
||||
progress: None,
|
||||
});
|
||||
self.active_tab = self.tabs.len() - 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn feature_matrix() -> Vec<(&'static str, &'static str)> {
|
||||
vec![
|
||||
(
|
||||
"Terminal",
|
||||
"VT-style terminal surface, tabs, nested split panes, Unicode-first rendering",
|
||||
),
|
||||
(
|
||||
"Connections",
|
||||
"Local shell, SSH, Telnet, and Serial profile model",
|
||||
),
|
||||
(
|
||||
"UX",
|
||||
"Command palette, configurable shortcuts, quake-mode window, remembered sessions",
|
||||
),
|
||||
(
|
||||
"Transfers",
|
||||
"Reserved Zmodem/SFTP transfer surface for SSH sessions",
|
||||
),
|
||||
(
|
||||
"Security",
|
||||
"Encrypted secret-store boundary in the Tauri/Rust backend",
|
||||
),
|
||||
(
|
||||
"Footprint",
|
||||
"Rust core + Tauri shell instead of Electron/Angular runtime",
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slug(input: &str) -> String {
|
||||
let mut out = String::new();
|
||||
let mut last_dash = false;
|
||||
for ch in input.trim().chars() {
|
||||
if ch.is_ascii_alphanumeric() {
|
||||
out.push(ch.to_ascii_lowercase());
|
||||
last_dash = false;
|
||||
} else if !last_dash {
|
||||
out.push('-');
|
||||
last_dash = true;
|
||||
}
|
||||
}
|
||||
out.trim_matches('-').to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn slug_normalizes_profile_ids() {
|
||||
assert_eq!(slug(" SSH: Prod Box "), "ssh-prod-box");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_validation_rejects_empty_command() {
|
||||
let err = Profile::new("p", "Prod", ConnectionKind::Ssh, " ").unwrap_err();
|
||||
assert!(err.contains("command"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_panes_count_nested_leaves() {
|
||||
let root = PaneNode::leaf("local")
|
||||
.split(SplitAxis::Horizontal, PaneNode::leaf("ssh"), 50)
|
||||
.unwrap()
|
||||
.split(SplitAxis::Vertical, PaneNode::leaf("serial"), 65)
|
||||
.unwrap();
|
||||
assert_eq!(root.leaf_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_ratio_has_sane_bounds() {
|
||||
assert!(PaneNode::leaf("a")
|
||||
.split(SplitAxis::Horizontal, PaneNode::leaf("b"), 5)
|
||||
.is_err());
|
||||
assert!(PaneNode::leaf("a")
|
||||
.split(SplitAxis::Horizontal, PaneNode::leaf("b"), 50)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_adds_tabs_for_known_profiles() {
|
||||
let mut ws = Workspace::default_linux();
|
||||
ws.profiles
|
||||
.push(Profile::new("ssh-prod", "Prod SSH", ConnectionKind::Ssh, "ssh prod").unwrap());
|
||||
ws.add_tab("ssh-prod").unwrap();
|
||||
assert_eq!(ws.active_tab, 1);
|
||||
assert_eq!(ws.tabs[1].title, "Prod SSH");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn feature_matrix_covers_tabby_readme_capabilities() {
|
||||
let text = Workspace::feature_matrix()
|
||||
.iter()
|
||||
.map(|(a, b)| format!("{a} {b}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
.to_lowercase();
|
||||
for needle in [
|
||||
"terminal", "ssh", "telnet", "serial", "split", "unicode", "quake", "zmodem",
|
||||
] {
|
||||
assert!(text.contains(needle), "missing {needle}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.96.1"
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "rabby"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
rabby-core = { path = "../rabby-core" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tauri = { version = "2", features = [] }
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build();
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,23 @@
|
||||
use rabby_core::Workspace;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FeatureRow {
|
||||
feature: &'static str,
|
||||
detail: &'static str,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn workspace_summary() -> Vec<FeatureRow> {
|
||||
Workspace::feature_matrix()
|
||||
.into_iter()
|
||||
.map(|(feature, detail)| FeatureRow { feature, detail })
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![workspace_summary])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("failed to run Rabby Tauri application");
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Rabby",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.thetomyou.rabby",
|
||||
"build": { "frontendDist": "../ui" },
|
||||
"app": {
|
||||
"windows": [
|
||||
{ "title": "Rabby", "width": 1240, "height": 760, "minWidth": 960, "minHeight": 620 }
|
||||
],
|
||||
"security": { "csp": null }
|
||||
},
|
||||
"bundle": { "active": true, "targets": "all" }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
async function loadFeatures(){
|
||||
const fallback = [
|
||||
["Terminal", "VT-style surface, tabs, split panes, Unicode"],
|
||||
["Connections", "Local shell, SSH, Telnet, Serial profile model"],
|
||||
["UX", "Command palette, shortcuts, quake window, restored sessions"],
|
||||
["Footprint", "Rust core + Tauri shell instead of Electron"]
|
||||
];
|
||||
let rows = fallback;
|
||||
try {
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
rows = (await invoke('workspace_summary')).map(x => [x.feature, x.detail]);
|
||||
} catch (_) {}
|
||||
document.getElementById('features').innerHTML = rows.map(([f,d]) => `<li><b>${f}</b>: ${d}</li>`).join('');
|
||||
}
|
||||
loadFeatures();
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Rabby</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar">
|
||||
<div class="brand"><span class="logo">▰</span><div><strong>Rabby</strong><small>Rust + Tauri terminal</small></div></div>
|
||||
<button class="primary">+ New tab</button>
|
||||
<nav>
|
||||
<a class="active">⌘ Local Shell</a>
|
||||
<a>🔐 SSH: production</a>
|
||||
<a>☎ Telnet lab</a>
|
||||
<a>🔌 Serial /dev/ttyUSB0</a>
|
||||
</nav>
|
||||
<section><h3>Features</h3><ul id="features"><li>Loading Rust backend…</li></ul></section>
|
||||
</aside>
|
||||
<main>
|
||||
<header class="tabs">
|
||||
<div class="tab active">Local Shell <span>●</span></div><div class="tab">Prod SSH</div><div class="spacer"></div><button>Command palette</button><button>Quake mode</button>
|
||||
</header>
|
||||
<section class="workspace">
|
||||
<div class="pane large"><div class="chrome">bash — rabby-core tests</div><pre>$ cargo test -p rabby-core
|
||||
running 6 tests
|
||||
test slug_normalizes_profile_ids ... ok
|
||||
test profile_validation_rejects_empty_command ... ok
|
||||
test split_panes_count_nested_leaves ... ok
|
||||
test split_ratio_has_sane_bounds ... ok
|
||||
test workspace_adds_tabs_for_known_profiles ... ok
|
||||
test feature_matrix_covers_tabby_readme_capabilities ... ok
|
||||
|
||||
Rabby keeps Tabby-style UX while moving the app shell to Rust/Tauri.</pre></div>
|
||||
<div class="pane"><div class="chrome">profile inspector</div><div class="cards"><article><b>Session restore</b><p>Tabs and split tree are represented in Rust.</p></article><article><b>Small footprint target</b><p>No Electron runtime; static HTML/CSS plus Tauri backend.</p></article></div></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
:root{font-family:Inter,ui-sans-serif,system-ui;color:#dce7ff;background:#090d18}*{box-sizing:border-box}body{margin:0;display:flex;min-height:100vh;background:radial-gradient(circle at 70% 10%,#203d7a55,transparent 38%),#090d18}.sidebar{width:290px;padding:22px;border-right:1px solid #263149;background:#0d1324cc;backdrop-filter:blur(14px)}.brand{display:flex;gap:12px;align-items:center;margin-bottom:24px}.logo{display:grid;place-items:center;width:42px;height:42px;border-radius:12px;background:linear-gradient(135deg,#7c5cff,#00d4ff);box-shadow:0 0 34px #3b82f6}.brand small{display:block;color:#8ea0c5}.primary,button{border:1px solid #34425f;background:#141d33;color:#e8f0ff;border-radius:10px;padding:10px 13px}button.primary{width:100%;background:linear-gradient(135deg,#5d5fef,#16c5d9);border:0;font-weight:700}nav{display:grid;gap:8px;margin:22px 0}nav a{padding:11px 12px;border-radius:10px;color:#aebfe4}.active,nav a:hover{background:#1d2a49;color:white}h3{font-size:12px;text-transform:uppercase;letter-spacing:.14em;color:#6f82aa}ul{padding-left:20px;color:#aebfe4;line-height:1.5}main{flex:1;display:flex;flex-direction:column}.tabs{height:62px;display:flex;gap:8px;align-items:center;padding:0 18px;border-bottom:1px solid #263149;background:#0b1020}.tab{padding:12px 16px;border-radius:12px 12px 0 0;background:#10182d;color:#aebfe4}.tab.active{background:#18233d;color:#fff}.tab span{color:#20e3b2}.spacer{flex:1}.workspace{flex:1;display:grid;grid-template-columns:1.35fr .65fr;gap:16px;padding:18px}.pane{border:1px solid #273753;border-radius:18px;background:#0c1222;overflow:hidden;box-shadow:0 24px 70px #0008}.chrome{height:42px;display:flex;align-items:center;padding:0 16px;background:#111a30;border-bottom:1px solid #273753;color:#8fa4d0;font-size:13px}pre{margin:0;padding:22px;color:#8df7c8;font:15px/1.6 'SFMono-Regular',Consolas,monospace}.cards{display:grid;gap:14px;padding:16px}article{padding:16px;border-radius:14px;background:#121d34;border:1px solid #283957}article p{color:#9fb0d4}
|
||||
Reference in New Issue
Block a user