Skip to content

Make every first-time write in the desktop UI actually land - #16

Merged
fylorn merged 2 commits into
mainfrom
dev
Sep 14, 2026
Merged

fylorn merged 2 commits into
mainfrom
dev

Conversation

@fylorn

@fylorn fylorn commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Walking the desktop UI end to end against a running gateway — from a config that contains only clients and providers — six of its write paths failed. Five were in the patch layer, one in the engine.

They share a shape: the thing being written is not in the file yet. That is the normal state for most of the schema, so it is exactly what the first use of each feature hits.

Engine

The implicit default route was synthesised only when the file declared no routes. Creating the first custom route therefore deleted the route every unbound key walks, and loading the config failed pointing at a field the user never touched. It is now synthesised whenever nothing answers to default_route; an explicitly written one is tracked separately so a typo in it still fails loudly.

Patch layer

What failed Why
default_route landed between two list items A container node's byte range is an empty marker at the start of its content, not its end. insert and append_first had each hit this separately — the block-and-indent logic now lives in one place.
limits / client_probes refused Those sections are never written by default, so "change one item in it" is their first write. Missing intermediate maps are now created; missing list entries are still refused, since those have names and inventing one is a guess.
allow[]: An empty value slot (key: with nothing after) has its marker on the colon, not after it.
A second allow: beside the first Overwriting a list with a scalar. That is the three-state control's way back to "no restriction", and it failed with "this is a bug, please file an issue".
Deleting the last pattern turned the guard off It left key:, which is null, not an empty list — for allow those mean "follow the client's dialect" and "allow nothing". It also swallowed the blank line the user had put between two sections.

Adds clear_seq and a Clear patch op: allow: [] and an absent allow are different answers, so the protocol needs both. Limits was the one struct reachable from the config that accepted unknown fields, so a mistyped limit reported success and did nothing.

Checks

1109 tests, 46 smoke checks, clippy clean, formatted.

19 write paths round-trip against a live gateway, and the resulting file differs from the original only in the lines the run meant to change — comments, blank lines and ordering all survive.

🤖 Generated with Claude Code

fylorn and others added 2 commits September 15, 2026 02:55
The implicit default route was synthesised only when the file declared no
routes at all. So the moment a user created their first one, the route that
every unbound key walks vanished, and loading the config failed with
"`default_route` points at `默认`, but there is no such route" — that is,
adding a route broke the gateway, and the message pointed at a field the
user never touched.

Synthesise it whenever nothing answers to `default_route` instead. A
`default_route` the user wrote by hand is tracked separately, so a typo in
it still fails loudly rather than being papered over by a synthesised route
of that name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Walking the desktop UI end to end against a running gateway, from a config
that contains only clients and providers, five of its write paths failed.
All five share a shape: the key being written is absent from the file,
which is the normal state for most of the schema — the first day's config
is six lines.

* A top-level key went in wherever the last node ended, and a container
  node's byte range is an empty marker at the *start* of its content. With
  a block sequence last in the file, `default_route:` landed between two
  list items and the guard rejected the result as unparseable. `insert` and
  `append_first` had each hit this separately, so the block-and-indent
  logic now lives in one place.
* Writing into a section that does not exist yet was refused. `limits` and
  `client_probes` are never written by default, so "change one item in it"
  *is* their first write; refusing made those settings dead in the UI.
  Missing intermediate maps are now created. Missing list *entries* are
  still refused — those have names, and inventing one is a guess.
* A value slot that is empty (`key:` with nothing after it) has its marker
  on the colon, not after it, so writing through it produced `allow[]:`.
* Overwriting a list with a scalar wrote a second key of the same name
  beside it. That is the three-state `allow` control's way back to "no
  restriction", and it failed with "this is a bug, please file an issue".
* Removing the last entry of a list left `key:`, which is null, not an
  empty list. For `allow` those are "follow the client's dialect" and
  "allow nothing" — deleting the last pattern quietly turned the guard off.
  It also swallowed the blank line the user had put between two sections.

Adds `clear_seq` and a `Clear` patch op: `allow: []` and an absent `allow`
are different answers, so the protocol needs both. `Limits` was the one
struct reachable from the config that accepted unknown fields, so a
mistyped limit reported success and did nothing.

19 write paths now round-trip against a live gateway, and the resulting
file differs from the original only in the lines the run meant to change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fylorn
fylorn merged commit 4d77706 into main Sep 14, 2026
3 checks passed
@fylorn
fylorn deleted the dev branch September 20, 2026 04:21
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