feat(shadertoy): add streaming partial input, visibility optimization, host styling

Shadertoy server improvements:
- Add ontoolinputpartial handler to show code preview during generation
- Add IntersectionObserver to pause/play shader based on visibility
- Apply host style variables (theme, colors, fonts)
- Add border-radius with fullscreen mode support
- Use gradient background and ghost text color for code preview

Skill documentation updates:
- Add streaming partial input section with use cases
- Add visibility-based resource management pattern
- Add fullscreen mode API and CSS patterns
- Expand host styling with CSS variable usage examples
This commit is contained in:
Anton Pidkuiko MacBook 2026-01-17 22:29:57 +00:00
commit f1a00f9e15
No known key found for this signature in database
2 changed files with 187 additions and 3 deletions

View file

@ -11,6 +11,12 @@ html, body {
width: 100%;
height: 100%;
min-height: 400px;
border-radius: var(--border-radius-lg);
overflow: hidden;
}
.main.fullscreen {
border-radius: 0;
}
#canvas {
@ -19,6 +25,34 @@ html, body {
display: block;
}
#canvas.hidden {
display: none;
}
#code-preview {
display: none;
width: 100%;
height: 100%;
margin: 0;
padding: 16px;
box-sizing: border-box;
overflow: auto;
background: linear-gradient(
135deg,
var(--color-background-secondary) 0%,
var(--color-background-tertiary) 100%
);
color: var(--color-text-ghost);
font-family: var(--font-mono);
font-size: var(--font-text-xs-size);
line-height: var(--font-text-xs-line-height);
white-space: pre-wrap;
}
#code-preview.visible {
display: block;
}
/* Fullscreen button */
.fullscreen-btn {
position: absolute;

View file

@ -1,7 +1,12 @@
/**
* ShaderToy renderer MCP App using ShaderToyLite.js
*/
import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps";
import {
App,
type McpUiHostContext,
applyHostStyleVariables,
applyDocumentTheme,
} from "@modelcontextprotocol/ext-apps";
import "./global.css";
import "./mcp-app.css";
import ShaderToyLite, {
@ -34,6 +39,7 @@ const log = {
// Get element references
const mainEl = document.querySelector(".main") as HTMLElement;
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const codePreview = document.getElementById("code-preview") as HTMLPreElement;
const fullscreenBtn = document.getElementById(
"fullscreen-btn",
) as HTMLButtonElement;
@ -49,8 +55,12 @@ function resizeCanvas() {
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
// Handle host context changes (display mode)
// Handle host context changes (display mode, styling)
function handleHostContextChanged(ctx: McpUiHostContext) {
// Apply host styling
if (ctx.theme) applyDocumentTheme(ctx.theme);
if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
// Note: We ignore safeAreaInsets to maximize shader display area
// Show fullscreen button if available (only update if field is present)
@ -112,9 +122,22 @@ app.onteardown = async () => {
return {};
};
app.ontoolinputpartial = (params) => {
// Show code preview, hide canvas
codePreview.classList.add("visible");
canvas.classList.add("hidden");
const code = params.arguments?.fragmentShader;
codePreview.textContent = typeof code === "string" ? code : "";
codePreview.scrollTop = codePreview.scrollHeight;
};
app.ontoolinput = (params) => {
log.info("Received shader input");
// Hide code preview, show canvas
codePreview.classList.remove("visible");
canvas.classList.remove("hidden");
if (!isShaderInput(params.arguments)) {
log.error("Invalid tool input");
return;
@ -162,6 +185,18 @@ app.onerror = log.error;
app.onhostcontextchanged = handleHostContextChanged;
// Pause/resume shader based on visibility
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
shaderToy?.play();
} else {
shaderToy?.pause();
}
});
});
observer.observe(mainEl);
// Connect to host
app.connect().then(() => {
log.info("Connected to host");