fused-studio pack, maintained by Fused. Your packs use the same versioned manifest, asset snapshots, contribution slots, and module lifecycle. The host owns authentication and recovery; packs own the visible interface.
Configure with a coding agent
Install the bundled coding-agent skills in the project where you maintain your packs:$harnest-studio for workspace, company-pack, and builder configuration. Use $harnest-studio-ui for visual changes, including a Tailwind or Panda CSS section replacement. The skills include manifest guidance, build boundaries, validation, and recovery checks. They configure Studio; they are separate from runtime Agent Skills or a pack’s builder guidance.
Choose your customization
UI contributions belong to Studio. They are not installed into your agent project and do not become agent tools or builder instructions. A pack may contain both UI contributions and the project resources used to build agents.
Choose a CSS framework
Studio consumes built HTML, CSS, and JavaScript. Each pack owns its build tools, so you can use Tailwind CSS, Panda CSS, or plain CSS independently of the default pack. Changing frameworks means rebuilding the relevant pack assets; there is no Studio-wide framework setting.
Follow Replace one section with Tailwind for a buildable example, exact configuration, and the file-by-file change record. Use the same compiled-asset boundary with another framework.
Load a theme and layout
Add aui section to an ordinary Studio Pack:
studio-pack.yaml
bg, panel, soft, hover, line, muted, text, accent, accent-ink, danger, font-ui, font-size, font-code, editor-bg, editor-gutter, and editor-size. Use literal values, such as hex colors, 14px, or a font stack. CSS functions, resource URLs, and stylesheet syntax are rejected. Names beginning with studio- are reserved for the host.
A layout lists sidebar, workspace, and inspector exactly once in order. It may hide sidebar or inspector. The default shell applies column order on desktop. At widths of 960 pixels or less, the assistant and inspector collapse instead of moving below the workspace. Click Build / Inspect to open the side panel; close it with Close, Escape, or the backdrop. Drafts and the selected tab survive closing and resizing. Hiding controls does not change backend permissions.
The default shell keeps guided actions under Steps. Click Focus to hide the workspace heading and tool row while retaining editor tabs and the side-panel control. Exit focus restores those controls without remounting the work area or losing edits.
Contribute an isolated view
Declare each asset explicitly under the pack’sassets/ directory. HTML panels use inline CSS and JavaScript; external resource loads are blocked by the panel’s content security policy.
assets/welcome.html
state.connection supplies { ready }; state.project supplies { id, name, framework }. Frames do not receive credentials, project source, filesystem paths, or a command bridge.
Contribution IDs are qualified as
pack-name/local-id. Use replaces to replace an existing contribution in the same slot. Two replacements for the same contribution, replacement cycles, and missing targets are rejected. Pack load order never chooses a winner. The resolved interface must have one shell and one welcome contribution.
The bundled pack exposes fused-studio/shell, fused-studio/welcome, fused-studio/appearance, and fused-studio/deployment. You can retain the shell and replace a welcome view or deployment workflow, or replace the shell with your own implementation.
Write a trusted module
Modules exportactivate(studio, host) and may return a cleanup function. The entrypoint and every imported module or HTML fragment must be listed in ui.assets. Stylesheets listed in ui.styles also require trust.
studio-pack.yaml
assets/summary.css must exist before validation or launch. For a replacement welcome module, change slot to welcome and add replaces: fused-studio/welcome. For a replacement shell, use slot: shell and replaces: fused-studio/shell. The referenced JavaScript must implement that view; changing a slot does not generate a shell.
assets/summary.js
Command registration, slot registration, navigation guards, and state subscriptions return cleanup functions. Module cleanup runs when mounting fails. The default shell publishes
connection and project state and registers studio.project.create, studio.project.refresh, and studio.file.open. Those commands are supplied by the default shell, not guaranteed by an arbitrary replacement shell.
The shell also publishes jobs and supplies studio.editor.isDirty, studio.jobs.run(body), studio.notice(message), and studio.proposal.review(projectId, proposal). The deployment service owns studio.deployment.open. See the deployment UI contract for arguments, completion handling, and server checks.
A replacement shell declares its regions using data-studio-region="sidebar", workspace, and inspector, and supplies data-studio-slot containers for its selected views. It can register slot adapters to build navigation for contributed views. Service modules receive null as their mount container.
TypeScript and IDE support
Export the UI types from the Harnest installation that will run your pack:import type adds no browser code and
works with React, other frameworks, and ordinary TypeScript:
src/summary.ts
ViewActivate for visual slots, ServiceActivate for a service with a null
container, or Activate when the implementation handles both. StudioContext
is available for helper and component props. Build the entrypoint to JavaScript
and declare its output in the manifest as usual. JavaScript authors can use
/** @type {import('@harnest/studio-ui').ViewActivate} */ above an exported
const activate with checkJs enabled.
Commands, state, slot names, appearance, navigation, and request arguments have
shared types. Add your company’s command and state contracts through declaration
merging; this preserves argument checking for the built-in names:
acme.deploy at runtime. Types do not install
commands or grant trust. The built-in command signatures describe the default
shell and services; a replacement shell must supply those commands if its packs
use them. request<T>(path, method, body) defaults its response to unknown;
specifying T describes your expected response without validating it at runtime.
JSON requests use paths relative to /api/, while transport takes /api/....