Skip to content

feat(dialogs): publish the dialogs contract at its registry key - #76

Merged
fx merged 2 commits into
mainfrom
feat/publish-dialogs-contract
Sep 6, 2026
Merged

feat(dialogs): publish the dialogs contract at its registry key#76
fx merged 2 commits into
mainfrom
feat/publish-dialogs-contract

Conversation

@fx

@fx fx commented Sep 6, 2026

Copy link
Copy Markdown
Owner

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.ts declares the filter, field, option, request, and result types plus Dialogs. DialogElement and the provider's rendering vocabulary stay behind, and types.ts keeps its CoreDependencies import — DialogElement is defined in terms of CoreDependencies["react"]["createElement"], which is a rendering detail no consumer of select and input needs. 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.ts is now requireDialogsCapability alone, and plugins/grid/select.ts imports the option and request types from @fx/tx/dialogs directly.

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:

  1. test/theme-plugin.test.ts restated the contract inline in a type argumentregistrations<{ select(request: {...}): Promise<unknown> }>("dialogs"), twice. No list names it, and no grep for a type or interface declaration would have found it.
  2. test/dialogs-columns.test.ts and test/grid-select.test.ts reached dialogs vocabulary through provider-internal paths.

Final sweeps both return empty, and that empty result is the evidence:

grep -rnE '(registrations|register|read)<[^>]*>\("dialogs"\)' --include=*.ts src plugins demo test build.ts
grep -rnE '^ *(export )?(type|interface) (Config|ConfigValidator|FilterMode|TextField|SelectOption|SelectRequest|SelectResult|InputRequest|Dialogs|Cell|Row|Grid|Hue|Appearance|ThemeVariable|Theme|Theming|ThemeOverride|…)\b' --include=*.ts src plugins demo test build.ts | grep -v 'contract.ts:'

Also verified: no registrations<{ / register<{ / read<{ anywhere, so no anonymous restatement survives either.

Test plan

  • bun run check passes — lint clean over 93 files, tsc clean, 1145 tests, 0 failures, 100% statement/function/line coverage across production sources, build clean.
  • The change document's completion observable is pinned by a test, not just a grep. test/plugin-boundary.test.ts gains "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 under src/, plugins/, demo/, and test/ and fails on any alias or interface redeclaring one.
  • That test is negative-tested. Adding type Dialogs to test/helpers.ts produced test/helpers.ts declares Dialogs, which plugins/dialogs/contract.ts publishes and failed the suite. Reverted.
  • "Importing the published dialogs contract compiles no rendering" — the import closure of @fx/tx/dialogs is the contract alone, with types.ts, filter.ts, and select.ts each checked to exist and then asserted absent, so the absence assertions cannot pass by naming files that are not there.
  • @fx/tx/dialogs joins the named published-specifier set, the exact packed-file assertion, and the consumer fixture, which imports Dialogs/SelectRequest from the installed tarball and calls select through the registry.
  • test/plugins.test.ts proves 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, and test/demo.test.ts reading a key nothing registers, and its tests hung. One commit is the smallest state that builds.

test/grid-select.test.ts needed a narrowing the contract does not export (the old plugins/grid/dialogs.ts exported CellOption; the contract keeps that helper unexported, matching what the provider itself exposed). It uses Extract<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, and docs/index.yml are what record completion. plugins/dialogs/types.ts is 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

fx added 2 commits September 6, 2026 05:31
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
Copilot AI lite review requested due to automatic review settings September 6, 2026 05:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.ts and 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.

@fx
fx merged commit 47b10a4 into main Sep 6, 2026
2 checks passed
@fx
fx deleted the feat/publish-dialogs-contract branch September 6, 2026 05:40
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.

2 participants