Files
open-design/docs/prompt-composition.md
open-design-crew[bot]GitHubbone3deep1962-collabWilliamopen-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>NJUHuaAmy
14fee234e6 feat: add motion design workflow and curated motion skills (#8619)
* feat: add Motion Design skill and OD Next task route

* fix(web): rename motion design entry and place it second

* fix(web): reconcile motion design catalog consumers

* feat(web): add motion design templates to Home and Community

Five motion design example templates (3D procedural island, explainer,
product launch, showreel, data story), chosen by community reach, under the
Home motion-design chip and a new Community "Motion design" type.

Each template follows the bundled example layout: an OD-generated
example.html with its runtime libraries under assets/, an HTML preview baked
by the existing preview pipeline, and a SKILL.md that carries example.html
as the reference when the card is picked on the OD Next motion-design route.
Home cards ship curated copy for every UI locale. Motion templates stay off
the Video and HyperFrames chips.

* fix(skills): keep motion-design procedural layers visible in video export

The MP4 exporter seeks timelines with callbacks suppressed
(renderSeek(t, { suppressEvents: true })), so content drawn only from a GSAP
onUpdate callback is missing from exported video. Recommend a per-frame draw
loop that reads the timeline clock, and state that visible content must not
depend on timeline callbacks. The OD Next task asset is re-synced with
scripts/sync-motion-design-skill.ts.

* fix(motion): preserve reference assets and exported animation frames

* feat(skills): integrate curated motion helpers with complete runtime files

* feat(skills): add Huashu, Oil Motion and Motion.Lab adapters

* test: classify animate design guidance in narration guard

* feat(motion): refine five community templates and portable previews

* fix(motion): refine launch transitions and center gallery previews

* fix: allow free-form briefs on automatic motion task route

* feat(motion): add template soundtracks and audio export

* fix(motion): require rendered MP4 alongside editable video source

* test(motion): align strategy activation expectation with package version

* fix: scope motion delivery and make editable sources playable

* fix(motion): bundle source dependencies and valid closing protocol

* feat(motion): rebuild the five templates on the most-viewed community posts

Re-select topics by single-post views in the latest awesome-opus-5.5-video
snapshot and anchor each template to the top eligible post of its genre:
camera-focus explainer, showreel, product launch, procedural island and a
new wordless animated short that replaces the data story.

All five example.html files are hand-authored canvas/Three.js films drawn by
one render(t) from the GSAP master timeline, so preview, scrubbing and MP4
export produce identical frames. Each ships an original procedural
soundtrack, updated SKILL.md and 19-locale copy; Home order follows reach.

* fix(plugins): load bundled fonts in plugin previews

Plugin previews blocked every bundled font: the preview CSP had no font-src,
and relative url() references inside inline <style> blocks kept pointing at
the preview URL instead of the plugin folder. Allow same-origin fonts, map
inline-style url() references through the existing asset route (escapes,
absolute and data: URLs stay untouched), and serve font files with font
MIME types.

* feat(community): make motion template Use match the Home example pick

Community 「做同款」 on a motion design template now binds it as that type's
own example card, carrying the type's project metadata, so the run takes the
same OD Next motion-design route as a pick from Home's motion design list and
delivers the MP4 together with the editable source. Other task types keep
the existing plugin-pinning hand-off.

Community use clicks now record use_source (card | detail_modal) and
use_action (use | use_with_query); the details modal Use is tracked for the
first time. The Home hand-off no longer records a second community_gallery
click for uses Community already tracked.

* fix(plugins): count the five motion templates in the official registry metadata

* fix(media): keep HyperFrames audio filters compatible with FFmpeg 4.1

CI's bundled Linux FFmpeg 4.1 rejects adelay all=1 and amix normalize=0.
Make each track stereo and delay both channels explicitly, skip amix for a
single track, and undo amix averaging with a gain for several tracks.

* fix(motion): keep template previews bakeable on slow CI renderers

CI's software-GL baker skipped the 3D island (preview readiness timeout):
under slow frames GSAP lag smoothing slowed the timeline, so the 8 s
poster frame never arrived within the baker's 15 s wait. Play every
template on the wall clock and move the island poster to 6 s. With
simulated 300 ms frames all five now report ready within 10 s.

* feat(motion): add the kinetic typography template

Template selection now follows the motion design spec's four content types
(brand and product films, kinetic typography, explainer animation, procedural
art). Kinetic typography takes the fifth slot, replacing the animated short.
The new template is a 15-second, 120 BPM type film with an original
soundtrack and 19-locale copy.

* fix(motion): let the project motion player fit template stages

Inside a project the motion source player wraps the composition, fits it and
adds its own controls. The templates also scaled their stage, so editable
sources from Remix and Community use rendered at a fraction of the preview.
Templates now keep their own fit and controls only outside that player.

* feat(motion): restage the 3D island as a midday lagoon

The island moves from golden hour to a bright tropical midday closer to its
anchor post: a shader lagoon coloured by a baked depth map with ripples,
shallow-water shimmer and shore foam, a wide white-sand beach, palms and
instanced bushes, a village with a pier, soft sun shadows and a lower
camera path. The CPU wave mesh is gone, so frames cost no more than before.
Copy is updated in all 19 locales.

* fix(motion): keep each soundtrack inside the composition root

A Community use regenerated the island source tail and dropped the root
<audio> element, so its MP4 had no sound. Templates now place the element
inside the composition root next to the stage.

* docs(motion): state the template selection rule as complete works

* fix(motion): enforce video clocks and bound content verification

* feat(motion): replace the motion templates with remakes of viral posts

The five Motion design templates are now frame-faithful remakes of viral
motion posts, so each preview shows the look users came for: the 15-second
motion designer showreel, a high-energy motion reel (copy set in English), a
one-shape UI motion piece, a pixel-art wizard and a 20-second electric-blue
tech reel. Each ships open-licensed fonts, an original soundtrack, the
creator's published prompt where one exists, and credits its source post.

The explainer, product launch, 3D island and kinetic type templates are
removed. Tests follow the new set; a backward-seek check now covers hidden
WebGL layers.

* feat(motion): feature The Future Is Already Here first and start preview sound with the film

* chore: ensure PR CI

* fix(motion): match The Future Is Already Here controls and reorder the motion templates

* fix(motion): make the one-shape UI preview controls legible

* fix(motion): hide The Future Is Already Here controls inside the project player

* fix(motion): plan suitable sound and synchronize source audio playback

* feat(motion): keep remake soundtracks by default and fade exported audio where it stops

* test(motion): cover audible source controls in Chromium

* fix(test): use explicit ESM imports in audio player regression

* fix(motion): align preview track discovery with video export

* feat(motion): autoplay looping HTML without a player bar

* fix(test): consume the built motion playback contract

* docs(motion): state explicit soundtrack rules for make-same and the music line in replies

* chore(motion): version the audible looping delivery profile

* docs(motion): let template sound rules lead the general policy and freeze the audio contract

* fix(motion): scope autoplay to declared video motion sources

* fix(motion): reject audible MP4s under an explicit silent contract

* docs(motion): drop the routine music note from make-same replies

* test(motion): align delivery regressions with looping source playback

* fix(motion): isolate preview and audio export from legacy HyperFrames

* test(motion): retain the existing desktop legacy audio limitation

* fix(motion): suspend audio when previews leave the active workspace

* feat(motion): replace two motion templates with a product promo and a visual lesson; sign the showreel as Open Design

* feat(motion): give the showreel the Open Design palette and a full-size stacked wordmark

* feat(motion): give the product promo a livelier mascot girl

* refactor(motion): rebuild The Future Is Already Here as readable, editable scene code

* fix(motion): pick gallery cover frames that survive the baker's capture delay

* feat(motion): sharpen the showreel opener, depth, truchet and end card

* feat(motion): make the product promo mascot talk, add a speaking score and the flow pop

* feat(motion): give the visual lesson the original's paper-collage texture

* fix(motion): bundle gallery covers for fresh branch checkouts

* feat(motion): feature and refine Bring Ideas to Life

Adapt the paper story around OpenDesign capabilities, with short rounded hands, clearer timing, continuous paper actions, and a larger audience invitation. Feature it first in Motion and synchronize localized names, metadata, cover, guidance, and regressions.

Keep product promo audio music-only with sparse percussion.

* fix(motion): stabilize artifact cards and capture declared covers

* fix(web): share creation type order with community

* test(motion): exercise audio controls through project UI lifecycle

* fix(motion): scope live cover behavior to actual video sources

* test(motion): use real HEAD responses in cover regressions

---------

Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
Co-authored-by: William <zhouhaoran@refly.ai>
Co-authored-by: open-design-crew[bot] <4197780+open-design-crew[bot]@users.noreply.github.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: Amy <1184569493@qq.com>
2026-10-14 15:55:52 +00:00

13 KiB

Prompt composition — the variant axes

Read this before changing any prompt text, wherever it lives.

The one thing to know first

A generation run is composed by ONE of two independent implementations, and apps/daemon/src/prompts/ is only one of them. The fork is an early return at the top of composeSystemPrompt:

// apps/daemon/src/prompts/system.ts:905
}: ComposeInput): string {
  if (odNextStrategyRecipe) {
    return composeOdNextStrategyRequestPromptV2(odNextStrategyRecipe, {...});
  }
  // ↓ everything below — the entire legacy stack — is skipped on the OD Next path

The API/BYOK mirror at packages/contracts/src/prompts/system.ts:318 forks the same way. The two sides share no composition floor: a rule added to one holds only for the runs that take that side.

Which runs take which path

OD Next is on by default and gated. evaluateOdNextRollout (apps/daemon/src/strategies/od-next/rollout.ts:151) is an AND over the policy and one run's facts, and some of its inputs vary run to run on one machine — which is why a divergence between the two sides surfaces as an intermittent bug rather than a reproducible one:

  • Scenario provenance (rollout.ts:134, odNextTaskTypeForProjectScenarioBinding) requires provenance === 'automatic_default'. A project where the user explicitly picked a scenario resolves taskType to null and takes the legacy path.
  • agentId must be one of codex / claude / opencode / amr, sourceKind must be bundled, and the runtime capability preflight must have passed.

Nothing outside one run's own inputs takes part. There used to be a stop latch — a row a failing run wrote that disabled OD Next for the whole daemon instance, outranked the saved mode, and survived restart while the Labs switch still read "on". It is gone, so a divergence you are chasing cannot be a leftover from an earlier run on the same machine.

User-facing switch: Settings → Labs → Design Harness (apps/web/src/components/LabsSection.tsx, reading GET /api/strategies/od-next/rollout). Process override: OD_NEXT_STRATEGY_ROLLOUT. Saved preference: app-config odNextStrategyMode.

The variant axes

Changing "the prompt" can mean up to four edits. Check each one:

Axis Switch A side B side
Strategy odNextStrategyRecipe (apps/daemon/src/prompts/system.ts:905) apps/daemon/src/prompts/ plugins/_official/scenarios/od-next-strategy/assets/** plus TypeScript in packages/contracts/src/prompts/od-next-strategy.ts
Legacy core OD_PROMPT_CORE (apps/daemon/src/server.ts:10049) — default is slim, classic is the opt-out apps/daemon/src/prompts/core-slim.ts official-system.ts + discovery.ts
Execution mode none; hand-maintained mirror apps/daemon/src/prompts/*.ts packages/contracts/src/prompts/*.ts (API/BYOK)
OD Next internals none; the same list is declared twice OD_NEXT_PROMPT_STAGE_CONTRACT_V2 (packages/contracts/src/prompts/od-next-strategy.ts:214) od.pipeline.stages in the plugin's open-design.json

On the third axis, five pairs are still hand-maintained and have already drifted, so diffing a pair is not a reliable way to find what a change is missing:

File daemon contracts
system.ts 2048 1141
discovery.ts 317 286
directions.ts 321 284
media-contract.ts 594 154
official-system.ts 202 163
core-slim.ts 429 absent

core-slim.ts having no contracts counterpart means the API/BYOK path cannot receive the slim charter at all — packages/contracts/src/prompts/system.ts has no promptCoreVariant input. Whether that is intended has not been established here; treat it as an observed asymmetry, not a known bug.

deck-framework.ts is the exception and the model: see below.

Host runtime contracts

These are not style preferences. Each is consumed by product code, and a generated artifact that omits one is not controllable by the host. They belong to the host, not to either strategy — if a strategy is retired, these stay.

Contract Host consumer Legacy OD Next
data-od-deck-protocol="1" on <html> apps/web/src/runtime/srcdoc.ts:3136 — how the host recognizes a v1-native deck ✅ ✅
od:deck-ready announce (protocolVersion, capabilities) srcdoc.ts ready listener — a message without protocolVersion === 1 is ignored ✅ ✅
od:slide-state {active, count} posts unified slide counter and toolbar state ✅ ✅
od:slide navigate listener host-driven paging (next/prev/first/last/go) ✅ ✅
id="deck-stage" srcdoc.ts:3145 isFrameworkDeck → stage style fix, disables click-nav ✅ ✅
@media print block Share → PDF multi-page stitching ✅ ✅
<question-form> AssistantMessage.tsx → QuestionFormView; runAskedUserQuestion analytics ✅ discovery.ts ✅ od-next-strategy.ts:434
Motion video export Actual source/MP4 linkage, local audio and encoded-output checks run inside the exporter; no model-plan gate ✅ selected motion skill ✅ motion task profile
Shared motion source player Composition-only preview controls and persisted playback; suppressed during frame capture ✅ motion source ✅ motion source
Motion data-poster-time Agent-selected seconds; daemon validates the linked MP4 and freezes its frame with the composition dimensions ✅ selected motion skill ✅ motion task profile
.od-frames/ device shells prototype device frames ❌ ✅ OD Next only

Two things to read off this table.

The deck rows are the model for how a host contract should be carried. One source — renderDeckFrameworkDirective / renderLegacyDeckCompatibilityDirective in packages/contracts/src/prompts/deck-framework.ts:573, whose skeleton pins data-od-deck-protocol="1" at line 48 — feeds all three prompt paths. The daemon file is now a ten-line re-export whose own docblock states the reason: "the canonical deck scaffold lives in contracts so classic, BYOK, and OD Next prompt paths cannot drift." OD Next reaches it through resolveOdNextDeckFrameworkMode (od-next-strategy.ts:187), which picks canonical for a blank deck and legacy_compatible when a seed or existing deck HTML is present, and the directive is emitted as an <od-next-context kind="instruction" name="deck-framework"> block (od-next-strategy.ts:548). The wiring that supplies those inputs lives at apps/daemon/src/server.ts:10125. Copy this shape for the next contract.

The divergence still runs both ways. OD Next is not a superset of legacy; it owns .od-frames/ device shells that legacy has no equivalent for. And OD Next content has two possible homes — the plugin's markdown assets and the TypeScript in od-next-strategy.ts. The deck contract lives in the TypeScript, not in ppt.md. Check both before concluding a contract is absent on that side.

Worked example: #7568, then #7651

These two PRs are the same author, three days apart, and together they are the clearest statement of what this document is for.

#7568 (fix(deck): make legacy thumbnail navigation instant, merged 2026-08-29, 973e868ce) introduced deck protocol v1 across 18 files, +510/-44. Its stated purpose, in its own PR body, was to give newly generated decks "one canonical, versioned navigation protocol so future agents do not create more navigation dialects."

It handled the daemon ↔ contracts axis completely: a shared constant so the two prompt copies could not drift on the protocol, both copies changed, a guard added on each side, and validation that ran the full web (7,139 tests) and contracts (500) suites plus two packaged DMG end-to-end runs. By every convention in this repository it was a thorough PR.

It touched nothing under plugins/_official/scenarios/ and nothing in od-next-strategy.ts. Every deck generated on the OD Next path still shipped no protocol: the PR whose goal was to stop new navigation dialects left one live.

Three things made the miss invisible. All three are worth checking against your own change:

  • The guards it added could not reach the other side. Both new tests called composeSystemPrompt({ skillMode: 'deck' }) with no odNextStrategyRecipe, so they never passed the fork. The daemon one was named 'ships new Agent decks with OD Deck Protocol v1' — a claim about Agent decks that was false for every OD Next run, asserted by a test that was green. A test name is not coverage; check what its inputs can actually reach.
  • The snapshot moved and stayed green. apps/daemon/tests/prompts/__snapshots__/system-prompt-matrix.test.ts.snap updated cleanly, because every scenario in that matrix takes the legacy branch. That is still true today.
  • Sharing a constant fixed the machine-readable half only. The directive prose around the shared protocol constant had already drifted between the two copies before #7568, and #7568 inserted the same new clause into both — into two sentences that already disagreed.

#7651 (fix(deck): pin deck protocol in OD Next prompts, merged 2026-08-31, f65d245de) repaid it: 16 files, +855/-622, including a new od-next-prompt-recipe.test.ts, a new plugins-strategy-recipe.test.ts, and new e2e/ui/real-daemon-run.test.ts coverage. The fix is the right shape — it collapsed the deck scaffold to a single source in contracts and reached OD Next through the recipe rather than copying the skeleton into markdown — and it is the reason the deck rows above now read ✅ on both sides.

The transferable rule is the cost, not the outcome: closing a one-sided change took a second PR nearly twice the size of the first. Before landing a change to generated-artifact behavior, name every path that composes a prompt, then say for each one whether your change reaches it. "The tests are green" answers a narrower question than that.

Editing rules

  • Changing a rule that affects generated artifacts? Apply it to every affected path, or state in the PR body why one is genuinely out of scope. "I'll do OD Next later" leaves the bug live for whichever runs take that path.
  • Adding a host contract? Give it one source that all paths consume, the way deck-framework.ts now does, and add its row to the table above in the same PR.
  • Prompt-facing vs. maintenance text. Everything under plugins/_official/scenarios/od-next-strategy/assets/ is sent to the model verbatim. Never put repository-maintenance notes there — put them in that folder's AGENTS.md, which is not part of the asset roster. In apps/daemon/src/prompts/ and packages/contracts/src/prompts/ the exported string is the prompt, so /** */ docblocks above an export are safe.
  • A green suite is not evidence that both sides carry your change. apps/daemon/tests/prompts/system-prompt-matrix.test.ts freezes which sections each scenario receives, but every one of its scenarios takes the legacy branch, so it cannot see an OD Next regression.

Editing the OD Next plugin assets

assets/core-system-prompt.md, assets/general-orchestration.md, and exactly one assets/task-profiles/<taskType>.md are decoded and concatenated verbatim into the prompt bundle (apps/daemon/src/strategies/od-next/initial-prompt-bundle-service.ts).

apps/daemon/src/plugins/strategy-package.ts:158 builds an explicit roster:

./open-design.json, ./SKILL.md,
assets.core.path, assets.orchestration.path,
<selected task profile>.path, assets.taskProfileMapping.path,
<selected profile>.resources[*].path

Only those files are read, and only those are hashed into the package identity.

  • A file the manifest does not declare (that folder's AGENTS.md, for instance) has zero runtime effect — not read, not hashed.
  • open-design.json and SKILL.md are in the roster. Editing either moves the package identity.
  • Only the selected profile's resources join the roster, so one task type's resources changing does not move another task type's hash.
  • Changing a task profile's body should come with a version bump on its entry in open-design.json.

Two shapes for adding content a profile needs. Prompt text goes in the profile's own .md. Non-prompt files (skeletons, shells, stylesheets) get a resources entry on that profile in open-design.json; prototype does this with assets/task-profiles/prototype/device-frames/*.html and layout.css, staged into the project as .od-frames/ and referenced by the profile, never concatenated into the prompt head. Prefer neither when the content is a host contract — give it one source in packages/contracts and have every path consume it, as the deck framework now does.

Known gaps

Verified on 6fb440a86 (2026-08-31).

  • Five daemon/contracts prompt pairs remain hand-maintained and already divergent (table above). deck-framework.ts shows what unifying one looks like; none of the others has been done.
  • core-slim.ts exists only on the daemon side, so the API/BYOK path cannot select the slim charter.
  • No machine check covers the OD Next column of the contract table. packages/contracts/tests/system-prompt.test.ts and apps/daemon/tests/prompts/system.test.ts keep the legacy copies honest, but neither passes an OD Next recipe. #7651 added packages/contracts/tests/od-next-prompt-recipe.test.ts, which does exercise the OD Next recipe for the deck contract specifically; nothing generalizes that to the other rows.