Skip to content

proposal(artifacts): managed dynamic React runtime - #3230

Draft
reachjalil wants to merge 27 commits into
different-ai:devfrom
reachjalil:feature/dynamic-artifact-runtime
Draft

proposal(artifacts): managed dynamic React runtime#3230
reachjalil wants to merge 27 commits into
different-ai:devfrom
reachjalil:feature/dynamic-artifact-runtime

Conversation

@reachjalil

@reachjalil reachjalil commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Important

Proposal only. This draft is a concrete implementation sketch for design discussion and validation. It is not a request to merge, is not marked ready for review, and should not be treated as committed product architecture.

Summary

Depends on #3054.

This proposal explores turning chat-native UI artifacts into editable, reusable React projects with a managed end-to-end lifecycle:

  • adds a versioned five-file project and attachment contract with adapter-based repository, compiler, validator, settings, state, intent, and renderer ports;
  • injects a bounded React Artifact Builder skill only when a human enables it for the workspace, and removes artifact authoring affordances when disabled;
  • lets people create, list, edit, rebuild, enable, disable, reopen, and reuse generated projects from the UI Artifacts library;
  • compiles JSX into immutable revision-pinned builds and preserves existing chat attachments and state across later edits, source loss, disabled authoring, and failed rebuilds;
  • renders generated components in opaque-origin script-only iframes with CSP, verified digests, bounded and sequenced MessageChannel envelopes, Suspense, and host/frame error boundaries;
  • exposes persisted local state and declared prompt-only intents without network, filesystem, provider credentials, host DOM, or arbitrary tool access;
  • keeps generated React projects separate from the existing validated standard artifact catalog;
  • keeps the master artifact preference usable in signed-out workspaces through local persistence while retaining active-organization synchronization when signed in;
  • documents the project contract, lifecycle, adapter boundaries, and alpha threat model;
  • constrains generated output to host-owned metric, summary, or collection chat shapes with explicit density and action budgets.

Storage, execution, and scheduling model

This proposal deliberately separates mutable project source, immutable compiled builds, per-instance interaction state, human-owned settings, and chat history. A chat attachment is a small pinned reference; it is not another copy of the source tree or bundle.

Workspace-local filesystem layout

All project-owned material lives under the root of the workspace served by the owning OpenWork server:

<workspace-root>/.opencode/openwork/
├── artifacts/
│   └── <artifact-slug>/
│       ├── artifact.json
│       ├── src/App.tsx
│       ├── styles.css
│       ├── data.json
│       └── data.schema.json
├── artifact-builds/
│   └── <artifact-slug>/<project-revision-sha256>/
│       ├── bundle.js
│       └── build.json
├── artifact-state/
│   └── <artifact-slug>/<instance-id>.json
└── artifact-settings.json
Stored object Responsibility and lifecycle
artifacts/<slug>/ Mutable, human-readable project source. The fixed five-file contract is the only source shape accepted by the repository. Coordinated edits can be committed atomically with an expected revision.
artifact-builds/<slug>/<revision>/ Immutable, content-addressed delivery objects. bundle.js contains the compiled module; build.json contains the pinned manifest, styles, data, schema, compiler receipt, and digests. A different result may never overwrite the same project revision.
artifact-state/<slug>/<instance-id>.json Mutable interaction state for one published card instance. It remains pinned to one project revision and carries its own state revision for conflict detection.
artifact-settings.json Workspace-scoped builderSkillEnabled and per-project enable/disable overrides. Disabling authoring changes capability, not stored source, builds, or state.
Chat attachment A small reference containing workspaceId, slug, projectRevision, buildDigest, instanceId, buildPath, stateRevision, and presentation metadata. The conversation therefore points to an exact immutable build instead of following latest.

