feat(dx): idiomatic adopt output, dynamic sugar, located errors, quiet state - #61
Merged
Merged
Conversation
… logical sugar (#52)
2000game
force-pushed
the
feat/authorable-configs-52
branch
from
July 9, 2026 12:50
a2fb86c to
dc392dc
Compare
managedFields for group-type/age-group/target-group/relationship-type/
group-role yields undefined-valued optional keys (nameTranslated,
sortKey, degreeNameA/B) when the API omits them, but JSON.stringify
drops those keys on save. Re-adopting compared the persisted {name}
against a fresh {name, nameTranslated: undefined} by raw key count,
saw a mismatch, and spuriously bumped updatedAt.
fieldsEqual now filters undefined-valued keys before comparing, both
top-level and recursively, mirroring JSON round-trip semantics.
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.
Addresses the tool-side items (A–D) of #52 (human-authorable configs). Item E (the blueprint rewrite of the real config) remains for ct-structure/#23; the cut items (
ct fmt, defaults engine) are out of scope.What's here (commits in D→C→B→A order)
D — State is machine-only.
upsertnow deep-compares managed fields and bumpsupdatedAtonly when they actually change (keeping the existingfieldsobject so serialization is byte-identical);adoptedAtis set once. No more state-file churn on a no-op apply.C — Located validation errors. Eval-time config errors and the #51 unknown-field warnings are now prefixed
file:line — <type> "<key>": message(e.g.ct.config.ts:42 — group "kids": unknown field "shortName" (ignored)). The call site comes fromnew Error().stack; jiti maps transpiled TS frames back to the original file+line. No identifiable frame → bare message, never a crash.B — Dynamic sugar (pure eval-time desugaring, engine untouched):
dynamic: true→{ status: "active", ruleset: { ref: "./rulesets/<key>.json" } }dynamic: "<path>.json"→{ status: "active", ruleset: { ref: "<path>" } }A —
ct adoptemits idiomatic config.configSnippetis now multi-line, prettier-compatible (2-space indent, trailing commas, one field per line). A newReverseResolver(mirror of the forward resolver) turns numericcampusId/groupTypeId/groupStatusIdinto logicalcampus/groupType/statussugar via the live catalogs; unresolvable ids stay numeric with a trailing// TODO: no logical match.dynamic:blocks emit the item-B sugar. Bulk adopt keeps its type-grouped// groupheader.Test evidence
npm run typecheckclean;npm run lint(eslint) clean.npm test: 466 passed, 4 skipped (skipped = live/gated files, untouched).buildPlanall no-op, zero hand edits (tests/adopt-group-command.test.ts).tests/located-errors.test.ts).Notes
format:checkfails on pre-existing files); the enforced linter is eslint. The diff is deliberately scoped to 6 changed files + 1 new (src/resolve/reverse.ts) so parallel branches rebase cleanly. No live instance was contacted; personFields/customFields/securityLevels and live/gated tests were left untouched.