These rules apply to all AI coding agents working on this project.
noxed is an Electron app with three isolated layers. Never leak concerns across them.
- Main process (
src/main/) — Node.js. Owns all system access: SSH, SFTP, Docker, databases, Redis, K8s, tunnels, filesystem, keychain. Exposes functionality exclusively through IPC handlers. - Preload (
src/preload/) — Thin bridge. OnlycontextBridge+ipcRenderer. No logic, no transforms. - Renderer (
src/renderer/) — React + Zustand. Pure UI. All system access goes throughwindow.api.*. Never import Node.js modules.
IPC is the security boundary. Every handler in src/main/ipc/ must validate its inputs — the renderer is untrusted.
- Never trust renderer input. Validate types, ranges, and paths in every IPC handler.
- Never send secrets to the renderer without gating behind
isUnlocked(). - Never execute arbitrary commands from user input without a blocklist check.
- Filesystem reads must be restricted to allowlisted directories.
- Use
sandbox: trueandcontextIsolation: true. Always. - Credentials go through OS keychain (
keytar), never electron-store.
- Files: PascalCase for React components (
TerminalView.tsx). camelCase for non-component modules (security.ts,ssh.ts). Tests go in__tests__/adjacent to source. - Exports: Default export name must match filename.
FilesDrawer.tsxexportsFilesDrawer, notFilesPanel. - Components: One primary component per file. Supporting sub-components are fine, but keep them private (not exported).
- Types/interfaces: PascalCase. No
Iprefix, noTprefix. - Constants: UPPER_SNAKE_CASE for true constants. Regular camelCase for derived values.
- Feature folders: PascalCase matching the feature (
K8s/,SFTP/,Redis/).
- Prefer small, reusable primitives over one-off UI or IPC patterns. If two features share behavior or chrome, extract a shared component/helper before adding a second implementation.
- Treat new code as if it will be reviewed publicly. Keep APIs narrow, names precise, and behavior easy to reason about from the call site.
- Before adding a new helper, search for an existing canonical helper. Before duplicating logic, either reuse it or explicitly justify why it cannot be shared.
- No orphaned
console.log. Use structured error handling instead. - No
catch {}orcatch () {}— always handle or explicitly annotate why it's safe to swallow. - No
anyin function signatures. Useunknownand narrow, or define a proper type.anyin catch blocks is acceptable only ascatch (err: any)for Electron IPC error messages. - Never duplicate utility logic across files. Shared formatters, colors, and helpers go in
src/renderer/src/lib/. - No dead code. No commented-out blocks. No unused imports.
- Aim to keep files under ~500 lines; extract sub-components or hooks when one grows past it. A handful of existing files (e.g.
K8sDashboard.tsx,AddConnectionModal.tsx,Settings.tsx) already exceed this — treat them as extraction candidates, and don't pile more onto them without splitting.
Write comments only when the code cannot explain itself:
- Why something is done a non-obvious way
- Constraints from external systems (e.g., "ssh2 requires rows before cols")
- Security rationale for validation logic
Never narrate what the code does. Never leave TODO comments without a linked issue. Never comment changes you're making.
- Main process: always
throw new Error(message)or a typed subclass — neverreject(string). - Renderer: catch errors from IPC and surface them to the user via the notification system or inline error state.
- Custom error classes live in
src/main/ipc/errors.ts:ValidationError,AuthError,ConnectionError,NotFoundError,OwnershipError.
These are the canonical locations. Do not duplicate this logic elsewhere.
src/renderer/src/lib/format.ts— byte/size formatting, path joining, relative time, uptime, sparklines, date formatting, and IPC error message extraction (ipcErrorMessage)src/renderer/src/lib/colors.ts— metric threshold colors, connection type colors, group color hashing, pod status colorssrc/main/ipc/security.ts— path/key-path validation, command blocklists, text/binary file detection, and input validators (validateHost,validatePort,validateK8sName)src/main/ipc/errors.ts— custom error classes for IPC handlers
- Functional components only. No class components except error boundaries.
- Props interfaces live in the same file, directly above the component.
- Destructure props in the function signature.
- Keep render logic flat — extract sub-components over deep ternary nesting.
useReffor values that must survive re-renders without causing them (timers, client IDs, stream IDs).- Cleanup effects must use refs for current values, never stale closure state.
- Global state in Zustand store.
useStateonly for ephemeral UI state. - Persisted settings go through IPC to electron-store, never local state.
- Tailwind for layout/spacing. CSS custom properties (
var(--nox-*)) for theme colors. Never hardcode hex colors outsidelib/colors.ts.
- Every handler must validate inputs before acting. The renderer is untrusted.
- Validate ownership for every connection/resource ID. A renderer window may only operate on clients, streams, timers, and files it created or is explicitly allowed to use.
- Validate path strings before any filesystem or remote filesystem operation. Reject null bytes, empty paths where invalid, unreasonable lengths, and paths outside the intended trust boundary.
- Throw typed
Errorsubclasses, neverreject(string). - Store active connections in a
Map<string, T>keyed by UUID. - Always clean up on disconnect: close streams, clear timers, remove from map.
- Use a
require*(id)guard (e.g.requireClient,requireOwnedConn,requireSession) to validate and ownership-check connection/resource IDs, throwing a clear typed error. - Credentials stored in OS keychain via
keytar, never in electron-store. - Secret values require
isUnlocked()gate. Never log credential values.
- Tests live in
__tests__/directories adjacent to source. - Test pure functions by importing them directly. Never duplicate production logic in test files.
- Name test files
<module>.test.ts. - Use
describe/itblocks with clear descriptions that read as specifications. - Run with
npm test(Vitest).
Before handing work back, review the touched code for:
- Security boundary violations between main, preload, and renderer.
- Missing IPC validation or missing ownership checks.
- Duplicated UI chrome, duplicated protocol logic, or one-off styling that should be shared.
- Hidden lifecycle coupling, especially mounted/unmounted terminal, SFTP, Redis, database, and K8s clients.
- Files over 500 lines, deeply nested render logic, dead code, unused imports, and stale comments.
- User-facing error states that imply the wrong subsystem failed.