Codex stop-gate: `get_active_theme.options` joins per-axis value
lists with `,` but theme values can legitimately contain commas
(e.g. "red, white, and blue"). The previous escape set (`\;|`)
left `,` unescaped, so the comma joiner would mis-split a single
value into multiple fake ones on decode.
`escape_record_field` + `unescape_record_field` extended to also
escape `,` (and accept `\,` on decode). The Rust-side helpers
stay backward-compatible because no existing encoder previously
emitted a literal `\,`. `list_variables` is unaffected because
that wire format doesn't use `,` as a delimiter; the escaped form
is harmless there.
New layered-decoder pattern in the test: `get_active_theme.options`
is a TWO-LEVEL split (outer `|`, inner `,`), so the decoder must
unescape only the delimiter for the current level — leaving
`\,` intact during the outer `|` split, then unescaping `\,`
during the inner `,` split. The previous test had an over-eager
walker that unescaped every delimiter at once, which broke the
inner split. Helper `layered_split(s, delim)` makes this contract
explicit:
let outer = layered_split(&opts, '|'); // unescape only \|
let inner = layered_split(&outer[1], ','); // unescape only \,
Tests (1 added, 304 shell-core total):
- `get_active_theme_round_trips_comma_in_value` builds an axis
with value `"a,b,c"` plus a plain second value. After encode
+ layered decode, asserts the values vec is exactly
`["a,b,c", "plain"]`. Pre-fix the inner comma split saw 4
values; with the layered decoder + comma escape it sees 2.
The wire format is now safe for every char a `.op` theme value
can legally carry.