The routes resolve the workspace path first and instantiate the filesystem-backed service against that root. A workspace represented as remote is rejected by this server: dynamic artifacts must be managed by the OpenWork server that owns that workspace locally. Project paths are constrained to lowercase kebab-case slugs and the five declared files; traversal, unexpected files, symlinks present during access, null bytes, and oversized inputs are rejected.

The master UI Artifacts preference is separate from this tree. When signed out it is cached in the app preference record in local browser storage; when signed in it synchronizes through Den. The builder and individual project controls remain workspace-local in artifact-settings.json.

From editable files to a pinned chat card

list/reuse or create five files
        ↓
projectRevision = SHA-256(stable five-file snapshot)
        ↓
explicit build or publish request
        ↓
builder enabled? + project enabled? + expected revision matches?
        ↓
validate manifest + data.schema.json + data.json + source policy
        ↓
compile src/App.tsx → ES module bundle
        ↓
buildDigest = SHA-256(bundle + manifest + styles + data + schema)
        ↓
atomically store immutable bundle.js + build.json
        ↓
initialize revision-pinned instance state
        ↓
return a small attachment for the chat transcript

POST /workspace/:id/ui-artifacts/:slug/build performs the gated build. POST /workspace/:id/ui-artifacts/:slug/publish calls the same build path, initializes or reuses an instance only when it is pinned to the same project revision, and returns the attachment. If the exact revision already has a valid build, the service returns it instead of compiling again.

A validation or compiler failure returns structured diagnostics and does not replace a previous successful build. The source remains editable and the last known-good pinned attachment remains valid. Changing any of the five files produces a different project revision; publishing that revision creates a new immutable build and normally a new instance. Existing conversation cards stay on their original revision and state.

How generated code executes

Generated source is never executed directly from the editable project directory and it never executes inside the OpenWork server or agent process. Execution happens client-side only when the host mounts a pinned chat card or editor preview:

  1. The host resolves the attachment's buildPath, fetches the exact pinned build and instance state, and verifies the workspace, slug, project revision, build digest, instance ID, and host-owned presentation shape.
  2. The host creates an opaque-origin iframe with sandbox="allow-scripts", referrerpolicy="no-referrer", an empty permissions policy, and CSP that denies network connections. The generated component receives no host DOM, cookies, credentials, filesystem, provider, arbitrary tool, or direct network capability.
  3. The frame receives one schema-validated initialization envelope over a dedicated MessageChannel. The compiled bundle becomes an in-memory Blob URL and is dynamically imported with React.lazy; React itself is injected by the host.
  4. The component is rendered with only { data, state, runtime }. The narrow runtime exposes replaceState(next) and invoke(intentId, payload).
  5. Bridge traffic is schema checked, byte bounded, rate limited, nonce bound, instance bound, and monotonically sequenced. Navigation and form submission are blocked. Frame errors are sanitized and contained by host and frame error boundaries.
  6. replaceState sends an optimistic-concurrency update using the current stateRevision; the server serializes writes for that instance and rejects stale updates.
  7. invoke can address only an intent declared by the pinned manifest. The server validates its payload and state revision, then returns a prompt for the host to stage visibly in the composer. The artifact never sends the prompt, runs a tool, or performs an external effect automatically; the user reviews and sends it.
  8. When the frame closes, its channel, React root, pending intent timers, and Blob URL are disposed.

Triggering, ordering, and “scheduling”

There is no cron scheduler, durable job queue, background refresh loop, or autonomous artifact process in this PR. Artifact work is event-driven:

  • enabling the managed builder makes the authoring instructions and affordances available to the agent;
  • a user or agent edit creates a new content revision;
  • an explicit UI, API, or MCP build/publish call validates and compiles that revision;
  • mounting a pinned card or preview starts the sandboxed client runtime;
  • user interaction triggers a state update or declared-intent request; and
  • revisiting a conversation mounts the same pinned build and reloads its persisted instance state.

