- Clarified the usage of `NyaScrollable` for scroll containers in the documentation. - Removed redundant information regarding `overflow_y_scroll()` and `scrollbar_width` to enhance readability.
15 KiB
Repository Guidelines
Project Overview
NyaTerm is a native GPUI desktop application written in Rust. It provides SSH, local shell, Telnet, Serial, RDP, VNC, SFTP, tunnels, OTP, AI assistance, and encrypted sync and backup in one workspace.
This repository is a Rust 2024 Cargo workspace using resolver 3. Preserve
compatibility with existing NyaTerm configuration, credentials, backups,
cloud-sync data, known hosts, and sessions. Any incompatible data-format change
must include explicit conversion logic and tests.
Workspace Structure
crates/nyaterm-app: executable entry point, bundled assets, logging setup, and root-window creation.crates/nyaterm-core: UI-independent domain models, parsing, policies, AI settings/risk/provider logic, schema-neutral serialization and encryption contracts, and shared pure logic. Do not add GPUI dependencies here.crates/nyaterm-desktop: GPUI application composition,AppShell,NyaTermApp, feature state, views, platform adapters, background-job coordination, native HTTP adapters, and GPUI Entity stores.crates/nyaterm-terminal: terminal state machine, snapshots, control-sequence handling, encoding, and graphics protocols. It must remain independent of UI frameworks.crates/nyaterm-terminal-gpui: GPUI-specific terminal layout, input, highlighting, images, and painting.crates/nyaterm-transport: local PTY, SSH, Telnet, Serial, SFTP, tunnels, remote operations, and transfer-protocol runtime. It must remain independent of GPUI and desktop presentation types.crates/nyaterm-ui: shared GPUI theme tokens, thegpui-componentintegration boundary, and reusable NyaTerm presentation and interaction widgets.crates/nyaterm-store: persistence implementation, transactions, redb schema, encryption adapters, and database compatibility readers. Keep pure data models and serialization policies innyaterm-core.crates/nyaterm-remote-desktop: UI-independent RDP/VNC session management, framebuffer and input models, certificate policy, clipboard state, and the RDP/VNC helper IPC contracts. It owns no protocol decoder; both live in the helper crates below.crates/nyaterm-rdp-helper: isolated IronRDP helper process that communicates with the application through the typed IPC protocol innyaterm-remote-desktop.crates/nyaterm-vnc-helper: isolated VNC helper process using the same IPC protocol. It owns the forkedvnc-rsdecoders, the VNC reconnect ladder, and the server-facing policy gates (view_only,shared, clipboard enablement). Those gates must stay enforced here, not only in the application.crates/nyaterm-otp: bundled HOTP/TOTP implementation.crates/nyaterm-app/assets: bundled icons and images. Assets undericons/**are normally tintable and rendered throughsvg()ormono_icon(); full-color assets useimg()orcolor_icon(). Preserve the rendering distinction when adding assets.- Third-party dependencies that NyaTerm patches are not vendored. Each is a
patch series on a fork under https://github.com/nyakang on branch
nyaterm, consumed from a revision pinned in the rootCargo.toml:alacritty(alacritty_terminal),gpui-component,IronRDP(ironrdp-client,ironrdp-connector),russh,russh-sftp,sspi-rs,vnc-rs,zed(gpui,gpui_platform), andzmodem2. Each branch carries aNYATERM.mdrecording its base revision, its patches, and how they were validated. temp/vendor/: untracked, read-only copies of those sources, kept only so they can be read locally. Nothing undertemp/is compiled. Editing anything there has no effect on the build and produces no error — change the fork branch instead.
Application Architecture
NyaTermApp is the central GPUI composition owner. Feature-specific state
lives in focused structs for connections, commands, remote operations, remote
desktop, security, settings, AI, terminal presentation, sessions, transfers,
sync, translation, updates, tunnels, recording, and shell behavior.
Persisted collections and compatibility-sensitive catalogs belong to their
feature owners. Schema-neutral persistence formats and parsing contracts belong
in nyaterm-core; database execution and compatibility readers belong in
nyaterm-store.
The GPUI Entity stores each own state that NyaTermApp does not own:
StartupRestoreStore: startup-restore queue.OverlayStore: authoritative quick-switch overlay state.
For new or substantially changed features:
- Prefer a focused feature-state struct or a deliberately authoritative GPUI
Entity over new top-level
NyaTermAppfields. - Keep exactly one authoritative mutable owner for each piece of state. Do not
mirror independently mutable state between
NyaTermApp, feature state, and Entity stores. - If a method only reads and writes one feature-state struct, place that logic
on the state and keep the
NyaTermAppmethod as a notifier or adapter when needed. - Keep GPUI element construction in views. Pure state types should not render UI.
- Use typed events to communicate from background jobs to GPUI state.
- Never perform filesystem, database, network, SSH, SFTP, subprocess, image decoding, or other blocking work in a render path or a long-running GPUI update callback.
- Keep terminal parsing, snapshots, graphics protocols, and wire handling in
nyaterm-terminal. - Keep terminal layout, GPUI input adapters, highlighting, image presentation,
and painting in
nyaterm-terminal-gpui. - Keep transport code independent of GPUI and desktop presentation types.
- Keep remote-desktop protocol and session logic in
nyaterm-remote-desktop; keep GPUI presentation innyaterm-desktop. - Keep protocol decoders that parse server-controlled bytes in the helper crates, never in a crate the application links. Both helpers must translate a decoder panic into a fatal IPC error rather than dying silently.
- Keep persistence logic out of GPUI views.
Use normal Rust module trees with mod.rs files or sibling module
declarations. Do not add #[path = "..."] declarations or use super::*
imports. Name dependencies explicitly, including in tests.
Avoid broad exports from crate roots and shared feature preludes. Import models, services, GPUI types, and helpers from their authoritative modules.
UI and Input Rules
Ordinary forms, prompts, searches, menus, selects, switches, and dialogs should
use the wrappers exposed by nyaterm-ui. That crate owns the
gpui-component integration, theme mapping, and stable NyaTerm component API;
desktop feature modules must not depend directly on gpui-component.
Ordinary text inputs should use nyaterm-ui::NyaInput and NyaInputState,
either owned by a focused feature or through the id-keyed registry in
features/text_inputs.rs. Do not add hand-painted ordinary text inputs.
Terminal input, paste review, and RemoteTextEditor are full editing surfaces
with dedicated selection, undo, IME, and command handling. Do not replace them
with single-line registry fields.
Text-field wrappers must have a definite height and width. Parent click handlers must not immediately steal focus from a field. Keep controls usable with keyboard navigation and ensure overlays and child windows restore focus predictably.
GPUI's overflow_y_scroll() enables scrolling but paints nothing, and scrollbar_width only reserves gutter space, so use NyaScrollable (the ScrollableElement trait re-exported by nyaterm-ui):
- In-flow containers: use
overflow_*_scrollbar()instead ofoverflow_*_scroll(), and do not also reserve ascrollbar_widthgutter - the bar overlays the content. - Virtualized lists (
uniform_list) and any caller-owned scroll handle: keeptrack_scrollon the list and attach.vertical_scrollbar(&handle)to the non-scrolling parent. An absolutely positioned overlay inside a scrolling element is translated by the scroll offset and would scroll away. - Absolutely positioned popups must not use
overflow_*_scrollbar(). The wrapper inherits only size and flex styles, soposition/top/leftwould move onto the inner content node and break the popup's placement. Give the popup a non-scrolling shell and scroll an in-flow child instead. - A
Scrollbarinside a caller-positioned overlay needsviewport_from_layout(); otherwise it paints at the scroll handle's own bounds and ignores the overlay.
Scrollbars auto-hide (ScrollbarMode::Hover), installed once in
theme_bridge.rs. A bar fades in while the pointer is anywhere over its scroll
viewport or that axis is scrolling, and out after idle, so a horizontal bar is
discoverable without pinning it open. Two further consequences:
- A hand-positioned
Scrollbaroverlay must span the whole scroll viewport, not just the strip the track occupies. Reveal-on-hover watches the bar's own hitbox, so a strip-sized overlay only reacts within a track width of the edge.overflow_*_scrollbar()andvertical_scrollbar(&handle)already do this. - Where the two axes ride different scroll handles they are separate
Scrollbarelements with separate reveal state, and the upstream component's corner-avoidance does not apply, so inset one overlay by a track width.
Anything that writes Theme::scrollbar_mode or the scrollbar* colors directly
must call Theme::sync_base(cx) afterwards. Scrollbar reads the
gpui_base::Theme projection, which those assignments do not touch.
The terminal scrollback scrollbar is hand-rolled as a reserved flex column with its own drag and overview ruler, and is deliberately exempt from all of the above.
Persistence and Compatibility
Changes involving redb, credentials, known hosts, OTP, cloud sync, portable
snapshots, .nya backups, AI or translation secrets, and application settings
are compatibility-sensitive.
Before changing these areas:
- Preserve table names, keys, serialized field names, document keys, encryption prefixes, master-key wrapping, backup formats, and fallback decryption behavior unless the change explicitly updates the data contract.
- Test both new-data round trips and loading representative data written by supported NyaTerm versions.
- Do not silently discard unknown or unsupported fields.
- Validate fully before overwriting existing user data.
- Keep secret-bearing values masked when returning settings to the UI.
nyaterm-store/src/storage/mod.rs owns the database implementation, with
domain-specific modules under nyaterm-store/src/storage/. Treat existing
redb data, .nya backups, master-key wrapping, encrypted payload formats, and
text-document fallbacks as public compatibility contracts.
Security Rules
Never log or commit:
- passwords or decrypted saved credentials;
- private-key contents or passphrases;
- OTP secrets or generated codes;
- AI, cloud-sync, OAuth, snippet, or translation API secrets;
- unredacted diagnostics containing terminal output, command context, or user data.
Use custom redacted Debug implementations for secret-bearing structs when
debug output is required. Prefer typed secret wrappers and zeroize sensitive
buffers where practical.
Patching a third-party dependency means committing to its fork branch, pushing,
and bumping the pinned revision in the root Cargo.toml. Do not edit temp/.
Record the reason for the change and the validation performed on the patch
commit and in that branch's NYATERM.md, and keep the patch series split by
concern rather than squashed. Prefer rebasing the series onto a newer upstream
revision over accumulating snapshots.
Build and Development Commands
Use package-specific checks while iterating:
cargo check -p nyaterm-appcargo test -p <crate-name>cargo run -p nyaterm-app --bin nyaterm
RDP and VNC each run in a helper process that the application resolves beside its
own executable. cargo run -p nyaterm-app --bin nyaterm builds only the
application, so build the helpers into the same target directory first or both
protocols fail with HelperMissing:
cargo build -p nyaterm-rdp-helper -p nyaterm-vnc-helper
A bare cargo build or cargo check covers all three: they are the workspace
default-members. NYATERM_RDP_HELPER and NYATERM_VNC_HELPER override the
lookup with an explicit path. scripts/release/package_native.py is what puts the
helpers next to the application in release packages; its HELPER_BINS list must
name every helper.
Before review, run the relevant broader checks:
cargo check --workspacecargo test --workspacecargo fmt --all -- --checkcargo clippy --workspace --all-targets
Use cargo fmt --all only when intentionally applying formatting changes.
Platform-specific GPUI, PTY, Serial, SSH, clipboard, window, path, RDP/VNC, and icon-rendering behavior must be verified on the affected operating system.
Coding Style
Use standard rustfmt formatting and Rust 2024 idioms.
- Use
snake_casefor modules, functions, fields, and variables. - Use
PascalCasefor structs, enums, and traits. - Use
SCREAMING_SNAKE_CASEfor constants. - Prefer explicit imports and narrow public interfaces.
- Prefer typed models and errors over loosely structured strings.
- Use small adapters at crate boundaries instead of importing application-wide models into low-level crates.
- Comments should explain invariants, compatibility constraints, ownership, or non-obvious performance decisions rather than restating code.
When splitting a large file, preserve its public facade where practical so structural changes do not cause unrelated call-site churn. Prefer domain cuts that move constants, records, helpers, tests, and dependencies together over type-only splits that leave coupling behind.
Testing Guidelines
Add tests beside the behavior being changed.
- Terminal parsing, snapshots, graphics, selection, input, and rendering tests
belong in
nyaterm-terminalornyaterm-terminal-gpui. - SSH, SFTP, Telnet, Serial, tunnel, transfer, and terminal-session lifecycle
tests belong in
nyaterm-transport. - RDP/VNC protocol, framebuffer, input mapping, IPC, certificate, clipboard,
and reconnect tests belong in
nyaterm-remote-desktop,nyaterm-rdp-helper, ornyaterm-vnc-helper. Helper crates carry atests/lifecycle.rscovering the handshake, an ordinary disconnect, and crash/hang reaping; keep both in step when the IPC contract changes. - Storage changes require round-trip and supported-format compatibility tests.
- Credential and encryption changes require success, invalid-password, corrupted-data, and compatibility-format tests.
- GPUI state changes should test state transitions separately from visual rendering where possible.
Use descriptive, behavior-oriented test names.
Commits and Pull Requests
Use Conventional Commit-style subjects:
type(scope): imperative summary
Common scopes include terminal, transport, desktop, storage, ui,
remote-desktop, ai, and sync.
Pull requests should include:
- a concise description of behavior and architectural impact;
- linked issues where applicable;
- commands and platforms tested;
- explicit notes for persistence, credentials, data compatibility, or forked dependency changes, including the fork branch and revision a bump moves to.