archy/.planning/codebase/CONVENTIONS.md
2026-07-29 10:25:54 -04:00

8.6 KiB
Raw Blame History

Coding Conventions

Analysis Date: 2026-07-29

Naming Patterns

Files:

  • TypeScript/Vue: PascalCase for components (e.g., ToggleSwitch.vue, SendBitcoinModal.vue), camelCase for composables and stores (e.g., useFileType.ts, controller.ts)
  • Rust: snake_case for modules and files (e.g., bitcoin_rpc.rs, storage_crypto.rs)
  • Test files: co-located with source in __tests__/ subdirectories with .test.ts or .spec.ts suffix for Vitest, .bats for shell tests
  • Constants in TypeScript use UPPER_SNAKE_CASE within modules (e.g., IMAGE_EXTS, CATEGORY_COLORS in useFileType.ts)

Functions:

  • TypeScript/Vue: camelCase for all functions (e.g., getFileCategory, formatSize, useFileType)
  • Composables: use prefix for Vue composables (e.g., useFileType, useToast, useMessageToast) — exported as named exports or default exports
  • Store functions (Pinia): defined with snake_case action names, exported from defineStore factory
  • Rust: snake_case for all functions and methods (e.g., doesnt_reallocate, following Rust conventions)

Variables:

  • TypeScript: camelCase for local variables and reactive refs (e.g., modelValue, isActive, gamepadCount)
  • Refs (Vue 3): prefix not required, but convention is lowercase start (e.g., const ext = ref('jpg'))
  • Computed properties: camelCase, explicit .value suffix in templates when needed
  • Parameters: camelCase, typed explicitly in TypeScript (e.g., password: string, isDir: Ref<boolean>)

Types:

  • TypeScript: PascalCase for type aliases and interfaces (e.g., RPCOptions, FileCategory, CatalogVersionInfo)
  • Union types: PascalCase (e.g., PendingState = 'pending' | 'sent' | 'approved')
  • Component props: typed with defineProps<{ ... }>() syntax in <script setup>
  • Rust: PascalCase for structs and enums, snake_case for fields within them

Code Style

Formatting:

  • No global Prettier config; code style follows project patterns incrementally
  • Vue components: single-file components (.vue) with <template>, <script setup>, optional <style scoped>
  • TypeScript: indentation is 2 spaces (visible in vitest.config.ts, Vue components, test files)
  • Line width: no strict enforcement observed; pragmatic wrapping around 80100 characters
  • Arrow functions preferred for short callbacks: (x) => x * 2
  • Template strings for multi-line formatting

