VelaShell (App) -> Desktop entry point and composition root
VelaShell.Presentation -> ViewModels, navigation, orchestration
VelaShell.Controls -> Custom controls, styles, tokens, behaviors
VelaShell.Terminal -> Terminal engine, parser, renderer, interaction model
VelaShell.Core -> Stable domain models and contracts
VelaShell.Infrastructure -> SSH, storage, platform integration, background work
The desktop entry project is named
VelaShell(assemblyVelaShell,OutputType=WinExe).VelaShell.Appis a legacy alias that still appears in some older docs. Each project also carries its ownREADME.mddescribing its internal structure and dependencies.
App(VelaShell) -> Presentation
App(VelaShell) -> Controls
App(VelaShell) -> Infrastructure
App(VelaShell) -> Terminal
App(VelaShell) -> Core
Presentation -> Core
Presentation -> Terminal
Controls -> Core (optional, only for shared UI contracts)
Infrastructure -> Core
Infrastructure -> Terminal (only if an adapter truly belongs here)
Terminal -> Core
- The design file contains many reusable surfaces instead of one-off screens.
- Theme tokens, shared panel shells, session tree items, terminal tab strip, transfer rows, and tunnel cards should evolve independently from the application bootstrap.
- Runtime theme changes become simpler when token dictionaries and custom control styles live in a dedicated assembly.
The terminal area should be treated as a subsystem, not a single control.
Suggested internal layers:
SSH Stream Adapter
-> ANSI Parser
-> Buffer / Scrollback Model
-> Semantic Highlight / Link Detection
-> Render Snapshot
-> Avalonia Drawing Surface
This split makes it easier to support:
- incremental streaming (character-by-character / line-by-line)
- ANSI escape sequences
- URL / error / warning highlighting
- selection without hijacking
Ctrl+C - multiline paste input
- common Linux progress bar redraw behavior
The renderer also supports an optional line-number / timestamp gutter on the left
edge (Terminal/Rendering/GutterLayout + GutterFoldModel): two independently toggled
side columns with fold markers and blank-gap handling, wired to keyboard shortcuts.
SSH/SFTP is provided by Tmds.Ssh, a fully managed,
async-first .NET SSH library (it replaced SSH.NET). The library's types never leave
Infrastructure/Ssh/: TmdsSshClientWrapper / TmdsSftpClientWrapper / ShellStreamWrapper
adapt SshClient / SftpClient / RemoteProcess to the neutral Core.Ssh interfaces, and
TmdsSshInterop translates library exceptions into the VelaSsh*Exception family declared in
Core. ProxyJump uses the library's native SshProxy chain, assembled by
InfrastructureServiceCollectionExtensions.BuildProxyChain from the saved jump-host profiles
(≤5 hops, cycle-checked, host key verified per logical hop).
In-terminal rz / sz file transfer is implemented in-house and split across two projects:
Core/ZModem/ -> transport-agnostic protocol engine (frames, ZDLE, CRC-16/32,
ZModemSender / ZModemReceiver) over an IByteDuplex abstraction
Terminal/ZModem/ -> ZModemDetector (spots the ZRQINIT / ZRINIT boot sequence in the
output stream) + ZModemTerminalRouter (sits between the bridge read
loop and the emulator; hands bytes to the engine for the duration of
a session, then resets) + ShellStreamByteDuplex
App/Services/ZModem/ -> file source (upload), folder sink (download), progress observer
Because the engine only needs an IByteDuplex, the same path serves SSH, local ConPTY, and
future serial / Telnet sessions. Set VELASHELL_ZMODEM_TRACE=1 to dump protocol frames when
diagnosing interop problems.
Split/tabbed layout is provided by an in-house, dependency-free docking framework
(VelaDock, src/VelaShell/Docking/) that replaced Dock.Avalonia. See
docs/dock-replacement-plan.md.
Docking/Model/ -> pure INPC model (unit-testable): DockWorkspace / DockGroup / DockSplit / DockDocument
Docking/Controls/ -> DockWorkspaceControl (renders the split tree), DockGroupControl (tab strip),
DockTabItem, DockDragController + DockDropOverlay (five-zone drag-drop split)
Documents are live SSH/local sessions; each TerminalTabView is cached per document and
reused across tab switches. Floating windows are intentionally not implemented (product decision).
The main window is a self-drawn borderless window (WindowDecorations="None"), not
native chrome — Avalonia 12.x's ExtendClientArea / decoration-role input redirection is
broken on Win32 (title-bar buttons and drag stop working), so it was abandoned. Instead
Views/TitleBarView draws a 36px bar with logo+name (left), the global action icon group
(right; Broadcast opens the multi-terminal input bar, while SyncGroup remains disabled) and its own minimize/maximize/close
caption buttons. Native feel is restored programmatically: BeginMoveDrag (native move
loop, Win11 edge-snap works), double-click to maximize, and Win11 Snap Layouts via a
MainWindow WndProc hook on HTMAXBUTTON. All dialogs use the same borderless mode.
The text menu bar (会话/编辑/…) was removed in favor of the command palette (Ctrl+P/Ctrl+K).
- Map
.penvariables to semantic Avalonia resources. - Support three user modes:
Dark,Light,System. - Keep accent color as a separate override layer.
- Use
DynamicResourceso changes are reflected without restart.
All persistence goes through SonnetDB (https://github.com/IoTSharp/SonnetDB), used as an
embedded multi-model database (SonnetDB.Core, opened via Tsdb.Open under
%LocalAppData%/VelaShell/sonnetdb). Legacy JSON files (sessions.json, settings.json,
state.json, known_hosts.json, quick-commands.json) are imported once on first run.
- Document collections (JSON documents) hold business/config data:
session_groups,session_profiles(indexed by$.groupId),app_config(settings/state/sync singleton docs —syncholds the Gist cloud-sync config with the PAT encrypted viaISecretProtector),known_hosts,ui_config,quick_commands(a schema-v2 singleton containing groups plus custom commands),tunnels(one doc per profile id holding its tunnel configs),recordings(session-recording metadata). - Time-series measurements hold time-oriented data:
conn_history(recent connections, powers the sidebar list),audit_log(security auditing) andsession_recording_chunks(session recording output chunks: tagrecording_id, fieldsoffset_ms+ Base64data; point time = recording start + offset, replayed on a timeline by the recording player). - SQL dialect note:
ORDER BY timerequires thetimecolumn to be present in the SELECT list; plainDELETE FROM measurementmay be unsupported — the recording store falls back to a drop-and-rewrite compaction during retention cleanup to reclaim orphaned chunk bytes. - Sensitive fields (passwords, key passphrases, sync tokens) are encrypted at
rest with AES-256-GCM via
ISecretProtector(local key file). - Device-local shell layout is stored in
app_config/state: sidebar section collapse state and remembered heights are restored on startup and are not included in Gist sync. - Keep persistence interfaces in
Core(ISessionRepository,ISettingsService,IRecentConnectionService,IAuditLogService,IAppDataStore,ISessionRecordingStore,IQuickCommandRepository,ISecretProtector), implementations inInfrastructure/Persistence(SonnetDb*), all sharing oneSonnetDbEnginesingleton that is disposed on app exit (WAL flush).
Quick commands are loaded through IQuickCommandRepository, which owns v1 SonnetDB and
legacy quick-commands.json migration, backup creation, schema validation, and Gist-compatible
v1/v2 snapshots. The UI and sync service never manipulate the SonnetDB document directly.
Do not move everything at once.
Preferred order:
- Add new assemblies.
- Wire solution references.
- Migrate ViewModels to
Presentation. - Migrate themes and reusable controls to
Controls. - Migrate storage and SSH implementations to
Infrastructure. - Reduce
Appto startup and shell hosting only.