Skip to content

Append to a list the file does not have yet - #15

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

The gap

append required an existing, non-empty sequence. Almost none of the lists it needs to reach are written until someone uses them — routes, proxies, allow_from, a key's allow are all optional, and the starting configuration is six lines.

So every "create the first one" in the UI failed at exactly the moment a new user reached for it: the first route, the first proxy, the first CIDR in the allow list.

End-to-end testing against a running gateway is what surfaced this. The unit tests all started from a file that already had the list — which is the one shape a new user never has.

Three starting points work now, all producing block sequences: the key is absent, the key holds [], or the key is present with nothing under it.

Two positioning bugs, both caught by the self-check

Neither reached disk — the re-parse guard rejected the result — but both would have been silent corruption without it.

The last child's byte range is not the last key's line. A block sequence's value node starts at its first -, not at key:. Taking indentation from there copies a list item's indentation, and the insertion point lands inside the list: appending routes to a config whose last key was clients wrote routes: between two clients.

clients:
  - name: a
  routes:          # ← here
    - name: 默认
    key: tw-a

The block is now measured across every node under the parent, and the indentation is the smallest one in that block.

A map that is itself a list entry keeps its first key on the dash line. - name: a indents two while the keys live at four, so the smallest indentation in the block is two and the new key lands outside the entry. Lines starting with - are excluded from that measurement, falling back to dash + 2 when the entry is a single line.

Tests

Four, one per path a first-time user actually walks: a key that is not in the file, [] becoming a block list, comments surviving the creation of a new key, and a nested key (clients[].allow) that does not exist yet.

Verification

cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, 1092 tests, and scripts/smoke.sh — 46 checks, 0 failures.

🤖 Generated with Claude Code

`append` required an existing, non-empty sequence. Almost none of the
lists it needs to reach are written until someone uses them: `routes`,
`proxies`, `allow_from`, a key's `allow` are all optional, and the
starting configuration is six lines.

So **every "create the first one" in the UI failed at exactly the moment
a new user reached for it** — the first route, the first proxy, the first
CIDR in the allow list. End-to-end testing against a running gateway is
what surfaced it; the unit tests all started from a file that already had
the list.

Three starting points now work, all producing block sequences: the key is
absent, the key holds `[]`, or the key is there with nothing under it.

## Two positioning bugs, both caught by the self-check

Neither reached disk — the re-parse guard rejected the result — but both
would have been silent corruption without it.

**The last child's byte range is not the last key's line.** A block
sequence's value node starts at its first `-`, not at `key:`. Taking the
indentation from there copies a list item's indentation, and the
insertion point lands *inside* the list: appending `routes` to a config
whose last key was `clients` wrote `routes:` between two clients. The
block is now measured across every node under the parent, and the
indentation is the smallest one in that block.

**A map that is itself a list entry keeps its first key on the dash
line.** `  - name: a` indents two, while the keys live at four, so the
smallest indentation in the block is two and the new key lands outside
the entry. Lines starting with `-` are excluded from that measurement,
falling back to dash + 2 when the entry is a single line.

Four tests, one per path that a first-time user actually walks: a key
that is not in the file, `[]` becoming a block list, comments surviving
the creation of a new key, and a nested key (`clients[].allow`) that does
not exist yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fylorn
fylorn merged commit 4ce0259 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