Repository navigation
Conversation
`set` and `insert` only ever touched scalars, so the patch protocol could
express exactly one thing: replace a value that already exists. **Every
structural edit was therefore impossible from the UI** — creating a key,
adding a rule, defining a proxy, assigning a route. None of those are a
replacement.
That was not a missing feature so much as a ceiling: the configuration
page could read anything and create nothing.
## `tw_yaml::append` / `tw_yaml::remove`
Block sequences only, and they work on the text rather than a re-
serialised document, so comments and layout survive — which is the whole
reason this crate exists.
Positions come from walking lines, not from the container nodes' byte
ranges. Those ranges run on to the next token (the note at the top of the
file says so), and slicing with them takes the following entry's opening
with it.
Four behaviours worth naming, each with a test:
· A blank line between entries belongs to the **following** entry.
Attached to the preceding one, deleting a middle entry glues its
neighbours together.
· Indentation is copied from the entry already there. A four-space
file gets four-space entries — it is the user's file.
· A trailing comment on the last entry stays on the last entry.
· Nested sequences work, which is what the routes page needs:
`/routes/默认/rules`.
Empty sequences are refused with a message that says so, rather than
writing something that will not parse. `clients: []` cannot be appended
to yet; validation already requires at least one client, so the case that
matters in practice is covered.
The structural guard mirrors the scalar one: re-parse in memory and count
the entries. The scalar path asserts "the value reads back"; this one
asserts "the list is exactly one longer". Writing a file that cannot be
parsed is what this layer exists to prevent, and it would otherwise only
surface at the next load.
## `PatchOp::Append` / `PatchOp::Remove`
`Remove` takes a path to **the entry**, by name — `/clients/codex` — not
an index. That is the rule already written on `PatchOp`: an index points
at something else after the user reorders, and that failure is silent.
The index is resolved internally, from a name the user wrote, with no
reordering in between.
A new entry can carry nested structure in one write, so creating a key
and assigning its routes is a single operation. Two writes would leave a
moment where a key exists with no routes — a state the user can see.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
setandinsertonly ever touched scalars, so the patch protocol could express exactly one thing: replace a value that already exists. Every structural edit was therefore impossible from the UI — creating a key, adding a rule, defining a proxy, assigning a route. None of those is a replacement.That was less a missing feature than a ceiling: the configuration page could read anything and create nothing.
tw_yaml::append/tw_yaml::removeBlock sequences, operating on the text rather than a re-serialised document, so comments and layout survive — the whole reason this crate exists.
Positions come from walking lines, not from container nodes' byte ranges. Those ranges run on to the next token (the note at the top of the file says so), and slicing with them takes the following entry's opening with it.
Four behaviours worth naming, each with a test:
/routes/默认/rules.Empty sequences are refused with a message saying so, rather than writing something that will not parse.
clients: []cannot be appended to yet; validation already requires at least one client, so the case that matters in practice is covered.The structural guard mirrors the scalar one — re-parse in memory, then count. The scalar path asserts "the value reads back"; this one asserts "the list is exactly one longer". Writing a file that cannot be parsed is what this layer exists to prevent, and it would otherwise surface only at the next load.
PatchOp::Append/PatchOp::RemoveRemovetakes a path to the entry, by name —/clients/codex— not an index. That is the rule already written onPatchOp: an index points at something else once the user reorders, and that failure is silent. The index is resolved internally, from a name the user wrote, with no reordering in between.A new entry can carry nested structure in one write, so creating a key and assigning its routes is a single operation. Two writes would leave a moment where a key exists with no routes — a state the user can see.
Verification
cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings, 1088 tests (10 new: 7 on the YAML operations, 3 on the patch path end to end), andscripts/smoke.sh— 46 checks, 0 failures.🤖 Generated with Claude Code