Skip to content

fix(desktop): emit camelCase config-write payload fields - #6062

Open
Chessing234 wants to merge 5 commits into
block:mainfrom
Chessing234:fix/config-write-mechanism-wire-casing
Open

fix(desktop): emit camelCase config-write payload fields#6062
Chessing234 wants to merge 5 commits into
block:mainfrom
Chessing234:fix/config-write-mechanism-wire-casing

Conversation

@Chessing234

Copy link
Copy Markdown
Contributor

Fixes #6015.

ConfigWriteMechanism is internally tagged and carried only rename_all = "camelCase". On an internally tagged enum that renames the variants, never the variants' fields, so the payload went out snake_case:

{"type":"respawnWithEnvVar","env_key":"K"}
{"type":"acpSetConfigOption","config_id":"c"}
{"type":"acpSetSessionModel"}
{"type":"gooseNativeConfigWrite","config_key":"g"}
{"type":"readOnly"}

against envKey / configId / configKey in desktop/src/shared/api/types.ts:615-620. That output is a probe run of the real module before the fix, not a reading of the code.

What makes it read as correct is the asymmetry: the variant names rename fine, so the type discriminant and every switch (writeVia.type) behave; and the enclosing NormalizedField's own fields (writeVia, overriddenValue, isRequired) rename fine too, because rename_all does apply to struct fields. Only the variant's field is wrong.

Adding rename_all_fields = "camelCase" fixes it. rename_all_fields appeared zero times in desktop/src-tauri before this.