Ordering is enforced close to the data being changed rather than by a global worker:

  • project repository operations are serialized by artifact slug so competing reads, atomic edits, and immutable build commits cannot corrupt one project;
  • state initialization and updates are serialized by artifact slug plus instance ID;
  • settings updates use one serialized workspace queue; and
  • expected project, settings, and state revisions provide optimistic concurrency across callers.

Compilation itself is request-driven and content-addressed. Concurrent callers may ask for the same revision, but the immutable revision directory and digest check converge them on the same stored result. A new source revision does not schedule a silent rebuild, republish old messages, or rerun an artifact. Time-based refresh or scheduled execution would require a separate authorized scheduler that explicitly calls these APIs; it is outside this proposal.

Compact chat-shape contract (July 29 follow-up)

Generated artifacts are cards, not web pages. The manifest no longer accepts arbitrary width, height, or resizable flags. It declares an inline-capable placement and exactly one host-owned shape:

Shape Fixed frame Content budget Intended use
metric 160px high, up to 480px wide One primary result, up to four short values, one action Status, KPI, balance, progress
summary 240px high, up to 640px wide Up to four compact blocks and two actions Briefs, recommendations, comparisons
collection 360px high, up to 720px wide Up to five visible rows and two actions Agendas, inboxes, task lists

The host, rather than generated CSS, owns those dimensions. The renderer removes the artifact.resize bridge, bounds the iframe and document roots, exposes data-openwork-artifact-shape for density-aware styling, and verifies that the pinned build and chat attachment agree on shape. The schema also caps declared intent actions by shape.

The injected Artifact Builder skill now tells an agent to:

  • choose the smallest shape that communicates the answer before writing JSX;
  • treat the component as one bounded chat box rather than a landing page, dashboard page, or scrolling mini-app;
  • rely on host-owned title, description, revision, refresh, and editor chrome instead of duplicating it;
  • avoid navigation, sidebars, hero sections, footers, viewport units, nested scrolling, and content-driven minimum heights;
  • truncate single-line values, line-clamp prose, keep controls compact, and preserve narrow-width behavior; and
  • slice collections to the visible-row budget and render + N more instead of growing the card.

The human-created starter and Launch Radar example now use the same bounded roots, compact controls, row limits, and remainder pattern as the validated standard artifacts.

Note

The linked 23-frame Fraimz was produced from the July 28 baseline before this compact-shape follow-up. It is retained as baseline lifecycle and isolation evidence and may show the earlier wider/resizable presentation. It has not been regenerated for commit 0c6040191.

Complete experience walkthrough

Open the narrated 23-frame Fraimz on Vercel. Every image below is also stored independently in Vercel Blob under the PR-specific evidence path.

1. Managed workspace authoring

The Generated catalog lists reusable projects and makes the injected Artifact Builder skill a visible, human-controlled workspace capability. The skill is available to agents only while the switch is enabled.

Frame 1 — managed Artifact Builder skill

2. Live artifact attached in chat

A deterministic code-mode project is compiled, published as an immutable revision, and attached directly to the conversation as a bounded React artifact. The host provides compact refresh and editor affordances without letting generated content expand beyond its declared chat shape.

Frame 2 — live artifact in chat

3. Isolated renderer

The component renders through an opaque-origin iframe with sandbox="allow-scripts", referrerpolicy="no-referrer", an empty feature policy, and CSP connect-src 'none'. It receives no host DOM, credential, network, filesystem, provider, or arbitrary tool authority.

Frame 3 — isolated renderer

4. Bounded persistent interaction

A user can interact within the bounded card. Watch launch crosses the narrow state adapter, produces a new persisted state revision, and stays scoped to this artifact instance without turning the artifact into a scrolling application.

Frame 4 — persisted local state

5. Reusable five-file project editor

Open editor reveals the reusable workspace project and its complete file system: src/App.tsx, styles.css, data.json, data.schema.json, and artifact.json, together with Library/Editor navigation and the pinned revision.

Frame 5 — five-file editor

6. Source, contract, revision, and preview together

