From 4c7375db85ff64611326bd8cefc45d6290d45a48 Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Mon, 19 Oct 2026 21:41:23 +0800 Subject: [PATCH 1/2] docs(adr): add ADR-0031, paginated vocabulary listing for structured editors Records option 1 from #782: a separate listEntries operation pages the owner's vocabulary for UIs, while lookup keeps its agent bounds. Amends ADR-0021 decision 3 for this operation only. Refs #782 --- docs/adr/0031-paginated-vocabulary-listing.md | 95 +++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 96 insertions(+) create mode 100644 docs/adr/0031-paginated-vocabulary-listing.md diff --git a/docs/adr/0031-paginated-vocabulary-listing.md b/docs/adr/0031-paginated-vocabulary-listing.md new file mode 100644 index 00000000..af6f9fab --- /dev/null +++ b/docs/adr/0031-paginated-vocabulary-listing.md @@ -0,0 +1,95 @@ +# ADR-0031: Paginated vocabulary listing for structured editors + +- Status: Proposed +- Date: 2026-10-19 +- Related: [Issue #782](https://github.com/wrightkit/wright/issues/782), [Issue #781](https://github.com/wrightkit/wright/issues/781), [Issue #798](https://github.com/wrightkit/wright/issues/798), [ADR-0021](0021-name-and-signature-lookup.md), [ADR-0022](0022-shared-surface-for-semantic-queries.md), [ADR-0024](0024-structured-rule-content.md), [ADR-0029](0029-structured-statement-edits.md), [Agent contract](../agent-contract.md) + +Amends ADR-0021 decision 3 ("The service never returns a whole catalog or settings table") for one new operation. `lookup` itself is unchanged. + +## Context + +ADR-0029 names a structured view as a consumer of statement edits: a form-based editor that presents rules as conditions and actions, like the in-game Workshop editor. In that editor every argument slot is a dropdown. A person opens it and picks from the full list of actions, of values, or of an enum's members, and typing to filter is optional. + +The structured editor prototype builds these forms from `lookup` and cannot fill the dropdowns: + +- An empty query returns nothing, so the prototype cannot list all actions or values without the user typing first. On `practice-range`, `lookup` with `kind: action` and `query: ""` returns `[]`. +- `limit` is at most 10. Listing an enum domain through `within` is truncated: `Map` and `Hero` both have more than 10 members, so the dropdown for `createDummyBot.hero` can never show all heroes. +- The `enum` facts on a callable parameter list member display names only. A form needs member identities to build a `{enum, member}` node, so it has to call `within` anyway. + +These limits are deliberate. ADR-0021 bounds results for agent token cost, and its evidence still holds: tool results stay in the agent's context and are re-read on every turn. A UI has the opposite need. It holds the list in memory, renders it once, and must show every choice so the person never has to guess a name. + +The owner already has the data. `workshop-rs` iterates its entries in catalog order (`Catalog::entries_of`, `Catalog::enum_domains`), and `Catalog::lookup_within` without a query returns every member of an enum domain in owner order. It has no public call that returns full lookup facts (`LookupMatch` with its `Signature`) for every entry of a kind. Wright would have to rebuild signatures itself, which ADR-0021 decision 4 rules out. + +Issue #782 compared three options: a separate paginated operation, having the UI read `workshop-rs` directly, and lifting the cap on `lookup`. This ADR records the first. + +## Decision + +1. **A new operation, `listEntries`, on the shared `ToolService` surface.** It lists the owner's vocabulary of one kind in owner order, page by page. It does not match or rank: there is no `query`. `lookup` keeps its bounds, its defaults, and its ranking, and ADR-0021 decision 3's bound now applies to `lookup` only. + +2. **Request.** + - `language`: `workshop`. Any other language id gets the `unavailable` result of decision 6. + - `kind` (required): `action`, `value`, `event`, `enum`, or `enumMember`. Other `lookup` kinds (settings, parameters, structural forms, operators) are not listable here. They are added when a consumer needs them. + - `within`: required for `enumMember` and refused for the other kinds. It is an enum domain identity as `lookup` returns it, for example `Hero`. + - `locale` (optional): resolved as in `lookup`. + - `pageSize` (optional): the default is 100 and the maximum is 500. One page of the largest current kind is therefore the whole kind, but a consumer must not rely on that. + - `cursor` (optional): the `nextCursor` of the previous page. Without it the first page is returned. + +3. **Result.** + - `language`, `owner`, and `entries`, as in `lookup`. + - `entries` uses exactly the `lookup` entry shape for the same kind, including `callable` facts and the rendered `signature`. A consumer parses one entry type, and any member later added to `lookup` entries (for example a return type under #781 or `variadic` under #798) appears here too. + - `total`: the number of entries the request lists across all pages. + - `nextCursor`: present when more entries follow, absent on the last page. + - Order is the owner's order. Wright does not sort or group entries. A UI that shows the in-game alphabetical order sorts by `displayName` itself. + - **Deterministic.** The same request against the same owner version returns the same pages. + +4. **Cursors are opaque and bound to the request and the owner version.** A cursor encodes the position, the request's `language`, `kind`, `within`, and `locale`, and the owner's catalog identity. A cursor that does not match the request or the running owner version is refused with `invalid-cursor`. The consumer restarts from the first page. Wright keeps no server-side cursor state. + +5. **`listEntries` does not read a program.** Like `lookup`, it answers without requiring or triggering a project load (ADR-0021 decision 5). + +6. **OverPy is out of scope for now.** A `listEntries` request for `opy` returns `unavailable` naming `opy-rs` and the `listEntries` capability. Listing OverPy vocabulary needs a negotiated LPP capability. That is decided when an OverPy structured editor exists, under the protocol's additive change rules. + +7. **Owners supply the listing.** `workshop-rs` adds a public call that returns the lookup facts for every entry of a kind in catalog order, the same facts `Catalog::lookup` returns per match. Wright pages that list and renders entries with the code that renders `lookup` entries. Enum members already come from `Catalog::lookup_within` without a query. Wright stores no names and assembles no signatures. + +8. **Surfaces.** `capabilities.operations` lists `listEntries`, and `wright serve` answers it on both the stdio and JSON-RPC transports. It is not in the MCP tool set (ADR-0020): it exists for UIs, an agent has `lookup`, and a listing tool would put the token cost ADR-0021 rejected into every session. No CLI command is added. A terminal user has `wright lookup`, and a person who browses a whole kind uses an editor, not the terminal. ADR-0022 is unaffected: `listEntries` returns the same model to every surface that exposes it, and it is not a project-semantic query. + +9. **Contract change.** The operation is additive within `wright-agent/v1`. The committed schema and `docs/agent-contract.md` gain the request and result. + +## Alternatives considered + +- **The UI reads `workshop-rs` directly** (Rust API or an exported catalog snapshot). Rejected. The UI would integrate with two components, which goal principle 1 rules out, and a snapshot would be a second copy of owner data next to the one `serve` uses. +- **Lift the cap on `lookup` for an explicit `kind` with an empty query.** Rejected. It contradicts ADR-0021, and an agent that sends an empty query would get a whole catalog in its context. +- **One unpaginated response per kind.** Rejected. The current sizes would fit, but nothing bounds a catalog's growth. A page limit keeps every response bounded with little cost to the consumer. +- **Server-side grouping into in-game categories.** Rejected. The in-game dropdowns are alphabetical lists, and grouping would be Wright presentation policy that no owner fact backs. +- **Offset and limit instead of a cursor.** Rejected. An offset carries no owner version, so a page fetched after an upgrade would silently skip or repeat entries. + +## Consequences + +- A structured editor can fill every action, value, event, and enum dropdown through `serve`, without the person typing first. Enum slots offer every member with its identity. +- Agents are unaffected. `lookup` and the MCP tool set do not change. +- Entries stay with their owner, and their shape is shared with `lookup`, so a fact added to one appears in both. +- Follow-up work: + - A `workshop-rs` issue for the listing call in decision 7. + - The Wright operation, the schema, and the agent-contract documentation. + - The prototype switches its dropdowns from `lookup` to `listEntries`. +- Filtering a value list by an argument slot's type still needs return types (#781). Until then a UI lists every value for every slot. + +## Compatibility impact + +No syntax, diagnostic, normalized-output, or semantic behavior changes. The operation is additive under `wright-agent/v1`. The implementation must show: + +- For each listable kind, the identities across all pages equal the owner's entries of that kind, in owner order, with no duplicates. +- Each listed entry equals the `lookup` entry for the same identity. +- Repeated requests return identical pages. +- A cursor from another request, or one with a changed owner identity, is refused with `invalid-cursor`. +- `listEntries` answers when the project cannot load, and does not trigger a load. +- `wright serve` stdio and JSON-RPC return the same result for the same request. + +## Scope boundaries + +This ADR does not decide: + +- type-based filtering of values, which depends on #781; +- listing settings, parameters, structural forms, or operators; +- the OverPy listing capability, its wire shape, or DEL/OSTW; +- a CLI command or an MCP tool for listing, which are added only for a demonstrated terminal or agent workflow; +- how a UI sorts, groups, or caches entries. diff --git a/docs/adr/README.md b/docs/adr/README.md index 54921070..2f46127b 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -50,4 +50,5 @@ its audit date. * [ADR-0028: Transformation profiles apply to Workshop input only](0028-profile-applies-to-workshop-input-only.md) * [ADR-0029: Structured statement edits for raw Workshop](0029-structured-statement-edits.md) * [ADR-0030: One result model, three renderers, and the CLI/action boundary](0030-renderers-and-the-cli-action-boundary.md) +* [ADR-0031: Paginated vocabulary listing for structured editors](0031-paginated-vocabulary-listing.md) * [Post-ADR-0010 decision inventory](post-0010-inventory.md) From 0ee7300389e5727ef1d8d3a8c57761c914d5731e Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Mon, 19 Oct 2026 21:48:00 +0800 Subject: [PATCH 2/2] docs(adr): accept ADR-0031 on paginated vocabulary listing Closes #782 --- docs/adr/0031-paginated-vocabulary-listing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/0031-paginated-vocabulary-listing.md b/docs/adr/0031-paginated-vocabulary-listing.md index af6f9fab..3d842b9a 100644 --- a/docs/adr/0031-paginated-vocabulary-listing.md +++ b/docs/adr/0031-paginated-vocabulary-listing.md @@ -1,6 +1,6 @@ # ADR-0031: Paginated vocabulary listing for structured editors -- Status: Proposed +- Status: Accepted - Date: 2026-10-19 - Related: [Issue #782](https://github.com/wrightkit/wright/issues/782), [Issue #781](https://github.com/wrightkit/wright/issues/781), [Issue #798](https://github.com/wrightkit/wright/issues/798), [ADR-0021](0021-name-and-signature-lookup.md), [ADR-0022](0022-shared-surface-for-semantic-queries.md), [ADR-0024](0024-structured-rule-content.md), [ADR-0029](0029-structured-statement-edits.md), [Agent contract](../agent-contract.md)