Summary
Broken UX due to invalid category
Affected component
runtime/daemon
Severity
S2 - degraded behavior
Current behavior
The memory API returns category as {"custom": "travel"} for custom categories,
but the React frontend expects a plain string. This causes [object Object]
rendering errors on the Memory page.
The root cause is in Rust's serde: #[serde(rename_all = "snake_case")] with the default externally-tagged enum
representation serializes unit variants as strings ("core", "daily") but wraps
data-carrying variants in objects ({"custom": "travel"}).
Fix: Custom serde for MemoryCategory
File: src/memory/traits.rs
Replace the derived Serialize/Deserialize on MemoryCategory with custom
implementations that always serialize/deserialize as a plain string — matching
the existing Display impl and category_to_str/str_to_category pattern in
sqlite.rs.
- Remove Serialize, Deserialize from the derive macro on MemoryCategory
- Remove
#[serde(rename_all = "snake_case")]
- Add manual Serialize impl: serialize as the string form ("core", "daily",
"conversation", or the custom name)
- Add manual Deserialize impl: deserialize a string, mapping known names to
their variants and everything else to Custom(s)
- Update the existing serde test (memory_category_serde_uses_snake_case) to
also cover Custom("travel") → "travel" round-trip
No frontend changes needed — the TS type category: string is already correct;
it's the backend that's violating the contract.
Verification
cargo test -p zeroclaw -- memory_category_serde
cargo clippy --all-targets -- -D warnings
Then hit GET /api/memory and confirm custom categories return as plain
strings.
Expected behavior
Page will Render correctly the TS type category: string is already correct
Steps to reproduce
#[test]
fn memory_category_serde_uses_snake_case() {
// Serialization: all variants produce plain strings
let core = serde_json::to_string(&MemoryCategory::Core).unwrap();
let daily = serde_json::to_string(&MemoryCategory::Daily).unwrap();
let conversation = serde_json::to_string(&MemoryCategory::Conversation).unwrap();
let custom = serde_json::to_string(&MemoryCategory::Custom("travel".into())).unwrap();
assert_eq!(core, "\"core\"");
assert_eq!(daily, "\"daily\"");
assert_eq!(conversation, "\"conversation\"");
assert_eq!(custom, "\"travel\"");
// Deserialization: round-trip for all variants
assert_eq!(
serde_json::from_str::<MemoryCategory>("\"core\"").unwrap(),
MemoryCategory::Core
);
assert_eq!(
serde_json::from_str::<MemoryCategory>("\"daily\"").unwrap(),
MemoryCategory::Daily
);
assert_eq!(
serde_json::from_str::<MemoryCategory>("\"conversation\"").unwrap(),
MemoryCategory::Conversation
);
assert_eq!(
serde_json::from_str::<MemoryCategory>("\"travel\"").unwrap(),
MemoryCategory::Custom("travel".into())
);
}
Impact
Broken Memory Page
Logs / stack traces
ZeroClaw version
f2ba33f
Rust version
1.93.1
Operating system
Unbuntu
Regression?
Unknown
Pre-flight checks
Summary
Broken UX due to invalid category
Affected component
runtime/daemon
Severity
S2 - degraded behavior
Current behavior
The memory API returns
categoryas{"custom": "travel"}for custom categories,but the React frontend expects a plain string. This causes
[object Object]rendering errors on the Memory page.
The root cause is in Rust's serde:
#[serde(rename_all = "snake_case")]with the default externally-tagged enumrepresentation serializes unit variants as strings ("core", "daily") but wraps
data-carrying variants in objects (
{"custom": "travel"}).Fix: Custom serde for MemoryCategory
File:
src/memory/traits.rsReplace the derived Serialize/Deserialize on MemoryCategory with custom
implementations that always serialize/deserialize as a plain string — matching
the existing Display impl and category_to_str/str_to_category pattern in
sqlite.rs.
#[serde(rename_all = "snake_case")]"conversation", or the custom name)
their variants and everything else to Custom(s)
also cover Custom("travel") → "travel" round-trip
No frontend changes needed — the TS type category: string is already correct;
it's the backend that's violating the contract.
Verification
cargo test -p zeroclaw -- memory_category_serde
cargo clippy --all-targets -- -D warnings
Then hit GET /api/memory and confirm custom categories return as plain
strings.
Expected behavior
Page will Render correctly the TS type category: string is already correct
Steps to reproduce
Impact
Broken Memory Page
Logs / stack traces
ZeroClaw version
f2ba33f
Rust version
1.93.1
Operating system
Unbuntu
Regression?
Unknown
Pre-flight checks