Repository navigation
UI Layer
English · Русский
One Compose Multiplatform module (composeApp/) draws every platform. The phone UI is common
code, not an Android-only branch — which is why desktop tests can drive it.
composeApp/src/commonMain/kotlin/app/skerry/ui/
app/ shell state, locals, UI tags, render backend
design/ the design system: primitives, dialogs, menus, fields, folders
desktop/ desktop shell: rail, viewport, work bar
mobile/ phone shell: tabs, sheets, screens
terminal/ session/ files/ sftp/ remote/ vnc/ rdp/ the working surfaces
host/ vault/ identity/ known/ trust/ tunnel/ snippet/ runbook/ teams/ share/ ai/ sync/ metrics/ settings/
i18n/ theme/ nav/ update/ keepalive/
AppDependencies.kt
AppDependencies is the graph the platform entry point assembles and hands to the root
composable (DesktopDesignApp / MobileDesignApp). A subsystem that is not wired up on a
platform is null, and the UI shows a placeholder rather than failing.
Screens do not use raw Material components. The catalogue in
docs/coding-guidelines.md
is normative; the load-bearing entries:
| Need | Primitive |
|---|---|
| Any text |
Txt — never a raw Text() (100+ files) |
| Any icon |
Sym (Material Symbols glyph) + DesignFonts
|
| Buttons |
PrimaryButton, GhostButton, CancelButton, IconBtn, GlyphButton
|
| Small controls |
Toggle, Badge, Dot, MeterBar, NumberStepper, HoverTooltip
|
| Rows |
ToggleRow, KeyValueRow, RowNote / NoteBlock
|
| Menus |
MenuPanel + MenuItem / MenuActionRow — every right-click and "⋮" menu, the text context menu included |
| Dialogs |
ConfirmActionDialog, NoticeDialog, ModalScrim, modalBody
|
| A command a dialog quotes |
CommandQuote (+ ClippedNotice); a command drawn in a row: CommandQuote.CommandLine
|
| Fields |
FormField + Modifier.fieldName(), ModalTextField, FieldDraft
|
| Folders |
Folders + FolderSectionHeader, GroupSelectField (desktop) / MobileGroupSelectField
|
| Untrusted text |
UntrustedText.untrustedLabel, sanitizeServerText, sanitizeServerHost
|
| A question a connection asks | PromptQueue |
| Announcements for a screen reader | StatusAnnouncer |
| Keeping the keyboard on a live widget | KeyboardClaim |
Two rules behind most of them: one definition per shape (a third hand-rolled copy is a bug),
and accessibility comes from the primitive — a caption published by FormField becomes the
input's accessible name, so a field cannot end up anonymous.
Colours come from D.* design tokens mirrored from the prototype's :root block
(docs/design/Skerry Tablet.html), never as hex in a screen. A new shade means a new token,
checked against the prototype. Themes: dark, light, and System following the OS; the terminal
follows the app theme.
Chrome follows the prototypes 1:1 — the UI is built from them rather than invented.
Every user-visible string is a resource in composeResources, translated into English, Russian
and Simplified Chinese at once. A hardcoded literal fails the deterministic checks.
Two gotchas the code encodes:
- each locale quotes with its own marks (
" "in en and zh,« »in ru — never the straight"); - in
composeResourcesthe plugin decodes\n,\t,\uXXXXand\\and nothing else, and substitution is a regex over%N$s/%N$d, notString.format— an Android-style\"or a bare%sis a bug.StringEscapeTestsweeps every string in every locale for all four.
Desktop and mobile share form state and logic (*FormState); only the layout differs. A new
form starts as a shared state class and gets two thin views. Parity is checked in review, and the
Android compilation of a UI change is a gate stage (:androidApp:compileDebugKotlin).
Logic does not live in composables. Controllers (ConnectionController, SessionsController,
RemoteDesktopController, SyncCoordinator, RunbookRunner, …) take their dependencies through
the constructor — a class that cannot be tested without network, disk or UI is designed wrong.
Concurrent controllers ship with tests for cancellation and re-entry.
desktopTest/ui/desktop/ShellHarness runs the real UI over a demo graph: runDesktopShell,
runMobileShell, runForm, plus onField / onCatalog / onTab / press. Both shells run on
the desktop JVM — the mobile UI is common code, so Android parity is checked without an emulator.
Navigation is addressed through ui/app/UiTags, because a rail button draws a font ligature and
every label is localized.
User guide
- Installation
- Getting started
- Hosts and connections
- Vault and credentials
- Terminal
- Files (SFTP)
- Port forwarding
- Remote desktops
- Snippets and runbooks
- Monitoring and guard
- AI assistant
- Sync and devices
- Teams and sharing
- Settings and shortcuts
- Android
- Troubleshooting
Development
- Architecture
- Building and running
- Connections and transports
- Terminal engine
- Vault and crypto
- Sync protocol
- Remote desktop stacks
- UI layer
- Testing and the gate
- Contributing and releases
Русский — оглавление