Skip to content
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e346d37
feat: add @runtimed/components package and migrate output components
markmiro Feb 2, 2026
36afdd0
Add @runtimed/components dev command to ecosystem file
markmiro Feb 2, 2026
5040765
Bump version
markmiro Feb 2, 2026
064cb7e
Remove unused @runtimed/components
markmiro Feb 2, 2026
b0821c2
Fix iframe outputs docker build
markmiro Feb 2, 2026
bd48f29
Make min iframe height taller
markmiro Feb 2, 2026
923401e
Add text to incrementor component
markmiro Feb 2, 2026
6dcb096
Watch styles during dev build
markmiro Feb 2, 2026
11caa4c
Bump version
markmiro Feb 2, 2026
a3aafa1
Fix iframe height
markmiro Feb 2, 2026
704b598
Build package in CI
markmiro Feb 2, 2026
7e438f6
Allow removing message handler
markmiro Feb 2, 2026
c214c61
Fix type-check command failing because of .ts imports
markmiro Feb 2, 2026
218dbf2
Set iframeUri from parent
markmiro Feb 2, 2026
7d78b4a
Show url in iframe output demo page
markmiro Feb 2, 2026
3b98715
feat: add @runtimed/components package and migrate output components
markmiro Feb 2, 2026
4d277f8
Add notebook-preview package from extract-components
markmiro Feb 2, 2026
c978cc2
Remove unused
markmiro Feb 2, 2026
5b8bd71
Make @runtimed/notebook-preview publishable
markmiro Feb 2, 2026
c890807
Set version to alpha
markmiro Feb 2, 2026
78a7ce9
Add finalIframeUriPrefix
markmiro Feb 4, 2026
eb71542
Get workspace dep
markmiro Feb 4, 2026
c09e747
Optional prop
markmiro Feb 4, 2026
d255c79
Accept json sent down
markmiro Feb 4, 2026
c392194
Add demo and test page
markmiro Feb 4, 2026
279f2a4
Make the demo a health check instead
markmiro Feb 4, 2026
73168d2
Clean up health check
markmiro Feb 4, 2026
68ab44f
Add iframe loaded
markmiro Feb 4, 2026
c002d47
Create notebook renderer
markmiro Feb 4, 2026
8c4c0bf
Fix health getting exported instead of demo
markmiro Feb 5, 2026
2bab6ba
Execution count from nteract, remove left colored bar
markmiro Feb 5, 2026
5aa4c0b
Extract Syntax highlighter from markdown and use it in the notebook r…
markmiro Feb 5, 2026
d70d308
Formatting
markmiro Feb 5, 2026
54e50a0
Add prebuild
markmiro Feb 5, 2026
544bfba
Adjust spacing
markmiro Feb 5, 2026
8028a57
Scroll to top when new notebook data is received
markmiro Feb 5, 2026
0fc764e
Smaller font size for code cells
markmiro Feb 5, 2026
98bdffa
Bump version
markmiro Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
- name: Lint check
run: pnpm lint

- name: Build workspace packages
run: pnpm --filter @runtimed/components build

- name: Type check
run: pnpm type-check

Expand Down
8 changes: 6 additions & 2 deletions Dockerfile.iframe-outputs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ WORKDIR /app
# Copy workspace files
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./

# Copy schema package.json (only workspace dependency needed for iframe)
# Copy workspace package.json files (needed for pnpm workspace resolution)
COPY packages/schema/package.json packages/schema/tsconfig.json ./packages/schema/
COPY packages/components/package.json packages/components/tsconfig.json ./packages/components/

# Install dependencies with cache mount
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
Expand All @@ -19,10 +20,13 @@ COPY packages/schema/src ./packages/schema/src
COPY src/components/ui ./src/components/ui
COPY src/lib ./src/lib
COPY src/util/iframe.ts ./src/util/iframe.ts
COPY src/components/outputs/shared-with-iframe ./src/components/outputs/shared-with-iframe
COPY packages/components ./packages/components
COPY iframe-outputs ./iframe-outputs
COPY vite.config.ts tsconfig.json tsconfig.node.json ./

# Build the components package first (required for iframe-outputs)
RUN pnpm --filter @runtimed/components build

# Build the iframe outputs
RUN pnpm build:iframe

Expand Down
4 changes: 4 additions & 0 deletions Dockerfile.sync
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ COPY packages/ai-core/package.json ./packages/ai-core/
COPY packages/pyodide-runtime/package.json ./packages/pyodide-runtime/
COPY packages/schema/package.json ./packages/schema/

# Remove @runtimed/components from dependencies (sync service doesn't need it)
# This allows pnpm install to succeed without the components package
RUN node -e "const pkg = require('./package.json'); delete pkg.dependencies['@runtimed/components']; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));"

# Install dependencies
RUN pnpm install

Expand Down
1 change: 1 addition & 0 deletions Dockerfile.web
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ COPY packages/agent-core/package.json ./packages/agent-core/
COPY packages/ai-core/package.json ./packages/ai-core/
COPY packages/pyodide-runtime/package.json ./packages/pyodide-runtime/
COPY packages/schema/package.json ./packages/schema/
COPY packages/components/package.json ./packages/components/
Comment thread
cursor[bot] marked this conversation as resolved.

# Install dependencies with cache mount
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
Expand Down
8 changes: 8 additions & 0 deletions ecosystem.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
"env": {
"NODE_ENV": "development"
}
},
{
"name": "components",
"script": "pnpm",
"args": "--filter @runtimed/components dev",
"env": {
"NODE_ENV": "development"
}
}
]
}
7 changes: 3 additions & 4 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,10 @@ export default [
},
{
ignores: [
"dist/**",
"node_modules/**",
"*.d.ts",
"**/dist/**",
"**/node_modules/**",
"**/*.d.ts",
"scripts/**",
"iframe-outputs/worker/dist/**",
"iframe-outputs/worker/.wrangler/**",
],
},
Expand Down
3 changes: 1 addition & 2 deletions iframe-outputs/src/react-main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createRoot } from "react-dom/client";
import { IframeReactApp } from "./components/IframeReactApp";
import { IframeReactApp, sendFromIframe } from "@runtimed/components";
import "./style.css";
import { sendFromIframe } from "@/components/outputs/shared-with-iframe/comms";

// Main React initialization for iframe outputs
function initializeReactIframe() {
Expand Down
2 changes: 1 addition & 1 deletion iframe-outputs/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Docs: https://tailwindcss.com/docs/preflight#overview

@import "tw-animate-css";
@plugin "@tailwindcss/typography";
@source "../../src/components/outputs/shared-with-iframe";
@source "../../packages/components/src";
@source "../../src/components/ui";

:root {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"@react-spring/web": "^10.0.1",
"@runtimed/agent-core": "workspace:*",
"@runtimed/ai-core": "workspace:*",
"@runtimed/components": "workspace:*",
"@runtimed/pyodide-runtime": "workspace:*",
"@runtimed/schema": "workspace:*",
"@tanstack/react-query": "^5.85.5",
Expand Down
177 changes: 177 additions & 0 deletions packages/components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# @runtimed/components

React components for rendering notebook cell outputs. This package provides a comprehensive set of output renderers for displaying code execution results, including rich multimedia formats, terminal output, AI tool interactions, and geographic data.

## Installation

```bash
pnpm add @runtimed/components
# or
npm install @runtimed/components
```

**Peer Dependencies**: React 19+

## Quick Start

```tsx
import { SingleOutput, OutputsContainer } from "@runtimed/components";
import "@runtimed/components/styles.css";

function NotebookOutputs({ outputs }) {
return (
<OutputsContainer>
{outputs.map((output) => (
<SingleOutput key={output.id} output={output} />
))}
</OutputsContainer>
);
}
```

## Components

### Output Renderers

| Component | Description |
| ------------------- | ----------------------------------------------------------------------- |
| `SingleOutput` | Smart router that selects the appropriate renderer based on output type |
| `OutputsContainer` | Wrapper for consistent output styling |
| `RichOutputContent` | Renders multimedia content by MIME type |

### Specific Renderers

| Component | MIME Types |
| ------------------ | ------------------------------------------------------ |
| `PlainTextOutput` | `text/plain` |
| `MarkdownRenderer` | `text/markdown` - GFM, KaTeX math, syntax highlighting |
| `HtmlOutput` | `text/html` |
| `JsonOutput` | `application/json` - interactive tree view |
| `ImageOutput` | `image/png`, `image/jpeg`, `image/gif`, `image/webp` |
| `SvgOutput` | `image/svg+xml` |
| `AnsiOutput` | Terminal output with ANSI color codes |
| `GeoJsonMapOutput` | `application/geo+json` - MapLibre-powered maps |

### AI Tool Components

| Component | Purpose |
| ---------------------- | ------------------------------------------- |
| `AiToolCallOutput` | Displays AI tool invocation details |
| `AiToolResultOutput` | Renders tool execution results |
| `AiToolApprovalOutput` | UI for human-in-the-loop approval workflows |

### Iframe Integration

For sandboxed output rendering:

```tsx
import { IframeReactApp, IframeOutput } from "@runtimed/components";

// Parent: embed outputs in an iframe
<IframeOutput outputs={cellOutputs} />

// Child iframe: render the app
<IframeReactApp />
```

Communication utilities:

```tsx
import {
useIframeCommsParent,
useIframeCommsChild,
sendToIframe,
sendFromIframe,
} from "@runtimed/components";
```

### UI Components

Basic UI building blocks:

```tsx
import { Button, Card, Spinner } from "@runtimed/components";

<Button variant="outline" size="sm">Click me</Button>
<Spinner size="md" />
```

## Utilities

```tsx
import { cn, groupConsecutiveStreamOutputs } from "@runtimed/components";

// Merge Tailwind classes
cn("px-2 py-1", condition && "bg-blue-500");

// Group stdout/stderr streams for cleaner display
const grouped = groupConsecutiveStreamOutputs(outputs);
```

## Styling

Import the CSS for proper styling:

```tsx
import "@runtimed/components/styles.css";
```

The package uses Tailwind CSS v4. Components are designed to work in both light and dark themes.

## Features

- **Lazy loading**: Heavy components like `MarkdownRenderer` are dynamically imported
- **Error boundaries**: Outputs gracefully handle rendering failures
- **Artifact support**: Handles both inline data and artifact URLs for large outputs
- **Suspense-ready**: Built-in loading states with `SuspenseSpinner`

## Output Data Format

Components expect outputs conforming to `@runtimed/schema` types:

```typescript
import type { OutputData, OutputType } from "@runtimed/components";

interface OutputData {
id: string;
outputType:
| "multimedia_display"
| "multimedia_result"
| "terminal"
| "markdown"
| "error";
data?: string | null;
representations?: Record<string, MediaContainer>;
streamName?: "stdout" | "stderr";
}
```

## Development

```bash
# Build
pnpm build

# Watch mode
pnpm dev

# Type check
pnpm type-check

# Lint
pnpm lint
```

## Demo

The package includes a demo page for testing all output types:

```tsx
import { OutputTypesDemoPage } from "@runtimed/components";

<OutputTypesDemoPage iframeUri="localhost:8000" />;
```

## License

MIT
6 changes: 6 additions & 0 deletions packages/components/jsr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@runtimed/components",
"version": "0.3.0-beta.1",
"exports": "./src/index.ts",
"license": "BSD-3-Clause"
}
96 changes: 96 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"name": "@runtimed/components",
"version": "0.3.0-beta.1",
"description": "React components for rendering notebook cell outputs",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"source": "src/index.ts",
"files": [
"dist",
"src"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.mjs"
},
"./styles.css": "./dist/styles.css"
},
"scripts": {
"type-check": "tsc --noEmit --allowImportingTsExtensions",
"lint": "eslint src/",
"lint:check": "eslint src/ --max-warnings 0",
"format": "prettier --write .",
"format:check": "prettier --check .",
"build": "tsdown; pnpm build:css",
"build:css": "tailwindcss -i src/styles.css -o dist/styles.css --minify",
"dev": "tsdown --watch & tailwindcss -i src/styles.css -o dist/styles.css --watch",
"prepublishOnly": "pnpm build"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"dependencies": {
"@uiw/react-json-view": "2.0.0-alpha.40",
"react-error-boundary": "^6.0.0",
"@radix-ui/react-slot": "^1.2.3",
"@runtimed/schema": "workspace:*",
"ansi-to-react": "^6.1.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geojson-map-fit-mercator": "^1.1.0",
"katex": "^0.16.22",
"lucide-react": "^0.545.0",
"maplibre-gl": "^5.7.1",
"maplibre-react-components": "^0.2.6",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.1",
"react-use": "^17.6.0",
"rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@tailwindcss/cli": "^4.1.10",
"@types/node": "^24.0.10",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
"eslint": "^9.30.1",
"prettier": "^3.6.0",
"react": "19.2.1",
"react-dom": "19.2.1",
"tsdown": "0.20.0-beta.1",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=23.0.0",
"pnpm": ">=10.9.0"
},
"keywords": [
"react",
"components",
"notebook",
"outputs",
"runt",
"anode"
],
"repository": {
"type": "git",
"url": "git+https://github.com/runtimed/anode.git",
"directory": "packages/components"
},
"publishConfig": {
"access": "public"
},
"author": "Runt Team",
"license": "MIT"
}
6 changes: 6 additions & 0 deletions packages/components/src/Incrementor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useState } from "react";

export function Incrementor() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count is {count}</button>;
}
Loading