feat(dialogs): publish the dialogs contract at its registry key - #76
Merged
Conversation
Publish the dialogs capability's structural contract over the mechanism the theming, grid, and config contracts established, and retire every remaining restated copy of a capability's vocabulary with it. `plugins/dialogs/contract.ts` declares the request, option, field, filter, expand, and result types and `Dialogs` as types alone, and `package.json` publishes them at `./dialogs` under a `types` condition with no runtime condition beside it, so the capability's value keeps coming from the registry alone. Dialogs is the case where the contract is a subset of a file rather than the file. `plugins/dialogs/types.ts` mixed the capability's vocabulary with the provider's rendering internals: `DialogElement` is defined in terms of `CoreDependencies["react"]["createElement"]`, which no consumer of `select` and `input` ever sees. Publishing it would put React's element type in a consumer's build and make an internal rendering decision a breaking change, so it and the view type over it stay behind with the `CoreDependencies` import that names them. `FilterMode` moved the other way, out of `filter.ts`: a caller sets it on a request, and the matcher code it was declared beside is not a consumer's to compile. The registry key becomes that same specifier in the same commit. A key that named a specifier publishing nothing would be a tx violating the rule its own specification states, so `dialogs` is renamed at the provider, at the grid's lookup, at the demo, and in every test naming it. This is a breaking change to anything reading the capability and is accepted as one: no alias and no dual registration, because registering under both spellings would put two entries in one snapshot. The plugin's own identity name stays bare — that names the plugin, not the key. The copies go with it, in the same commit, because the rename is what makes them wrong rather than merely redundant. `plugins/grid/dialogs.ts` keeps only `requireDialogsCapability`; the shape it held described the part the grid sends and compiled whatever the dialogs plugin did. `demo/scenarios.ts`, the demo runner, and the dialogs, demo, grid-select, and theme tests import the published contract instead of restating it — the theme tests were restating it inline in a type argument, which the file lists in the change document did not name and the search found. The lookup's strictness is documented as each consumer's own policy rather than one the contract imposes, because the dialogs specification leaves the absent-capability decision to the consumer and a published type says nothing about how many providers registered. The boundary suite gains the two checks the change document asks for. Importing `@fx/tx/dialogs` reaches the contract alone, with `types.ts`, `filter.ts`, and `select.ts` named and checked to exist so their absence cannot pass by naming files that are not there. And no module under `src/`, `plugins/`, `demo/`, or `test/` declares a type name a capability contract exports, with the names read from the contracts themselves rather than listed, so the completion observable is a search whose empty result is the evidence rather than a list somebody has to keep. Claude-Session: https://claude.ai/code/session_0165ZoEriyA7L7iLAG81AkR3
The guide's dialogs section told a consumer to declare the capability's shape locally, printed a block restating a subset of it, and called the provider and that shape implementation details rather than public exports. All three are now false: the contract is published at the key it is read from, so the section shows the import, describes what the contract carries rather than copying declarations that could drift from it, and says plainly that the provider's rendering vocabulary is deliberately not published. Who owns the absent-capability decision is unchanged and is said so. The dialogs specification leaves it to the consumer, and publishing a type says nothing about how many providers registered, so the section names the grid's strict lookup as the grid's own choice with the reason it made it — exactly as the config section names the marketplace's — and leaves a consumer with a reason to tolerate an absent provider free to choose otherwise. The published-contract lists in the guide, in `AGENTS.md`, and in `REVIEW.md` each name `@fx/tx/dialogs` beside the four that were already there. The dialogs specification's status note said the contract was not published and the key still bare; both are now false. The config note said change 0031 stayed open for dialogs, and the plugin-system note described the change as under way with the dialogs key still bare and its consumers still restating the shape. Published Capability Contracts is now fully implemented — every bundled capability's registry key is the specifier its contract is published at, and no key is bare — so that note says so rather than describing a partial state. Change 0031's remaining task groups are checked off and the change is marked complete, in the documentation index as well as in the document itself. Its Motivation is left in the present tense of its proposal moment, and its open questions are left open: whether `plugins/dialogs/types.ts` should be renamed now that only the provider's rendering vocabulary is in it, and whether the marketplace's own configured-marketplace shape belongs in a published contract, are both deliberately unanswered here. Claude-Session: https://claude.ai/code/session_0165ZoEriyA7L7iLAG81AkR3
There was a problem hiding this comment.
🟢 Approval recommended
The key rename and contract publication appear consistently applied across provider/consumers/tests/docs, with added boundary tests to prevent regressions.
Pull request overview
Publishes the dialogs capability contract at @fx/tx/dialogs (matching its registry key), migrates all bundled consumers/tests/demos off the last bare capability key and any restated dialogs vocabulary, and updates specs/docs to mark “Published Capability Contracts” as fully implemented.
Changes:
- Added a types-only published contract at
plugins/dialogs/contract.tsand exported it as@fx/tx/dialogs. - Renamed the dialogs registry key from
"dialogs"to"@fx/tx/dialogs"across provider, bundled consumers, demo, and tests; removed remaining local/restated copies of the dialogs vocabulary. - Expanded boundary/consumer tests and updated docs/specs/change-status indices to reflect completion.
File summaries
| File | Description |
|---|---|
| test/theme-plugin.test.ts | Switches dialogs typing to @fx/tx/dialogs and updates select options to include value. |
| test/plugin-consumer.test.ts | Extends packed-tarball consumer fixture to import and use @fx/tx/dialogs. |
| test/plugin-boundary.test.ts | Adds dialogs contract closure test and a new “no restated contract types” rule across repo modules. |
| test/grid-select.test.ts | Moves grid-select tests to published dialogs types and replaces internal CellOption narrowing with Extract<>. |
| test/dialogs-plugin.test.ts | Removes local dialogs type restatements; reads dialogs capability via @fx/tx/dialogs. |
| test/dialogs-columns.test.ts | Imports SelectOption from published dialogs contract. |
| test/demo.test.ts | Updates demo stubs to register dialogs under @fx/tx/dialogs. |
| test/demo-scenarios.test.ts | Replaces restated dialogs vocabulary imports with @fx/tx/dialogs. |
| REVIEW.md | Updates published-capability-contract list to include @fx/tx/dialogs. |
| plugins/grid/select.ts | Imports dialogs request/option types from @fx/tx/dialogs instead of a local restatement module. |
| plugins/grid/dialogs.ts | Replaces local structural dialogs restatement with @fx/tx/dialogs import and updates lookup key. |
| plugins/dialogs/types.ts | Narrows to provider-internal rendering vocabulary only (keeps React element typing internal). |
| plugins/dialogs/select.ts | Consumes published contract types from ./contract.ts; keeps rendering types internal. |
| plugins/dialogs/index.ts | Registers dialogs capability under @fx/tx/dialogs and centralizes the key constant. |
| plugins/dialogs/filter.ts | Imports FilterMode from published contract so matcher code doesn’t define consumer vocabulary. |
| plugins/dialogs/contract.ts | New published dialogs contract module (types-only). |
| plugins/dialogs/columns.ts | Imports SelectOption from ./contract.ts (published vocabulary source). |
| package.json | Adds ./dialogs export pointing to plugins/dialogs/contract.ts and includes it in packed files. |
| docs/specs/plugin-system/index.md | Updates status text to reflect the full set of published capability contracts is complete. |
| docs/specs/dialogs/index.md | Marks Change 0031 dialogs-contract publication as complete and updates key description. |
| docs/specs/config/index.md | Updates Change 0031 language to fully complete (no longer “half landed”). |
| docs/manual/plugins.md | Rewrites dialogs section to import from @fx/tx/dialogs and explains consumer-owned provider-count policy. |
| docs/index.yml | Marks change 0031 as complete. |
| docs/index.md | Updates change 0031 status to complete. |
| docs/changes/0031-publish-the-dialogs-and-config-contracts.md | Flips status to complete and checks off dialogs publication/sweep tasks. |
| demo/scenarios.ts | Replaces restated dialogs vocabulary with imports from @fx/tx/dialogs. |
| demo/index.ts | Updates demo runner to read dialogs capability from @fx/tx/dialogs. |
| AGENTS.md | Updates published-capability-contract list to include @fx/tx/dialogs. |
Review details
- Files reviewed: 28/28 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Publishes the dialogs contract at
@fx/tx/dialogs— the last bare registry key — and retires every remaining restated copy of a capability's vocabulary.This completes docs/changes/0031-publish-the-dialogs-and-config-contracts.md, and with it Plugin System: Published Capability Contracts is fully implemented: every bundled capability's registry key is now the specifier its contract is published at, and no key is bare.
What this does
Publishes a subset of
plugins/dialogs/types.ts, not the file.plugins/dialogs/contract.tsdeclares the filter, field, option, request, and result types plusDialogs.DialogElementand the provider's rendering vocabulary stay behind, andtypes.tskeeps itsCoreDependenciesimport —DialogElementis defined in terms ofCoreDependencies["react"]["createElement"], which is a rendering detail no consumer ofselectandinputneeds. Publishing it would put React's element type in every consumer's build and make an internal rendering decision a breaking change.Reduces the lookups to lookups.
plugins/grid/dialogs.tsis nowrequireDialogsCapabilityalone, andplugins/grid/select.tsimports the option and request types from@fx/tx/dialogsdirectly.Introduces no one-provider obligation for dialogs consumers. docs/specs/dialogs/index.md makes the absent-capability decision the consumer's, and a published type says nothing about how many providers registered. The guide names the grid's strictness as the grid's own choice, with its reason — the same shape #75 settled on for config.
The sweep found two sites the change document's own lists missed
The document says its file lists are illustrative and the search is authoritative, "because two of these lists have already been found incomplete". That held again:
test/theme-plugin.test.tsrestated the contract inline in a type argument —registrations<{ select(request: {...}): Promise<unknown> }>("dialogs"), twice. No list names it, and no grep for atypeorinterfacedeclaration would have found it.test/dialogs-columns.test.tsandtest/grid-select.test.tsreached dialogs vocabulary through provider-internal paths.Final sweeps both return empty, and that empty result is the evidence:
Also verified: no
registrations<{/register<{/read<{anywhere, so no anonymous restatement survives either.Test plan
bun run checkpasses — lint clean over 93 files,tscclean, 1145 tests, 0 failures, 100% statement/function/line coverage across production sources, build clean.test/plugin-boundary.test.tsgains "no capability contract type is declared outside the contract that owns it": it reads the exported type names from the capability contracts themselves (excluding@fx/tx/plugin, the host's own contract rather than a capability's), asserts that name set exactly so a contract yielding nothing cannot pass vacuously, then walks every module undersrc/,plugins/,demo/, andtest/and fails on any alias or interface redeclaring one.type Dialogstotest/helpers.tsproducedtest/helpers.ts declares Dialogs, which plugins/dialogs/contract.ts publishesand failed the suite. Reverted.@fx/tx/dialogsis the contract alone, withtypes.ts,filter.ts, andselect.tseach checked to exist and then asserted absent, so the absence assertions cannot pass by naming files that are not there.@fx/tx/dialogsjoins the named published-specifier set, the exact packed-file assertion, and the consumer fixture, which importsDialogs/SelectRequestfrom the installed tarball and callsselectthrough the registry.test/plugins.test.tsproves the new subpath unloadable at run time.Notes
The two task groups landed as one code commit rather than two. Splitting them was attempted and reverted: the change document requires the key rename to land in the same commit as the publication, and the same test files carry both the rename and the restatement removal — so a group-2-only commit left
demo/index.ts,test/dialogs-plugin.test.ts, andtest/demo.test.tsreading a key nothing registers, and its tests hung. One commit is the smallest state that builds.test/grid-select.test.tsneeded a narrowing the contract does not export (the oldplugins/grid/dialogs.tsexportedCellOption; the contract keeps that helper unexported, matching what the provider itself exposed). It usesExtract<SelectOption<RowChoice>, { readonly cells: readonly string[] }>and a one-line helper rather than widening the published surface — so the test still restates nothing.Change 0031's Motivation is deliberately not retensed, per
REVIEW.md;**Status:**, the checkboxes, anddocs/index.ymlare what record completion.plugins/dialogs/types.tsis not renamed — that is an explicit Open Question and out of scope.Spec: docs/specs/dialogs/, docs/specs/plugin-system/index.md § Published Capability Contracts
Change: docs/changes/0031-publish-the-dialogs-and-config-contracts.md (completed by this PR)
https://claude.ai/code/session_0165ZoEriyA7L7iLAG81AkR3