Files

8.0 KiB

Qortal Hub — agent guide

Desktop/web/mobile client for the Qortal blockchain, plus Reticulum-based peer-to-peer chat and voice. GPL-3.0. React 19 + TypeScript + Vite 7 + MUI 7, wrapped by Capacitor into an Electron desktop app and an Android app.

The repo is a single npm project at the root (the renderer) with a second, separate npm project in electron/ (the Electron main process). They have their own package.json and node_modules. Changes to renderer code only reach the desktop app after a cap sync.

Commands

Root (renderer):

Command What
npm run dev Vite dev server on http://localhost:5173/ — browser only, no Reticulum
npm run build Production build into dist/
npm test Vitest watch mode
npx vitest run <path> Run one test file — preferred while iterating
npm run coverage Vitest with coverage
npm run lint ESLint, --max-warnings 0
npm run format Prettier over the whole repo

Desktop (needs Python 3.9+ and Git on PATH):

npm run build                                # or npm run dev for the web part
npx cap sync @capacitor-community/electron   # REQUIRED after web/native changes
cd electron && npm install && npm run electron:start

Packaging lives in electron/: electron:make-win, electron:make-mac, electron:make-lin-docker, electron:make-arm-docker. Linux release artifacts should use the Docker variants so native binaries build against an older glibc.

There is no typecheck script; tsc runs via vite-plugin-checker during dev.

Layout

src/
  components/   382 files — UI, grouped by feature (Chat/, Group/, Apps/, QortalLand/, ...)
  lib/          call/, dm/, group-call/, reticulum/, webrtc/ — transport + protocol logic
  hooks/        useReticulumGroupChat, useVoiceCall, useAuth, ...
  atoms/        Jotai global state
  contexts/     React contexts (GroupCallContext, CallSwitchGuardContext, ...)
  i18n/         i18next setup + locales/<lang>/<namespace>.json
  qortal/       Qortal Core API calls, q-app request handling
  qdn/          QDN publish/fetch + encryption
  transactions/ signed blockchain transactions
  background/   POW + background tasks
  utils/        events bus, storage, chat helpers
  test/         Vitest setup + mocks for Electron/native modules
electron/src/   Electron main: reticulum-*.ts, group-call.ts, chat-db.ts, core.ts
electron/resources/presence_bridge.py   Python bridge to the Reticulum daemon
docs/           architecture docs — read these before touching Reticulum or calls

The Reticulum path

Renderer (React)  → window.electronAPI.reticulum*() / window.groupCall.*()
Electron main     → reticulum-daemon.ts, reticulum-bridge.ts, group-call.ts
Python bridge     → presence_bridge.py  (fd3/fd4 binary IPC)
rnsd              → Reticulum Network Stack daemon

Reticulum features do not work in the browser dev server — they need the Electron shell. See docs/reticulum.md and docs/group-audio-calls.md.

Conventions

i18n is mandatory. Every user-visible string goes through i18next — including aria-label, placeholders, tooltips, and error messages. See the i18n skill in .agents/skills/i18n/ and docs/i18n_languages.md. Large parts of components/QortalLand/ and the Reticulum* chat components are still hardcoded English and are being migrated; do not copy that pattern.

State. Jotai atoms in src/atoms/ for global state. Cross-component signalling uses a DOM CustomEvent bus, not a store:

import {
  executeEvent,
  subscribeToEvent,
  unsubscribeFromEvent,
} from '../utils/events';

Always unsubscribe in the effect cleanup.

Styling. MUI sx with theme.palette.* — light and dark themes both ship (src/styles/theme-light.ts, theme-dark.ts). Do not hardcode hex colors.

Formatting. Prettier: single quotes, semicolons, 80 columns, 2 spaces, es5 trailing commas. Run npm run format rather than hand-aligning.

TypeScript is not strictstrict: false, noImplicitAny: false. Don't assume null-safety is enforced by the compiler; check at runtime.

Tests. Vitest + jsdom, ~144 test files colocated next to their subject as *.test.ts(x). Files under electron/** run in the node environment instead. Native and Electron modules are aliased to fakes in src/test/mocks/ — extend those rather than mocking ad hoc. Test setup: src/test/setup.ts.

Agent config — skills and commands

Definitions live in .agents/; .claude/ symlinks to them. Claude Code reads only .claude/, so the links are what make the config load.

AGENTS.md                          the file you are reading
CLAUDE.md            → AGENTS.md   loaded unconditionally every session

.agents/skills/<name>/SKILL.md     skills — edit here
.agents/skills/<name>/scripts/     tooling a skill owns, run from the repo root
.agents/commands/<name>.md         slash commands — edit here
.agents/settings.json              shared project settings — edit here

.claude/skills        → ../.agents/skills
.claude/commands      → ../.agents/commands
.claude/settings.json → ../.agents/settings.json
.claude/settings.local.json        real file, machine-local permissions — never commit

Edit the .agents/ originals, never the links. Skills load on demand when their description matches the task; commands are invoked as /<name>.

Currently present: the i18n skill and the write_tests command.

The i18n skill owns the four locale scripts in .agents/skills/i18n/i18n_scan_hardcoded.py, i18n_add_keys.py, i18n_apply_translations.py and i18n_sort.py. Invoke them as python3 .agents/skills/i18n/<name>.py from the repo root; they find src/i18n/locales/ themselves. They used to live in a top-level scripts/ directory, which is gone.

Personal, cross-project equivalents live in ~/.claude/skills/ and ~/.claude/commands/.

.claude/ is the only discovery path. .agents/ and .github/ are not — .github/ belongs to GitHub and Copilot, .agents/ is a third-party installer convention. Files there are silently ignored unless a .claude/ symlink points at them, which is precisely why the links above exist. Note the near-miss: .claude/agents/ (subagent definitions) is real; a top-level .agents/ is not.

If skills or commands stop loading, check the symlinks first — a fresh clone will not have them, since .claude is git-ignored.

Git workflow

develop is the working branch and the PR target. master is stable and release-tagged. Branch as feature/... or fix/... from develop; releases go through release/x.y.z cut from master. Details in docs/contribution.md.

Do not commit or push unless asked.

Gotchas

  • Renderer changes are invisible to Electron until npx cap sync @capacitor-community/electron.
  • First Electron run downloads a Reticulum runtime venv into electron/resources/reticulum-runtime/venv. If it breaks: cd electron && rm -rf resources/reticulum-runtime/venv && npm run bundle:reticulum-venv.
  • Vite workers are built as ES modules (worker.format: 'es') because the audio-decrypt worker dynamically imports WASM — don't switch it to iife.
  • src/components/QortalLand/QortalLand.tsx is ~11k lines. Search within it rather than reading it whole.
  • Some legacy paths are gated by src/constants/featureFlags.ts (isDisabledLegacy).

Docs worth reading first

docs/development.md · docs/contribution.md · docs/reticulum.md · docs/group-audio-calls.md · docs/i18n_languages.md · docs/reticulum-chat-sync-architecture.md · docs/qortalland-asset-standard.md