Files
Codeman/packages/desktop/Cargo.toml
T
Codeman maintainer 4cedeec529 feat(desktop): server tabs, several Codeman servers open in one window
The Mac app showed one server at a time, and moving to another machine
meant Switch Server and a fresh connect. Now every server gets a tab in
the title bar, and every tab stays live.

- Tab strip: a small bundled page (assets/tabs.html) in its own web view
  across the top of the window, with the traffic lights inset into it.
  Each tab has a colour and a status badge: a red count when sessions
  need you, an amber dot when they are waiting, a blue ring while they
  work, a dashed circle while the server does not answer.
- The status comes from the app, not from the page: one thread per tab
  reads GET /api/sessions every 2 seconds (macOS stalls hidden web
  views), backs off for 60 s on a 429 and stops on 401/403, so a wrong
  password is never retried in a loop.
- + or ⌘T opens the connect screen in a new tab. A server that is
  already open just brings its tab forward instead of opening twice,
  also when two connects race (checked again when the probe ends).
- Double-click or the right-click menu renames a tab; the same menu
  sets the colour, reloads, reconnects, closes, or closes the others.
  Tabs can be dragged. A new Server menu carries ⌘T, ⌘W, ⇧⌘T (reopen
  closed tab, last 10), ⌘0, ⇧⌘[ / ⇧⌘] and ⌘1 to ⌘8 (⌘9 is the last
  tab once there are nine). Close Window moves to ⇧⌘W. Ctrl+Tab and
  Alt+1..9 stay with Codeman's own session switching.
- ⌘0 shows every server at once (assets/overview.html): the ones that
  need you first, up to four sessions each with what they ask, and a
  click on a session opens its tab and selects it.
- Tabs are saved in desktop.json (tabs, activeTab) and come back at
  launch, connecting by themselves when auto-connect is on.
- Logins: each tab keeps its own Basic credentials, and a challenge is
  answered only with the login of the tab whose web view asked
  (macos.rs binds each WKWebView to its tab).
- Cookies: on macOS 14 and later every server gets its own WebKit data
  store (WKWebsiteDataStore dataStoreForIdentifier, with a stable id
  hashed from the server URL), so a server on port 3000 and a beta on
  5000 of the same host cannot log each other out. Before macOS 14 a
  second tab on a host that is already open is refused with a message.
  The server used last before this change keeps the default store, so
  its per-device settings (font size, skin) survive; every other
  server starts those fresh once.
- The lid logo blink watches all open servers, and Keep This Mac Awake
  follows the tab that runs on this Mac.

New pure model in src/tabs.rs (names, colours, order, shortcuts, strip
and overview state) with unit tests; agents.rs reads the per-session
detail for the badges and the overview; policy.rs parses the strip and
overview IPC and refuses it from any other frame. Browser tests drive
both bundled pages (test/desktop-tabs-pages.browser.test.ts) and the
connect page's restored-tab path.

Not yet run in a real window: the Mac mini has no screen session. The
owner checks the traffic-light placement, clicks under the transparent
title bar, window drag and zoom from the strip, keyboard focus after
strip clicks, and two logged-in servers side by side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 18:36:08 +01:00

101 lines
2.9 KiB
TOML

[package]
name = "codeman-desktop"
version = "0.1.0"
edition = "2021"
rust-version = "1.90"
description = "Codeman Desktop: a small native window for a Codeman server"
license = "MIT"
publish = false
[[bin]]
name = "codeman-desktop"
path = "src/main.rs"
[dependencies]
# Window + event loop (tao), the system web view (wry: WKWebView on macOS,
# WebView2 on Windows, WebKitGTK on Linux) and native menus (muda). Tauri is
# built on these three; using them directly keeps the app small and gives us
# `load_url_with_headers`, which Tauri's navigate() does not have (see README).
tao = "0.37"
wry = { version = "0.57", features = ["devtools"] }
muda = "0.21"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
url = "2"
# The versions ureq and wry already pull in, so neither crate is built twice
# (ureq builds base64 without its default SIMD engines, and so do we).
base64 = { version = "0.23", default-features = false, features = ["std"] }
dirs = "7"
# The reachability probe. platform-verifier = the OS trust store, the same one
# WKWebView uses, so the probe and the window agree on which certificates pass.
ureq = { version = "3", features = ["platform-verifier"] }
[target.'cfg(target_os = "macos")'.dependencies]
security-framework = "3"
# Ignoring signals in the logo blinker (src/logo.rs). Already in the tree
# through wry and tao, so no new crate is added.
libc = "0.2"
# JavaScript dialogs, HTTP Basic challenges, clearing one server's cookies and
# the window's content size (src/macos.rs). The versions wry already pulls in, so no new
# crate is added.
objc2 = { version = "0.6.5", default-features = false, features = ["std"] }
block2 = { version = "0.6.2", default-features = false, features = ["std"] }
objc2-foundation = { version = "0.3.2", default-features = false, features = [
"std",
"objc2-core-foundation",
"block2",
"NSArray",
"NSBundle",
"NSDate",
"NSDictionary",
"NSGeometry",
"NSHTTPCookie",
"NSObject",
"NSOperation",
"NSProcessInfo",
"NSRange",
"NSSet",
"NSString",
"NSURL",
"NSURLAuthenticationChallenge",
"NSURLCredential",
"NSURLProtectionSpace",
"NSURLRequest",
"NSURLResponse",
"NSURLSession",
"NSUUID",
"NSValue",
] }
objc2-app-kit = { version = "0.3.2", default-features = false, features = [
"std",
"objc2-core-foundation",
"NSAlert",
"NSApplication",
"NSButton",
"NSControl",
"NSResponder",
"NSTextField",
"NSView",
"NSWindow",
] }
objc2-web-kit = { version = "0.3.2", default-features = false, features = [
"std",
"block2",
"objc2-app-kit",
"WKFrameInfo",
"WKHTTPCookieStore",
"WKNavigationDelegate",
"WKSecurityOrigin",
"WKUIDelegate",
"WKWebView",
"WKWebsiteDataRecord",
"WKWebsiteDataStore",
] }
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
strip = true
panic = "abort"