The editor keeps code and data contracts beside a live isolated preview. The visible revision identifies the exact immutable build attached to the conversation.

Frame 6 — source and data contract

7. Reload, reopen, and reuse

Reloading preserves the original project revision and watched-launch state. The project reopens from the Artifacts library instead of being trapped in one message or one process lifetime.

Frame 7 — persisted and reopened

8. Per-project lifecycle control

A project can be disabled independently. New builds are blocked while its source, editor, immutable revision, and last known-good preview remain available.

Frame 8 — disabled but editable

9. Human-created artifacts use the same model

Create artifact is not agent-only. It asks for a human-readable name and purpose, previews the durable workspace slug, and starts from the same safe React project contract.

Frame 9 — create dialog

10. Workspace and project controls are independent

The injected skill switch and per-project build switch are separate, visible controls. Restoring one does not silently rewrite the other or mutate existing conversation attachments.

Frame 10 — managed controls

11. Prompt-only agent bridge

Ask agent about launch risk invokes only a manifest-declared intent. The host stages a prompt in the composer with artifact, instance, effects, confirmation policy, payload, and state revision. It does not send the prompt or execute tools or external effects automatically.

Frame 11 — declared intent staged in composer

12. Versioned manifest contract

artifact.json declares protocol and API versions, React entrypoint, style/data/schema paths, an inline-capable host-owned shape, intents, typed arguments, effect policy, and confirmation policy. Arbitrary dimensions and self-resizing are rejected.

Frame 12 — artifact manifest

13. Typed JSX and narrow runtime adapter

src/App.tsx is ordinary typed JSX. Data and state arrive as props; the injected runtime exposes only bounded state replacement and declared intent invocation. The component has no direct fetch, host, filesystem, or tool surface.

Frame 13 — React source

14. Presentation remains independently expressive

styles.css separates compact layout, color, responsive presentation, and interaction states from the component, manifest, and data contract. The artifact remains expressive and forever enhanceable inside its declared chat-card budget.

Frame 14 — independent styles

15. Portable data file

data.json makes rendered values inspectable and independently editable. Enhancements can update data without reconstructing the component or weakening the manifest boundary.

Frame 15 — portable data

16. Build-time JSON Schema boundary

data.schema.json defines required fields, types, bounds, arrays, and additional-property rules. Data must satisfy this contract before a component becomes a pinned build.

Frame 16 — JSON Schema

17. Unsafe generated code is rejected

An attempted unbounded loop is saved as an editable project revision but cannot be published. The compiler returns a structured line-and-column diagnostic and preserves the last known-good isolated preview.

Frame 17 — compiler diagnostic

18. Safe enhancement creates a new immutable revision

Restoring the safe component and changing Apollo readiness from 94 to 97 publishes a distinct immutable revision. The editor retains a visible compiler receipt with build digest, byte size, and zero diagnostics.

Frame 18 — enhanced revision and build receipt

19. Existing chat history stays pinned

The original chat card remains on its original revision and state even after the reusable project evolves. Project enhancement never silently rewrites the history of a conversation.

Frame 19 — chat stays pinned

20. Disable the enhanced project without data loss

Disabling the enhanced project blocks new publication while retaining the enhanced source, exact revision, editor, and last known-good preview.

Frame 20 — enhanced project disabled safely

21. Disable injected authoring without deleting work

Disabling the workspace Artifact Builder removes injected instructions from future agent context. Reusable projects remain listed, and already-pinned artifacts continue to render and interact.

Frame 21 — builder disabled, project retained

22. Generated and standard artifacts coexist

Generated React projects and validated standard answer cards remain separate catalogs. Workspace brief, calendars, widgets, communication, inbox, attention, and approval artifacts retain their existing independent lifecycle controls.

Frame 22 — standard artifact coexistence

23. Local-first master artifact preference

