From fb532ff3c57e5d0ad41a72326d7ed0045bfa5f84 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:08:34 -0800 Subject: [PATCH 01/32] Try to add an app console and reproduce the error; Uncaught NotSupportedError: Failed to execute 'define' on 'CustomElementRegistry': the name close-cell-button has already been used with this registry --- packages/react-components/package.json | 3 +- .../src/components/AppConsole/AppConsole.tsx | 82 +++++++++++++++++++ packages/react-components/src/layout.tsx | 34 +++++--- pnpm-lock.yaml | 17 +--- 4 files changed, 109 insertions(+), 27 deletions(-) create mode 100644 packages/react-components/src/components/AppConsole/AppConsole.tsx diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 75f2d8a..8284ae9 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -19,7 +19,8 @@ "dist" ], "devDependencies": { - "@runmedev/react-console": "workspace:*" + "@runmedev/react-console": "workspace:*", + "@runmedev/renderers": "workspace:*" }, "scripts": { "clean": "rimraf dist", diff --git a/packages/react-components/src/components/AppConsole/AppConsole.tsx b/packages/react-components/src/components/AppConsole/AppConsole.tsx new file mode 100644 index 0000000..706a608 --- /dev/null +++ b/packages/react-components/src/components/AppConsole/AppConsole.tsx @@ -0,0 +1,82 @@ +import { useMemo, useRef } from 'react' + +import { ClientMessages, setContext } from '@runmedev/renderers' +import type { RendererContext } from 'vscode-notebook-renderer' +/** + * AppConsole console panel rendered with Runme's ConsoleView web component. + * + * @runmedev/react-console (imported at app entry) registers the custom + * elements (console-view, close-cell-button, etc.) as a side effect, so + * by the time this renders the elements should already be defined. + */ +export default function AppConsole() { + const elemRef = useRef(null) + const consoleId = useMemo( + () => `console-${Math.random().toString(36).substring(2, 9)}`, + [] + ) + + const eventHandler = (eventName: string) => (e: Event) => { + console.log(eventName, e) + } + + return ( +
{ + if (!el || el.hasChildNodes()) { + return + } + const elem = document.createElement('console-view') as any + + elemRef.current = elem + + elem.addEventListener('stdout', eventHandler('stdout')) + elem.addEventListener('stderr', eventHandler('stderr')) + elem.addEventListener('exitcode', eventHandler('exitcode')) + elem.addEventListener('pid', eventHandler('pid')) + elem.addEventListener('mimetype', eventHandler('mimetype')) + + setContext({ + postMessage: (message: unknown) => { + // Only need this if, e.g., we received stdin + console.log('message', message) + }, + onDidReceiveMessage: (listener: (message: unknown) => void) => { + listener({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: 'Welcome to the Runme console\n', + }, + } as any) + return { + dispose: () => {}, + } + }, + } as RendererContext) + + // Keep the element id in sync with messages dispatched via setContext + elem.setAttribute('id', consoleId) + elem.setAttribute('buttons', 'false') + elem.setAttribute('initialContent', 'Welcome to the Runme console\r\n') + elem.setAttribute('theme', 'dark') + elem.setAttribute('fontFamily', 'monospace') + elem.setAttribute('fontSize', '12') + elem.setAttribute('cursorStyle', 'block') + elem.setAttribute('cursorBlink', 'true') + elem.setAttribute('cursorWidth', '1') + elem.setAttribute('smoothScrollDuration', '0') + elem.setAttribute('scrollback', '4000') + + el.appendChild(elem) + }} + >
+ ) +} + +// let context: RendererContext | undefined + +// export function setContext(c: RendererContext) { +// context = c +// } diff --git a/packages/react-components/src/layout.tsx b/packages/react-components/src/layout.tsx index 8bbcc34..d823069 100644 --- a/packages/react-components/src/layout.tsx +++ b/packages/react-components/src/layout.tsx @@ -5,6 +5,7 @@ import { Code } from '@buf/googleapis_googleapis.bufbuild_es/google/rpc/code_pb' import { Box, Flex, Text } from '@radix-ui/themes' import { AppBranding } from './App' +import AppConsole from './components/AppConsole/AppConsole' import TopNavigation from './components/TopNavigation' import { useSettings } from './contexts/SettingsContext' @@ -72,21 +73,28 @@ function Layout({ - {/* Main content */} - - {/* Left */} - - {left ??
} - + {/* Main content with bottom console */} + + + {/* Left */} + + {left ??
} + - {/* Middle */} - - {middle ??
} - + {/* Middle */} + + {middle ??
} + + + {/* Right */} + + {right ??
} + + - {/* Right */} - - {right ??
} + {/* Console spanning the full width at the bottom */} + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e2754a..d2ff7b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -232,6 +232,9 @@ importers: '@runmedev/react-console': specifier: workspace:* version: link:../react-console + '@runmedev/renderers': + specifier: workspace:* + version: link:../renderers packages/react-console: dependencies: @@ -299,7 +302,7 @@ importers: version: 14.6.1(@testing-library/dom@10.4.1) '@vitejs/plugin-react': specifier: ^4.0.0 - version: 4.7.0(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.30.1)) + version: 4.7.0(vite@6.3.6(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.30.1)) '@vitest/ui': specifier: ^2.0.0 version: 2.1.9(vitest@2.1.9) @@ -5738,18 +5741,6 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.7.0(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.30.1))': - dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.30.1) - transitivePeerDependencies: - - supports-color - '@vitejs/plugin-react@4.7.0(vite@6.3.6(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.30.1))': dependencies: '@babel/core': 7.28.4 From e5a5bd52440765bdcae16cfaab94f03bb2392071 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:16:16 -0800 Subject: [PATCH 02/32] Make custom element registration idempotent --- packages/renderers/src/components/closeCellButton.ts | 6 ++++-- .../renderers/src/components/console/actionButton.ts | 6 ++++-- packages/renderers/src/components/console/gistCell.ts | 6 ++++-- packages/renderers/src/components/console/open.ts | 6 ++++-- packages/renderers/src/components/console/runme.ts | 6 ++++-- packages/renderers/src/components/console/saveButton.ts | 6 ++++-- packages/renderers/src/components/console/shareButton.ts | 6 ++++-- packages/renderers/src/components/console/view.ts | 6 ++++-- packages/renderers/src/components/copyButton.ts | 6 ++++-- packages/renderers/src/components/dropdownlist.ts | 7 +++++-- packages/renderers/src/components/env/store.ts | 6 ++++-- packages/renderers/src/components/envViewer.ts | 6 ++++-- packages/renderers/src/components/table/index.ts | 7 +++++-- packages/renderers/src/components/tooltip.ts | 7 +++++-- packages/renderers/src/utils/defineCustomElement.ts | 9 +++++++++ 15 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 packages/renderers/src/utils/defineCustomElement.ts diff --git a/packages/renderers/src/components/closeCellButton.ts b/packages/renderers/src/components/closeCellButton.ts index c0513cb..dd5651d 100644 --- a/packages/renderers/src/components/closeCellButton.ts +++ b/packages/renderers/src/components/closeCellButton.ts @@ -1,7 +1,7 @@ import { LitElement, css, html } from 'lit' -import { customElement } from 'lit/decorators.js' -@customElement('close-cell-button') +import { defineCustomElement } from '../utils/defineCustomElement' + export class CloseCellButton extends LitElement { /* eslint-disable */ static styles = css` @@ -103,3 +103,5 @@ export class CloseCellButton extends LitElement { ` } } + +defineCustomElement('close-cell-button', CloseCellButton) diff --git a/packages/renderers/src/components/console/actionButton.ts b/packages/renderers/src/components/console/actionButton.ts index 5f1d9eb..fb92f17 100644 --- a/packages/renderers/src/components/console/actionButton.ts +++ b/packages/renderers/src/components/console/actionButton.ts @@ -1,11 +1,11 @@ import { LitElement, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' +import { defineCustomElement } from '../../utils/defineCustomElement' import { SaveIcon } from '../icons/save' import { ShareIcon } from '../icons/share' -@customElement('action-button') export class ActionButton extends LitElement { @property({ type: String }) text: string = 'Copy' @@ -78,3 +78,5 @@ export class ActionButton extends LitElement { ` } } + +defineCustomElement('action-button', ActionButton) diff --git a/packages/renderers/src/components/console/gistCell.ts b/packages/renderers/src/components/console/gistCell.ts index 33c9aab..28f8374 100644 --- a/packages/renderers/src/components/console/gistCell.ts +++ b/packages/renderers/src/components/console/gistCell.ts @@ -1,10 +1,10 @@ import { LitElement, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' +import { defineCustomElement } from '../../utils/defineCustomElement' import { GistIcon } from '../icons/gistIcon' -@customElement('gist-cell') export class GistCell extends LitElement { @property({ type: String }) text: string = 'Preview & Gist' @@ -64,3 +64,5 @@ export class GistCell extends LitElement { ) } } + +defineCustomElement('gist-cell', GistCell) diff --git a/packages/renderers/src/components/console/open.ts b/packages/renderers/src/components/console/open.ts index c156140..abff42b 100644 --- a/packages/renderers/src/components/console/open.ts +++ b/packages/renderers/src/components/console/open.ts @@ -1,10 +1,10 @@ import { LitElement, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' +import { defineCustomElement } from '../../utils/defineCustomElement' import { EyeIcon } from '../icons/eye' -@customElement('open-cell') export class OpenCell extends LitElement { @property({ type: String }) openText: string = 'Open' @@ -58,3 +58,5 @@ export class OpenCell extends LitElement { ) } } + +defineCustomElement('open-cell', OpenCell) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index afc5c3f..e4c647f 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -10,12 +10,13 @@ import { import { create } from '@bufbuild/protobuf' import { Interceptor } from '@connectrpc/connect' import { LitElement, PropertyValues, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { Disposable } from 'vscode' import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' import { setContext } from '../../messaging' +import { defineCustomElement } from '../../utils/defineCustomElement' import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' @@ -31,7 +32,6 @@ export interface RunmeConsoleStream { export const RUNME_CONSOLE = 'runme-console' -@customElement(RUNME_CONSOLE) export class RunmeConsole extends LitElement { protected disposables: Disposable[] = [] @@ -429,3 +429,5 @@ export class RunmeConsole extends LitElement { this.disposables.forEach(({ dispose }) => dispose()) } } + +defineCustomElement(RUNME_CONSOLE, RunmeConsole) diff --git a/packages/renderers/src/components/console/saveButton.ts b/packages/renderers/src/components/console/saveButton.ts index f166e5d..3bcab1d 100644 --- a/packages/renderers/src/components/console/saveButton.ts +++ b/packages/renderers/src/components/console/saveButton.ts @@ -1,9 +1,9 @@ import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' +import { defineCustomElement } from '../../utils/defineCustomElement' import './actionButton' -@customElement('save-button') export class SaveButton extends LitElement { @property({ type: Boolean, reflect: true }) loading: boolean = false @@ -33,3 +33,5 @@ export class SaveButton extends LitElement { ` } } + +defineCustomElement('save-button', SaveButton) diff --git a/packages/renderers/src/components/console/shareButton.ts b/packages/renderers/src/components/console/shareButton.ts index 54dac10..8746ea0 100644 --- a/packages/renderers/src/components/console/shareButton.ts +++ b/packages/renderers/src/components/console/shareButton.ts @@ -1,9 +1,9 @@ import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' +import { defineCustomElement } from '../../utils/defineCustomElement' import './actionButton' -@customElement('share-button') export class ShareButton extends LitElement { @property({ type: Boolean, reflect: true }) loading: boolean = false @@ -28,3 +28,5 @@ export class ShareButton extends LitElement { ` } } + +defineCustomElement('share-button', ShareButton) diff --git a/packages/renderers/src/components/console/view.ts b/packages/renderers/src/components/console/view.ts index e3a999a..18828e0 100644 --- a/packages/renderers/src/components/console/view.ts +++ b/packages/renderers/src/components/console/view.ts @@ -3,7 +3,7 @@ import { Unicode11Addon } from '@xterm/addon-unicode11' import { WebLinksAddon } from '@xterm/addon-web-links' import { ITheme, Terminal as XTermJS } from '@xterm/xterm' import { LitElement, PropertyValues, css, html, unsafeCSS } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' import { Observable } from 'rxjs' import { @@ -28,6 +28,7 @@ import './open' import './saveButton' import './shareButton' import { darkStyles, lightStyles } from './vscode.css' +import { defineCustomElement } from '../../utils/defineCustomElement' export interface ConsoleViewConfig { theme: 'dark' | 'light' | 'vscode' @@ -90,7 +91,6 @@ const ANSI_COLORS = [ export const CONSOLE_VIEW = 'console-view' -@customElement(CONSOLE_VIEW) export class ConsoleView extends LitElement { protected copyText = 'Copy' @@ -1245,6 +1245,8 @@ export class ConsoleView extends LitElement { } } +defineCustomElement(CONSOLE_VIEW, ConsoleView) + function convertXTermDimensions( dimensions: ITerminalDimensions ): TerminalDimensions diff --git a/packages/renderers/src/components/copyButton.ts b/packages/renderers/src/components/copyButton.ts index 498e6a8..652ae08 100644 --- a/packages/renderers/src/components/copyButton.ts +++ b/packages/renderers/src/components/copyButton.ts @@ -1,9 +1,9 @@ import { LitElement, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { CopyIcon } from './icons/copy' +import { defineCustomElement } from '../utils/defineCustomElement' -@customElement('copy-button') export class CopyButton extends LitElement { @property({ type: String }) copyText: string = 'Copy' @@ -43,3 +43,5 @@ export class CopyButton extends LitElement { ` } } + +defineCustomElement('copy-button', CopyButton) diff --git a/packages/renderers/src/components/dropdownlist.ts b/packages/renderers/src/components/dropdownlist.ts index 76a7928..b5aa74b 100644 --- a/packages/renderers/src/components/dropdownlist.ts +++ b/packages/renderers/src/components/dropdownlist.ts @@ -1,5 +1,7 @@ import { LitElement, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' + +import { defineCustomElement } from '../utils/defineCustomElement' export interface DropdownListOption { text: string @@ -12,7 +14,6 @@ export type DropdownListEvent = { key: string } -@customElement('dropdown-list') export class DropdownList extends LitElement { @property({ type: String }) label: string | undefined @@ -94,3 +95,5 @@ export class DropdownList extends LitElement { ` } } + +defineCustomElement('dropdown-list', DropdownList) diff --git a/packages/renderers/src/components/env/store.ts b/packages/renderers/src/components/env/store.ts index 001dec9..2e7ca3b 100644 --- a/packages/renderers/src/components/env/store.ts +++ b/packages/renderers/src/components/env/store.ts @@ -1,10 +1,11 @@ import { MonitorEnvStoreResponseSnapshot_SnapshotEnv } from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' import { MonitorEnvStoreResponseSnapshot_Status } from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' import { LitElement, TemplateResult, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { Disposable } from 'vscode' import { formatDate, formatDateWithTimeAgo } from '../../utils' +import { defineCustomElement } from '../../utils/defineCustomElement' import '../envViewer' import { CustomErrorIcon } from '../icons/error' import '../table' @@ -45,7 +46,6 @@ const COLUMNS = [ const HIDDEN_COLUMNS = ['resolvedValue', 'errors', 'status', 'specClass'] -@customElement('env-store') export default class Table extends LitElement { protected disposables: Disposable[] = [] @@ -191,3 +191,5 @@ export default class Table extends LitElement { return html`${format()}` } } + +defineCustomElement('env-store', Table) diff --git a/packages/renderers/src/components/envViewer.ts b/packages/renderers/src/components/envViewer.ts index 13b91ba..922ddf7 100644 --- a/packages/renderers/src/components/envViewer.ts +++ b/packages/renderers/src/components/envViewer.ts @@ -1,6 +1,6 @@ import { MonitorEnvStoreResponseSnapshot_Status } from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' import { LitElement, TemplateResult, css, html } from 'lit' -import { customElement, property, state } from 'lit/decorators.js' +import { property, state } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' import { Disposable } from 'vscode' @@ -9,8 +9,8 @@ import { CopyIcon } from './icons/copy' import { EyeIcon } from './icons/eye' import { EyeClosedIcon } from './icons/eyeClosed' import './tooltip' +import { defineCustomElement } from '../utils/defineCustomElement' -@customElement('env-viewer') export class EnvViewer extends LitElement implements Disposable { protected disposables: Disposable[] = [] @@ -163,3 +163,5 @@ export class EnvViewer extends LitElement implements Disposable { ` } } + +defineCustomElement('env-viewer', EnvViewer) diff --git a/packages/renderers/src/components/table/index.ts b/packages/renderers/src/components/table/index.ts index 8c2e210..39bf08b 100644 --- a/packages/renderers/src/components/table/index.ts +++ b/packages/renderers/src/components/table/index.ts @@ -1,13 +1,14 @@ import { LitElement, TemplateResult, css, html, nothing } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' +import { defineCustomElement } from '../../utils/defineCustomElement' + export interface Column { text: string colspan?: number | undefined } -@customElement('table-view') export class Table extends LitElement { @property({ type: Array }) columns?: Column[] = [] @@ -220,3 +221,5 @@ export class Table extends LitElement { ` } } + +defineCustomElement('table-view', Table) diff --git a/packages/renderers/src/components/tooltip.ts b/packages/renderers/src/components/tooltip.ts index 59321ba..e4d5bb9 100644 --- a/packages/renderers/src/components/tooltip.ts +++ b/packages/renderers/src/components/tooltip.ts @@ -1,8 +1,9 @@ import { LitElement, TemplateResult, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' -@customElement('tooltip-text') +import { defineCustomElement } from '../utils/defineCustomElement' + export class Tooltip extends LitElement { @property({ type: String }) tooltipText: string | TemplateResult<1> | undefined @@ -77,3 +78,5 @@ export class Tooltip extends LitElement {
` } } + +defineCustomElement('tooltip-text', Tooltip) diff --git a/packages/renderers/src/utils/defineCustomElement.ts b/packages/renderers/src/utils/defineCustomElement.ts new file mode 100644 index 0000000..52ef37b --- /dev/null +++ b/packages/renderers/src/utils/defineCustomElement.ts @@ -0,0 +1,9 @@ +export function defineCustomElement( + tag: string, + ctor: CustomElementConstructor +) { + if (customElements.get(tag)) { + return + } + customElements.define(tag, ctor) +} From 708d055b96b4517af84e0dee417b789c03153433 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:17:58 -0800 Subject: [PATCH 03/32] Guard console messaging when context is missing --- packages/renderers/src/messaging.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/renderers/src/messaging.ts b/packages/renderers/src/messaging.ts index a35b179..1ac59fc 100644 --- a/packages/renderers/src/messaging.ts +++ b/packages/renderers/src/messaging.ts @@ -4,6 +4,12 @@ import { RendererContext } from 'vscode-notebook-renderer' import { ClientMessage, ClientMessagePayload } from './types' let context: RendererContext | undefined +const noopDisposable: Disposable = { dispose: () => {} } +const fallbackContext: RendererContext = { + postMessage: () => {}, + onDidReceiveMessage: () => noopDisposable, +} +let warnedMissingContext = false interface Messaging { postMessage(msg: unknown): Thenable | Thenable | void @@ -32,7 +38,11 @@ export function onClientMessage( export function getContext() { if (!context) { - throw new Error('Renderer context not defined') + if (!warnedMissingContext) { + console.warn('Renderer context not defined, using fallback messaging') + warnedMissingContext = true + } + return fallbackContext } return context } From e20aa2f8ddefed13262e12a8e1266b5815559ec6 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:23:15 -0800 Subject: [PATCH 04/32] Declare renderers as peer dependency for react-console --- packages/react-console/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-console/package.json b/packages/react-console/package.json index ac21897..7f7cb78 100644 --- a/packages/react-console/package.json +++ b/packages/react-console/package.json @@ -22,6 +22,7 @@ "@runmedev/renderers": "workspace:*" }, "peerDependencies": { + "@runmedev/renderers": "workspace:*", "@bufbuild/protobuf": "^2.9.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -41,4 +42,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} From 144dfe7ecdcaf4e26f25a66818890bcf11139c7a Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:31:46 -0800 Subject: [PATCH 05/32] Externalize runme libs and declare as peers --- packages/react-components/package.json | 4 +++- packages/renderers/src/messaging.ts | 12 +----------- packages/vite.common.ts | 2 ++ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 8284ae9..1a7498f 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -30,6 +30,8 @@ "test:coverage": "vitest run --coverage" }, "peerDependencies": { + "@runmedev/react-console": "workspace:*", + "@runmedev/renderers": "workspace:*", "js-cookie": "^3.0.5", "@monaco-editor/react": "^4.7.0", "@radix-ui/react-dropdown-menu": "^2.1.15", @@ -44,4 +46,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/renderers/src/messaging.ts b/packages/renderers/src/messaging.ts index 1ac59fc..a35b179 100644 --- a/packages/renderers/src/messaging.ts +++ b/packages/renderers/src/messaging.ts @@ -4,12 +4,6 @@ import { RendererContext } from 'vscode-notebook-renderer' import { ClientMessage, ClientMessagePayload } from './types' let context: RendererContext | undefined -const noopDisposable: Disposable = { dispose: () => {} } -const fallbackContext: RendererContext = { - postMessage: () => {}, - onDidReceiveMessage: () => noopDisposable, -} -let warnedMissingContext = false interface Messaging { postMessage(msg: unknown): Thenable | Thenable | void @@ -38,11 +32,7 @@ export function onClientMessage( export function getContext() { if (!context) { - if (!warnedMissingContext) { - console.warn('Renderer context not defined, using fallback messaging') - warnedMissingContext = true - } - return fallbackContext + throw new Error('Renderer context not defined') } return context } diff --git a/packages/vite.common.ts b/packages/vite.common.ts index f514bec..f992b14 100644 --- a/packages/vite.common.ts +++ b/packages/vite.common.ts @@ -26,6 +26,8 @@ export function createSharedConfig({ entry, name, fileName }) { rollupOptions: { external: [ '@buf/googleapis_googleapis.bufbuild_es', + '@runmedev/renderers', + '@runmedev/react-console', 'react', 'react-dom', 'react/jsx-runtime', From 79b76384bd000b29249bd86a5516f7c62ba167df Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:33:48 -0800 Subject: [PATCH 06/32] Tweak AppConsole welcome message --- .../react-components/src/components/AppConsole/AppConsole.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/src/components/AppConsole/AppConsole.tsx b/packages/react-components/src/components/AppConsole/AppConsole.tsx index 706a608..5fa1e3f 100644 --- a/packages/react-components/src/components/AppConsole/AppConsole.tsx +++ b/packages/react-components/src/components/AppConsole/AppConsole.tsx @@ -47,7 +47,7 @@ export default function AppConsole() { type: ClientMessages.terminalStdout, output: { 'runme.dev/id': consoleId, - data: 'Welcome to the Runme console\n', + data: 'Welcome to the app console\n', }, } as any) return { From 4a54e2768802d1ebada74bb2d85431b623e7c754 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:36:10 -0800 Subject: [PATCH 07/32] Add stub prompt/echo behavior to AppConsole --- .../src/components/AppConsole/AppConsole.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/react-components/src/components/AppConsole/AppConsole.tsx b/packages/react-components/src/components/AppConsole/AppConsole.tsx index 5fa1e3f..9676718 100644 --- a/packages/react-components/src/components/AppConsole/AppConsole.tsx +++ b/packages/react-components/src/components/AppConsole/AppConsole.tsx @@ -37,17 +37,28 @@ export default function AppConsole() { elem.addEventListener('pid', eventHandler('pid')) elem.addEventListener('mimetype', eventHandler('mimetype')) + let messageListener: ((message: unknown) => void) | undefined + setContext({ - postMessage: (message: unknown) => { - // Only need this if, e.g., we received stdin - console.log('message', message) + postMessage: (message: any) => { + if (message?.type === ClientMessages.terminalStdin) { + const input = (message.output?.input as string) ?? '' + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: `\r\nGot input: ${input.trim()}\r\n> `, + }, + }) + } }, onDidReceiveMessage: (listener: (message: unknown) => void) => { + messageListener = listener listener({ type: ClientMessages.terminalStdout, output: { 'runme.dev/id': consoleId, - data: 'Welcome to the app console\n', + data: '> ', }, } as any) return { @@ -59,7 +70,7 @@ export default function AppConsole() { // Keep the element id in sync with messages dispatched via setContext elem.setAttribute('id', consoleId) elem.setAttribute('buttons', 'false') - elem.setAttribute('initialContent', 'Welcome to the Runme console\r\n') + elem.setAttribute('initialContent', '') elem.setAttribute('theme', 'dark') elem.setAttribute('fontFamily', 'monospace') elem.setAttribute('fontSize', '12') From 89380fe62c05c5935ec1f3409d1faa698bde24c4 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 17:41:34 -0800 Subject: [PATCH 08/32] Buffer AppConsole input and add welcome banner --- .../src/components/AppConsole/AppConsole.tsx | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/packages/react-components/src/components/AppConsole/AppConsole.tsx b/packages/react-components/src/components/AppConsole/AppConsole.tsx index 9676718..5afe4b5 100644 --- a/packages/react-components/src/components/AppConsole/AppConsole.tsx +++ b/packages/react-components/src/components/AppConsole/AppConsole.tsx @@ -5,9 +5,8 @@ import type { RendererContext } from 'vscode-notebook-renderer' /** * AppConsole console panel rendered with Runme's ConsoleView web component. * - * @runmedev/react-console (imported at app entry) registers the custom - * elements (console-view, close-cell-button, etc.) as a side effect, so - * by the time this renders the elements should already be defined. + * Intended to provide a console for interacting with the app iteself. + * Right now its just a stub that echoes input. */ export default function AppConsole() { const elemRef = useRef(null) @@ -38,18 +37,48 @@ export default function AppConsole() { elem.addEventListener('mimetype', eventHandler('mimetype')) let messageListener: ((message: unknown) => void) | undefined + let inputBuffer = '' setContext({ postMessage: (message: any) => { if (message?.type === ClientMessages.terminalStdin) { const input = (message.output?.input as string) ?? '' - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: `\r\nGot input: ${input.trim()}\r\n> `, - }, - }) + for (const ch of input) { + if (ch === '\r' || ch === '\n') { + const trimmed = inputBuffer.trim() + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: `\r\nGot input: ${trimmed}\r\n> `, + }, + }) + inputBuffer = '' + continue + } + + // handle backspace/delete + if (ch === '\u0008' || ch === '\u007f') { + inputBuffer = inputBuffer.slice(0, -1) + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: '\b \b', + }, + }) + continue + } + + inputBuffer += ch + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: ch, + }, + }) + } } }, onDidReceiveMessage: (listener: (message: unknown) => void) => { @@ -58,7 +87,7 @@ export default function AppConsole() { type: ClientMessages.terminalStdout, output: { 'runme.dev/id': consoleId, - data: '> ', + data: 'Welcome to the app console\n> ', }, } as any) return { @@ -85,9 +114,3 @@ export default function AppConsole() { >
) } - -// let context: RendererContext | undefined - -// export function setContext(c: RendererContext) { -// context = c -// } From 09643bb1dbd6b3897152ad34e8bc023d90751b2c Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 18:17:04 -0800 Subject: [PATCH 09/32] Allow per-instance console context and wire RunmeConsole --- .../src/components/AppConsole/AppConsole.tsx | 9 +++- .../renderers/src/components/console/runme.ts | 4 +- .../renderers/src/components/console/view.ts | 43 ++++++++++++------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/packages/react-components/src/components/AppConsole/AppConsole.tsx b/packages/react-components/src/components/AppConsole/AppConsole.tsx index 5afe4b5..0035218 100644 --- a/packages/react-components/src/components/AppConsole/AppConsole.tsx +++ b/packages/react-components/src/components/AppConsole/AppConsole.tsx @@ -39,7 +39,7 @@ export default function AppConsole() { let messageListener: ((message: unknown) => void) | undefined let inputBuffer = '' - setContext({ + const ctxBridge = { postMessage: (message: any) => { if (message?.type === ClientMessages.terminalStdin) { const input = (message.output?.input as string) ?? '' @@ -94,7 +94,12 @@ export default function AppConsole() { dispose: () => {}, } }, - } as RendererContext) + } as RendererContext + + setContext(ctxBridge) + // Attach the per-instance context directly so this ConsoleView + // doesn't rely on the module-level singleton. + ;(elem as any).context = ctxBridge // Keep the element id in sync with messages dispatched via setContext elem.setAttribute('id', consoleId) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index e4c647f..54afb52 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -15,7 +15,7 @@ import { Disposable } from 'vscode' import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' -import { setContext } from '../../messaging' +import { getContext, setContext } from '../../messaging' import { defineCustomElement } from '../../utils/defineCustomElement' import Streams from '../../streams' import { ClientMessages } from '../../types' @@ -146,6 +146,8 @@ export class RunmeConsole extends LitElement { // Create ConsoleView element this.consoleView = document.createElement('console-view') as ConsoleView + // Share the messaging context with the child ConsoleView + this.consoleView.context = getContext() // Set all properties on ConsoleView this.#updateConsoleViewProperties() diff --git a/packages/renderers/src/components/console/view.ts b/packages/renderers/src/components/console/view.ts index 18828e0..49e9c25 100644 --- a/packages/renderers/src/components/console/view.ts +++ b/packages/renderers/src/components/console/view.ts @@ -16,7 +16,11 @@ import { import { Disposable, TerminalDimensions } from 'vscode' import { FitAddon, type ITerminalDimensions } from '../../fitAddon' -import { getContext, onClientMessage, postClientMessage } from '../../messaging' +import { + getContext, + onClientMessage, + postClientMessage, +} from '../../messaging' import { ClientMessages, OutputType, WebViews } from '../../types' import { ClientMessage } from '../../types' import { closeOutput } from '../../utils' @@ -29,6 +33,7 @@ import './saveButton' import './shareButton' import { darkStyles, lightStyles } from './vscode.css' import { defineCustomElement } from '../../utils/defineCustomElement' +import type { RendererContext } from 'vscode-notebook-renderer' export interface ConsoleViewConfig { theme: 'dark' | 'light' | 'vscode' @@ -427,6 +432,14 @@ export class ConsoleView extends LitElement { @property({ type: Boolean }) isDaggerOutput: boolean = false + // Optional per-instance messaging context; falls back to module-level context. + @property({ attribute: false }) + context?: RendererContext + + #ctx(): RendererContext { + return this.context ?? getContext() + } + protected applyThemeStyles(): void { if (!this.shadowRoot) { return @@ -525,7 +538,7 @@ export class ConsoleView extends LitElement { this.terminal.unicode.activeVersion = '11' this.terminal.options.drawBoldTextInBrightColors - const ctx = getContext() + const ctx = this.#ctx() this.disposables.push( // todo(sebastian): what's the type of e? @@ -726,7 +739,7 @@ export class ConsoleView extends LitElement { this.#subscribeSetTerminalRows(dims) terminalContainer.appendChild(resizeDragHandle) - const ctx = getContext() + const ctx = this.#ctx() ctx.postMessage && postClientMessage(ctx, ClientMessages.terminalOpen, { 'runme.dev/id': this.id!, @@ -905,7 +918,7 @@ export class ConsoleView extends LitElement { ) const sub = debounced$.subscribe(async (terminalDimensions) => { - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage) { return } @@ -930,7 +943,7 @@ export class ConsoleView extends LitElement { ) const sub = debounced$.subscribe(async (terminalRows) => { - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage) { return } @@ -963,7 +976,7 @@ export class ConsoleView extends LitElement { this.terminal?.focus() } - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage) { return } @@ -974,7 +987,7 @@ export class ConsoleView extends LitElement { } async #displayShareDialog(): Promise { - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage || !this.shareUrl) { return } @@ -993,7 +1006,7 @@ export class ConsoleView extends LitElement { } async #triggerEscalation(): Promise { - // const ctx = getContext() + // const ctx = this.#ctx() this.isCreatingEscalation = true // try { @@ -1016,7 +1029,7 @@ export class ConsoleView extends LitElement { } async #triggerOpenEscalation(): Promise { - const ctx = getContext() + const ctx = this.#ctx() if (!this.escalationUrl) { return @@ -1026,7 +1039,7 @@ export class ConsoleView extends LitElement { } #openSessionOutput(): Promise | undefined { - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage) { return } @@ -1042,7 +1055,7 @@ export class ConsoleView extends LitElement { async #shareCellOutput( _isUserAction: boolean ): Promise { - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage) { return } @@ -1084,17 +1097,17 @@ export class ConsoleView extends LitElement { } #onWebLinkClick(_event: MouseEvent, uri: string): void { - postClientMessage(getContext(), ClientMessages.openLink, uri) + postClientMessage(this.#ctx(), ClientMessages.openLink, uri) } #triggerOpenCellOutput(): void { - postClientMessage(getContext(), ClientMessages.openLink, this.shareUrl!) + postClientMessage(this.#ctx(), ClientMessages.openLink, this.shareUrl!) } #onEscalateDisabled(): void { const message = 'There is no Slack integration configured yet. \nOpen Dashboard to configure it' - postClientMessage(getContext(), ClientMessages.errorMessage, message) + postClientMessage(this.#ctx(), ClientMessages.errorMessage, message) } // Render the UI as a function of component state @@ -1205,7 +1218,7 @@ export class ConsoleView extends LitElement { } #copy() { - const ctx = getContext() + const ctx = this.#ctx() if (!ctx.postMessage) { return } From 2a70530af656a33cfbb7ae7a3bca4c34e2a0cbc2 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 18:26:22 -0800 Subject: [PATCH 10/32] Let each RunmeConsole use its own messaging context --- .../renderers/src/components/console/runme.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 54afb52..6c713d5 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -42,6 +42,7 @@ export class RunmeConsole extends LitElement { #streams?: Streams #streamsUnsubs: Array<() => void> = [] #winsize = { rows: 34, cols: 100, x: 0, y: 0 } + #contextBridge?: RendererContext // Properties delegated to ConsoleView @property({ type: String }) @@ -83,7 +84,7 @@ export class RunmeConsole extends LitElement { constructor() { super() - this.#installContextBridge() + this.#contextBridge = this.#installContextBridge() } // Delegate theme styles to ConsoleView @@ -146,8 +147,10 @@ export class RunmeConsole extends LitElement { // Create ConsoleView element this.consoleView = document.createElement('console-view') as ConsoleView - // Share the messaging context with the child ConsoleView - this.consoleView.context = getContext() + // Share the per-instance messaging context with the child ConsoleView + if (this.#contextBridge) { + this.consoleView.context = this.#contextBridge + } // Set all properties on ConsoleView this.#updateConsoleViewProperties() @@ -415,10 +418,16 @@ export class RunmeConsole extends LitElement { }, } as RendererContext try { + // Retain legacy behavior of setting the module-level context so + // existing consumers continue to work, but also return the instance + // bridge for per-instance use. setContext(ctxLike) + this.consoleView && (this.consoleView.context = ctxLike) + this.#contextBridge = ctxLike } catch { console.error('Failed to set context bridge') } + return ctxLike } // Render the UI - just render ConsoleView which handles everything From dc802e9418d5918cfd6f7571c75faa130483f6c1 Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 18:31:49 -0800 Subject: [PATCH 11/32] Add a commit message. --- packages/renderers/src/utils/defineCustomElement.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/renderers/src/utils/defineCustomElement.ts b/packages/renderers/src/utils/defineCustomElement.ts index 52ef37b..7aa1737 100644 --- a/packages/renderers/src/utils/defineCustomElement.ts +++ b/packages/renderers/src/utils/defineCustomElement.ts @@ -1,3 +1,11 @@ +// defineCustomElement defines a custom element if it has not already been defined. +// The custom element gets defined as a side effect of importing the module. +// We were hitting problems (https://github.com/runmedev/web/pull/28) when trying +// to use RunmeConsole and ConsoleView by importing both +// runmedev/renderers and runmedev/react-console because runmedev/react-console was +// importing its own copy of runmedev/renderers causing the custom element to be defined twice +// leading to the error +// Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "console-view" has already been used with this registry export function defineCustomElement( tag: string, ctor: CustomElementConstructor From cbe57d6d72969e2f461a0a453d0f1d6c6a5e012e Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sat, 20 Dec 2025 18:38:03 -0800 Subject: [PATCH 12/32] Remove defineCustomElement wrapper and restore decorators --- .../renderers/src/components/closeCellButton.ts | 6 ++---- .../src/components/console/actionButton.ts | 6 ++---- .../src/components/console/gistCell.ts | 6 ++---- .../renderers/src/components/console/open.ts | 6 ++---- .../renderers/src/components/console/runme.ts | 6 ++---- .../src/components/console/saveButton.ts | 6 ++---- .../src/components/console/shareButton.ts | 6 ++---- .../renderers/src/components/console/view.ts | 6 ++---- packages/renderers/src/components/copyButton.ts | 6 ++---- .../renderers/src/components/dropdownlist.ts | 7 ++----- packages/renderers/src/components/env/store.ts | 6 ++---- packages/renderers/src/components/envViewer.ts | 6 ++---- .../renderers/src/components/table/index.ts | 7 ++----- packages/renderers/src/components/tooltip.ts | 7 ++----- .../renderers/src/utils/defineCustomElement.ts | 17 ----------------- 15 files changed, 28 insertions(+), 76 deletions(-) delete mode 100644 packages/renderers/src/utils/defineCustomElement.ts diff --git a/packages/renderers/src/components/closeCellButton.ts b/packages/renderers/src/components/closeCellButton.ts index dd5651d..c0513cb 100644 --- a/packages/renderers/src/components/closeCellButton.ts +++ b/packages/renderers/src/components/closeCellButton.ts @@ -1,7 +1,7 @@ import { LitElement, css, html } from 'lit' +import { customElement } from 'lit/decorators.js' -import { defineCustomElement } from '../utils/defineCustomElement' - +@customElement('close-cell-button') export class CloseCellButton extends LitElement { /* eslint-disable */ static styles = css` @@ -103,5 +103,3 @@ export class CloseCellButton extends LitElement { ` } } - -defineCustomElement('close-cell-button', CloseCellButton) diff --git a/packages/renderers/src/components/console/actionButton.ts b/packages/renderers/src/components/console/actionButton.ts index fb92f17..5f1d9eb 100644 --- a/packages/renderers/src/components/console/actionButton.ts +++ b/packages/renderers/src/components/console/actionButton.ts @@ -1,11 +1,11 @@ import { LitElement, css, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' -import { defineCustomElement } from '../../utils/defineCustomElement' import { SaveIcon } from '../icons/save' import { ShareIcon } from '../icons/share' +@customElement('action-button') export class ActionButton extends LitElement { @property({ type: String }) text: string = 'Copy' @@ -78,5 +78,3 @@ export class ActionButton extends LitElement { ` } } - -defineCustomElement('action-button', ActionButton) diff --git a/packages/renderers/src/components/console/gistCell.ts b/packages/renderers/src/components/console/gistCell.ts index 28f8374..33c9aab 100644 --- a/packages/renderers/src/components/console/gistCell.ts +++ b/packages/renderers/src/components/console/gistCell.ts @@ -1,10 +1,10 @@ import { LitElement, css, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' -import { defineCustomElement } from '../../utils/defineCustomElement' import { GistIcon } from '../icons/gistIcon' +@customElement('gist-cell') export class GistCell extends LitElement { @property({ type: String }) text: string = 'Preview & Gist' @@ -64,5 +64,3 @@ export class GistCell extends LitElement { ) } } - -defineCustomElement('gist-cell', GistCell) diff --git a/packages/renderers/src/components/console/open.ts b/packages/renderers/src/components/console/open.ts index abff42b..c156140 100644 --- a/packages/renderers/src/components/console/open.ts +++ b/packages/renderers/src/components/console/open.ts @@ -1,10 +1,10 @@ import { LitElement, css, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' -import { defineCustomElement } from '../../utils/defineCustomElement' import { EyeIcon } from '../icons/eye' +@customElement('open-cell') export class OpenCell extends LitElement { @property({ type: String }) openText: string = 'Open' @@ -58,5 +58,3 @@ export class OpenCell extends LitElement { ) } } - -defineCustomElement('open-cell', OpenCell) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 6c713d5..ea37bee 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -10,13 +10,12 @@ import { import { create } from '@bufbuild/protobuf' import { Interceptor } from '@connectrpc/connect' import { LitElement, PropertyValues, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { Disposable } from 'vscode' import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' import { getContext, setContext } from '../../messaging' -import { defineCustomElement } from '../../utils/defineCustomElement' import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' @@ -32,6 +31,7 @@ export interface RunmeConsoleStream { export const RUNME_CONSOLE = 'runme-console' +@customElement(RUNME_CONSOLE) export class RunmeConsole extends LitElement { protected disposables: Disposable[] = [] @@ -440,5 +440,3 @@ export class RunmeConsole extends LitElement { this.disposables.forEach(({ dispose }) => dispose()) } } - -defineCustomElement(RUNME_CONSOLE, RunmeConsole) diff --git a/packages/renderers/src/components/console/saveButton.ts b/packages/renderers/src/components/console/saveButton.ts index 3bcab1d..f166e5d 100644 --- a/packages/renderers/src/components/console/saveButton.ts +++ b/packages/renderers/src/components/console/saveButton.ts @@ -1,9 +1,9 @@ import { LitElement, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' -import { defineCustomElement } from '../../utils/defineCustomElement' import './actionButton' +@customElement('save-button') export class SaveButton extends LitElement { @property({ type: Boolean, reflect: true }) loading: boolean = false @@ -33,5 +33,3 @@ export class SaveButton extends LitElement { ` } } - -defineCustomElement('save-button', SaveButton) diff --git a/packages/renderers/src/components/console/shareButton.ts b/packages/renderers/src/components/console/shareButton.ts index 8746ea0..54dac10 100644 --- a/packages/renderers/src/components/console/shareButton.ts +++ b/packages/renderers/src/components/console/shareButton.ts @@ -1,9 +1,9 @@ import { LitElement, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' -import { defineCustomElement } from '../../utils/defineCustomElement' import './actionButton' +@customElement('share-button') export class ShareButton extends LitElement { @property({ type: Boolean, reflect: true }) loading: boolean = false @@ -28,5 +28,3 @@ export class ShareButton extends LitElement { ` } } - -defineCustomElement('share-button', ShareButton) diff --git a/packages/renderers/src/components/console/view.ts b/packages/renderers/src/components/console/view.ts index 49e9c25..378798f 100644 --- a/packages/renderers/src/components/console/view.ts +++ b/packages/renderers/src/components/console/view.ts @@ -3,7 +3,7 @@ import { Unicode11Addon } from '@xterm/addon-unicode11' import { WebLinksAddon } from '@xterm/addon-web-links' import { ITheme, Terminal as XTermJS } from '@xterm/xterm' import { LitElement, PropertyValues, css, html, unsafeCSS } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' import { Observable } from 'rxjs' import { @@ -32,7 +32,6 @@ import './open' import './saveButton' import './shareButton' import { darkStyles, lightStyles } from './vscode.css' -import { defineCustomElement } from '../../utils/defineCustomElement' import type { RendererContext } from 'vscode-notebook-renderer' export interface ConsoleViewConfig { @@ -96,6 +95,7 @@ const ANSI_COLORS = [ export const CONSOLE_VIEW = 'console-view' +@customElement(CONSOLE_VIEW) export class ConsoleView extends LitElement { protected copyText = 'Copy' @@ -1258,8 +1258,6 @@ export class ConsoleView extends LitElement { } } -defineCustomElement(CONSOLE_VIEW, ConsoleView) - function convertXTermDimensions( dimensions: ITerminalDimensions ): TerminalDimensions diff --git a/packages/renderers/src/components/copyButton.ts b/packages/renderers/src/components/copyButton.ts index 652ae08..498e6a8 100644 --- a/packages/renderers/src/components/copyButton.ts +++ b/packages/renderers/src/components/copyButton.ts @@ -1,9 +1,9 @@ import { LitElement, css, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { CopyIcon } from './icons/copy' -import { defineCustomElement } from '../utils/defineCustomElement' +@customElement('copy-button') export class CopyButton extends LitElement { @property({ type: String }) copyText: string = 'Copy' @@ -43,5 +43,3 @@ export class CopyButton extends LitElement { ` } } - -defineCustomElement('copy-button', CopyButton) diff --git a/packages/renderers/src/components/dropdownlist.ts b/packages/renderers/src/components/dropdownlist.ts index b5aa74b..76a7928 100644 --- a/packages/renderers/src/components/dropdownlist.ts +++ b/packages/renderers/src/components/dropdownlist.ts @@ -1,7 +1,5 @@ import { LitElement, css, html } from 'lit' -import { property } from 'lit/decorators.js' - -import { defineCustomElement } from '../utils/defineCustomElement' +import { customElement, property } from 'lit/decorators.js' export interface DropdownListOption { text: string @@ -14,6 +12,7 @@ export type DropdownListEvent = { key: string } +@customElement('dropdown-list') export class DropdownList extends LitElement { @property({ type: String }) label: string | undefined @@ -95,5 +94,3 @@ export class DropdownList extends LitElement { ` } } - -defineCustomElement('dropdown-list', DropdownList) diff --git a/packages/renderers/src/components/env/store.ts b/packages/renderers/src/components/env/store.ts index 2e7ca3b..001dec9 100644 --- a/packages/renderers/src/components/env/store.ts +++ b/packages/renderers/src/components/env/store.ts @@ -1,11 +1,10 @@ import { MonitorEnvStoreResponseSnapshot_SnapshotEnv } from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' import { MonitorEnvStoreResponseSnapshot_Status } from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' import { LitElement, TemplateResult, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { Disposable } from 'vscode' import { formatDate, formatDateWithTimeAgo } from '../../utils' -import { defineCustomElement } from '../../utils/defineCustomElement' import '../envViewer' import { CustomErrorIcon } from '../icons/error' import '../table' @@ -46,6 +45,7 @@ const COLUMNS = [ const HIDDEN_COLUMNS = ['resolvedValue', 'errors', 'status', 'specClass'] +@customElement('env-store') export default class Table extends LitElement { protected disposables: Disposable[] = [] @@ -191,5 +191,3 @@ export default class Table extends LitElement { return html`${format()}` } } - -defineCustomElement('env-store', Table) diff --git a/packages/renderers/src/components/envViewer.ts b/packages/renderers/src/components/envViewer.ts index 922ddf7..13b91ba 100644 --- a/packages/renderers/src/components/envViewer.ts +++ b/packages/renderers/src/components/envViewer.ts @@ -1,6 +1,6 @@ import { MonitorEnvStoreResponseSnapshot_Status } from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' import { LitElement, TemplateResult, css, html } from 'lit' -import { property, state } from 'lit/decorators.js' +import { customElement, property, state } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' import { Disposable } from 'vscode' @@ -9,8 +9,8 @@ import { CopyIcon } from './icons/copy' import { EyeIcon } from './icons/eye' import { EyeClosedIcon } from './icons/eyeClosed' import './tooltip' -import { defineCustomElement } from '../utils/defineCustomElement' +@customElement('env-viewer') export class EnvViewer extends LitElement implements Disposable { protected disposables: Disposable[] = [] @@ -163,5 +163,3 @@ export class EnvViewer extends LitElement implements Disposable { ` } } - -defineCustomElement('env-viewer', EnvViewer) diff --git a/packages/renderers/src/components/table/index.ts b/packages/renderers/src/components/table/index.ts index 39bf08b..8c2e210 100644 --- a/packages/renderers/src/components/table/index.ts +++ b/packages/renderers/src/components/table/index.ts @@ -1,14 +1,13 @@ import { LitElement, TemplateResult, css, html, nothing } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' -import { defineCustomElement } from '../../utils/defineCustomElement' - export interface Column { text: string colspan?: number | undefined } +@customElement('table-view') export class Table extends LitElement { @property({ type: Array }) columns?: Column[] = [] @@ -221,5 +220,3 @@ export class Table extends LitElement { ` } } - -defineCustomElement('table-view', Table) diff --git a/packages/renderers/src/components/tooltip.ts b/packages/renderers/src/components/tooltip.ts index e4d5bb9..59321ba 100644 --- a/packages/renderers/src/components/tooltip.ts +++ b/packages/renderers/src/components/tooltip.ts @@ -1,9 +1,8 @@ import { LitElement, TemplateResult, css, html } from 'lit' -import { property } from 'lit/decorators.js' +import { customElement, property } from 'lit/decorators.js' import { when } from 'lit/directives/when.js' -import { defineCustomElement } from '../utils/defineCustomElement' - +@customElement('tooltip-text') export class Tooltip extends LitElement { @property({ type: String }) tooltipText: string | TemplateResult<1> | undefined @@ -78,5 +77,3 @@ export class Tooltip extends LitElement {
` } } - -defineCustomElement('tooltip-text', Tooltip) diff --git a/packages/renderers/src/utils/defineCustomElement.ts b/packages/renderers/src/utils/defineCustomElement.ts deleted file mode 100644 index 7aa1737..0000000 --- a/packages/renderers/src/utils/defineCustomElement.ts +++ /dev/null @@ -1,17 +0,0 @@ -// defineCustomElement defines a custom element if it has not already been defined. -// The custom element gets defined as a side effect of importing the module. -// We were hitting problems (https://github.com/runmedev/web/pull/28) when trying -// to use RunmeConsole and ConsoleView by importing both -// runmedev/renderers and runmedev/react-console because runmedev/react-console was -// importing its own copy of runmedev/renderers causing the custom element to be defined twice -// leading to the error -// Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "console-view" has already been used with this registry -export function defineCustomElement( - tag: string, - ctor: CustomElementConstructor -) { - if (customElements.get(tag)) { - return - } - customElements.define(tag, ctor) -} From 10be0d3255b4a2a3506215c6a6ed7517c8a54b83 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 20 Dec 2025 18:52:47 -0800 Subject: [PATCH 13/32] Remove unused getContext. --- packages/renderers/src/components/console/runme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index ea37bee..797fd82 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -15,7 +15,7 @@ import { Disposable } from 'vscode' import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' -import { getContext, setContext } from '../../messaging' +import { setContext } from '../../messaging' import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' From 8fc23f56110657d3ea15e469512ac66902ce66f5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 20 Dec 2025 19:33:49 -0800 Subject: [PATCH 14/32] Allow RunmeConsole to accept injected messaging context --- .../renderers/src/components/console/runme.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 797fd82..032cacb 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -44,6 +44,11 @@ export class RunmeConsole extends LitElement { #winsize = { rows: 34, cols: 100, x: 0, y: 0 } #contextBridge?: RendererContext + // Optional injected messaging context to allow multiple consoles to share + // or isolate their backend bridge. + @property({ attribute: false }) + context?: RendererContext + // Properties delegated to ConsoleView @property({ type: String }) id!: string @@ -84,7 +89,6 @@ export class RunmeConsole extends LitElement { constructor() { super() - this.#contextBridge = this.#installContextBridge() } // Delegate theme styles to ConsoleView @@ -118,6 +122,13 @@ export class RunmeConsole extends LitElement { protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties) + if (changedProperties.has('context')) { + this.#ensureContextBridge() + if (this.consoleView && this.#contextBridge) { + this.consoleView.context = this.#contextBridge + } + } + // Delegate property updates to ConsoleView if (this.consoleView) { this.#updateConsoleViewProperties() @@ -145,6 +156,8 @@ export class RunmeConsole extends LitElement { return } + this.#ensureContextBridge() + // Create ConsoleView element this.consoleView = document.createElement('console-view') as ConsoleView // Share the per-instance messaging context with the child ConsoleView @@ -430,6 +443,18 @@ export class RunmeConsole extends LitElement { return ctxLike } + #ensureContextBridge() { + // If a context prop is provided, use it and keep module-level as-is + if (this.context) { + this.#contextBridge = this.context + this.consoleView && (this.consoleView.context = this.context) + return + } + if (!this.#contextBridge) { + this.#contextBridge = this.#installContextBridge() + } + } + // Render the UI - just render ConsoleView which handles everything render() { return html`
` From b90a22f15208136bb968eb94ad541635dbbb05d2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 20 Dec 2025 22:20:45 -0800 Subject: [PATCH 15/32] Extract RunmeRenderContext and allow injected contexts --- .../renderers/src/components/console/runme.ts | 56 +++++--------- .../src/messaging/runmeRenderContext.ts | 76 +++++++++++++++++++ 2 files changed, 93 insertions(+), 39 deletions(-) create mode 100644 packages/renderers/src/messaging/runmeRenderContext.ts diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 032cacb..69f9b69 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -16,6 +16,10 @@ import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' import { setContext } from '../../messaging' +import { + RunmeRenderContext, + type RunmeTransport, +} from '../../messaging/runmeRenderContext' import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' @@ -389,47 +393,21 @@ export class RunmeConsole extends LitElement { } #installContextBridge() { - const encoder = new TextEncoder() - const ctxLike = { - postMessage: (message: unknown) => { - if ( - (message as any).type === ClientMessages.terminalOpen || - (message as any).type === ClientMessages.terminalResize - ) { - const cols = Number( - (message as any).output.terminalDimensions.columns - ) - const rows = Number((message as any).output.terminalDimensions.rows) - if (Number.isFinite(cols) && Number.isFinite(rows)) { - // If the dimensions are the same, return early - if (this.#winsize.cols === cols && this.#winsize.rows === rows) { - return - } - this.#winsize = create(WinsizeSchema, { - cols, - rows, - x: 0, - y: 0, - }) - const req = create(ExecuteRequestSchema, { - winsize: this.#winsize, - }) - this.#streams?.sendExecuteRequest(req) - } - } - - if ((message as any).type === ClientMessages.terminalStdin) { - const inputData = encoder.encode((message as any).output.input) - const req = create(ExecuteRequestSchema, { inputData }) - // const reqJson = toJson(ExecuteRequestSchema, req) - // console.log('terminalStdin', reqJson) - this.#streams?.sendExecuteRequest(req) - } - }, - onDidReceiveMessage: (listener: VSCodeEvent) => { + const transport: RunmeTransport = { + sendExecuteRequest: (req: any) => this.#streams?.sendExecuteRequest(req), + setCallback: (listener: VSCodeEvent) => { this.#streams?.setCallback(listener) }, - } as RendererContext + } + + const ctxLike = new RunmeRenderContext({ + transport, + winsize: this.#winsize as any, + onWinsizeChange: (winsize) => { + this.#winsize = winsize as any + }, + }) as RendererContext + try { // Retain legacy behavior of setting the module-level context so // existing consumers continue to work, but also return the instance diff --git a/packages/renderers/src/messaging/runmeRenderContext.ts b/packages/renderers/src/messaging/runmeRenderContext.ts new file mode 100644 index 0000000..b7a9f34 --- /dev/null +++ b/packages/renderers/src/messaging/runmeRenderContext.ts @@ -0,0 +1,76 @@ +import { create } from '@bufbuild/protobuf' +import { + ExecuteRequestSchema, + Winsize, + WinsizeSchema, +} from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' +import { type RendererContext } from 'vscode-notebook-renderer' +import { type VSCodeEvent } from 'vscode-notebook-renderer/events' + +import { ClientMessages } from '../types' + +export interface RunmeTransport { + sendExecuteRequest: (req: any) => void + setCallback: (cb: VSCodeEvent) => void +} + +export interface RunmeRenderContextOptions { + transport: RunmeTransport + /** + * Current window size for the terminal. Passed with terminalOpen if present. + */ + winsize?: Winsize + /** + * Update the cached winsize when a resize arrives. + */ + onWinsizeChange?: (winsize: Winsize) => void +} + +/** + * A lightweight RenderContext implementation for Runme consoles. It forwards + * stdin and resize/open events to the provided transport and propagates backend + * messages via setCallback. + */ +export class RunmeRenderContext implements RendererContext { + #transport: RunmeTransport + #winsize?: Winsize + #onWinsizeChange?: (winsize: Winsize) => void + + constructor(opts: RunmeRenderContextOptions) { + this.#transport = opts.transport + this.#winsize = opts.winsize + this.#onWinsizeChange = opts.onWinsizeChange + } + + postMessage(message: unknown) { + if ( + (message as any)?.type === ClientMessages.terminalOpen || + (message as any)?.type === ClientMessages.terminalResize + ) { + const cols = Number((message as any).output.terminalDimensions.columns) + const rows = Number((message as any).output.terminalDimensions.rows) + if (Number.isFinite(cols) && Number.isFinite(rows)) { + this.#winsize = create(WinsizeSchema, { cols, rows, x: 0, y: 0 }) + this.#onWinsizeChange?.(this.#winsize) + const req = create(ExecuteRequestSchema, { winsize: this.#winsize }) + this.#transport.sendExecuteRequest(req) + } + return + } + + if ((message as any)?.type === ClientMessages.terminalStdin) { + const inputData = new TextEncoder().encode((message as any).output.input) + const req = create(ExecuteRequestSchema, { inputData }) + this.#transport.sendExecuteRequest(req) + } + } + + onDidReceiveMessage(listener: VSCodeEvent) { + this.#transport.setCallback(listener) + return { + dispose: () => { + // no-op; transport manages teardown + }, + } + } +} From 41dcc92d28f1497692c57285a017c88091e5ee04 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 09:25:02 -0800 Subject: [PATCH 16/32] Start building a testApp. --- pnpm-lock.yaml | 282 +++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 1 + testApp/index.html | 12 ++ testApp/package.json | 24 ++++ testApp/src/App.tsx | 122 ++++++++++++++++++ testApp/src/main.tsx | 10 ++ testApp/tsconfig.json | 8 ++ testApp/vite.config.ts | 9 ++ 8 files changed, 468 insertions(+) create mode 100644 testApp/index.html create mode 100644 testApp/package.json create mode 100644 testApp/src/App.tsx create mode 100644 testApp/src/main.tsx create mode 100644 testApp/tsconfig.json create mode 100644 testApp/vite.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2ff7b4..70ab15c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,6 +313,40 @@ importers: specifier: ^2.0.0 version: 2.1.9(@types/node@20.12.7)(@vitest/ui@2.1.9)(jsdom@25.0.1)(lightningcss@1.30.1) + testApp: + dependencies: + '@runmedev/react-console': + specifier: workspace:* + version: link:../packages/react-console + '@runmedev/renderers': + specifier: workspace:* + version: link:../packages/renderers + react: + specifier: ^19.0.0 + version: 19.2.0 + react-dom: + specifier: ^19.0.0 + version: 19.2.0(react@19.2.0) + devDependencies: + '@types/react': + specifier: ^19.0.0 + version: 19.2.2 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.1(@types/react@19.2.2) + esbuild: + specifier: ^0.23.0 + version: 0.23.1 + rollup: + specifier: ^4.0.0 + version: 4.52.4 + typescript: + specifier: ^5.4.0 + version: 5.8.3 + vite: + specifier: ^5.0.0 + version: 5.4.20(@types/node@20.12.7)(lightningcss@1.30.1) + packages: '@adobe/css-tools@4.4.4': @@ -545,6 +579,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.10': resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} @@ -557,6 +597,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.10': resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} @@ -569,6 +615,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.10': resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} @@ -581,6 +633,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.10': resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} @@ -593,6 +651,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.10': resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} @@ -605,6 +669,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.10': resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} @@ -617,6 +687,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.10': resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} @@ -629,6 +705,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.10': resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} @@ -641,6 +723,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.10': resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} @@ -653,6 +741,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.10': resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} @@ -665,6 +759,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.10': resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} @@ -677,6 +777,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.10': resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} @@ -689,6 +795,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.10': resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} @@ -701,6 +813,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.10': resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} @@ -713,6 +831,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.10': resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} @@ -725,6 +849,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.10': resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} @@ -737,6 +867,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.10': resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} @@ -755,12 +891,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.10': resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.25.10': resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} @@ -773,6 +921,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.10': resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} @@ -791,6 +945,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.10': resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} @@ -803,6 +963,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.10': resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} @@ -815,6 +981,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.10': resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} @@ -827,6 +999,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.10': resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} @@ -2496,6 +2674,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.25.10: resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} @@ -4283,102 +4466,153 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.23.1': + optional: true + '@esbuild/aix-ppc64@0.25.10': optional: true '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.23.1': + optional: true + '@esbuild/android-arm64@0.25.10': optional: true '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.23.1': + optional: true + '@esbuild/android-arm@0.25.10': optional: true '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.23.1': + optional: true + '@esbuild/android-x64@0.25.10': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.23.1': + optional: true + '@esbuild/darwin-arm64@0.25.10': optional: true '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.23.1': + optional: true + '@esbuild/darwin-x64@0.25.10': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.23.1': + optional: true + '@esbuild/freebsd-arm64@0.25.10': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.23.1': + optional: true + '@esbuild/freebsd-x64@0.25.10': optional: true '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.23.1': + optional: true + '@esbuild/linux-arm64@0.25.10': optional: true '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.23.1': + optional: true + '@esbuild/linux-arm@0.25.10': optional: true '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.23.1': + optional: true + '@esbuild/linux-ia32@0.25.10': optional: true '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.23.1': + optional: true + '@esbuild/linux-loong64@0.25.10': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.23.1': + optional: true + '@esbuild/linux-mips64el@0.25.10': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.23.1': + optional: true + '@esbuild/linux-ppc64@0.25.10': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.23.1': + optional: true + '@esbuild/linux-riscv64@0.25.10': optional: true '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.23.1': + optional: true + '@esbuild/linux-s390x@0.25.10': optional: true '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.23.1': + optional: true + '@esbuild/linux-x64@0.25.10': optional: true @@ -4388,15 +4622,24 @@ snapshots: '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.23.1': + optional: true + '@esbuild/netbsd-x64@0.25.10': optional: true + '@esbuild/openbsd-arm64@0.23.1': + optional: true + '@esbuild/openbsd-arm64@0.25.10': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.23.1': + optional: true + '@esbuild/openbsd-x64@0.25.10': optional: true @@ -4406,24 +4649,36 @@ snapshots: '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.23.1': + optional: true + '@esbuild/sunos-x64@0.25.10': optional: true '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.23.1': + optional: true + '@esbuild/win32-arm64@0.25.10': optional: true '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.23.1': + optional: true + '@esbuild/win32-ia32@0.25.10': optional: true '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.23.1': + optional: true + '@esbuild/win32-x64@0.25.10': optional: true @@ -6127,6 +6382,33 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + esbuild@0.25.10: optionalDependencies: '@esbuild/aix-ppc64': 0.25.10 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 455d91c..8f89550 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - packages/* + - testApp onlyBuiltDependencies: - '@tailwindcss/oxide' diff --git a/testApp/index.html b/testApp/index.html new file mode 100644 index 0000000..b8f6460 --- /dev/null +++ b/testApp/index.html @@ -0,0 +1,12 @@ + + + + + + Runme Console Test App + + +
+ + + diff --git a/testApp/package.json b/testApp/package.json new file mode 100644 index 0000000..3a8edeb --- /dev/null +++ b/testApp/package.json @@ -0,0 +1,24 @@ +{ + "name": "testApp", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@runmedev/react-console": "workspace:*", + "@runmedev/renderers": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "esbuild": "^0.23.0", + "rollup": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "typescript": "^5.4.0", + "vite": "^5.0.0" + } +} diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx new file mode 100644 index 0000000..4e27d00 --- /dev/null +++ b/testApp/src/App.tsx @@ -0,0 +1,122 @@ +import { useEffect, useMemo, useRef } from 'react' + +import type { RendererContext } from 'vscode-notebook-renderer' +import type { VSCodeEvent } from 'vscode-notebook-renderer/events' + +import '@runmedev/react-console' +import { ClientMessages } from '@runmedev/renderers' + +class FakeContext implements RendererContext { + #id: string + #listener?: VSCodeEvent + + constructor(id: string, welcome: string) { + this.#id = id + this.#listener = undefined + // seed once listener is attached + this.#seed = welcome + } + + #seed: string + + postMessage(message: unknown) { + const m = message as any + if (m?.type === ClientMessages.terminalStdin) { + const input = (m.output?.input as string) ?? '' + this.#listener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': this.#id, + data: `\r\n[${this.#id}] got: ${input.trim()}\r\n> `, + }, + } as any) + } + } + + onDidReceiveMessage(cb: VSCodeEvent) { + this.#listener = cb + cb({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': this.#id, + data: this.#seed, + }, + } as any) + return { dispose: () => (this.#listener = undefined) } + } +} + +function ConsoleViewWithContext({ id, ctx }: { id: string; ctx: RendererContext }) { + const ref = useRef(null) + useEffect(() => { + if (ref.current) { + ref.current.context = ctx + } + }, [ctx]) + + return ( +
+ +
+ ) +} + +function RunmeConsoleWithContext({ + id, + ctx, +}: { + id: string + ctx: RendererContext +}) { + const ref = useRef(null) + useEffect(() => { + if (ref.current) { + ref.current.context = ctx + } + }, [ctx]) + + return ( +
+ +
+ ) +} + +export default function App() { + const ctxA = useMemo( + () => new FakeContext('backend-a', 'Welcome backend-a\r\n> '), + [] + ) + const ctxB = useMemo( + () => new FakeContext('backend-b', 'Welcome backend-b\r\n> '), + [] + ) + const ctxC = useMemo( + () => new FakeContext('backend-c', 'Welcome backend-c\r\n> '), + [] + ) + + return ( +
+

RunmeConsole A (backend-a)

+ + +

RunmeConsole B (backend-b)

+ + +

ConsoleView (backend-c)

+ +
+ ) +} diff --git a/testApp/src/main.tsx b/testApp/src/main.tsx new file mode 100644 index 0000000..0147987 --- /dev/null +++ b/testApp/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { createRoot } from 'react-dom/client' + +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + +) diff --git a/testApp/tsconfig.json b/testApp/tsconfig.json new file mode 100644 index 0000000..1d9979d --- /dev/null +++ b/testApp/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "types": ["vite/client"] + }, + "include": ["src"] +} diff --git a/testApp/vite.config.ts b/testApp/vite.config.ts new file mode 100644 index 0000000..537f6a8 --- /dev/null +++ b/testApp/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + resolve: { + dedupe: ['@runmedev/renderers'], + }, +}) From 7cdf28c2ef30bf9a778fe3d454dc48f1e57af91e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:05:00 -0800 Subject: [PATCH 17/32] Revert RunmeRenderContext helper and inline context bridge --- .../renderers/src/components/console/runme.ts | 85 +++++++++---------- .../src/messaging/runmeRenderContext.ts | 76 ----------------- 2 files changed, 41 insertions(+), 120 deletions(-) delete mode 100644 packages/renderers/src/messaging/runmeRenderContext.ts diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 69f9b69..ea37bee 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -15,11 +15,7 @@ import { Disposable } from 'vscode' import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' -import { setContext } from '../../messaging' -import { - RunmeRenderContext, - type RunmeTransport, -} from '../../messaging/runmeRenderContext' +import { getContext, setContext } from '../../messaging' import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' @@ -48,11 +44,6 @@ export class RunmeConsole extends LitElement { #winsize = { rows: 34, cols: 100, x: 0, y: 0 } #contextBridge?: RendererContext - // Optional injected messaging context to allow multiple consoles to share - // or isolate their backend bridge. - @property({ attribute: false }) - context?: RendererContext - // Properties delegated to ConsoleView @property({ type: String }) id!: string @@ -93,6 +84,7 @@ export class RunmeConsole extends LitElement { constructor() { super() + this.#contextBridge = this.#installContextBridge() } // Delegate theme styles to ConsoleView @@ -126,13 +118,6 @@ export class RunmeConsole extends LitElement { protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties) - if (changedProperties.has('context')) { - this.#ensureContextBridge() - if (this.consoleView && this.#contextBridge) { - this.consoleView.context = this.#contextBridge - } - } - // Delegate property updates to ConsoleView if (this.consoleView) { this.#updateConsoleViewProperties() @@ -160,8 +145,6 @@ export class RunmeConsole extends LitElement { return } - this.#ensureContextBridge() - // Create ConsoleView element this.consoleView = document.createElement('console-view') as ConsoleView // Share the per-instance messaging context with the child ConsoleView @@ -393,21 +376,47 @@ export class RunmeConsole extends LitElement { } #installContextBridge() { - const transport: RunmeTransport = { - sendExecuteRequest: (req: any) => this.#streams?.sendExecuteRequest(req), - setCallback: (listener: VSCodeEvent) => { - this.#streams?.setCallback(listener) + const encoder = new TextEncoder() + const ctxLike = { + postMessage: (message: unknown) => { + if ( + (message as any).type === ClientMessages.terminalOpen || + (message as any).type === ClientMessages.terminalResize + ) { + const cols = Number( + (message as any).output.terminalDimensions.columns + ) + const rows = Number((message as any).output.terminalDimensions.rows) + if (Number.isFinite(cols) && Number.isFinite(rows)) { + // If the dimensions are the same, return early + if (this.#winsize.cols === cols && this.#winsize.rows === rows) { + return + } + this.#winsize = create(WinsizeSchema, { + cols, + rows, + x: 0, + y: 0, + }) + const req = create(ExecuteRequestSchema, { + winsize: this.#winsize, + }) + this.#streams?.sendExecuteRequest(req) + } + } + + if ((message as any).type === ClientMessages.terminalStdin) { + const inputData = encoder.encode((message as any).output.input) + const req = create(ExecuteRequestSchema, { inputData }) + // const reqJson = toJson(ExecuteRequestSchema, req) + // console.log('terminalStdin', reqJson) + this.#streams?.sendExecuteRequest(req) + } }, - } - - const ctxLike = new RunmeRenderContext({ - transport, - winsize: this.#winsize as any, - onWinsizeChange: (winsize) => { - this.#winsize = winsize as any + onDidReceiveMessage: (listener: VSCodeEvent) => { + this.#streams?.setCallback(listener) }, - }) as RendererContext - + } as RendererContext try { // Retain legacy behavior of setting the module-level context so // existing consumers continue to work, but also return the instance @@ -421,18 +430,6 @@ export class RunmeConsole extends LitElement { return ctxLike } - #ensureContextBridge() { - // If a context prop is provided, use it and keep module-level as-is - if (this.context) { - this.#contextBridge = this.context - this.consoleView && (this.consoleView.context = this.context) - return - } - if (!this.#contextBridge) { - this.#contextBridge = this.#installContextBridge() - } - } - // Render the UI - just render ConsoleView which handles everything render() { return html`
` diff --git a/packages/renderers/src/messaging/runmeRenderContext.ts b/packages/renderers/src/messaging/runmeRenderContext.ts deleted file mode 100644 index b7a9f34..0000000 --- a/packages/renderers/src/messaging/runmeRenderContext.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { create } from '@bufbuild/protobuf' -import { - ExecuteRequestSchema, - Winsize, - WinsizeSchema, -} from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' -import { type RendererContext } from 'vscode-notebook-renderer' -import { type VSCodeEvent } from 'vscode-notebook-renderer/events' - -import { ClientMessages } from '../types' - -export interface RunmeTransport { - sendExecuteRequest: (req: any) => void - setCallback: (cb: VSCodeEvent) => void -} - -export interface RunmeRenderContextOptions { - transport: RunmeTransport - /** - * Current window size for the terminal. Passed with terminalOpen if present. - */ - winsize?: Winsize - /** - * Update the cached winsize when a resize arrives. - */ - onWinsizeChange?: (winsize: Winsize) => void -} - -/** - * A lightweight RenderContext implementation for Runme consoles. It forwards - * stdin and resize/open events to the provided transport and propagates backend - * messages via setCallback. - */ -export class RunmeRenderContext implements RendererContext { - #transport: RunmeTransport - #winsize?: Winsize - #onWinsizeChange?: (winsize: Winsize) => void - - constructor(opts: RunmeRenderContextOptions) { - this.#transport = opts.transport - this.#winsize = opts.winsize - this.#onWinsizeChange = opts.onWinsizeChange - } - - postMessage(message: unknown) { - if ( - (message as any)?.type === ClientMessages.terminalOpen || - (message as any)?.type === ClientMessages.terminalResize - ) { - const cols = Number((message as any).output.terminalDimensions.columns) - const rows = Number((message as any).output.terminalDimensions.rows) - if (Number.isFinite(cols) && Number.isFinite(rows)) { - this.#winsize = create(WinsizeSchema, { cols, rows, x: 0, y: 0 }) - this.#onWinsizeChange?.(this.#winsize) - const req = create(ExecuteRequestSchema, { winsize: this.#winsize }) - this.#transport.sendExecuteRequest(req) - } - return - } - - if ((message as any)?.type === ClientMessages.terminalStdin) { - const inputData = new TextEncoder().encode((message as any).output.input) - const req = create(ExecuteRequestSchema, { inputData }) - this.#transport.sendExecuteRequest(req) - } - } - - onDidReceiveMessage(listener: VSCodeEvent) { - this.#transport.setCallback(listener) - return { - dispose: () => { - // no-op; transport manages teardown - }, - } - } -} From 0de0fcd9053d322a4ac6f7bc111d092de51e00ee Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:10:10 -0800 Subject: [PATCH 18/32] Remove import of getContext. --- packages/renderers/src/components/console/runme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index ea37bee..797fd82 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -15,7 +15,7 @@ import { Disposable } from 'vscode' import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' -import { getContext, setContext } from '../../messaging' +import { setContext } from '../../messaging' import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' From 7a1c3abe5c1e66f3601fa639059610fd4c1d0e98 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:18:07 -0800 Subject: [PATCH 19/32] Add testApp consoles using AppConsole stub --- testApp/src/App.tsx | 154 ++++++++++--------------------------- testApp/src/AppConsole.tsx | 120 +++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 113 deletions(-) create mode 100644 testApp/src/AppConsole.tsx diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx index 4e27d00..0182872 100644 --- a/testApp/src/App.tsx +++ b/testApp/src/App.tsx @@ -1,122 +1,50 @@ -import { useEffect, useMemo, useRef } from 'react' +import { useEffect } from 'react' -import type { RendererContext } from 'vscode-notebook-renderer' -import type { VSCodeEvent } from 'vscode-notebook-renderer/events' +import AppConsole from './AppConsole' -import '@runmedev/react-console' -import { ClientMessages } from '@runmedev/renderers' - -class FakeContext implements RendererContext { - #id: string - #listener?: VSCodeEvent - - constructor(id: string, welcome: string) { - this.#id = id - this.#listener = undefined - // seed once listener is attached - this.#seed = welcome - } - - #seed: string - - postMessage(message: unknown) { - const m = message as any - if (m?.type === ClientMessages.terminalStdin) { - const input = (m.output?.input as string) ?? '' - this.#listener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': this.#id, - data: `\r\n[${this.#id}] got: ${input.trim()}\r\n> `, - }, - } as any) - } - } - - onDidReceiveMessage(cb: VSCodeEvent) { - this.#listener = cb - cb({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': this.#id, - data: this.#seed, - }, - } as any) - return { dispose: () => (this.#listener = undefined) } - } -} - -function ConsoleViewWithContext({ id, ctx }: { id: string; ctx: RendererContext }) { - const ref = useRef(null) - useEffect(() => { - if (ref.current) { - ref.current.context = ctx - } - }, [ctx]) - - return ( -
- -
- ) -} - -function RunmeConsoleWithContext({ - id, - ctx, -}: { - id: string - ctx: RendererContext -}) { - const ref = useRef(null) +export default function App() { useEffect(() => { - if (ref.current) { - ref.current.context = ctx + // Append two runme-console elements beneath the React root + const root = document.getElementById('root') + if (!root) return + + const container = document.createElement('div') + container.style.display = 'flex' + container.style.flexDirection = 'column' + container.style.gap = '16px' + + const labelA = document.createElement('h2') + labelA.textContent = 'RunmeConsole A' + + const runmeA = document.createElement('runme-console') + runmeA.setAttribute('id', 'rc-a') + runmeA.setAttribute('style', 'height:200px; display:block;') + runmeA.setAttribute('takeFocus', 'true') + + const labelB = document.createElement('h2') + labelB.textContent = 'RunmeConsole B' + + const runmeB = document.createElement('runme-console') + runmeB.setAttribute('id', 'rc-b') + runmeB.setAttribute('style', 'height:200px; display:block;') + runmeB.setAttribute('takeFocus', 'true') + + container.appendChild(labelA) + container.appendChild(runmeA) + container.appendChild(labelB) + container.appendChild(runmeB) + root.appendChild(container) + + return () => { + container.remove() } - }, [ctx]) - - return ( -
- -
- ) -} - -export default function App() { - const ctxA = useMemo( - () => new FakeContext('backend-a', 'Welcome backend-a\r\n> '), - [] - ) - const ctxB = useMemo( - () => new FakeContext('backend-b', 'Welcome backend-b\r\n> '), - [] - ) - const ctxC = useMemo( - () => new FakeContext('backend-c', 'Welcome backend-c\r\n> '), - [] - ) + }, []) return ( -
-

RunmeConsole A (backend-a)

- - -

RunmeConsole B (backend-b)

- - -

ConsoleView (backend-c)

- +
+

Runme Test App

+ +
) } diff --git a/testApp/src/AppConsole.tsx b/testApp/src/AppConsole.tsx new file mode 100644 index 0000000..1533e5e --- /dev/null +++ b/testApp/src/AppConsole.tsx @@ -0,0 +1,120 @@ +import { useMemo, useRef } from 'react' + +import { ClientMessages, setContext } from '@runmedev/renderers' +import type { RendererContext } from 'vscode-notebook-renderer' + +/** + * Local copy of AppConsole from react-components. Provides a stub console + * that echoes input on Enter. + */ +export default function AppConsole() { + const elemRef = useRef(null) + const consoleId = useMemo( + () => `console-${Math.random().toString(36).substring(2, 9)}`, + [] + ) + + const eventHandler = (eventName: string) => (e: Event) => { + console.log(eventName, e) + } + + return ( +
{ + if (!el || el.hasChildNodes()) { + return + } + const elem = document.createElement('console-view') as any + + elemRef.current = elem + + elem.addEventListener('stdout', eventHandler('stdout')) + elem.addEventListener('stderr', eventHandler('stderr')) + elem.addEventListener('exitcode', eventHandler('exitcode')) + elem.addEventListener('pid', eventHandler('pid')) + elem.addEventListener('mimetype', eventHandler('mimetype')) + + let messageListener: ((message: unknown) => void) | undefined + let inputBuffer = '' + + const ctxBridge = { + postMessage: (message: any) => { + if (message?.type === ClientMessages.terminalStdin) { + const input = (message.output?.input as string) ?? '' + for (const ch of input) { + if (ch === '\r' || ch === '\n') { + const trimmed = inputBuffer.trim() + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: `\r\nGot input: ${trimmed}\r\n> `, + }, + }) + inputBuffer = '' + continue + } + + // handle backspace/delete + if (ch === '\u0008' || ch === '\u007f') { + inputBuffer = inputBuffer.slice(0, -1) + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: '\b \b', + }, + }) + continue + } + + inputBuffer += ch + messageListener?.({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: ch, + }, + }) + } + } + }, + onDidReceiveMessage: (listener: (message: unknown) => void) => { + messageListener = listener + listener({ + type: ClientMessages.terminalStdout, + output: { + 'runme.dev/id': consoleId, + data: 'Welcome to the app console\n> ', + }, + } as any) + return { + dispose: () => {}, + } + }, + } as RendererContext + + setContext(ctxBridge) + // Attach the per-instance context directly so this ConsoleView + // doesn't rely on the module-level singleton. + ;(elem as any).context = ctxBridge + + // Keep the element id in sync with messages dispatched via setContext + elem.setAttribute('id', consoleId) + elem.setAttribute('buttons', 'false') + elem.setAttribute('initialContent', '') + elem.setAttribute('theme', 'dark') + elem.setAttribute('fontFamily', 'monospace') + elem.setAttribute('fontSize', '12') + elem.setAttribute('cursorStyle', 'block') + elem.setAttribute('cursorBlink', 'true') + elem.setAttribute('cursorWidth', '1') + elem.setAttribute('smoothScrollDuration', '0') + elem.setAttribute('scrollback', '4000') + + el.appendChild(elem) + }} + >
+ ) +} From 173092633f1638b510c72b5fef8603ba118c744e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:52:24 -0800 Subject: [PATCH 20/32] Expose Streams types and add StreamCreator hooks for testing --- .../react-console/src/components/Console.tsx | 12 ++- .../renderers/src/components/console/runme.ts | 13 ++- packages/renderers/src/index.ts | 7 +- packages/renderers/src/streams.ts | 16 +++- packages/renderers/src/streams/fakeStreams.ts | 44 +++++++++++ testApp/src/App.tsx | 28 +++++++ testApp/src/fakeStreams.ts | 79 +++++++++++++++++++ 7 files changed, 192 insertions(+), 7 deletions(-) create mode 100644 packages/renderers/src/streams/fakeStreams.ts create mode 100644 testApp/src/fakeStreams.ts diff --git a/packages/react-console/src/components/Console.tsx b/packages/react-console/src/components/Console.tsx index 5692075..1c51957 100644 --- a/packages/react-console/src/components/Console.tsx +++ b/packages/react-console/src/components/Console.tsx @@ -3,7 +3,12 @@ import { useEffect, useMemo, useRef } from 'react' import { Interceptor } from '@connectrpc/connect' import '@runmedev/renderers' -import type { RunmeConsoleStream, ConsoleViewConfig } from '@runmedev/renderers' +import type { + RunmeConsoleStream, + ConsoleViewConfig, + StreamsProps, + StreamsLike, +} from '@runmedev/renderers' interface ConsoleSettings { rows?: number @@ -29,6 +34,7 @@ export interface ConsoleProps { content?: string runner: ConsoleRunner settings?: ConsoleSettings + streamCreator?: (props: StreamsProps) => StreamsLike onStdout?: (data: Uint8Array) => void onStderr?: (data: Uint8Array) => void onExitCode?: (code: number) => void @@ -45,6 +51,7 @@ function Console({ content, runner, settings: settingsProp = {}, + streamCreator, onStdout, onStderr, onExitCode, @@ -170,6 +177,9 @@ function Console({ // Bypass attributes because serialization of funcs won't work elem.interceptors = runner.interceptors elem.commands = commands + if (streamCreator) { + elem.StreamCreator = streamCreator + } el.appendChild(elem) const terminalEnd = document.createElement('div') diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 797fd82..266de4f 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -16,7 +16,7 @@ import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' import { setContext } from '../../messaging' -import Streams from '../../streams' +import Streams, { type StreamsLike, type StreamsProps } from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' @@ -82,6 +82,10 @@ export class RunmeConsole extends LitElement { @property({ attribute: false }) interceptors: Interceptor[] = [] + // Optional Streams factory for testing/custom backends + @property({ attribute: false }) + StreamCreator?: (props: StreamsProps) => StreamsLike + constructor() { super() this.#contextBridge = this.#installContextBridge() @@ -245,7 +249,7 @@ export class RunmeConsole extends LitElement { if (!knownID || !this.stream.runID || !this.stream.runnerEndpoint) { throw new Error('Missing required stream properties') } - this.#streams = new Streams({ + const props: StreamsProps = { knownID: knownID, runID: this.stream.runID!, sequence: this.stream.sequence ?? 0, @@ -254,7 +258,10 @@ export class RunmeConsole extends LitElement { autoReconnect: this.stream.reconnect, interceptors: this.interceptors ?? [], }, - }) + } + this.#streams = this.StreamCreator + ? this.StreamCreator(props) + : new Streams(props) const latencySub = this.#streams.connect().subscribe() this.#streamsUnsubs.push(() => latencySub.unsubscribe()) diff --git a/packages/renderers/src/index.ts b/packages/renderers/src/index.ts index 54e2f56..80ab80a 100644 --- a/packages/renderers/src/index.ts +++ b/packages/renderers/src/index.ts @@ -2,7 +2,12 @@ import './components' import { getContext, setContext } from './messaging' import { ClientMessages } from './types' -export { default as Streams, type Authorization } from './streams' +export { + default as Streams, + type Authorization, + type StreamsProps, + type StreamsLike, +} from './streams' export { genRunID, Heartbeat, type StreamError } from './streams' export { ConsoleView, type ConsoleViewConfig } from './components/console' diff --git a/packages/renderers/src/streams.ts b/packages/renderers/src/streams.ts index 9f77bc7..f2e303d 100644 --- a/packages/renderers/src/streams.ts +++ b/packages/renderers/src/streams.ts @@ -81,7 +81,7 @@ type Latency = { updatedAt: bigint } -type StreamsProps = { +export type StreamsProps = { knownID: string runID: string sequence: number @@ -92,7 +92,19 @@ type StreamsProps = { } } -class Streams { +export interface StreamsLike { + stdout: Observable + stderr: Observable + exitCode: Observable + pid: Observable + mimeType: Observable + connect(): Observable + sendExecuteRequest(executeRequest: ExecuteRequest): void + setCallback(callback: VSCodeEvent): void + close(): void +} + +class Streams implements StreamsLike { private callback: VSCodeEvent | undefined private readonly queue = new Subject() diff --git a/packages/renderers/src/streams/fakeStreams.ts b/packages/renderers/src/streams/fakeStreams.ts new file mode 100644 index 0000000..46cfa9a --- /dev/null +++ b/packages/renderers/src/streams/fakeStreams.ts @@ -0,0 +1,44 @@ +import { Observable, Subject } from 'rxjs' +import { VSCodeEvent } from 'vscode-notebook-renderer/events' + +import { type StreamsLike } from '../streams' +import { + ExecuteRequest, +} from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' + +export class FakeStreams implements StreamsLike { + stdout = new Subject() + stderr = new Subject() + exitCode = new Subject() + pid = new Subject() + mimeType = new Subject() + + //#callback?: VSCodeEvent + + connect(): Observable { + // Immediately emit a fake pid and mimeType if desired + return new Observable((observer) => { + observer.next(undefined) + observer.complete() + }) + } + + sendExecuteRequest(executeRequest: ExecuteRequest): void { + const input = executeRequest.inputData + if (input && input.length) { + this.stdout.next(input) + } + } + + setCallback(_: VSCodeEvent): void { + //this.#callback = callback + } + + close(): void { + this.stdout.complete() + this.stderr.complete() + this.exitCode.complete() + this.pid.complete() + this.mimeType.complete() + } +} diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx index 0182872..e5f2e18 100644 --- a/testApp/src/App.tsx +++ b/testApp/src/App.tsx @@ -1,6 +1,8 @@ import { useEffect } from 'react' import AppConsole from './AppConsole' +import { Console } from '@runmedev/react-console' +import { FakeStreams } from './fakeStreams' export default function App() { useEffect(() => { @@ -44,6 +46,32 @@ export default function App() {

Runme Test App

+

React Console (A)

+ new FakeStreams()} + /> +

React Console (B)

+ new FakeStreams()} + />
) diff --git a/testApp/src/fakeStreams.ts b/testApp/src/fakeStreams.ts new file mode 100644 index 0000000..2c932e5 --- /dev/null +++ b/testApp/src/fakeStreams.ts @@ -0,0 +1,79 @@ +type Listener = (value: T) => void + +export type FakeStreamsProps = { + knownID: string +} + +export class FakeStreams { + stdoutListeners: Listener[] = [] + stderrListeners: Listener[] = [] + exitListeners: Listener[] = [] + pidListeners: Listener[] = [] + mimeListeners: Listener[] = [] + + connect() { + return { + subscribe: () => ({ unsubscribe() {} }), + } + } + + sendExecuteRequest(req: any) { + if (req?.inputData) { + const data = req.inputData instanceof Uint8Array ? req.inputData : new Uint8Array() + this.stdoutListeners.forEach((fn) => fn(data)) + } + } + + setCallback(_cb: any) { + // no-op for fake + } + + close() { + // no-op + } + + get stdout() { + return { + subscribe: (fn: Listener) => { + this.stdoutListeners.push(fn) + return { unsubscribe: () => {} } + }, + } + } + + get stderr() { + return { + subscribe: (fn: Listener) => { + this.stderrListeners.push(fn) + return { unsubscribe: () => {} } + }, + } + } + + get exitCode() { + return { + subscribe: (fn: Listener) => { + this.exitListeners.push(fn) + return { unsubscribe: () => {} } + }, + } + } + + get pid() { + return { + subscribe: (fn: Listener) => { + this.pidListeners.push(fn) + return { unsubscribe: () => {} } + }, + } + } + + get mimeType() { + return { + subscribe: (fn: Listener) => { + this.mimeListeners.push(fn) + return { unsubscribe: () => {} } + }, + } + } +} From 70890f73381c3d490d8405821efe46859f947519 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:53:16 -0800 Subject: [PATCH 21/32] Change definition of streams type to streamslike. --- packages/renderers/src/components/console/runme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 266de4f..3ee30a4 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -39,7 +39,7 @@ export class RunmeConsole extends LitElement { protected consoleView?: ConsoleView // Streams-specific state - #streams?: Streams + #streams?: StreamsLike #streamsUnsubs: Array<() => void> = [] #winsize = { rows: 34, cols: 100, x: 0, y: 0 } #contextBridge?: RendererContext From 2896c597834c80a88a99f74d05e160da49911f91 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:54:55 -0800 Subject: [PATCH 22/32] Wire testApp consoles to FakeStreams via StreamCreator --- testApp/src/App.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx index e5f2e18..a3ab5d9 100644 --- a/testApp/src/App.tsx +++ b/testApp/src/App.tsx @@ -22,6 +22,7 @@ export default function App() { runmeA.setAttribute('id', 'rc-a') runmeA.setAttribute('style', 'height:200px; display:block;') runmeA.setAttribute('takeFocus', 'true') + ;(runmeA as any).StreamCreator = () => new FakeStreams() const labelB = document.createElement('h2') labelB.textContent = 'RunmeConsole B' @@ -30,6 +31,7 @@ export default function App() { runmeB.setAttribute('id', 'rc-b') runmeB.setAttribute('style', 'height:200px; display:block;') runmeB.setAttribute('takeFocus', 'true') + ;(runmeB as any).StreamCreator = () => new FakeStreams() container.appendChild(labelA) container.appendChild(runmeA) @@ -57,7 +59,7 @@ export default function App() { reconnect: false, interceptors: [] as any, }} - StreamCreator={() => new FakeStreams()} + streamCreator={() => new FakeStreams()} />

React Console (B)

new FakeStreams()} + streamCreator={() => new FakeStreams()} />
From bf7890c65e9379d9caa81f35008036460291429a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 17:56:50 -0800 Subject: [PATCH 23/32] Log which Streams factory RunmeConsole uses --- packages/renderers/src/components/console/runme.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 3ee30a4..7a71d34 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -259,9 +259,13 @@ export class RunmeConsole extends LitElement { interceptors: this.interceptors ?? [], }, } - this.#streams = this.StreamCreator - ? this.StreamCreator(props) - : new Streams(props) + if (this.StreamCreator) { + console.log('RunmeConsole: using StreamCreator override') + this.#streams = this.StreamCreator(props) + } else { + console.log('RunmeConsole: using default Streams implementation') + this.#streams = new Streams(props) + } const latencySub = this.#streams.connect().subscribe() this.#streamsUnsubs.push(() => latencySub.unsubscribe()) From 7fa7d8ba15c37ac2cf5dea36c4ddf7bc63a56ef1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:18:23 -0800 Subject: [PATCH 24/32] Clean up testApp --- testApp/src/App.tsx | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx index a3ab5d9..d4f8589 100644 --- a/testApp/src/App.tsx +++ b/testApp/src/App.tsx @@ -5,44 +5,7 @@ import { Console } from '@runmedev/react-console' import { FakeStreams } from './fakeStreams' export default function App() { - useEffect(() => { - // Append two runme-console elements beneath the React root - const root = document.getElementById('root') - if (!root) return - - const container = document.createElement('div') - container.style.display = 'flex' - container.style.flexDirection = 'column' - container.style.gap = '16px' - - const labelA = document.createElement('h2') - labelA.textContent = 'RunmeConsole A' - - const runmeA = document.createElement('runme-console') - runmeA.setAttribute('id', 'rc-a') - runmeA.setAttribute('style', 'height:200px; display:block;') - runmeA.setAttribute('takeFocus', 'true') - ;(runmeA as any).StreamCreator = () => new FakeStreams() - - const labelB = document.createElement('h2') - labelB.textContent = 'RunmeConsole B' - - const runmeB = document.createElement('runme-console') - runmeB.setAttribute('id', 'rc-b') - runmeB.setAttribute('style', 'height:200px; display:block;') - runmeB.setAttribute('takeFocus', 'true') - ;(runmeB as any).StreamCreator = () => new FakeStreams() - - container.appendChild(labelA) - container.appendChild(runmeA) - container.appendChild(labelB) - container.appendChild(runmeB) - root.appendChild(container) - - return () => { - container.remove() - } - }, []) + console.log('Rendering App component') return (
From e7a5be045404c3347ee513bd4f3275776a2810f8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:18:40 -0800 Subject: [PATCH 25/32] Add some debug logging; revert this later. --- packages/react-console/src/components/Console.tsx | 1 + packages/renderers/src/components/console/runme.ts | 2 ++ packages/renderers/src/streams.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/react-console/src/components/Console.tsx b/packages/react-console/src/components/Console.tsx index 1c51957..c00a598 100644 --- a/packages/react-console/src/components/Console.tsx +++ b/packages/react-console/src/components/Console.tsx @@ -95,6 +95,7 @@ function Console({ [cellID, fontFamily, fontSize, takeFocus, scrollToFit, rows, content] ) + console.log('Creating RunmeConsoleStream: rendering with cellID', cellID, 'runID', runID) const webComponentStream: RunmeConsoleStream = useMemo( () => ({ knownID: cellID, diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index 7a71d34..dbf9c2c 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -242,7 +242,9 @@ export class RunmeConsole extends LitElement { // Streams integration helpers #maybeInitStreams() { + console.log('RunmeConsole: maybeInitStreams') if (this.#streams || !this.stream) { + console.log('RunmeConsole: skipping streams init') return } const knownID = this.stream.knownID ?? this.id diff --git a/packages/renderers/src/streams.ts b/packages/renderers/src/streams.ts index f2e303d..605e249 100644 --- a/packages/renderers/src/streams.ts +++ b/packages/renderers/src/streams.ts @@ -191,6 +191,7 @@ class Streams implements StreamsLike { private readonly autoReconnect: boolean constructor({ knownID, runID, sequence, options }: StreamsProps) { + console.log("Streams: initializing for knownID", knownID, "runID", runID, "sequence", sequence ) // Set the identifiers this.knownID = knownID this.runID = runID From bbd816a580fc2ba3ff874e209c1890990d515278 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:20:17 -0800 Subject: [PATCH 26/32] Export FakeStreams and use package fake in testApp --- packages/renderers/package.json | 5 +++ packages/renderers/src/index.ts | 1 + testApp/src/App.tsx | 2 +- testApp/src/fakeStreams.ts | 79 --------------------------------- 4 files changed, 7 insertions(+), 80 deletions(-) delete mode 100644 testApp/src/fakeStreams.ts diff --git a/packages/renderers/package.json b/packages/renderers/package.json index f049402..a1f83da 100644 --- a/packages/renderers/package.json +++ b/packages/renderers/package.json @@ -17,6 +17,11 @@ "types": "./dist/components.d.ts", "import": "./dist/components.mjs", "require": "./dist/components.cjs" + }, + "./streams/fakeStreams": { + "types": "./dist/streams/fakeStreams.d.ts", + "import": "./dist/streams/fakeStreams.mjs", + "require": "./dist/streams/fakeStreams.cjs" } }, "files": [ diff --git a/packages/renderers/src/index.ts b/packages/renderers/src/index.ts index 80ab80a..98649b2 100644 --- a/packages/renderers/src/index.ts +++ b/packages/renderers/src/index.ts @@ -9,6 +9,7 @@ export { type StreamsLike, } from './streams' export { genRunID, Heartbeat, type StreamError } from './streams' +export { FakeStreams } from './streams/fakeStreams' export { ConsoleView, type ConsoleViewConfig } from './components/console' export { type RunmeConsoleStream } from './components/console/runme' diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx index d4f8589..df2ccb7 100644 --- a/testApp/src/App.tsx +++ b/testApp/src/App.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react' import AppConsole from './AppConsole' import { Console } from '@runmedev/react-console' -import { FakeStreams } from './fakeStreams' +import { FakeStreams } from '@runmedev/renderers' export default function App() { console.log('Rendering App component') diff --git a/testApp/src/fakeStreams.ts b/testApp/src/fakeStreams.ts deleted file mode 100644 index 2c932e5..0000000 --- a/testApp/src/fakeStreams.ts +++ /dev/null @@ -1,79 +0,0 @@ -type Listener = (value: T) => void - -export type FakeStreamsProps = { - knownID: string -} - -export class FakeStreams { - stdoutListeners: Listener[] = [] - stderrListeners: Listener[] = [] - exitListeners: Listener[] = [] - pidListeners: Listener[] = [] - mimeListeners: Listener[] = [] - - connect() { - return { - subscribe: () => ({ unsubscribe() {} }), - } - } - - sendExecuteRequest(req: any) { - if (req?.inputData) { - const data = req.inputData instanceof Uint8Array ? req.inputData : new Uint8Array() - this.stdoutListeners.forEach((fn) => fn(data)) - } - } - - setCallback(_cb: any) { - // no-op for fake - } - - close() { - // no-op - } - - get stdout() { - return { - subscribe: (fn: Listener) => { - this.stdoutListeners.push(fn) - return { unsubscribe: () => {} } - }, - } - } - - get stderr() { - return { - subscribe: (fn: Listener) => { - this.stderrListeners.push(fn) - return { unsubscribe: () => {} } - }, - } - } - - get exitCode() { - return { - subscribe: (fn: Listener) => { - this.exitListeners.push(fn) - return { unsubscribe: () => {} } - }, - } - } - - get pid() { - return { - subscribe: (fn: Listener) => { - this.pidListeners.push(fn) - return { unsubscribe: () => {} } - }, - } - } - - get mimeType() { - return { - subscribe: (fn: Listener) => { - this.mimeListeners.push(fn) - return { unsubscribe: () => {} } - }, - } - } -} From 205b226ed8ca4489759586a8e92690012a987e23 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:41:07 -0800 Subject: [PATCH 27/32] Add some more logging; testApp seems to be working but you need to rebuild it. --- packages/renderers/src/streams/fakeStreams.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/renderers/src/streams/fakeStreams.ts b/packages/renderers/src/streams/fakeStreams.ts index 46cfa9a..3572ed1 100644 --- a/packages/renderers/src/streams/fakeStreams.ts +++ b/packages/renderers/src/streams/fakeStreams.ts @@ -24,9 +24,14 @@ export class FakeStreams implements StreamsLike { } sendExecuteRequest(executeRequest: ExecuteRequest): void { + console.log('FakeStreams: sendExecuteRequest called new version', executeRequest) const input = executeRequest.inputData if (input && input.length) { + console.log('FakeStreams: Sending input to stdout.') this.stdout.next(input) + } else { + console.log('FakeStreams: No input data provided.') + this.stdout.next(new TextEncoder().encode('FakeStreams: No input data provided.\r\n')) } } From e87c99b43352714f92c3418bcb9365ca97933f9c Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:43:56 -0800 Subject: [PATCH 28/32] Add a README. --- testApp/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 testApp/README.md diff --git a/testApp/README.md b/testApp/README.md new file mode 100644 index 0000000..d813fd4 --- /dev/null +++ b/testApp/README.md @@ -0,0 +1,15 @@ +# Test App + +A simple app for testing consoles using FakeStreams. + +I'm seeing weird issues where in order for changes to renderers package to be picked up by testApp I need to do the following recipe + +``` +cd ~/git_runmeweb +find ./ -name "node_modules" -exec rm -rf {} ";" +pnpm install +pnpm run build +``` + +Namely just running `run build` didn't seem to be enough. +When you run `pnpm run dev` in `testApp` which version of the dependencies does it pick up? \ No newline at end of file From 044f92f31baefa8d589c492b481101c1e11294a8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:44:52 -0800 Subject: [PATCH 29/32] Remove AppConsole from react-components. --- packages/react-components/package.json | 7 +- .../src/components/AppConsole/AppConsole.tsx | 121 ------------------ packages/react-components/src/layout.tsx | 34 ++--- 3 files changed, 15 insertions(+), 147 deletions(-) delete mode 100644 packages/react-components/src/components/AppConsole/AppConsole.tsx diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 1a7498f..75f2d8a 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -19,8 +19,7 @@ "dist" ], "devDependencies": { - "@runmedev/react-console": "workspace:*", - "@runmedev/renderers": "workspace:*" + "@runmedev/react-console": "workspace:*" }, "scripts": { "clean": "rimraf dist", @@ -30,8 +29,6 @@ "test:coverage": "vitest run --coverage" }, "peerDependencies": { - "@runmedev/react-console": "workspace:*", - "@runmedev/renderers": "workspace:*", "js-cookie": "^3.0.5", "@monaco-editor/react": "^4.7.0", "@radix-ui/react-dropdown-menu": "^2.1.15", @@ -46,4 +43,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/packages/react-components/src/components/AppConsole/AppConsole.tsx b/packages/react-components/src/components/AppConsole/AppConsole.tsx deleted file mode 100644 index 0035218..0000000 --- a/packages/react-components/src/components/AppConsole/AppConsole.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useMemo, useRef } from 'react' - -import { ClientMessages, setContext } from '@runmedev/renderers' -import type { RendererContext } from 'vscode-notebook-renderer' -/** - * AppConsole console panel rendered with Runme's ConsoleView web component. - * - * Intended to provide a console for interacting with the app iteself. - * Right now its just a stub that echoes input. - */ -export default function AppConsole() { - const elemRef = useRef(null) - const consoleId = useMemo( - () => `console-${Math.random().toString(36).substring(2, 9)}`, - [] - ) - - const eventHandler = (eventName: string) => (e: Event) => { - console.log(eventName, e) - } - - return ( -
{ - if (!el || el.hasChildNodes()) { - return - } - const elem = document.createElement('console-view') as any - - elemRef.current = elem - - elem.addEventListener('stdout', eventHandler('stdout')) - elem.addEventListener('stderr', eventHandler('stderr')) - elem.addEventListener('exitcode', eventHandler('exitcode')) - elem.addEventListener('pid', eventHandler('pid')) - elem.addEventListener('mimetype', eventHandler('mimetype')) - - let messageListener: ((message: unknown) => void) | undefined - let inputBuffer = '' - - const ctxBridge = { - postMessage: (message: any) => { - if (message?.type === ClientMessages.terminalStdin) { - const input = (message.output?.input as string) ?? '' - for (const ch of input) { - if (ch === '\r' || ch === '\n') { - const trimmed = inputBuffer.trim() - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: `\r\nGot input: ${trimmed}\r\n> `, - }, - }) - inputBuffer = '' - continue - } - - // handle backspace/delete - if (ch === '\u0008' || ch === '\u007f') { - inputBuffer = inputBuffer.slice(0, -1) - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: '\b \b', - }, - }) - continue - } - - inputBuffer += ch - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: ch, - }, - }) - } - } - }, - onDidReceiveMessage: (listener: (message: unknown) => void) => { - messageListener = listener - listener({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: 'Welcome to the app console\n> ', - }, - } as any) - return { - dispose: () => {}, - } - }, - } as RendererContext - - setContext(ctxBridge) - // Attach the per-instance context directly so this ConsoleView - // doesn't rely on the module-level singleton. - ;(elem as any).context = ctxBridge - - // Keep the element id in sync with messages dispatched via setContext - elem.setAttribute('id', consoleId) - elem.setAttribute('buttons', 'false') - elem.setAttribute('initialContent', '') - elem.setAttribute('theme', 'dark') - elem.setAttribute('fontFamily', 'monospace') - elem.setAttribute('fontSize', '12') - elem.setAttribute('cursorStyle', 'block') - elem.setAttribute('cursorBlink', 'true') - elem.setAttribute('cursorWidth', '1') - elem.setAttribute('smoothScrollDuration', '0') - elem.setAttribute('scrollback', '4000') - - el.appendChild(elem) - }} - >
- ) -} diff --git a/packages/react-components/src/layout.tsx b/packages/react-components/src/layout.tsx index d823069..8bbcc34 100644 --- a/packages/react-components/src/layout.tsx +++ b/packages/react-components/src/layout.tsx @@ -5,7 +5,6 @@ import { Code } from '@buf/googleapis_googleapis.bufbuild_es/google/rpc/code_pb' import { Box, Flex, Text } from '@radix-ui/themes' import { AppBranding } from './App' -import AppConsole from './components/AppConsole/AppConsole' import TopNavigation from './components/TopNavigation' import { useSettings } from './contexts/SettingsContext' @@ -73,28 +72,21 @@ function Layout({ - {/* Main content with bottom console */} - - - {/* Left */} - - {left ??
} - - - {/* Middle */} - - {middle ??
} - + {/* Main content */} + + {/* Left */} + + {left ??
} + - {/* Right */} - - {right ??
} - - + {/* Middle */} + + {middle ??
} + - {/* Console spanning the full width at the bottom */} - - + {/* Right */} + + {right ??
} From fad5fc4590523a70cac8b2a04ab7555e59e4d75d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 21 Dec 2025 18:47:57 -0800 Subject: [PATCH 30/32] Fix the lock file. --- pnpm-lock.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70ab15c..59bea09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -232,9 +232,6 @@ importers: '@runmedev/react-console': specifier: workspace:* version: link:../react-console - '@runmedev/renderers': - specifier: workspace:* - version: link:../renderers packages/react-console: dependencies: From 1baf0b8e2309d1ee560c5b447e2c7a4ca415e8b1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 29 Dec 2025 17:32:00 -0800 Subject: [PATCH 31/32] Changelog for jlewi dev release. --- packages/react-components/CHANGELOG.md | 6 ++++++ packages/react-components/package.json | 2 +- packages/react-console/CHANGELOG.md | 8 ++++++++ packages/react-console/package.json | 2 +- packages/renderers/CHANGELOG.md | 6 ++++++ packages/renderers/package.json | 2 +- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/react-components/CHANGELOG.md b/packages/react-components/CHANGELOG.md index 871974b..e4035c7 100644 --- a/packages/react-components/CHANGELOG.md +++ b/packages/react-components/CHANGELOG.md @@ -1,5 +1,11 @@ # @runmedev/react-components +## 3.16.5-jlewi.0 + +### Patch Changes + +- Test the changes to support renderers and react-console being imported + ## 3.16.4 ### Patch Changes diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 75f2d8a..e41ffbd 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@runmedev/react-components", - "version": "3.16.4", + "version": "3.16.5-jlewi.0", "license": "Apache-2.0", "type": "module", "private": false, diff --git a/packages/react-console/CHANGELOG.md b/packages/react-console/CHANGELOG.md index 0196835..e6fce52 100644 --- a/packages/react-console/CHANGELOG.md +++ b/packages/react-console/CHANGELOG.md @@ -1,5 +1,13 @@ # @runmedev/react-console +## 3.16.5-jlewi.0 + +### Patch Changes + +- Test the changes to support renderers and react-console being imported +- Updated dependencies + - @runmedev/renderers@3.16.5-jlewi.0 + ## 3.16.4 ### Patch Changes diff --git a/packages/react-console/package.json b/packages/react-console/package.json index 7f7cb78..a500125 100644 --- a/packages/react-console/package.json +++ b/packages/react-console/package.json @@ -1,6 +1,6 @@ { "name": "@runmedev/react-console", - "version": "3.16.4", + "version": "3.16.5-jlewi.0", "license": "Apache-2.0", "type": "module", "private": false, diff --git a/packages/renderers/CHANGELOG.md b/packages/renderers/CHANGELOG.md index aa759a4..d2bd4c3 100644 --- a/packages/renderers/CHANGELOG.md +++ b/packages/renderers/CHANGELOG.md @@ -1,5 +1,11 @@ # @runmedev/renderers +## 3.16.5-jlewi.0 + +### Patch Changes + +- Test the changes to support renderers and react-console being imported + ## 3.16.4 ### Patch Changes diff --git a/packages/renderers/package.json b/packages/renderers/package.json index a1f83da..36c1cb9 100644 --- a/packages/renderers/package.json +++ b/packages/renderers/package.json @@ -1,6 +1,6 @@ { "name": "@runmedev/renderers", - "version": "3.16.4", + "version": "3.16.5-jlewi.0", "license": "Apache-2.0", "type": "module", "private": false, From 20ecf08d59db775c6bbf5bca904778a5ac96cf3b Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 29 Dec 2025 17:41:38 -0800 Subject: [PATCH 32/32] Revert most changes to the bare minimum to get this to work for myself. --- .../react-console/src/components/Console.tsx | 13 +- .../renderers/src/components/console/runme.ts | 34 +---- packages/renderers/src/index.ts | 8 +- packages/renderers/src/streams.ts | 17 +-- packages/renderers/src/streams/fakeStreams.ts | 49 ------- pnpm-workspace.yaml | 1 - testApp/README.md | 15 --- testApp/index.html | 12 -- testApp/package.json | 24 ---- testApp/src/App.tsx | 43 ------- testApp/src/AppConsole.tsx | 120 ------------------ testApp/src/main.tsx | 10 -- testApp/tsconfig.json | 8 -- testApp/vite.config.ts | 9 -- 14 files changed, 9 insertions(+), 354 deletions(-) delete mode 100644 packages/renderers/src/streams/fakeStreams.ts delete mode 100644 testApp/README.md delete mode 100644 testApp/index.html delete mode 100644 testApp/package.json delete mode 100644 testApp/src/App.tsx delete mode 100644 testApp/src/AppConsole.tsx delete mode 100644 testApp/src/main.tsx delete mode 100644 testApp/tsconfig.json delete mode 100644 testApp/vite.config.ts diff --git a/packages/react-console/src/components/Console.tsx b/packages/react-console/src/components/Console.tsx index c00a598..5692075 100644 --- a/packages/react-console/src/components/Console.tsx +++ b/packages/react-console/src/components/Console.tsx @@ -3,12 +3,7 @@ import { useEffect, useMemo, useRef } from 'react' import { Interceptor } from '@connectrpc/connect' import '@runmedev/renderers' -import type { - RunmeConsoleStream, - ConsoleViewConfig, - StreamsProps, - StreamsLike, -} from '@runmedev/renderers' +import type { RunmeConsoleStream, ConsoleViewConfig } from '@runmedev/renderers' interface ConsoleSettings { rows?: number @@ -34,7 +29,6 @@ export interface ConsoleProps { content?: string runner: ConsoleRunner settings?: ConsoleSettings - streamCreator?: (props: StreamsProps) => StreamsLike onStdout?: (data: Uint8Array) => void onStderr?: (data: Uint8Array) => void onExitCode?: (code: number) => void @@ -51,7 +45,6 @@ function Console({ content, runner, settings: settingsProp = {}, - streamCreator, onStdout, onStderr, onExitCode, @@ -95,7 +88,6 @@ function Console({ [cellID, fontFamily, fontSize, takeFocus, scrollToFit, rows, content] ) - console.log('Creating RunmeConsoleStream: rendering with cellID', cellID, 'runID', runID) const webComponentStream: RunmeConsoleStream = useMemo( () => ({ knownID: cellID, @@ -178,9 +170,6 @@ function Console({ // Bypass attributes because serialization of funcs won't work elem.interceptors = runner.interceptors elem.commands = commands - if (streamCreator) { - elem.StreamCreator = streamCreator - } el.appendChild(elem) const terminalEnd = document.createElement('div') diff --git a/packages/renderers/src/components/console/runme.ts b/packages/renderers/src/components/console/runme.ts index dbf9c2c..afc5c3f 100644 --- a/packages/renderers/src/components/console/runme.ts +++ b/packages/renderers/src/components/console/runme.ts @@ -16,7 +16,7 @@ import { type RendererContext } from 'vscode-notebook-renderer' import { type VSCodeEvent } from 'vscode-notebook-renderer/events' import { setContext } from '../../messaging' -import Streams, { type StreamsLike, type StreamsProps } from '../../streams' +import Streams from '../../streams' import { ClientMessages } from '../../types' import { ConsoleView, ConsoleViewConfig } from './view' @@ -39,10 +39,9 @@ export class RunmeConsole extends LitElement { protected consoleView?: ConsoleView // Streams-specific state - #streams?: StreamsLike + #streams?: Streams #streamsUnsubs: Array<() => void> = [] #winsize = { rows: 34, cols: 100, x: 0, y: 0 } - #contextBridge?: RendererContext // Properties delegated to ConsoleView @property({ type: String }) @@ -82,13 +81,9 @@ export class RunmeConsole extends LitElement { @property({ attribute: false }) interceptors: Interceptor[] = [] - // Optional Streams factory for testing/custom backends - @property({ attribute: false }) - StreamCreator?: (props: StreamsProps) => StreamsLike - constructor() { super() - this.#contextBridge = this.#installContextBridge() + this.#installContextBridge() } // Delegate theme styles to ConsoleView @@ -151,10 +146,6 @@ export class RunmeConsole extends LitElement { // Create ConsoleView element this.consoleView = document.createElement('console-view') as ConsoleView - // Share the per-instance messaging context with the child ConsoleView - if (this.#contextBridge) { - this.consoleView.context = this.#contextBridge - } // Set all properties on ConsoleView this.#updateConsoleViewProperties() @@ -242,16 +233,14 @@ export class RunmeConsole extends LitElement { // Streams integration helpers #maybeInitStreams() { - console.log('RunmeConsole: maybeInitStreams') if (this.#streams || !this.stream) { - console.log('RunmeConsole: skipping streams init') return } const knownID = this.stream.knownID ?? this.id if (!knownID || !this.stream.runID || !this.stream.runnerEndpoint) { throw new Error('Missing required stream properties') } - const props: StreamsProps = { + this.#streams = new Streams({ knownID: knownID, runID: this.stream.runID!, sequence: this.stream.sequence ?? 0, @@ -260,14 +249,7 @@ export class RunmeConsole extends LitElement { autoReconnect: this.stream.reconnect, interceptors: this.interceptors ?? [], }, - } - if (this.StreamCreator) { - console.log('RunmeConsole: using StreamCreator override') - this.#streams = this.StreamCreator(props) - } else { - console.log('RunmeConsole: using default Streams implementation') - this.#streams = new Streams(props) - } + }) const latencySub = this.#streams.connect().subscribe() this.#streamsUnsubs.push(() => latencySub.unsubscribe()) @@ -431,16 +413,10 @@ export class RunmeConsole extends LitElement { }, } as RendererContext try { - // Retain legacy behavior of setting the module-level context so - // existing consumers continue to work, but also return the instance - // bridge for per-instance use. setContext(ctxLike) - this.consoleView && (this.consoleView.context = ctxLike) - this.#contextBridge = ctxLike } catch { console.error('Failed to set context bridge') } - return ctxLike } // Render the UI - just render ConsoleView which handles everything diff --git a/packages/renderers/src/index.ts b/packages/renderers/src/index.ts index 98649b2..54e2f56 100644 --- a/packages/renderers/src/index.ts +++ b/packages/renderers/src/index.ts @@ -2,14 +2,8 @@ import './components' import { getContext, setContext } from './messaging' import { ClientMessages } from './types' -export { - default as Streams, - type Authorization, - type StreamsProps, - type StreamsLike, -} from './streams' +export { default as Streams, type Authorization } from './streams' export { genRunID, Heartbeat, type StreamError } from './streams' -export { FakeStreams } from './streams/fakeStreams' export { ConsoleView, type ConsoleViewConfig } from './components/console' export { type RunmeConsoleStream } from './components/console/runme' diff --git a/packages/renderers/src/streams.ts b/packages/renderers/src/streams.ts index 605e249..9f77bc7 100644 --- a/packages/renderers/src/streams.ts +++ b/packages/renderers/src/streams.ts @@ -81,7 +81,7 @@ type Latency = { updatedAt: bigint } -export type StreamsProps = { +type StreamsProps = { knownID: string runID: string sequence: number @@ -92,19 +92,7 @@ export type StreamsProps = { } } -export interface StreamsLike { - stdout: Observable - stderr: Observable - exitCode: Observable - pid: Observable - mimeType: Observable - connect(): Observable - sendExecuteRequest(executeRequest: ExecuteRequest): void - setCallback(callback: VSCodeEvent): void - close(): void -} - -class Streams implements StreamsLike { +class Streams { private callback: VSCodeEvent | undefined private readonly queue = new Subject() @@ -191,7 +179,6 @@ class Streams implements StreamsLike { private readonly autoReconnect: boolean constructor({ knownID, runID, sequence, options }: StreamsProps) { - console.log("Streams: initializing for knownID", knownID, "runID", runID, "sequence", sequence ) // Set the identifiers this.knownID = knownID this.runID = runID diff --git a/packages/renderers/src/streams/fakeStreams.ts b/packages/renderers/src/streams/fakeStreams.ts deleted file mode 100644 index 3572ed1..0000000 --- a/packages/renderers/src/streams/fakeStreams.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Observable, Subject } from 'rxjs' -import { VSCodeEvent } from 'vscode-notebook-renderer/events' - -import { type StreamsLike } from '../streams' -import { - ExecuteRequest, -} from '@buf/runmedev_runme.bufbuild_es/runme/runner/v2/runner_pb' - -export class FakeStreams implements StreamsLike { - stdout = new Subject() - stderr = new Subject() - exitCode = new Subject() - pid = new Subject() - mimeType = new Subject() - - //#callback?: VSCodeEvent - - connect(): Observable { - // Immediately emit a fake pid and mimeType if desired - return new Observable((observer) => { - observer.next(undefined) - observer.complete() - }) - } - - sendExecuteRequest(executeRequest: ExecuteRequest): void { - console.log('FakeStreams: sendExecuteRequest called new version', executeRequest) - const input = executeRequest.inputData - if (input && input.length) { - console.log('FakeStreams: Sending input to stdout.') - this.stdout.next(input) - } else { - console.log('FakeStreams: No input data provided.') - this.stdout.next(new TextEncoder().encode('FakeStreams: No input data provided.\r\n')) - } - } - - setCallback(_: VSCodeEvent): void { - //this.#callback = callback - } - - close(): void { - this.stdout.complete() - this.stderr.complete() - this.exitCode.complete() - this.pid.complete() - this.mimeType.complete() - } -} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8f89550..455d91c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,5 @@ packages: - packages/* - - testApp onlyBuiltDependencies: - '@tailwindcss/oxide' diff --git a/testApp/README.md b/testApp/README.md deleted file mode 100644 index d813fd4..0000000 --- a/testApp/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Test App - -A simple app for testing consoles using FakeStreams. - -I'm seeing weird issues where in order for changes to renderers package to be picked up by testApp I need to do the following recipe - -``` -cd ~/git_runmeweb -find ./ -name "node_modules" -exec rm -rf {} ";" -pnpm install -pnpm run build -``` - -Namely just running `run build` didn't seem to be enough. -When you run `pnpm run dev` in `testApp` which version of the dependencies does it pick up? \ No newline at end of file diff --git a/testApp/index.html b/testApp/index.html deleted file mode 100644 index b8f6460..0000000 --- a/testApp/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Runme Console Test App - - -
- - - diff --git a/testApp/package.json b/testApp/package.json deleted file mode 100644 index 3a8edeb..0000000 --- a/testApp/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "testApp", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@runmedev/react-console": "workspace:*", - "@runmedev/renderers": "workspace:*", - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "esbuild": "^0.23.0", - "rollup": "^4.0.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", - "typescript": "^5.4.0", - "vite": "^5.0.0" - } -} diff --git a/testApp/src/App.tsx b/testApp/src/App.tsx deleted file mode 100644 index df2ccb7..0000000 --- a/testApp/src/App.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useEffect } from 'react' - -import AppConsole from './AppConsole' -import { Console } from '@runmedev/react-console' -import { FakeStreams } from '@runmedev/renderers' - -export default function App() { - console.log('Rendering App component') - - return ( -
-

Runme Test App

- -

React Console (A)

- new FakeStreams()} - /> -

React Console (B)

- new FakeStreams()} - /> -
-
- ) -} diff --git a/testApp/src/AppConsole.tsx b/testApp/src/AppConsole.tsx deleted file mode 100644 index 1533e5e..0000000 --- a/testApp/src/AppConsole.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useMemo, useRef } from 'react' - -import { ClientMessages, setContext } from '@runmedev/renderers' -import type { RendererContext } from 'vscode-notebook-renderer' - -/** - * Local copy of AppConsole from react-components. Provides a stub console - * that echoes input on Enter. - */ -export default function AppConsole() { - const elemRef = useRef(null) - const consoleId = useMemo( - () => `console-${Math.random().toString(36).substring(2, 9)}`, - [] - ) - - const eventHandler = (eventName: string) => (e: Event) => { - console.log(eventName, e) - } - - return ( -
{ - if (!el || el.hasChildNodes()) { - return - } - const elem = document.createElement('console-view') as any - - elemRef.current = elem - - elem.addEventListener('stdout', eventHandler('stdout')) - elem.addEventListener('stderr', eventHandler('stderr')) - elem.addEventListener('exitcode', eventHandler('exitcode')) - elem.addEventListener('pid', eventHandler('pid')) - elem.addEventListener('mimetype', eventHandler('mimetype')) - - let messageListener: ((message: unknown) => void) | undefined - let inputBuffer = '' - - const ctxBridge = { - postMessage: (message: any) => { - if (message?.type === ClientMessages.terminalStdin) { - const input = (message.output?.input as string) ?? '' - for (const ch of input) { - if (ch === '\r' || ch === '\n') { - const trimmed = inputBuffer.trim() - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: `\r\nGot input: ${trimmed}\r\n> `, - }, - }) - inputBuffer = '' - continue - } - - // handle backspace/delete - if (ch === '\u0008' || ch === '\u007f') { - inputBuffer = inputBuffer.slice(0, -1) - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: '\b \b', - }, - }) - continue - } - - inputBuffer += ch - messageListener?.({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: ch, - }, - }) - } - } - }, - onDidReceiveMessage: (listener: (message: unknown) => void) => { - messageListener = listener - listener({ - type: ClientMessages.terminalStdout, - output: { - 'runme.dev/id': consoleId, - data: 'Welcome to the app console\n> ', - }, - } as any) - return { - dispose: () => {}, - } - }, - } as RendererContext - - setContext(ctxBridge) - // Attach the per-instance context directly so this ConsoleView - // doesn't rely on the module-level singleton. - ;(elem as any).context = ctxBridge - - // Keep the element id in sync with messages dispatched via setContext - elem.setAttribute('id', consoleId) - elem.setAttribute('buttons', 'false') - elem.setAttribute('initialContent', '') - elem.setAttribute('theme', 'dark') - elem.setAttribute('fontFamily', 'monospace') - elem.setAttribute('fontSize', '12') - elem.setAttribute('cursorStyle', 'block') - elem.setAttribute('cursorBlink', 'true') - elem.setAttribute('cursorWidth', '1') - elem.setAttribute('smoothScrollDuration', '0') - elem.setAttribute('scrollback', '4000') - - el.appendChild(elem) - }} - >
- ) -} diff --git a/testApp/src/main.tsx b/testApp/src/main.tsx deleted file mode 100644 index 0147987..0000000 --- a/testApp/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { createRoot } from 'react-dom/client' - -import App from './App' - -createRoot(document.getElementById('root')!).render( - - - -) diff --git a/testApp/tsconfig.json b/testApp/tsconfig.json deleted file mode 100644 index 1d9979d..0000000 --- a/testApp/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "jsx": "react-jsx", - "types": ["vite/client"] - }, - "include": ["src"] -} diff --git a/testApp/vite.config.ts b/testApp/vite.config.ts deleted file mode 100644 index 537f6a8..0000000 --- a/testApp/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -export default defineConfig({ - plugins: [react()], - resolve: { - dedupe: ['@runmedev/renderers'], - }, -})