Extensibility
Core Principle: Optimization Axes
Section titled “Core Principle: Optimization Axes”Every optimization the app does is modelled as an axis in the combination space. An axis represents “for this slot/item/scope, here are N options to try.”
interface OptimizationAxis { id: string; // e.g. "slot:trinket1", "enchant:finger1" label: string; // human-readable for UI options: OptimizationOption[];}
interface OptimizationOption { id: string; label: string; simcLines: string[]; // SimC profile lines this option produces}The combination generator takes OptimizationAxis[] and returns the cartesian product. It doesn’t know or care whether an axis is a gear slot, a gem, an enchant, or a future feature.
Adding a new optimization type = defining new OptimizationAxis instances. Nothing else in the system needs to change.
Layered Architecture
Section titled “Layered Architecture”┌─────────────────────────────────────────────────┐│ LAYER 4: UI Components ││ GearSlotCard, GemSelector, EnchantSelector ││ Each produces OptimizationAxis[] for its domain │├─────────────────────────────────────────────────┤│ LAYER 3: Optimization Assembler ││ Collects all axes, computes combo count │├─────────────────────────────────────────────────┤│ LAYER 2: Combination Generator (combinator.ts) ││ Pure: OptimizationAxis[] → combinations[] │├─────────────────────────────────────────────────┤│ LAYER 1: ProfileSet Builder ││ combinations[] + base profile → .simc file │├─────────────────────────────────────────────────┤│ LAYER 0: SimC Runner (Rust / Tauri command) ││ Runs SimC, returns SimResult[] │└─────────────────────────────────────────────────┘Adding a new optimization type only touches Layer 4 (new UI component) and possibly Layer 3 (registering the new axis in the assembler).
How to Add a New Optimization Type
Section titled “How to Add a New Optimization Type”Example: adding embellishment comparison.
Step 1 — Create the UI component
Section titled “Step 1 — Create the UI component”src/components/optimizations/EmbellishmentSelector.tsxThis component lets the user select embellishments and exports:
function EmbellishmentSelector({ profile }: Props): { axes: OptimizationAxis[]; ui: JSX.Element;};Step 2 — Register in the Optimization Assembler
Section titled “Step 2 — Register in the Optimization Assembler”In optimization-assembler.ts, add the new component’s axes to the list passed to the combination generator.
Step 3 — Add presets (optional)
Section titled “Step 3 — Add presets (optional)”If the feature has a known set of options, add a preset file:
src/lib/presets/embellishment-presets.tsStep 4 — Feature flag it
Section titled “Step 4 — Feature flag it”export const FEATURES = { // ... EMBELLISHMENT_COMPARISON: false, // set true when ready} as const;Preset Files Pattern
Section titled “Preset Files Pattern”Presets export typed arrays for UI dropdowns:
src/lib/presets/├── season-config.ts ← all season data├── gem-presets.ts ← re-exports from season-config├── enchant-presets.ts ← re-exports from season-config└── (future) ├── embellishment-presets.ts └── consumable-presets.tsAdding New Tauri Commands
Section titled “Adding New Tauri Commands”src-tauri/src/commands/my_feature.rs- Define:
#[tauri::command] pub async fn my_feature(...) -> Result<MyOutput, String> - Register in
main.rs:tauri::generate_handler![..., my_feature] - Call from TS:
invoke<MyOutput>('my_feature', { args })
Never put business logic in main.rs — it’s only a wiring file.
What “Easily Extensible” Means
Section titled “What “Easily Extensible” Means”A feature is easy to add if:
- It requires changes to at most 3 files
- It does not modify the combination generator or ProfileSet builder
- It does not modify any existing component (only adds new ones)
- It can be feature-flagged with a single boolean
Testing Requirements
Section titled “Testing Requirements”Every new module in src/lib/ must have a corresponding .test.ts file.
The combinator must have tests covering:
- Single axis (no multiplication)
- Multiple axes (cartesian product)
- Cap enforcement (1000 max)
- Empty axis (no options) → locked/pinned