From 2ab35a2c1347c296afaacd8ddfccbd6988f8df80 Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Wed, 26 Aug 2026 23:02:36 -0500 Subject: [PATCH] Tighten Phase 7 protocol additions Document details sizing and deferred prefix selection, constrain numeric size values, and encode the bundled image through the SDK helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../JSAdapterProxyTests.cs | 6 +++++- .../cmdpal/doc/json-rpc-spec/02-typescript-sdk.md | 7 +++++++ .../doc/json-rpc-spec/03-jsonrpc-protocol.md | 5 +++++ src/modules/cmdpal/doc/json-rpc-spec/overview.md | 14 ++++++++++++++ src/modules/cmdpal/ext/SampleJSExtension/README.md | 2 ++ .../SampleJSExtension/src/pages/contentPages.ts | 5 ----- src/modules/cmdpal/ts-sdk/README.md | 2 +- src/modules/cmdpal/ts-sdk/src/types.ts | 8 ++++---- src/modules/cmdpal/ts-sdk/test/serialize.test.ts | 1 - 9 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.JsonRpc.UnitTests/JSAdapterProxyTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.JsonRpc.UnitTests/JSAdapterProxyTests.cs index 0dbf3000e9..d6ec9b632e 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.JsonRpc.UnitTests/JSAdapterProxyTests.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.JsonRpc.UnitTests/JSAdapterProxyTests.cs @@ -234,7 +234,9 @@ public class JSAdapterProxyTests "items": [ { "title": "Large by name", "details": { "title": "A", "size": "large" } }, { "title": "Medium by number", "details": { "title": "B", "size": 1 } }, - { "title": "Default size", "details": { "title": "C" } } + { "title": "Default size", "details": { "title": "C" } }, + { "title": "Unknown name", "details": { "title": "D", "size": "extra-large" } }, + { "title": "Unknown number", "details": { "title": "E", "size": 99 } } ] } """; @@ -247,6 +249,8 @@ public class JSAdapterProxyTests Assert.AreEqual((int)ContentSize.Large, GetDetailsSize(items[0].Details)); Assert.AreEqual((int)ContentSize.Medium, GetDetailsSize(items[1].Details)); Assert.AreEqual((int)ContentSize.Small, GetDetailsSize(items[2].Details)); + Assert.AreEqual((int)ContentSize.Small, GetDetailsSize(items[3].Details)); + Assert.AreEqual((int)ContentSize.Small, GetDetailsSize(items[4].Details)); } private static int GetDetailsSize(IDetails? details) diff --git a/src/modules/cmdpal/doc/json-rpc-spec/02-typescript-sdk.md b/src/modules/cmdpal/doc/json-rpc-spec/02-typescript-sdk.md index 29f286bb75..12d1467e56 100644 --- a/src/modules/cmdpal/doc/json-rpc-spec/02-typescript-sdk.md +++ b/src/modules/cmdpal/doc/json-rpc-spec/02-typescript-sdk.md @@ -287,8 +287,11 @@ interface Details { title?: string; body?: string; // Markdown-formatted body text metadata?: DetailsElement[]; + size?: DetailsSize; // Defaults to 'small' } +type DetailsSize = 'small' | 'medium' | 'large' | 0 | 1 | 2; + interface DetailsElement { key: string; // Label shown to the left data: DetailsData; // Value shown to the right @@ -302,6 +305,10 @@ type DetailsData = | DetailsSeparator; // { type: 'separator' } ``` +Use the named sizes for new extensions. The numeric values map to the host's +`ContentSize` values and remain available when you need to mirror an existing +host payload. + --- ## Page Types diff --git a/src/modules/cmdpal/doc/json-rpc-spec/03-jsonrpc-protocol.md b/src/modules/cmdpal/doc/json-rpc-spec/03-jsonrpc-protocol.md index 24f2942686..615b1cb6ae 100644 --- a/src/modules/cmdpal/doc/json-rpc-spec/03-jsonrpc-protocol.md +++ b/src/modules/cmdpal/doc/json-rpc-spec/03-jsonrpc-protocol.md @@ -246,6 +246,7 @@ Fetches items for a list page. "details": { "title": "Item One Details", "body": "**Rich** markdown description", + "size": "large", "metadata": [ { "key": "Author", "data": { "type": "tags", "tags": [{ "text": "mjolley" }] } }, { "key": "Link", "data": { "type": "link", "link": "https://github.com", "text": "GitHub" } } @@ -266,6 +267,10 @@ Fetches items for a list page. `hasMoreItems` is a boolean on the response envelope (it defaults to `false` when the extension omits it). `true` tells the host that more pages remain, so the host may issue a [`listPage/loadMore`](#listpageloadmore) request when the user scrolls to the end; `false` means the current items are the full set. The value comes straight from the list page's `hasMoreItems` property. +The optional `details.size` field accepts `small`, `medium`, or `large`. The +matching host `ContentSize` values `0`, `1`, and `2` are also accepted. Missing +or unknown values use `small`. + The `section` field is ignored on any item that carries a command. The host renders a command-bearing item as a normal list item, so it never becomes a group header. `section` takes effect only on a command-less row, where it turns that row into a section header. To group command items visually, emit a standalone separator row (see below) before the group. Items with `_isSeparator: true` are rendered as visual separators: diff --git a/src/modules/cmdpal/doc/json-rpc-spec/overview.md b/src/modules/cmdpal/doc/json-rpc-spec/overview.md index 9c4aec3bba..7c83bb008e 100644 --- a/src/modules/cmdpal/doc/json-rpc-spec/overview.md +++ b/src/modules/cmdpal/doc/json-rpc-spec/overview.md @@ -79,6 +79,20 @@ There is no per-page unload notification. A JS page learns it has become active A future solution would be additive and JS-only: a host to extension JSON-RPC notification (for example `page/unloaded` carrying the page id, and optionally a symmetric `page/loaded`), emitted from the host where a page is torn down. `PageViewModel.UnsafeCleanup` is the natural single choke point, since back-navigation and other disposal paths all pass through it. The TS SDK would expose an optional `onUnload` (and optionally `onLoad`) hook on the page base classes, following the existing `loadMore` lifecycle pattern. Because it is additive with no reply expected, older extensions that do not register the hook are unaffected. The item is deferred because it introduces JS-only surface with no C# ABI equivalent, and that asymmetry needs a broader decision. +### Prefixed token selection + +`textToSuggest` supports whole-query completion, but it does not provide the +native sample's token behavior. The native path opts into `TokenSearch`, tracks +the caret, wraps selected tokens with zero-width spaces, and lets the search box +delete a token as one unit. None of that state crosses the JSON-RPC boundary +today, so the JS sample does not pretend that right-arrow completion is the same +feature. + +A future design can stay additive. A list page could opt into token search, and +`listPage/setSearchText` could include an optional caret position. The host and +SDK would still need an agreed token shape before exposing selection because +zero-width-space markers are an implementation detail, not a wire contract. + ### Drag and drop (DataPackage) There is no drag-and-drop or `DataPackage` concept anywhere in the extension ABI, in either the C# or the JS surface. The only related primitive today is the clipboard, exposed through `IExtensionHost.copyToClipboard`. Items cannot declare draggable payloads, and the host list and content controls do not act as drag sources or drop targets for extension data. diff --git a/src/modules/cmdpal/ext/SampleJSExtension/README.md b/src/modules/cmdpal/ext/SampleJSExtension/README.md index 26951270a0..5b846204fd 100644 --- a/src/modules/cmdpal/ext/SampleJSExtension/README.md +++ b/src/modules/cmdpal/ext/SampleJSExtension/README.md @@ -43,6 +43,8 @@ inventing protocol methods: - Parameter pages (`SimpleParameterTest`, `ButtonParameterTest`, `MixedParamTestPage`) and the create-note list-parameter page. No parameter run protocol. +- Prefixed token selection. `textToSuggest` can complete the whole query, but + token search state and caret position do not cross the JSON-RPC boundary. - Drag and drop via `DataPackage`. `IListItem` has no `DataPackage`, so the clipboard demo copies to the clipboard instead. - Toast icon and toast action button (`IToastArgs2`). `ToastArgs` carries a diff --git a/src/modules/cmdpal/ext/SampleJSExtension/src/pages/contentPages.ts b/src/modules/cmdpal/ext/SampleJSExtension/src/pages/contentPages.ts index 4bd71a0cab..725845d2f8 100644 --- a/src/modules/cmdpal/ext/SampleJSExtension/src/pages/contentPages.ts +++ b/src/modules/cmdpal/ext/SampleJSExtension/src/pages/contentPages.ts @@ -113,13 +113,8 @@ export class SamplePlainTextContentPage extends ContentPageBase { /** * A page showing images. Mirrors the C# `SampleImageContentPage`. * -<<<<<<< HEAD * The C# page loads packaged JPG and SVG assets. This sample uses its bundled * hero PNG and sends the encoded image bytes rather than a machine-specific path. -======= - * The image ships with the sample, so the page works without a network - * connection and matches the details page. ->>>>>>> d644f02e36 (reword phase-7 comments in my voice) */ export class SampleImageContentPage extends ContentPageBase { readonly id = 'sample-image-content-page'; diff --git a/src/modules/cmdpal/ts-sdk/README.md b/src/modules/cmdpal/ts-sdk/README.md index 263cc9953e..b2c087a3ad 100644 --- a/src/modules/cmdpal/ts-sdk/README.md +++ b/src/modules/cmdpal/ts-sdk/README.md @@ -106,7 +106,7 @@ channel. Your logging is preserved on `stderr` and through - Icons and colors: `IconData`, `IconInfo`, `Color`, `OptionalColor`, `Tag`, `KeyChord`. - Commands and results: `ICommand`, `IInvokableCommand`, `CommandResult`, `CommandResultKind`, `NavigationMode`. - Items: `ICommandItem`, `IListItem`, `IFallbackCommandItem`, `ContextItem`. -- Details panel: `Details`, `DetailsElement`, and the `DetailsData` union (`tags`, `link`, `commands`, `separator`). +- Details panel: `Details`, `DetailsSize`, `DetailsElement`, and the `DetailsData` union (`tags`, `link`, `commands`, `separator`). - Pages: `IPage`, `IListPage`, `IDynamicListPage`, `IContentPage`, `Filters`, `GridProperties`. - Content: the `Content` union (`markdown`, `form`, `tree`, `plainText`, `image`). - Settings and host: `ICommandSettings`, `IExtensionHost`, `ICommandProvider`. diff --git a/src/modules/cmdpal/ts-sdk/src/types.ts b/src/modules/cmdpal/ts-sdk/src/types.ts index 43d2ca1b06..da11b6624a 100644 --- a/src/modules/cmdpal/ts-sdk/src/types.ts +++ b/src/modules/cmdpal/ts-sdk/src/types.ts @@ -32,7 +32,7 @@ export type ContentType = 'markdown' | 'form' | 'tree' | 'plainText' | 'image'; export type GridLayoutType = 'small' | 'medium' | 'gallery'; /** Size of the details pane shown alongside a list item or content page. */ -export type DetailsSize = 'small' | 'medium' | 'large'; +export type DetailsSize = 'small' | 'medium' | 'large' | 0 | 1 | 2; /** Font family used by plain text content. */ export type FontFamily = 'userInterface' | 'monospace'; @@ -349,11 +349,11 @@ export interface Details { /** Labeled metadata rows shown below the body. */ metadata?: DetailsElement[]; /** - * Size of the details pane. You can send 'small', 'medium', or 'large'. - * Numeric `ContentSize` values (0, 1, 2) also work to match the host. + * Size of the details pane. Named values are preferred, and the matching + * numeric `ContentSize` values are accepted for host compatibility. * Omitted values default to 'small'. */ - size?: DetailsSize | number; + size?: DetailsSize; } // === Filters === diff --git a/src/modules/cmdpal/ts-sdk/test/serialize.test.ts b/src/modules/cmdpal/ts-sdk/test/serialize.test.ts index 93e753efe8..25e6d63eca 100644 --- a/src/modules/cmdpal/ts-sdk/test/serialize.test.ts +++ b/src/modules/cmdpal/ts-sdk/test/serialize.test.ts @@ -144,4 +144,3 @@ describe('WireSerializer.commandItem fallback ids', () => { expect(new WireSerializer().commandItem(item).id).toBe('fallback-command'); }); }); -