mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* fix(bake): classify deck-vs-scroll by viewport, real-wheel pan, motion config
Systematic audit of all 126 baked previews surfaced three capture bugs:
- 2 vertical pages misread as decks (the input probe wheel-scrolled them and the
scroll-driven animation looked like a slide change), so they got walked
sideways. Classify by viewport height instead: a fixed-viewport page is a deck,
a vertically-scrollable page is a landing page (pan it) even with a horizontal
marquee/carousel sub-component.
- 9 scroll-hijack landing pages (custom/transform scroll) that window.scrollTo
can't move, so the pan was static. Pan those with REAL wheel events
(page.mouse.wheel), which drive the page's own scroll handler.
- single-screen pages now hold (static) instead of being forced down a deck path.
Plus an opt-in override: authors can declare od.preview.motion ('scroll' | 'deck'
| 'static') and the bake honors it, auto-detecting only when it's absent. Schema
+ plugins-spec document the field. (Also strips a stray NUL byte from the hash
line that made the file read as binary.) BAKE_VERSION -> 4 re-bakes everything.
* perf(plugins-home): only decode visible gallery clips + stream first frame
Two cheap wins for the baked gallery videos:
- Decouple mount from play. The tile mounts the <video> across the wider inView
margin (so scroll-in/hover never remounts + reloads), but only PLAYS while
truly visible — off-screen tiles in the mount margin hold their poster frame
paused instead of all running a simultaneous decode. Adds a 0-margin visible
observer in PreviewSurface alongside the existing near one.
- preload=metadata instead of auto: paints the first frame off the +faststart
header instead of eagerly buffering the whole clip up front, so tiles show fast
and don't saturate the network. The idle hold buffers the pan before hover.
* perf(plugins-home): keep baked clips mounted across a scroll window
Scrolling a tile out of view and back re-showed a load even though the clip
bytes are HTTP-cached (R2 immutable): the <video> unmounted at the tight 120px
margin, so scroll-back remounted a fresh element that re-fetches metadata and
re-decodes the first frame. Add a wide keep-mounted observer (~1500/1800px) so a
clip stays mounted for a few screens — instant scroll-back — while iframes keep
the tight margin and play stays gated to the truly-visible zone (paused, not
unmounted, off screen).
* fix(bake,contracts): probe scroll mechanism before recording; validate motion
Address review:
- Move the window.scrollTo probe before Page.startScreencast so its scrollTo
160 -> 0 jump isn't baked into the head of the pan as a visible lurch.
- Type od.preview.motion in the Zod PluginManifestSchema (enum scroll|deck|
static) so an invalid value fails doctor/install instead of silently parsing
via passthrough and being ignored by the bake; add contract test coverage.
* fix(bake): auto-detect single-screen fixed pages as static, not deck
A fixed-viewport page is only a deck if an input actually advances it; probe the
driver during auto-detect and fall back to 'static' (default viewport + a hold)
when nothing moves it, instead of routing every non-scrollable page through the
deck path where walkSlides(null) just held at the deck-sized capture. Extracted
the arrow/wheel probe into probeDeckDriver(). Verified: a waitlist page now bakes
a 2.5s static hold, guizang still walks, acreage still pans.
---------
Co-authored-by: audit <a@b.c>
249 lines
9.1 KiB
JSON
249 lines
9.1 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://open-design.ai/schemas/plugin.v1.json",
|
|
"title": "Open Design plugin manifest (v1)",
|
|
"description": "Schema for `open-design.json` sidecar files. Companion of `SKILL.md`; never duplicates skill body content. See docs/plugins-spec.md §5.",
|
|
"type": "object",
|
|
"required": ["specVersion", "name", "version"],
|
|
"additionalProperties": true,
|
|
"properties": {
|
|
"$schema": { "type": "string", "format": "uri" },
|
|
"specVersion": { "type": "string", "minLength": 1 },
|
|
"name": { "type": "string", "minLength": 1, "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
|
"title": { "type": "string", "minLength": 1 },
|
|
"title_i18n": { "$ref": "#/$defs/LocalizedText" },
|
|
"version": { "type": "string", "minLength": 1 },
|
|
"description": { "type": "string" },
|
|
"description_i18n": { "$ref": "#/$defs/LocalizedText" },
|
|
"author": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": { "type": "string" },
|
|
"url": { "type": "string", "format": "uri" }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"license": { "type": "string" },
|
|
"homepage": { "type": "string", "format": "uri" },
|
|
"icon": { "type": "string" },
|
|
"tags": { "type": "array", "items": { "type": "string" } },
|
|
"compat": {
|
|
"type": "object",
|
|
"properties": {
|
|
"agentSkills": { "type": "array", "items": { "$ref": "#/$defs/RefPath" } },
|
|
"claudePlugins": { "type": "array", "items": { "$ref": "#/$defs/RefPath" } }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"od": {
|
|
"type": "object",
|
|
"properties": {
|
|
"kind": { "type": "string", "enum": ["skill", "scenario", "atom", "bundle"] },
|
|
"taskKind": { "type": "string", "enum": ["new-generation", "code-migration", "figma-migration", "tune-collab"] },
|
|
"mode": { "type": "string" },
|
|
"platform": { "type": "string" },
|
|
"scenario": { "type": "string" },
|
|
"engineRequirements": {
|
|
"type": "object",
|
|
"properties": { "od": { "type": "string" } },
|
|
"additionalProperties": true
|
|
},
|
|
"preview": {
|
|
"type": "object",
|
|
"properties": {
|
|
"type": { "type": "string" },
|
|
"entry": { "type": "string" },
|
|
"poster": { "type": "string" },
|
|
"video": { "type": "string" },
|
|
"gif": { "type": "string" },
|
|
"motion": {
|
|
"type": "string",
|
|
"enum": ["scroll", "deck", "static"],
|
|
"description": "How the gallery should capture this HTML preview's hover clip: 'scroll' = vertical-scroll landing page (pan top->bottom), 'deck' = horizontal slideshow (walk slides), 'static' = single fixed screen (hold its in-place animation). Omit to auto-detect from the page's scroll height."
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"useCase": {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {
|
|
"oneOf": [
|
|
{ "type": "string" },
|
|
{ "$ref": "#/$defs/LocalizedText" }
|
|
]
|
|
},
|
|
"exampleOutputs": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["path"],
|
|
"properties": {
|
|
"path": { "type": "string" },
|
|
"title": { "type": "string" }
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"context": {
|
|
"type": "object",
|
|
"properties": {
|
|
"skills": { "type": "array", "items": { "$ref": "#/$defs/Reference" } },
|
|
"designSystem": {
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/Reference" },
|
|
{ "type": "object", "properties": { "ref": { "type": "string" }, "primary": { "type": "boolean" } }, "additionalProperties": true }
|
|
]
|
|
},
|
|
"craft": { "type": "array", "items": { "type": "string" } },
|
|
"assets": { "type": "array", "items": { "type": "string" } },
|
|
"claudePlugins": { "type": "array", "items": { "$ref": "#/$defs/Reference" } },
|
|
"mcp": { "type": "array", "items": { "$ref": "#/$defs/McpServerSpec" } },
|
|
"atoms": { "type": "array", "items": { "type": "string" } }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"pipeline": {
|
|
"type": "object",
|
|
"required": ["stages"],
|
|
"properties": {
|
|
"stages": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["id", "atoms"],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"atoms": { "type": "array", "items": { "type": "string" } },
|
|
"repeat": { "type": "boolean" },
|
|
"until": { "type": "string" },
|
|
"onFailure": { "type": "string", "enum": ["abort", "skip", "retry"] }
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"genui": {
|
|
"type": "object",
|
|
"properties": {
|
|
"surfaces": {
|
|
"type": "array",
|
|
"items": { "$ref": "#/$defs/GenUISurface" }
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"connectors": {
|
|
"type": "object",
|
|
"properties": {
|
|
"required": { "type": "array", "items": { "$ref": "#/$defs/ConnectorRef" } },
|
|
"optional": { "type": "array", "items": { "$ref": "#/$defs/ConnectorRef" } }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"inputs": {
|
|
"type": "array",
|
|
"items": { "$ref": "#/$defs/InputField" }
|
|
},
|
|
"capabilities": { "type": "array", "items": { "type": "string" } }
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
},
|
|
"$defs": {
|
|
"RefPath": {
|
|
"type": "object",
|
|
"required": ["path"],
|
|
"properties": { "path": { "type": "string", "minLength": 1 } },
|
|
"additionalProperties": true
|
|
},
|
|
"LocalizedText": {
|
|
"type": "object",
|
|
"minProperties": 1,
|
|
"additionalProperties": { "type": "string" }
|
|
},
|
|
"Reference": {
|
|
"type": "object",
|
|
"properties": {
|
|
"ref": { "type": "string" },
|
|
"path": { "type": "string" }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"McpServerSpec": {
|
|
"type": "object",
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": { "type": "string" },
|
|
"command": { "type": "string" },
|
|
"args": { "type": "array", "items": { "type": "string" } },
|
|
"env": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
"url": { "type": "string" }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"GenUISurface": {
|
|
"type": "object",
|
|
"required": ["id", "kind", "persist"],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"kind": { "type": "string", "enum": ["form", "choice", "confirmation", "oauth-prompt"] },
|
|
"persist": { "type": "string", "enum": ["run", "conversation", "project"] },
|
|
"trigger": {
|
|
"type": "object",
|
|
"properties": {
|
|
"stageId": { "type": "string" },
|
|
"atom": { "type": "string" }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"schema": { "type": "object" },
|
|
"prompt": { "type": "string" },
|
|
"capabilitiesRequired": { "type": "array", "items": { "type": "string" } },
|
|
"timeout": { "type": "integer", "minimum": 1 },
|
|
"onTimeout": { "type": "string", "enum": ["abort", "default", "skip"] },
|
|
"default": {},
|
|
"oauth": {
|
|
"type": "object",
|
|
"required": ["route"],
|
|
"properties": {
|
|
"route": { "type": "string", "enum": ["connector", "mcp", "plugin"] },
|
|
"connectorId": { "type": "string" },
|
|
"mcpServerId": { "type": "string" }
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"ConnectorRef": {
|
|
"type": "object",
|
|
"required": ["id"],
|
|
"properties": {
|
|
"id": { "type": "string" },
|
|
"tools": { "type": "array", "items": { "type": "string" } }
|
|
},
|
|
"additionalProperties": true
|
|
},
|
|
"InputField": {
|
|
"type": "object",
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": { "type": "string", "minLength": 1 },
|
|
"label": { "type": "string" },
|
|
"type": { "type": "string", "enum": ["string", "text", "select", "number", "boolean", "file"] },
|
|
"required": { "type": "boolean" },
|
|
"options": { "type": "array", "items": { "type": "string" } },
|
|
"placeholder": { "type": "string" },
|
|
"default": {}
|
|
},
|
|
"additionalProperties": true
|
|
}
|
|
}
|
|
}
|