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>
This commit is contained in:
Michael Jolley
2026-08-26 23:02:36 -05:00
parent 45b8ab9de1
commit 2ab35a2c13
9 changed files with 38 additions and 12 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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.

View File

@@ -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

View File

@@ -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';

View File

@@ -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`.

View File

@@ -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 ===

View File

@@ -144,4 +144,3 @@ describe('WireSerializer.commandItem fallback ids', () => {
expect(new WireSerializer().commandItem(item).id).toBe('fallback-command');
});
});