Skip to content

Append and remove list entries, keeping comments - #11

Merged
fylorn merged 1 commit into
mainfrom
dev
Sep 14, 2026
Merged

fylorn merged 1 commit into
mainfrom
dev

Conversation

@fylorn

@fylorn fylorn commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Why

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 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::remove

Block 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:

  • 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 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::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 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), and scripts/smoke.sh — 46 checks, 0 failures.

🤖 Generated with Claude Code

`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>
@fylorn
fylorn merged commit 2b6f774 into main Sep 14, 2026
3 checks passed
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.

1 participant