Severity, stated plainly: latent, not currently user-visible. Nothing in desktop/src/** reads .envKey/.configId/.configKey off a writeViaAgentConfigPanel.tsx is the only RuntimeConfigSurface consumer and never touches the field, and no Rust code deserializes the type either. The write-back path these fields exist for is not wired yet. The hazard is for whoever wires it: invokeTauri<T> is an unchecked cast, so they get undefined with a green tsc.

I also carried the attribute onto ConfigFieldType. Its only payload field is options, single-word, so that half is not a fix — it is the attribute the next multi-word field would silently need.

One divergence from the issue's suggested step 3: the 20 e2eBridge.ts sites already emit camelCase, and camelCase is the contract, so they are correct as written — changing them would have been wrong. What they lacked was provenance, since agreeing with api/types.ts while the backend emitted something else is exactly what let this sit. They now name the Rust test that pins the bytes.

Tests (wire_format_tests, 4 cases, whole-value not key-set — a key-set assertion still passes when a variant name regresses):

  • every variant against the TypeScript spelling;
  • the nested NormalizedField, which is the shape the renderer actually receives;
  • a camelCase round-trip plus an assertion that the old env_key spelling is now rejected, so a revert cannot quietly keep deserializing;
  • ConfigFieldType::Enum.

Removing rename_all_fields again turns three of the four red.

Verified locally: full Tauri library suite 2444 passed / 15 ignored / 0 failed; cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warnings clean; cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --check; in desktop/: pnpm typecheck, pnpm check, pnpm test 4954 passed; git diff --check. Not run: the app itself — there is no UI path to this field yet, which is the same reason the bug is latent.

On an internally tagged enum, serde's rename_all renames the variants, not
the variants' fields. ConfigWriteMechanism carried only rename_all, so it
serialized as

  {"type":"respawnWithEnvVar","env_key":"GOOSE_MODE"}

while desktop/src/shared/api/types.ts declares envKey — likewise configId
and configKey. The variant names were right, so the type discriminant and
every switch on it behaved, and the enclosing NormalizedField's own fields
renamed correctly; only the variant payload was snake_case.

invokeTauri<T> is an unchecked cast, so tsc reports nothing: whoever wires
the config write-back path would read writeVia.envKey, get undefined, and
have a green typecheck.

rename_all_fields is added to ConfigFieldType too. Its only payload field is
single-word today, so that is not a fix — it is the attribute the next
multi-word field would silently need.

Refs block#6015

Signed-off-by: Taksh <takshkothari09@gmail.com>
Whole-value assertions against the TypeScript contract, not key-set checks:
a key-set assertion still passes when the variant name regresses, and the
type discriminant is what every switch (writeVia.type) reads.

Four cases: all five ConfigWriteMechanism variants; the nested
NormalizedField, which is the shape the renderer actually receives and where
the mismatch hid, since the enclosing struct renamed correctly; a round-trip
that also asserts the old snake_case spelling is now rejected, so a revert
cannot quietly keep deserializing; and ConfigFieldType's payload variant.

Removing rename_all_fields turns three of the four red.

Refs block#6015

Signed-off-by: Taksh <takshkothari09@gmail.com>
The mock already hand-wrote the camelCase shape at 20 sites, so it agreed
with api/types.ts while the real serializer emitted env_key — a test against
it certified a contract nothing produced. Name the Rust test that now pins
the bytes, so the two move together.

Refs block#6015

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234
Chessing234 requested a review from a team as a code owner August 16, 2026 17:16

@themiguelamador themiguelamador left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I found and fixed two minor review issues:

  • P3 — Keep the patch scoped to the broken wire contract. rename_all_fields was also added to ConfigFieldType, but its only payload field (options) is already a single camel-case word. That change has no effect today and silently chooses serialization behavior for future fields unrelated to this bug, so I removed it and its non-regression test.
  • P3 — Assert JSON semantics, not serializer property order. The new tests compared exact serialized strings, coupling the contract tests to object-key order even though JSON object order is not semantic. I changed them to whole-value serde_json::Value comparisons, which still verify every key and value (including nested fields) without overconstraining the encoder.

Fix branch: https://github.com/Complear/buzz/tree/review/pr-6062-fix
Commit: d0f57d1de

Validation: full Tauri library suite (2,443 passed, 15 ignored), strict Tauri workspace/all-target clippy, focused wire-format tests, cargo fmt, desktop typecheck, full desktop checks, and git diff --check all passed. The desktop checks reported only the four pre-existing advisory Biome diagnostics outside this PR.

Review finding (P3, themiguelamador on block#6062): `rename_all_fields` was also
added to `ConfigFieldType`, whose only payload field is `options` — already a
single camel-case word. Verified inert: the other three variants are unit
variants, and `desktop/src/shared/api/types.ts` declares the enum as
`{ type: "enum"; options: string[] }`, so the attribute changes no byte the
renderer sees today. It only pre-decides serialization for fields that do not
exist yet and are not part of this bug.

Removed it and the test that pinned it — that test was explicitly a shape pin,
not a non-regression; nothing it asserted could have failed.

`ConfigWriteMechanism` keeps the attribute: there it is the fix.

Signed-off-by: Taksh <takshkothari09@gmail.com>
Review finding (P3, themiguelamador on block#6062): the wire-format tests compared
exact serialized strings, which couples them to the encoder's object-key order
even though JSON object order is not semantic. A field reordered in the struct
would fail these tests without changing anything a reader sees.

They now compare whole `serde_json::Value`s built with `json!`. That still
verifies every key and value, nested ones included, and still catches a
renamed variant or field — which a key-set assertion would not — without
overconstraining the encoder.

The round-trip test keeps its string literals: there the exact input bytes are
the point, since it asserts what the renderer sends is accepted and the
pre-fix snake_case spelling is not.

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234

Copy link
Copy Markdown
Contributor Author

Both applied, one commit each (3ae0add, 935d6a4). (Complear/buzz 404s, so written from your description.)

P3 — scope. Verified inert before removing it: ConfigFieldType's other three variants are unit variants, and api/types.ts declares the enum as { type: "enum"; options: string[] }, so rename_all_fields changes no byte the renderer sees today. Removed it and its test — that test was explicitly a shape pin, not a non-regression, and nothing it asserted could have failed. ConfigWriteMechanism keeps the attribute, where it is the fix.

Worth stating the trade-off you're accepting, since it's the same footgun this PR exists to close: a future multi-word field on ConfigFieldType will serialize snake_case and invokeTauri<T>'s unchecked cast won't catch it. I agree that's not this patch's job to pre-empt.

P3 — JSON semantics. Agreed, key order isn't semantic. The two serialization tests now compare whole serde_json::Values built with json!, which still catches a renamed variant or field. The round-trip test keeps its string literals deliberately: there the exact input bytes are the point, since it asserts the renderer's shape is accepted and the pre-fix snake_case spelling isn't.

Verification: full Tauri lib suite (2,443 passed, 15 ignored), strict workspace/all-target clippy, and cargo fmt --check.

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.

Desktop IPC: ConfigWriteMechanism variant fields serialize snake_case against a camelCase TS contract (missing rename_all_fields)

2 participants