ADR-002: Rust-First Core with Thin Host Adapters
The hot path in Palamedes is dominated by work that benefits from a native implementation:
Status: Accepted Date: 2026-03-17 Revised: 2026-08-11
Context
- parsing and traversing source files
- extracting messages
- transforming macro syntax into runtime calls
- reading and writing catalogs
- validating and normalizing ICU message structures
If that path is split across many TypeScript layers and utility packages, the project pays several costs:
- semantic logic gets duplicated
- boundaries become chatty
- performance improvements are harder to realize end-to-end
- it becomes unclear which layer actually owns i18n behavior
At the same time, Palamedes still lives in JavaScript application ecosystems. Some responsibilities are naturally specific to those hosts:
- JavaScript-host config loading
- bundler integration
- framework hooks
- packaging and host-facing API ergonomics
That means Palamedes needs a strict ownership split rather than a vague "native helpers under a JavaScript toolchain" model.
Decision
Palamedes adopts a Rust-first architecture.
The architectural rule is:
- Rust owns semantic i18n logic
- TypeScript owns host integration
Rust is the primary home for:
- macro transformation
- message extraction
- catalog reading and updating
- ICU parsing and validation
- compiled catalog preparation
- other host-neutral i18n semantics
Rust also owns the native CLI's config discovery and validation, source-file traversal and watch orchestration, command-line UX, and reporting. TypeScript is the primary home for:
- Vite and Next.js adapter wiring
- JavaScript-host configuration integration
- packaging and host-facing API ergonomics
The TypeScript layer should call coarse native operations rather than rebuilding semantic pipelines locally.
Alternatives Considered
1. TypeScript-first architecture with native helpers
This would keep JavaScript as the main execution model and use Rust only for selected hotspots.
Rejected because it preserves duplicated semantics and encourages the exact split-brain architecture Palamedes is trying to avoid.
2. Move everything possible into Rust
This would also push JavaScript-host config loading, bundler integration, framework hooks, and packaging concerns into the native layer.
Rejected because those concerns are defined by JavaScript ecosystem APIs and become awkward when forced into the core. Native CLI config, file watching, and command-line UX remain Rust responsibilities because they do not depend on those host APIs.
3. Keep separate semantic pipelines per package
This would let CLI, bundler adapters, and low-level packages each own more local semantics.
Rejected because it weakens the product model and makes correctness harder to maintain.
Consequences
- Native APIs should be shaped around meaningful domain operations instead of small helper calls.
- TypeScript packages should increasingly become orchestration layers over the same core semantics.
- Performance work should prefer end-to-end coarse operations over isolated helper acceleration.
- Architectural discussions should start by asking whether a concern is semantic and host-neutral or host-specific and orchestration-oriented.