Linting:

  • No .eslintrc detected at repo root or neode-ui/ level
  • Rust: Clippy allowances declared at crate level in main.rs (#![allow(...)]) to suppress stylistic lints and focus CI on correctness issues
  • Examples of suppressed Clippy lints: too_many_arguments, type_complexity, enum_variant_names, unused_io_amount

Import Organization

Order:

  1. Vue framework imports (import { computed, ref, type Ref } from 'vue')
  2. Library imports (import { defineStore } from 'pinia', import { format } from 'date-fns')
  3. Local module imports (import { useFileType } from '../useFileType', import { rpcClient } from '../api/rpc-client')
  4. Types/interfaces (inline in import statements via type keyword when needed)
  5. No blank lines required between groups in practice

Path Aliases:

  • TypeScript: @ alias maps to src/ (configured in vitest.config.ts and tsconfig.json)
  • Usage: import { displayVersion } from '@/utils/version'
  • Rust: crate-relative paths (use crate::module::submodule) and external crate paths

Error Handling

Patterns:

  • TypeScript: explicit try-catch with error type narrowing (e.g., if (error instanceof Error) { ... })
  • RPC client (rpc-client.ts): catches fetch errors, AbortError, and HTTP errors; distinguishes retryable (502, 503) from permanent (401, 403)
  • Rust: anyhow::Result<T> for fallible operations; ? operator for error propagation; .context("message") for adding context
  • Backend error responses: JSON-RPC format with error: { code, message, data? } structure; UI catches and displays via toast system
  • Network errors: automatic retry with exponential backoff (600ms × (attempt + 1) with jitter); configurable maxRetries per call

Logging

Framework: console object for frontend, tracing crate for Rust backend

Patterns:

  • Frontend: console.warn, console.error used selectively (e.g., [RPC] prefixed logs in rpc-client.ts for session/CSRF events)
  • Rust: tracing::info!, tracing::warn! for structured logging; println! avoided in production code
  • No log levels enforced or documented; pragmatic use based on severity

Comments

When to Comment:

  • Explain non-obvious retry logic, timeout decisions, CSRF handling (see rpc-client.ts lines 138178 for example)
  • Clarify why a workaround exists (e.g., "Already on the login page: redirecting = a full reload")
  • Document integration points with backend RPC methods and their expected response shapes
  • Avoid redundant comments restating what the code obviously does

JSDoc/TSDoc:

  • Function parameter types documented inline via TypeScript type annotations (e.g., ext: Ref<string>)
  • Minimal use of explicit JSDoc blocks; type signature is the primary documentation
  • Comments above exports explain purpose in one sentence (e.g., "// RPC Client for connecting to Archipelago backend")
  • Optional fields in interfaces documented via property-level comments (e.g., /** Abort the call from the outside … */)

Function Design

Size: Functions are typically 550 lines; error-handling paths in callInner<T> (rpc-client.ts) stretch to 120 lines but remain single-responsibility (retry logic + error classification)

Parameters:

  • Prefer object parameters for 3+ arguments (e.g., RPCOptions object over separate method, params, timeout)
  • Vue composables accept Ref<T> types to maintain reactivity (e.g., useFileType(ext: Ref<string>, isDir: Ref<boolean>))
  • Store action functions accept only necessary parameters; broader state via closure

Return Values:

  • Composables return object with properties (computed values + reactive refs): { category, isImage, isAudio, ... }
  • Store actions return void or the modified state
  • Utilities return simple values or objects (e.g., formatSize returns string, formatDate returns string)
  • Async functions return Promise<T> with explicit type parameters (e.g., async call<T>(options): Promise<T>)

Module Design

Exports:

  • Composables export a single named function and helper functions: export function useFileType(...), export function getFileCategory(...)
  • Stores export the Pinia store factory: export const useControllerStore = defineStore(...)
  • RPC client exports as singleton instance: export const rpcClient = new RPCClient()
  • Utilities export multiple helpers from the same file (e.g., formatSize, formatDate from same module)

Barrel Files:

  • Not observed as a primary pattern; each file self-documents its exports
  • Imports use direct paths (e.g., from '../composables/useFileType') rather than barrel index.ts
  • Test files import specific utilities directly to minimize test setup complexity

Type Safety

Vue 3 with TypeScript:

  • Components use <script setup lang="ts"> with defineProps<{ ... }>() and defineEmits<{ ... }>()
  • Props explicitly typed as interfaces/objects with required/optional fields marked
  • Events typed as call signatures (e.g., 'update:modelValue': [value: boolean])
  • Reactive variables typed at declaration: const isActive = ref<boolean>(false), or via inference when obvious

Rust:

  • Explicit type annotations on public APIs; inference acceptable inside functions
  • Generic parameters used to encode interface contracts (e.g., struct DataUrl<'a>)
  • Pattern matching to handle enums and Option<T> / Result<T, E> types safely

Component Architecture (Vue)

File structure:

  • Single-file components with template → script → (optional) style
  • Props come first, emits second, internal state/computed/methods follow
  • One component per file (naming matches the file name)
  • Slots used minimally; prefer explicit prop configuration over slot forwarding

Reactivity:

  • ref() for primitive/object state; computed() for derived values
  • watch() used for side effects on ref changes (not extensively shown in samples but implied)
  • Pinia stores used for global state (authentication, app list, mesh status, etc.)

Constants and Enums

Pattern: Constants are module-level const with UPPER_SNAKE_CASE names and immutable type annotations:

const IMAGE_EXTS = new Set(['jpg', 'jpeg', ...])
const CATEGORY_COLORS: Record<FileCategory, string> = { ... }

Enums: Type aliases preferred over TypeScript enum keyword (e.g., type FileCategory = 'folder' | 'image' | ...)


Convention analysis: 2026-07-29