Skip to content

implement derive(Configurable) macro#228

Open
cooper (czxtm) wants to merge 3 commits into
developfrom
cm/configurable-derive
Open

implement derive(Configurable) macro#228
cooper (czxtm) wants to merge 3 commits into
developfrom
cm/configurable-derive

Conversation

@czxtm
Copy link
Copy Markdown
Member

@czxtm cooper (czxtm) commented May 28, 2026

Summary

Introduces a #[derive(Configurable)] macro (configurable + configurable-derive crates) that generates a store-backed load(app) for dev settings, and applies it to a new EvolutionLimits struct (maxIterations, maxBuildAttempts) read fresh on every evolution run so edits in the dev-settings UI hot-reload. Defaults are unchanged (25 iterations / 5 build attempts).

Test Plan

  • cargo test --manifest-path apps/native/src-tauri/Cargo.toml evolve::config — runs the new EvolutionLimits::load unit tests against a Tauri mock app:
    • empty store falls back to documented defaults (25 / 5)
    • values persisted under maxIterations / maxBuildAttempts override the defaults
    • malformed JSON (schema drift) degrades gracefully to the default
  • Full suite: bun -F native desktop:test (runs cargo test + TS unit) passes.
  • Manual: in the dev-settings UI, change Max Iterations / Max Build Attempts, start a new evolution run, and confirm the new limits take effect without a rebuild.

Docs

  • Docs updated (companion PR in darkmatter/nixmac-web: #___)
  • No docs update needed

… (PoC)

Introduces a proc-macro that eliminates the 6-file boilerplate currently
required for every store-backed setting. A new knob today touches:
storage/store.rs (getter+setter), shared_types/prefs.rs (UiPrefs +
UiPrefsUpdate fields), commands/ui_prefs.rs (read+write branches), TS
bindings, and UI forms — ~25 LOC across 6 files. With this derive, a new
knob is one struct field.

PoC scope — EvolutionLimits only:

  #[derive(Configurable)]
  #[config(store_path = "settings.json")]
  pub struct EvolutionLimits {
      #[config(default = 25, key = "maxIterations")]
      pub max_iterations: usize,
      #[config(default = 5, key = "maxBuildAttempts")]
      pub max_build_attempts: usize,
  }

The derive generates `EvolutionLimits::load(app) -> Result<Self>` that
reads each field from tauri-plugin-store with the per-field default
fallback. Reads happen on every call, so edits via dev settings take
effect on next agent run (hot-reload).

Two new workspace crates:
- configurable/         — runtime trait + read_field() helper
- configurable-derive/  — proc-macro

Bug fix included: evolve/mod.rs:1467 was logging
DEFAULT_MAX_BUILD_ATTEMPTS (the const) instead of the configured value
— exactly the drift this derive is designed to prevent. Threaded
max_build_attempts into process_tool_result().

UiPrefs / UiPrefsUpdate / ui_set_prefs left untouched on purpose — full
migration is tracked under nixmac-e53 with one sub-issue per category.

Closes: nixmac-8ka

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 28, 2026 22:17
Copy link
Copy Markdown
Member Author

cooper (czxtm) commented May 28, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Messages
📖 No docs update needed — acknowledged.

📋 PR Overview

Lines changed 348 (+341 / -7)
Files 5 added, 5 modified, 0 deleted
Draft / WIP no
Has Test Plan yes
New UI components no
New Storybook stories no
New Rust modules yes (1)
New TS source files no
New tests no
package.json touched no
Cargo.toml touched yes
Infra / CI touched no

🔬 Coverage

Report Lines Statements Functions Branches
apps/native/coverage/coverage-summary.json 18.4% 18.4% 31.3% 54.4%

Generated by 🚫 dangerJS against d931010

@czxtm cooper (czxtm) changed the title seralize configurables for ui implement derive(Configurable) macro May 28, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a configurable proc-macro crate (PoC for issue nixmac-8ka) that derives a load(app) method reading typed fields from tauri-plugin-store, and migrates the evolution loop's max_iterations / max_build_attempts knobs to use it. This is the first step toward dissolving the centralized UiPrefs god-struct into co-located, per-module config structs.

Changes:

  • New workspace crates configurable (runtime helper read_field) and configurable-derive (proc-macro emitting load).
  • New evolve/config.rs defining EvolutionLimits via #[derive(Configurable)], replacing the local DEFAULT_MAX_BUILD_ATTEMPTS constant and store::get_max_iterations / store::get_max_build_attempts calls in generate_evolution.
  • Wired max_build_attempts through to process_tool_result so the build-attempt log message uses the configured limit instead of a constant.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Cargo.toml Adds the two new crates to the workspace.
Cargo.lock Lock entries for configurable and configurable-derive.
apps/native/src-tauri/Cargo.toml Depends on the new configurable crate.
apps/native/src-tauri/configurable/Cargo.toml Manifest for the runtime crate.
apps/native/src-tauri/configurable/src/lib.rs Re-exports the derive and defines read_field.
apps/native/src-tauri/configurable-derive/Cargo.toml Proc-macro crate manifest.
apps/native/src-tauri/configurable-derive/src/lib.rs Implements #[derive(Configurable)] parsing store_path, per-field default / key.
apps/native/src-tauri/src/evolve/config.rs Declares EvolutionLimits using the new derive.
apps/native/src-tauri/src/evolve/mod.rs Replaces store getter + constant with EvolutionLimits::load; threads max_build_attempts into process_tool_result.
.beads/issues.jsonl Adds the PoC bead and follow-up migration beads.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +654 to +660
} = config::EvolutionLimits::load(app).unwrap_or_else(|e| {
warn!("EvolutionLimits::load failed ({e}); using defaults");
config::EvolutionLimits {
max_iterations: 25,
max_build_attempts: 5,
}
});
Comment on lines +39 to +42
let store = app.store(store_path)?;
Ok(store
.get(key)
.and_then(|value| serde_json::from_value(value.clone()).ok()))
Comment on lines +53 to +58
Ok(quote! {
#ident: ::configurable::read_field::<R, #ty>(app, #store_path, #key)?
.unwrap_or_else(|| #default),
})
})
.collect::<syn::Result<Vec<_>>>()?;
Adds a #[cfg(test)] module exercising the Configurable-derived load():
defaults on empty store, stored-value override, and graceful fallback on
schema drift. Enables the tauri 'test' feature in dev-dependencies for
mock_builder. Satisfies the danger 'new Rust module without tests' gate.
@czxtm cooper (czxtm) self-assigned this May 31, 2026
…rive

# Conflicts:
#	apps/native/src-tauri/Cargo.toml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants