Skip to content

Extract every config mutation into pure core operations - #57

Closed
jellologic wants to merge 1 commit into
feat/remove-wizardfrom
feat/core-operations
Closed

Extract every config mutation into pure core operations#57
jellologic wants to merge 1 commit into
feat/remove-wizardfrom
feat/core-operations

Conversation

@jellologic

Copy link
Copy Markdown
Owner

Closes #38. Stacked on #55 — review that first; the base will retarget to main once it merges.

Configuration editing used to live in whichever front end you were looking at: the wizard minted account+setup+profile in its own finish(), while shape parsing, reference checking and mutation lived in web/api.ts. Two editors, two implementations, no way to tell they agreed.

src/core/operations.ts is now the single home. api.ts went 628 → 433 lines, and its remaining job is HTTP: routing, revision conflicts, redaction, status codes.

Why this is the highest-leverage issue in #52

It is the hedge behind dropping the CLI editor (#35). What makes a second front end cheap is not shared UI — it is that neither front end owns the logic. test/core/operations.test.ts proves it by driving a full lifecycle importing nothing from adapters/ or web/.

Design notes

  • Follows bindPath/unbindPath in core/binding.ts, which already returned {ok, state, reason}.
  • Refusals carry kind: 'invalid' | 'missing' rather than a status code, so core stays ignorant of HTTP. The adapter maps them to 400/404; a terminal caller can map them to exit codes.
  • Provider rules (reservedIds, knownCompatFlags, credentialEnvs) are parameters, because they are Claude Code's and core may not name a CLAUDE_CODE_/ANTHROPIC_ identifier.

This is a pure extraction

Every rule, comment and message carried across unchanged — which is why the 802 existing tests pass untouched. I reverted two places where I had started to improve behaviour, because a refactor that quietly changes semantics can't be reviewed:

  • deleting the default profile clears to null; no promoting a survivor
  • binding pruning keeps its string-only comparison

That second one is a real latent bug and is filed separately: BindingValue is string | {profile, overrides}, so an object-form binding outlives the profile it names.

813 tests green.

Configuration editing used to live in whichever front end you were looking at.
The Ink wizard minted account+setup+profile in its own `finish()`; shape
parsing, reference checking and state mutation lived in adapters/web/api.ts.
Two editors, two implementations of the same rules, and no way to tell they
agreed.

src/core/operations.ts is now the single home: state in, state out, no I/O.
The web API's remaining job is HTTP — routing, revision conflicts, redaction
and status codes — and it went from 628 lines to 433.

The shape follows bindPath/unbindPath in core/binding.ts, which already worked
this way. Refusals carry a `kind` of 'invalid' or 'missing' rather than a
status code, so core/ stays ignorant of HTTP: the adapter maps those to 400 and
404, and a terminal caller can map them to exit codes.

Provider validation takes `reservedIds`, `knownCompatFlags` and
`credentialEnvs` as parameters rather than importing them. They are Claude
Code's, core/ may not name a CLAUDE_CODE_ or ANTHROPIC_ identifier, and the
adapter that owns them supplies them.

This is a pure extraction. Every rule, comment and refusal message is carried
across unchanged, which is why the 802 existing tests pass untouched. Two
places where I had started to improve behaviour were reverted to match the
original exactly, because a refactor that quietly changes semantics cannot be
reviewed:

- deleting the default profile clears it to null; it does not promote a
  survivor
- binding pruning keeps its string-only comparison

That second one is a real latent bug — a BindingValue is
`string | {profile, overrides}` and the comparison only prunes the string form,
so an object-form binding outlives the profile it names. Preserved here and
filed separately.

test/core/operations.test.ts drives a whole lifecycle — account, setup,
profile, edit, default, delete, plus every refusal path — importing nothing
from adapters/ or web/. That is the point of the change: what makes a second
front end cheap is not shared UI, it is that neither front end owns the logic.

813 tests green.

Refs #38

Signed-off-by: jellologic <31935831+jellologic@users.noreply.github.com>
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