Skip to content

fix(hands): merge Hand settings on save instead of replacing the whole config#6213

Merged
houko merged 9 commits into
mainfrom
fix/6204-hands-settings
Jun 19, 2026
Merged

fix(hands): merge Hand settings on save instead of replacing the whole config#6213
houko merged 9 commits into
mainfrom
fix/6204-hands-settings

Conversation

@houko

@houko houko commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses #6204 (Hands settings bug). The issue is a screen-recording with no text, so I traced the Hands settings surfaces for the strongest concrete, code-grounded bug.

Root cause

The Hand settings editor builds its PUT payload only from draft — the per-session diff containing just the keys whose input fired onChange (HandsPage.tsx). update_hand_settings forwards that partial map straight to HandRegistry::update_config, which does a wholesale replace (entry.config = config, registry.rs:1355, pinned by the update_config_replaces_and_refreshes unit test). So for a Hand with more than one setting, saving one field overwrote the instance config with just that key and reverted every other previously-saved setting to its default. Invisible for single-setting Hands; data-loss for multi-setting Hands.

Fix

  • crates/librefang-api/src/routes/skills/hands.rsupdate_hand_settings now reads the active instance's current config and merges the incoming keys over it, making the endpoint a true partial update. The registry's update_config replace contract is left unchanged (it has one other caller and a unit test pinning it).
  • crates/librefang-api/dashboard/src/pages/HandsPage.tsx — the editor seeds its payload from the saved current_values before overlaying the draft, as defense in depth.

Confidence / scope note

Medium confidence this is the bug the (text-less) video depicts — it's the strongest concrete settings-save defect in this area. If the video instead shows the agent-config tab (system prompt / tools), that path persists and refetches correctly; the only residual there is the state-resync useEffect potentially wiping an unsaved draft on a window-focus refetch — flagging for whoever has seen the video.

No backend integration test was added: the hands test harness installs no fixture Hand, so the settings happy-path is unreachable without new fixture infrastructure (out of scope for this fix). Recommend a follow-up that adds a fixture Hand with ≥2 settings and asserts PUT{a}→PUT{b}→GET keeps both.

Verification

⚠️ Local verification was not possible (host disk full, OrbStack down → no cargo; disk too tight to risk pnpm). Both changes are static-reviewed: backend — HandInstance.config and the incoming body are both HashMap<String, Value>, so read→extendupdate_config(merged) type-checks; frontend — current_values?: Record<string, unknown> spreads cleanly into the payload. CI is the verifiercargo check --workspace --lib, cargo test -p librefang-api, cargo test -p librefang-hands (replace contract unchanged), and pnpm typecheck.

…e config

The Hand settings editor PUTs only the keys the user changed this session, but update_hand_settings forwarded that partial map straight to HandRegistry::update_config, which does a wholesale entry.config = config replace. For a Hand with more than one setting, saving one field overwrote the instance config with just that key and reverted every other previously-saved setting to its default.
Read the active instance's current config in the route and merge the incoming keys over it, so untouched settings survive; the registry's replace contract is left unchanged (it has one other caller and a unit test pinning the replace semantics). The dashboard editor also seeds its payload from the saved current_values as defense in depth.

Closes #6204
@github-actions github-actions Bot added size/M 50-249 lines changed area/docs Documentation and guides labels Jun 18, 2026

@houko houko left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Style fixes pushed directly (1 commit); one blocking item flagged inline.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Per CLAUDE.md §"What you MUST do for any route / wiring change": every route change needs a #[tokio::test] against TestServer before merge.

The PR body explains the blocker honestly — no fixture Hand exists in the test harness, so the settings happy-path is unreachable. That's a real constraint, not an excuse to skip, so it needs to be resolved in this PR rather than deferred to a follow-up.

Minimal unblocking path (no new fixture infrastructure required): add a test that POSTs to PUT /api/hands/{id}/settings against start_test_server() with a non-existent hand_id and asserts the 404 + "Activate the hand first" error shape. That covers the route registration, auth middleware pass-through, and None branch without needing a live Hand. For the merge path, a second test can install a minimal stub Hand (or mock HandRegistry) and assert the extend semantics — but the 404 test alone satisfies the "route wired and gated" bar that reviewers check.


Generated by Claude Code

@houko houko left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

CLAUDE.md requires a #[tokio::test] against TestServer for any route behaviour change (update_hand_settings now merges instead of replacing). The PR body acknowledges the gap and proposes a follow-up, but CLAUDE.md's rule is: defer only if the fix would touch a different crate or domain — the test would live in crates/librefang-api/tests/, the same domain as this route. "New fixture infrastructure" for a Hand instance is worth adding here rather than deferring; the suggested test shape from the PR body is exactly right: install a fixture Hand with ≥ 2 settings, PUT {a}, PUT {b}, GET, assert both keys are present. Please add that before merge.


Generated by Claude Code

Adds a TestServer integration test that installs a two-setting hand,
activates it with both keys set, then PUTs a single key and asserts the
response config preserves the untouched key. Guards against a revert of
the merge fix this PR introduced.
@github-actions github-actions Bot added the has-conflicts PR has merge conflicts that need resolution label Jun 18, 2026
# Conflicts:
#	.secrets.baseline
@houko
houko enabled auto-merge (squash) June 19, 2026 04:51
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review has-conflicts PR has merge conflicts that need resolution and removed has-conflicts PR has merge conflicts that need resolution ready-for-review PR is ready for maintainer review labels Jun 19, 2026
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels Jun 19, 2026
@github-actions github-actions Bot added has-conflicts PR has merge conflicts that need resolution and removed ready-for-review PR is ready for maintainer review labels Jun 19, 2026
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review has-conflicts PR has merge conflicts that need resolution and removed has-conflicts PR has merge conflicts that need resolution ready-for-review PR is ready for maintainer review labels Jun 19, 2026
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review has-conflicts PR has merge conflicts that need resolution and removed has-conflicts PR has merge conflicts that need resolution ready-for-review PR is ready for maintainer review labels Jun 19, 2026
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels Jun 19, 2026
@houko
houko merged commit 9fa36b2 into main Jun 19, 2026
33 checks passed
@houko
houko deleted the fix/6204-hands-settings branch June 19, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides ready-for-review PR is ready for maintainer review size/M 50-249 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants