Skip to content

CmdPal JS/TS Extensions - Phase 3: adapters + proxies - #49324

Open
michaeljolley wants to merge 14 commits into
dev/mjolley/phase-2-cmdpal-jsonrpc-transportfrom
dev/mjolley/phase-3-cmdpal-adapters
Open

CmdPal JS/TS Extensions - Phase 3: adapters + proxies#49324
michaeljolley wants to merge 14 commits into
dev/mjolley/phase-2-cmdpal-jsonrpc-transportfrom
dev/mjolley/phase-3-cmdpal-adapters

Conversation

@michaeljolley

@michaeljolley michaeljolley commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Warning

This PR is one in a series of PRs focused on adding TypeScript/JavaScript extension support to Command Palette.

This PR should not be merged until PR #49325 is merged into it.

Note

To test the final result, run the branch associated with PR #49364.

Summary

Phase 3 of adding JavaScript/TypeScript extensions to Command Palette (CmdPal). This adds the C# translation layer: adapters and proxies that present JSON-RPC 2.0 data (from out-of-process Node.js extensions) as the native CmdPal WinRT interfaces. This is additive library code under Microsoft.CmdPal.UI.ViewModels/Models; it may be unreferenced by app code for now.

Tracking issue: #48707

Note

This PR stacks on Phase 2 (PR #49323) and targets its branch dev/mjolley/phase-2-cmdpal-jsonrpc-transport, not main. It stays a draft until reviewed. Please review Phase 2 first.

What is in scope

  • Maps JSON-RPC responses to ICommandProvider, ICommand/IInvokableCommand, IListPage/IDynamicListPage, IContentPage, IIconInfo/IconData, tags, details, content, filters, and settings.
  • Reuses the Phase 2 JsonRpcConnection for outbound requests and inbound notifications.

What is NOT in scope (Phase 4)

  • No spawning of Node.js processes, no extension service / discovery / hot-reload, no DI or UI wiring.

Adapter / proxy surface

Static mappers:

  • JSModelMapper - materializes leaf/data objects (icons via the four-format pipeline: font glyph, file/URI path, raw base64 to in-memory stream, data URI parse+decode; tags, color, details + detail data, grid layouts, filter items, separators, content items).
  • JSCommandResultParser - maps invoke result Kind 0..7 to the Toolkit CommandResult (Dismiss, GoHome, GoBack, Hide, KeepOpen, GoToPage, ShowToast, Confirm).
  • JSCommandFactory - routes command JSON to the right page proxy or invokable adapter (pageType/_type discriminator).

Adapters:

  • JSInvokableCommandAdapter - Invoke to command/invoke request.
  • JSCommandItemAdapter, JSListItemAdapter - lazy command, tags/details/section/moreCommands.
  • JSFallbackCommandItemAdapter - mutable DisplayTitle, reacts to command/propChanged.
  • JSFiltersAdapter - CurrentFilterId to listPage/setFilter.

Proxies:

  • JSListPageProxy - getItems to listPage/getItems, filters, grid properties, load more, empty content, listPage/itemsChanged.
  • JSDynamicListPageProxy - setSearchText to listPage/setSearchText.
  • JSContentPageProxy - getContent to contentPage/getContent, details, commands.
  • JSFormContentProxy - form submit to form/submit.
  • JSCommandProviderProxy - TopLevelCommands/FallbackCommands/GetCommand/Settings over provider/*, host notifications (log/status/copy).
  • JSCommandSettingsProxy - exposes the extension settings page id.

Tests

Added MSTest coverage in Microsoft.CmdPal.UI.ViewModels.UnitTests, driven through the public JSCommandProviderProxy against an in-memory fake extension (JSFakeExtension) backed by a real JsonRpcConnection over pipes. No real Node.js process is spawned. Covers top-level commands, the icon pipeline (all four formats), invoke Kind mapping 0..7, list items (tags/details/sections/separators/moreCommands), dynamic setSearchText + itemsChanged, content types (markdown/plainText/image/form/tree) with form submit, fallback propChanged, and settings.

Build is clean (x64/Debug, warnings-as-errors, StyleCop, nullable) with no suppressions. All 178 ViewModels unit tests pass.

Add the C# translation layer that presents JSON-RPC data from Node.js
extensions as native CmdPal WinRT interfaces. Additive library code under
Microsoft.CmdPal.UI.ViewModels/Models plus MSTest coverage driven by an
in-memory fake extension. No node spawning, discovery, hot-reload, or
DI/UI wiring (those are Phase 4).

Adapters/proxies:
- JSModelMapper, JSCommandResultParser, JSCommandFactory (static mappers)
- JSInvokableCommandAdapter, JSCommandItemAdapter, JSListItemAdapter,
  JSFallbackCommandItemAdapter, JSFiltersAdapter
- JSListPageProxy, JSDynamicListPageProxy, JSContentPageProxy,
  JSFormContentProxy
- JSCommandProviderProxy, JSCommandSettingsProxy

Reuses the Phase 2 JsonRpcConnection for outbound requests and notifications.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: f9b90e37-dcfc-4ea7-8a79-87eb2af072b6
michaeljolley and others added 4 commits July 15, 2026 11:02
…pc-transport' into dev/mjolley/phase-3-cmdpal-adapters
- Filter separators read the SDK "separator" flag instead of "_isSeparator"
  (list-item separators still use "_isSeparator" per the SDK serializer).
- Grid layout reads the SDK "type" discriminator instead of "layout".
- fallback/updateQuery is sent as a request, matching the protocol and SDK.
- Scope listPage/itemsChanged routing per connection via a ConditionalWeakTable
  keyed by the JsonRpcConnection, holding proxies as weak references so ids from
  different extensions no longer collide and collected proxies are pruned.
- Use a ConcurrentDictionary for the fallback adapter map shared between the
  request path and the JSON-RPC reader thread.
- Add tests covering filter separators, grid type, and fallback updateQuery.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: f9b90e37-dcfc-4ea7-8a79-87eb2af072b6
ParseContextItem only read title/command/icon and dropped any nested moreCommands array, so JS/TS extensions could not surface nested context-menu commands even when the wire carried them. Recursively parse the moreCommands array (reusing ParseContextItems) and assign the children onto the existing CommandContextItem.MoreCommands, leaving the default empty array when the field is absent.

Pairs with the Phase 1 SDK serialization change (8f123a8).

Co-authored-by: Copilot App <[email protected]>

Copilot-Session: 8e96286c-f6eb-42d5-bcd3-68fc6a76622f
michaeljolley and others added 9 commits July 22, 2026 13:08
…lisher fallback)

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 762976e0-2453-40ca-8034-978a04a19d1e
Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 3f0875f9-177f-40db-888c-248a044d2bb9
Wire the JS extension proxies and adapters to the locked wire contract:
form identity on submit, toast continuation results, per-page proxy
routing, pagination state refresh, full settings page metadata,
context-menu shortcuts, icon fallback, status identity, provider
dispose cleanup, frozen metadata and page accent color. Adds
fixture-driven unit tests that consume the shared TS SDK wire fixtures.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 9470f549-1997-44de-b0a6-1ce4562a51f0
Propagates the stale-test and golden-fixture corrections so the SDK
typecheck stays green at this layer.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 311db8aa-f8db-47bb-943f-5447037946c7
r2-p3-01 pagination: JSListPageProxy.LoadMore now settles HasMoreItems from
the loadMore result and raises ItemsChanged so the host clears its loading
state and re-queries appended items end to end.
r2-p3-03 registry-init race: the itemsChanged notification handler is now
wired via EnsureSubscribed on the retained PageRegistry (pure CWT factory),
so a discarded registry can never be the subscribed one.
r2-p3-04 context-icon-fallback: a context/more-commands item that omits its
own icon now inherits the command icon instead of being cleared to empty.
r2-p3-05 status-disposal race: status message bookkeeping is guarded by a
lock and Dispose snapshots under the lock before hiding, so enumerate and
mutate no longer collide.
r2-p3-02 settings-metadata: verified already correct (full serialized
settings page is consumed), no change.

Adds JSAdapterRemediationTests.Round2.cs covering the above.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 311db8aa-f8db-47bb-943f-5447037946c7
Bring the phase-2 JSON-RPC transport round-2 fixes down onto phase-3. Clean merge.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 311db8aa-f8db-47bb-943f-5447037946c7
Address the round-3 review findings in the C# JS adapters at root cause.

r3-p3-01 JSListPageProxy.LoadMore: a failed loadMore (RPC error or
transport exception) settled paging to HasMoreItems=false and raises
ItemsChanged so the host clears its loading spinner instead of hanging.

r3-p3-02 JSListPageProxy.PageRegistry.EnsureSubscribed: register the
itemsChanged handler before marking the registry subscribed, under one
lock, so a concurrent caller cannot observe the registry as subscribed
before the handler is bound and drop a notification.

r3-p3-03 JSCommandProviderProxy: buffer host showStatus, hideStatus and
logMessage notifications that arrive before InitializeWithHost, then
replay them in arrival order once the host is attached.

r3-p3-04 JSCommandProviderProxy: consume the initialize handshake id,
displayName and icon, falling back to the package manifest when a field
is absent, so the palette reflects the extension declared identity.

r3-p3-05 JSCommandProviderProxy: dispatch host.ShowStatus while holding
the status lock so a dispose-triggered hide serializes strictly after a
pending show and cannot strand a status shown but never hidden.

r3-p3-06 JSModelMapper.ParseIconInfo: when exactly one theme variant is
supplied, mirror it onto the other theme so the icon renders in both
light and dark rather than disappearing in the omitted theme.

Add Round3 MSTest coverage plus JSFakeExtension error-response support.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 311db8aa-f8db-47bb-943f-5447037946c7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant