Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 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
d276ce6
Bump version
markmiro Feb 4, 2026
3ac564c
Add finalIframeUriPrefix
markmiro Feb 4, 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
829607b
Merge branch 'main' into notebook-preview-package-merge-test
markmiro Feb 5, 2026
748fa77
Remove jsr.json configuration file from components package
markmiro Feb 5, 2026
53b0dba
fix: CI failures - add prebuild step and fix formatting
markmiro Feb 5, 2026
33455ac
Bump version to 0.3.0 in package.json
markmiro Feb 6, 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"docker:sync": "wrangler dev --ip 0.0.0.0 --port 8787",
"dev:runtime": "./scripts/dev-runtime.sh",
"build:iframe": "cd iframe-outputs && vite build",
"prebuild": "pnpm --filter @runtimed/components build",
"build": "vite build --mode development",
"bump-schema": "./scripts/bump-schema-version.sh",
"test-publish": "./scripts/test-publish.sh",
Expand Down
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@runtimed/components",
"version": "0.3.0-beta.1",
"version": "0.3.0",
"description": "React components for rendering notebook cell outputs",
"main": "dist/index.js",
"module": "dist/index.mjs",
Expand Down
23 changes: 23 additions & 0 deletions packages/components/src/ExecutionCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { cn } from "./utils/cn";

interface ExecutionCountProps {
count: number | null;
isExecuting?: boolean;
className?: string;
}

export function ExecutionCount({
count,
isExecuting,
className,
}: ExecutionCountProps) {
const display = isExecuting ? "*" : (count ?? " ");
return (
<span
data-slot="execution-count"
className={cn("text-muted-foreground font-mono text-sm", className)}
>
[{display}]:
</span>
);
}
8 changes: 5 additions & 3 deletions packages/components/src/OutputTypesDemoPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ const createOutput = (
} as OutputData;
};

export const OutputTypesDemoPage: React.FC<{ iframeUri: string }> = ({
export const OutputTypesDemoPage: React.FC<{ iframeUri?: string }> = ({
iframeUri,
}) => {
const finalIframeUriPrefix = iframeUri ?? ".";

// Terminal outputs
const stdoutOutput: OutputData = createOutput("terminal-stdout", "terminal", {
streamName: "stdout",
Expand Down Expand Up @@ -313,10 +315,10 @@ def hello():
{section.type === "html" || section.type === "svg" ? (
<div className="border border-dotted border-gray-300">
<pre className="bg-gray-100 p-1 text-xs">
{iframeUri}/react.html
{finalIframeUriPrefix}/react.html
</pre>
<IframeOutput
iframeUri={iframeUri ?? "."}
iframeUri={finalIframeUriPrefix}
outputs={section.outputs}
className="min-h-[200px] w-full"
isReact={true}
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export { JsonOutput } from "./outputs/JsonOutput.js";
export { HtmlOutput } from "./outputs/HtmlOutput.js";
export { ImageOutput } from "./outputs/ImageOutput.js";
export { SvgOutput } from "./outputs/SvgOutput.js";
export { SyntaxHighlighter } from "./outputs/SyntaxHighlighter.js";
export type { SyntaxHighlighterProps } from "./outputs/SyntaxHighlighter.js";
export { GeoJsonMapOutput } from "./outputs/geojson/GeoJsonMapOutput.js";
export { MapFeature } from "./outputs/geojson/MapFeature.js";
export {
Expand Down Expand Up @@ -63,6 +65,7 @@ export {
} from "./ui/card.js";
export { Spinner } from "./ui/Spinner.js";
export type { SpinnerSize } from "./ui/Spinner.js";
export { ExecutionCount } from "./ExecutionCount.js";

// Utilities
export { cn } from "./utils/cn.js";
Expand Down
68 changes: 6 additions & 62 deletions packages/components/src/outputs/MarkdownRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React, { useEffect } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Check, Copy } from "lucide-react";
import rehypeRaw from "rehype-raw";

// latex imports
Expand All @@ -12,6 +9,7 @@ import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import { sendFromIframe } from "./comms.js";
import { VerifiedImage } from "./VerifiedImage";
import { SyntaxHighlighter } from "./SyntaxHighlighter.js";

interface MarkdownRendererProps {
content: string;
Expand All @@ -20,63 +18,6 @@ interface MarkdownRendererProps {
generateHeadingIds?: boolean;
}

interface CodeBlockProps {
children: string;
language?: string;
enableCopy?: boolean;
}

const CodeBlock: React.FC<CodeBlockProps> = ({
children,
language = "",
enableCopy = true,
}) => {
const [copied, setCopied] = React.useState(false);

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy code:", err);
}
};

return (
<div className="group/codeblock relative">
<SyntaxHighlighter
language={language}
style={oneLight}
PreTag="div"
customStyle={{
margin: 0,
padding: "0.75rem",
fontSize: "0.875rem",
overflow: "auto",
background: "#fafafa",
borderRadius: "0.375rem",
}}
>
{children}
</SyntaxHighlighter>
{enableCopy && (
<button
onClick={handleCopy}
className="absolute top-2 right-2 z-10 rounded border border-gray-200 bg-white p-1.5 text-gray-600 opacity-0 shadow-sm transition-opacity group-hover/codeblock:opacity-100 hover:bg-gray-50 hover:text-gray-800"
title={copied ? "Copied!" : "Copy code"}
>
{copied ? (
<Check className="h-3 w-3" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
)}
</div>
);
};

const generateSlug = (text: string): string => {
return text
.toLowerCase()
Expand Down Expand Up @@ -130,9 +71,12 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
const isBlockCode = codeContent.includes("\n") || className;

return isBlockCode ? (
<CodeBlock language={language} enableCopy={enableCopyCode}>
<SyntaxHighlighter
language={language}
enableCopy={enableCopyCode}
>
{codeContent}
</CodeBlock>
</SyntaxHighlighter>
) : (
<code
className={`${className} rounded bg-gray-100 px-1 py-0.5 text-sm text-gray-800`}
Expand Down
82 changes: 82 additions & 0 deletions packages/components/src/outputs/SyntaxHighlighter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react";
import { Prism as PrismHighlighter } from "react-syntax-highlighter";
import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Check, Copy } from "lucide-react";

export interface SyntaxHighlighterProps {
/** Code content to highlight */
children: string;
/** Programming language for syntax highlighting */
language?: string;
/** Show copy button on hover (default: true) */
enableCopy?: boolean;
/** Custom styles to apply to the code block */
customStyle?: React.CSSProperties;
/** Additional class name for the container */
className?: string;
/** Show line numbers (default: false) */
showLineNumbers?: boolean;
}

const defaultStyle: React.CSSProperties = {
margin: 0,
padding: "0.75rem",
fontSize: "0.875rem",
overflow: "auto",
background: "#fafafa",
borderRadius: "0.375rem",
};

/**
* Syntax highlighter component using Prism.
* Supports copy-to-clipboard and configurable styling.
*/
export const SyntaxHighlighter: React.FC<SyntaxHighlighterProps> = ({
children,
language = "",
enableCopy = true,
customStyle,
className,
showLineNumbers = false,
}) => {
const [copied, setCopied] = React.useState(false);

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy code:", err);
}
};

return (
<div className={`group/codeblock relative ${className || ""}`}>
<PrismHighlighter
language={language}
style={oneLight}
PreTag="div"
showLineNumbers={showLineNumbers}
customStyle={{ ...defaultStyle, ...customStyle }}
>
{children}
</PrismHighlighter>
{enableCopy && (
<button
onClick={handleCopy}
className="absolute top-2 right-2 z-10 rounded border border-gray-200 bg-white p-1.5 text-gray-600 opacity-0 shadow-sm transition-opacity group-hover/codeblock:opacity-100 hover:bg-gray-50 hover:text-gray-800"
title={copied ? "Copied!" : "Copy code"}
>
{copied ? (
<Check className="h-3 w-3" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
)}
</div>
);
};

export default SyntaxHighlighter;
12 changes: 12 additions & 0 deletions packages/notebook-preview/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Output Types Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/demo-main.tsx"></script>
</body>
</html>
12 changes: 12 additions & 0 deletions packages/notebook-preview/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Notebook Preview</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions packages/notebook-preview/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@runtimed/notebook-preview",
"version": "0.3.0-beta.1",
"type": "module",
"scripts": {
"dev": "vite build --watch --mode development",
"prebuild": "pnpm --filter @runtimed/components build",
"build": "vite build",
"preview": "vite --port 5175",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@runtimed/components": "workspace:*",
"react": "19.2.1",
"react-dom": "19.2.1"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.10",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.5.2",
"tailwindcss": "^4.1.10",
"typescript": "^5.8.3",
"vite": "^6.3.5"
},
"engines": {
"node": ">=23.0.0",
"pnpm": ">=10.9.0"
},
"publishConfig": {
"access": "public"
}
}
33 changes: 33 additions & 0 deletions packages/notebook-preview/public/health.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Health Check</title>
<style>
pre {
padding: 1rem;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<h1>Health Check</h1>
<p>
Put this page in an iframe and send a message to the iframe to check
health.
</p>
<pre>iframe.contentWindow?.postMessage({ healthy: true }, "*");</pre>
<p>Messages received:</p>
<pre id="message">No message received</pre>
<script>
window.addEventListener("message", (event) => {
document.getElementById("message").textContent = JSON.stringify(
event.data,
null,
2
);
});
</script>
</body>
</html>
12 changes: 12 additions & 0 deletions packages/notebook-preview/react.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React IFrame Content</title>
</head>
<body class="overflow-y-hidden">
<div id="react-root"></div>
<script type="module" src="/src/react-main.tsx"></script>
</body>
</html>
Loading