The master UI artifacts control remains usable without a cloud account. Signed-out workspaces persist the enabled state on this device and restore it after reload; signed-in workspaces synchronize the same setting to the active organization.

Frame 23 — local preference persists without cloud

Integration and trust boundaries

  • OpenWork-owned contracts: project, attachment, build, state, intent, settings, and normalized errors are versioned browser-safe schemas.
  • Replaceable adapters: filesystem repository, TypeScript compiler, JSON Schema validator, settings store, state store, renderer host, and OpenCode-facing authoring integration sit behind narrow ports.
  • Immutable delivery: chat cards carry exact project/build digests and never follow mutable project heads.
  • Capability and presentation isolation: the iframe receives only initialization data, bounded state updates, and manifest-declared prompt staging over a sequenced MessageChannel; the host owns the selected shape and never accepts component-driven resizing.
  • Human governance: master rendering, workspace skill enablement, and project enablement are separate settings; the master preference syncs to the active organization when signed in and persists locally otherwise, while disabling authoring never deletes source or invalidates pinned builds.
  • Code-mode workflow: the injected skill teaches an agent or delegated subagent to select the smallest chat shape, design against its density budget, create the five files, validate contracts, build, inspect diagnostics, publish, attach, and preserve provenance.

Verification

July 29 compact-shape follow-up

  • No tests, typechecks, builds, app launches, coded evals, or Fraimz regeneration were run for commit 0c6040191 (level 0).
  • The complete staged diff was reviewed and git diff --check passed.

July 28 baseline evidence

The earlier evidence pass:

  • dynamic-artifact-runtime desktop Fraimz passed all 23 frames plus voice-over coverage;
  • the flow exercised create/list/open/edit/rebuild, workspace and project switches, opaque-iframe interaction, state persistence, prompt-only intents, all five files, reload/reuse, rejected unsafe source, successful enhancement, immutable chat pinning, generated/standard coexistence, and signed-out master-preference persistence across reload;
  • Vercel production gallery and representative Vercel Blob image returned HTTP 200 with the expected content types;
  • flow syntax, exact 23-paragraph voice-over cardinality, and git diff --check passed.

Baseline implementation checks run before the compact-shape follow-up:

  • 493 app tests passed across 93 files;
  • 48 artifact runtime and OpenCode adapter tests passed;
  • 10 eval-runner tests passed;
  • app and server TypeScript checks passed;
  • app, shared types, and server production builds passed.

The app build retains the existing large-chunk warning.

Alpha security boundary

The generated component boundary is capability-isolated, not a CPU or memory quota. Obvious unbounded loops and unsafe globals are rejected, but pathological code can still consume renderer resources, so this alpha is not for arbitrary untrusted third-party JSX.

Filesystem authoring follows the trusted local-workspace model. Traversal and symlinks present during access are rejected, but Node path APIs cannot eliminate a same-user symlink-swap race without portable descriptor-relative filesystem operations. A concurrently hostile process with the same OS account is therefore outside this alpha boundary; generated components never receive filesystem access.

Proposal questions before adoption

  • Are metric, summary, and collection the right durable shape vocabulary and size budgets for chat?
  • Should artifact projects remain workspace-local only, or gain an explicit export/import and sharing contract?
  • Which compiler/runtime quotas are required before accepting code from parties outside the trusted workspace boundary?
  • Should intent confirmation remain manifest-declared with host enforcement, or should organization policy be able to override it centrally?
  • What migration and compatibility guarantees should apply to future project schema, bridge protocol, and renderer API versions?
  • Which parts belong upstream in OpenWork versus a replaceable agent-engine adapter?

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-landing Ready Ready Preview, v0 Jul 30, 2026 2:57pm

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@reachjalil is attempting to deploy a commit to the Different AI Team on Vercel.

A member of the Team first needs to authorize it.

@reachjalil reachjalil changed the title feat(artifacts): add managed dynamic React runtime proposal(artifacts): managed dynamic React runtime Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant