Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Conventions

Coding and project conventions for contributing to Hone.

Perry-Safe TypeScript

All code that will be compiled by Perry must avoid the constrained patterns. The key rules:

  • No optional chaining (?.) — use explicit null checks.
  • No nullish coalescing (??) — use if (x !== undefined).
  • No dynamic key access (obj[variable]) — use if/else chains.
  • No for...of on arrays — use indexed for loops.
  • No regex — use indexOf and character checks.
  • No ES6 shorthand properties — write { key: key }.
  • No Array.map/filter/reduce on class fields — use for loops.
  • No closures capturing this — use module-level state and named functions.

When in doubt, prefer explicit and verbose over concise and clever.

Database Schemas

All database identifiers use camelCase:

CREATE TABLE magicLinks (
    id INT PRIMARY KEY AUTO_INCREMENT,
    userId INT NOT NULL,
    tokenHash VARCHAR(64) NOT NULL,
    expiresAt BIGINT NOT NULL,
    createdAt BIGINT NOT NULL
);

This applies to table names, column names, and index names.

Test Imports

Bun-based packages (hone-core, hone-editor, hone-terminal, hone-relay, hone-build) import test utilities from bun:test:

import { describe, it, expect, beforeEach, afterEach } from 'bun:test';

Do not import from vitest, jest, or @jest/globals in these packages.

Platform Detection

Use the __platform__ compile-time constant for platform-specific code:

declare const __platform__: number;

// 0 = macOS, 1 = iOS, 2 = Android
if (__platform__ === 0) {
  // macOS-specific code
}

Non-matching branches are dead-code-eliminated at compile time.

Configuration Files

Service configuration uses KEY=VALUE format in .conf files:

PORT=8445
DB_HOST=webserver.skelpo.net
AUTH_SECRET=my-secret

No quotes around values. No section headers. One key-value pair per line.

Project Structure

  • No Rust in hone-ide. All Rust code is in ../perry/ (compiler, runtime, UI libraries) or in hone-editor/native/ and hone-terminal/native/ (FFI crates). The IDE package is pure TypeScript.
  • No workspace manager. Each package is independent with its own package.json, its own node_modules, and its own test runner.
  • No shared dependencies. Packages do not hoist or share node_modules. Install dependencies in each package individually.

Closure Rule

Perry captures closure variables by value at creation time. Mutations after closure creation are invisible to the closure.

// WRONG — Perry captures the initial value of count
let count = 0;
setInterval(() => { count++; }, 1000);  // always increments from 0

// RIGHT — module-level named function reads current state
let _count = 0;
function tick() { _count++; }
setInterval(tick, 1000);

Store mutable state in module-level let variables. Access it through module-level named functions. Never capture this in a closure.

FFI Conventions

  • Use f64 for numeric parameters, i64 for string/pointer parameters. Never i32.
  • Functions are exported as __wrapper_<name> symbols.
  • All FFI functions must be declared in package.json under perry.nativeLibrary.functions.

See FFI Conventions for full details.