Let a record name the field that tells one from another - #34
Merged
Conversation
@FlagRecord
struct Endpoint {
@FlagRecordKey var name: String
var url: URL
}
flags.endpoints["staging"]?.url
A key is what makes one record a different record rather than an edited one. Two
sharing it has no correct behaviour — picking one would leave the app running on a
value nobody chose — so a list containing a duplicate is unreadable and falls back
to the flag's default, as any other malformed value does. A payload or a document
carrying one is refused outright: "expected every record to have its own name, got
two with "staging"".
The companion enforces the same rule rather than writing what the app will not
read. An edit that would collide is refused the way an unparseable one is, and
"Add" gives a new record a distinct key — without that, pressing it twice would
write two records sharing an empty key and the flag would silently revert on the
second press. The key also titles each row, which was otherwise the first declared
field and therefore a guess.
It marks the field rather than being an argument on '@FlagRecord' because a key
path cannot be written there, which is worth recording: '\.name' fails as "cannot
infer key path type from context", since an attached macro's generic parameters are
not bound to the type it is attached to, and '\Endpoint.name' fails as "circular
reference resolving attached macro". Marking the field also removes the repetition
and follows '@FlagGroup', which is read the same way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD
…left
@FlagContainer(description: "Everything the checkout team can turn on")
struct AppFlags { … }
The companion shows it above the flags, and reads it from the schema like
everything else — so a host that adds one needs no companion rebuild, and one that
says nothing looks exactly as it did. The application name answers "whose flags are
these"; this answers "what are they", which is the question someone handed an
unfamiliar debug build actually has. Hidden while searching, since a set being
filtered is not the set any more.
Single-line fields are leading aligned again, like the block editor beside them. A
value is read from its start — a URL by its scheme and host, a number by its
leading digits — and every field beginning in the same place is what lets a column
of them be scanned rather than read one at a time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD
The row already carries the flag's description and its key, so the count and the field list are the fourth and fifth things in it. At body size they competed with the description for the eye. Sized to footnote and caption they read as what they are. Seeing it on device also turned up a message that had stopped being true. A list whose records share a key is a perfectly well-formed list breaking a different rule, and both the row and the editor called it "not a list of records" — which starts a search for a syntax problem that is not there. They now say which key is shared, and which two records share it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD
The array row carried its count and preview at body size, so like the record row before it, the two lines under a flag's description read as a second title rather than as a count and a taste of the value. Its preview still truncates in the middle where the record row truncates at the tail. An array preview is bracketed, so keeping both ends shows that it is a list and where it stops; a record's field list has no brackets, and cutting through the middle of it produced "enabled:…ue". Single-line fields now use one fixed size rather than scaling to fit. Scaling made a field's text size a function of its contents, so a column of them arrived at three different sizes and none matched anything else on screen. They stay full width: a value too long for a fixed narrow box could only ever be read a fragment at a time, and a URL is exactly the value you most need to read whole. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD
**A date key collapsed on the way out.** Keys were compared as they are held, not as they are written, and a Date carries more precision in memory than the wire format keeps. Two records added within the same second looked distinct to every check here and identical to the reader — so pressing Add twice wrote a list, and the flag went back to its default when the host read it. Keys are compared by stored identity now, everywhere that asks whether one is taken, and "Add" advances a date key by a second rather than giving up on it. **setRecords only checked the top level.** A nested list with a repeated key was accepted and made the whole outer flag unreadable — the host applies the rule at every depth. It is checked at every depth now. The nested check asks for the duplicate rather than for the records: reading them applies this very rule and hands back nothing, so the first attempt looked straight past the thing it was looking for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD
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.
A record can name the field that tells one from another
A key is what makes one record a different record rather than an edited one. Two
sharing it has no correct behaviour — picking one would leave the app running on a
value nobody chose — so a list containing a duplicate is unreadable and falls back
to the flag's default, as any other malformed value does. A payload or a document
carrying one is refused outright:
The companion enforces the same rule rather than writing what the app will not read:
a colliding edit is refused the way an unparseable one is, and "Add" gives a new
record a distinct key. The key also titles each row, which was otherwise the first
declared field and therefore a guess.
It marks the field rather than being an argument on
@FlagRecord, because a keypath cannot be written there.
\.namefails as cannot infer key path type fromcontext — an attached macro's generics are not bound to the type it is attached to —
and
\Endpoint.namefails as circular reference resolving attached macro. Markingthe field also removes the repetition, and follows
@FlagGroup, which is read thesame way.
A container can say what its flags are for
Shown above the flags, read from the schema like everything else — so a host that
adds one needs no companion rebuild, and one that says nothing looks as it did.
Hidden while searching, since a set being filtered is not the set any more.
Sizing
Single-line fields are leading aligned again, and use one fixed small size rather
than scaling to fit — scaling made a field's text size a function of its contents,
so a column arrived at three sizes and none matched anything else on screen. They
stay full width: a value too long for a narrow box could only be read a fragment at
a time, and a URL is exactly the value you need whole.
The record and array rows carry their count and preview at footnote and caption. At
body size they sat under the flag's description and key and read as a second title.
Adversarial review
Two defects, both in the same place: the editor could still write a list the host
refuses.
A date key collapsed on the way out. Keys were compared as they are held rather
than as they are written, and a
Datecarries more precision in memory than the wireformat keeps. Two records added within the same second looked distinct to every check
and identical to the reader — so Add twice wrote a list, and the flag reverted to its
default when the host read it. Keys compare by stored identity now, and Add advances
a date key by a second rather than giving up.
setRecordsonly checked the top level. A nested list with a repeated key wasaccepted and made the whole outer flag unreadable, since the host applies the rule at
every depth. Now checked at every depth — and the check asks for the duplicate rather
than for the records, because reading them applies this very rule and hands back
nothing, so the first attempt looked straight past what it was looking for.
Seeing it on device also caught a message that had stopped being true: a list whose
records share a key is a well-formed list breaking a different rule, and both the row
and the editor called it "not a list of records", which starts a search for a syntax
problem that is not there.
Verified
721 tests on a clean build. DocC clean with
--warnings-as-errors, both example appsbuild, and every change here was looked at on a simulator rather than inferred from a
passing test.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NnwCqKNm8rZ63Au6bpYdaD