@reopt-ai/opt-shell
v1.1.0
Published
Internal-first product harness layer for app shell, page rhythm, and shared state UX.
Maintainers
Readme
@reopt-ai/opt-shell
The runtime product-frame layer for apps built on opt-ui.
Solves problems that theme tokens alone cannot:
app shell structure, workspace recipes, page rhythm, empty/error/loading consistency,
and shared wrappers for opt-datagrid and opt-editor.
opt-shell is the product frame, not a replacement UI kit. opt-ui owns
primitives and visual components; opt-shell owns the frame around them:
manifests, policy + theme resolution, workspace recipes, state fallbacks,
adapter chrome, and inspect snapshots. Authoring-time helpers (completeness
scoring, coverage, rollout audits, scaffold codegen) live behind the
./audit sub-export — tooling, not the production runtime.
Module map
| Area | Source | Public entry |
| --------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------- |
| Runtime components | src/components, src/provider, src/adapters, src/hooks | @reopt-ai/opt-shell |
| Server-safe factory and contracts | src/create-harness-app.ts, src/defaults.ts, src/recipes.ts, src/tokens | @reopt-ai/opt-shell/core |
| Managed draft definitions | src/managed-harness.ts | @reopt-ai/opt-shell, @reopt-ai/opt-shell/core |
| Authoring & audit toolkit | src/audit (completeness, coverage, patterns, PR/SARIF, rollout, scaffold) | @reopt-ai/opt-shell/audit |
| Component doc metadata | src/meta.ts, src/components/_meta-*.ts | @reopt-ai/opt-shell/meta |
Agent skill setup (recommended)
From the consumer project root:
npx skills add reopt-ai/reopt-skills/opt-shell-installThen ask your agent: Use the opt-shell-install skill to set up
@reopt-ai/opt-shell in this project and verify the result. The skill detects
legacy @reopt-ai/opt-harness usage, distinguishes a new install from an
upgrade, idempotently updates the reopt marker block in AGENTS.md (or
CLAUDE.md), reads shell-llms.txt and this README, installs the required
opt-palette peer, and verifies recipe, policy, and adapter wiring. The source
of truth is
reopt-ai/reopt-skills.
Manual fallback:
bun add @reopt-ai/opt-shell @reopt-ai/opt-palette @reopt-ai/opt-uiGetting Started
1. Define your harness manifest
// lib/harness.ts
import { createShellApp } from "@reopt-ai/opt-shell/core";
export const appHarness = createShellApp({
id: "my-internal-tool",
label: "My Tool",
defaults: {
density: "comfortable", // "comfortable" | "compact"
contentWidth: "wide", // "narrow" | "normal" | "wide" | "full"
navigationMode: "sidebar", // "sidebar" | "stacked"
},
});2. Wrap your app with ShellProvider
// app/layout.tsx
import { ShellProvider } from "@reopt-ai/opt-shell";
import { appHarness } from "@/lib/harness";
export default function RootLayout({ children }) {
return <ShellProvider manifest={appHarness}>{children}</ShellProvider>;
}3. Build your app shell
import {
ShellAppShell,
ShellCollapsibleNav,
ShellNavGroup,
ShellNavItem,
} from "@reopt-ai/opt-shell";
export function AppFrame({ children }) {
return (
<ShellAppShell
nav={
<ShellCollapsibleNav width={260}>
<ShellNavGroup label="Workspaces">
<ShellNavItem href="/users" active>
Users
</ShellNavItem>
<ShellNavItem href="/settings">Settings</ShellNavItem>
</ShellNavGroup>
</ShellCollapsibleNav>
}
mobileHeader={<MobileMenuButton />}
>
{children}
</ShellAppShell>
);
}mobileHeader can use useShellNav().open() to toggle the nav — the context is
shared with the shell via an internal bridge.
4. Choose a workspace recipe and build your page
import {
ListWorkspace,
ShellSection,
ShellDataGridAdapter,
} from "@reopt-ai/opt-shell";
export function UsersPage() {
return (
<ListWorkspace
header={<PageHeader title="Users" />}
toolbar={<FilterBar />}
>
<ShellSection title="Active users">
<ShellDataGridAdapter
rows={users}
columns={columns}
loading={isLoading}
empty={{
title: "No users",
description: "Create one to get started",
}}
/>
</ShellSection>
</ListWorkspace>
);
}Which component do I need?
App frame
└─ ShellAppShell (nav + ShellCollapsibleNav)
Page content — pick ONE workspace per page:
├─ Data list / table → ListWorkspace + ShellDataGridAdapter
├─ Single resource → DetailWorkspace
├─ Content editing → EditorWorkspace + ShellEditorAdapter
├─ Overview / triage → DashboardWorkspace
├─ Landing / marketing → LandingWorkspace (or ShellLandingPage)
└─ Full-screen tool → ShellFullscreenToolSurface
Within a workspace:
├─ Content grouping → ShellSection
├─ Loading/empty/error → ShellStateBoundary
├─ Density-aware gaps → ShellSpacing
├─ Bottom action bar → ShellBottomBar
└─ Temporary side panel → ShellFlyoutAsideRecipe selection guide
| Signal | Recipe | Why | | --------------------------------------------- | ------------- | ----------------------------------------- | | Rows of records, DataGrid, filter/sort | list | Collection browsing is the primary action | | Single resource, summary cards, metadata | detail | Inspecting one entity at a time | | Rich content editing, save/draft state | editor | Authoring workflows need state management | | Metrics, charts, activity feed, quick actions | dashboard | Overview and triage | | Public-facing, hero sections, promotional | landing | Engagement and conversion |
Use selectRecipe() from @reopt-ai/opt-shell/core for programmatic selection:
import { selectRecipe } from "@reopt-ai/opt-shell/core";
selectRecipe({ hasDataGrid: true }); // → "list"
selectRecipe({ hasEditor: true }); // → "editor"
selectRecipe({ isPublicFacing: true }); // → "landing"
selectRecipe({ primaryAction: "inspect" }); // → "detail"Runtime ownership
ShellProvider resolves policy from three layers:
runtime override → manifest defaults → framework defaultsThe resolved policy controls:
| Policy field | Effect | Default |
| ---------------- | ----------------------------------------------- | ---------------- |
| density | page and section spacing rhythm | comfortable |
| contentWidth | narrow / normal / wide / full max width | wide |
| navigationMode | app-level sidebar or stacked nav framing | sidebar |
| motionPolicy | harness-managed motion allowance | reduced |
| stateLabels | shared loading, empty, and error fallback copy | English baseline |
| panelBehavior | sticky aside / sticky toolbar defaults | sticky aside |
| adapters | DataGrid and Editor adapter chrome | card |
| theme | inherited, generated, or promoted opt-ui theme | preset default |
The provider also emits inspect snapshots. ShellStateBoundary,
ShellDataGridAdapter, ShellEditorAdapter, nav, aside, bottom bar, header
stack, and AI runtime state report into that snapshot so @reopt-ai/opt-devtool
can show policy, adapter, layout, diff, and staleness state.
Hooks
| Hook | Where | Purpose |
| ----------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| useShell() | Inside ShellProvider | Manifest + resolved policy. Emits a dev warning if no Provider ancestor. |
| useShellOptional() | Anywhere | Same return shape as useShell() but silent fallback (no warning) when no Provider. Used by adapters / chrome that may render standalone. |
| useShellNav() | Inside ShellAppShell | Nav mode, collapsed, open/close/toggle |
| useShellAI() | Inside ShellProvider | AI runtime configuration injected via <ShellProvider ai={…}> |
| useShellEditorAI() | Inside ShellProvider | opt-editor AI bridge — auto-wires the AI transport into <Editor> props |
| useShellDevtoolController() | Inside ShellProvider | Reports adapter/layout state to the @reopt-ai/opt-devtool inspect panel |
0.2.0 note:
useShellLayout,useShellBreakpoint,useShellPageContext,useResizable,useShellCssVar,useShellDensityare removed. They had zero documented consumers; if you need any of them, please open an issue describing the use case.
z-index scale
All overlay/sticky elements use a documented scale (tokens/z-index.ts):
| z-index | Constant | Element |
| ------- | --------------- | ------------------------------------------ |
| 10 | stackedHeader | Sticky page headers (ShellStackedHeader) |
| 20 | bottomBar | Fixed/sticky bottom bar (ShellBottomBar) |
| 40 | backdrop | Overlay backdrops (nav + flyout) |
| 50 | navOverlay | Collapsible nav overlay panel |
| 51 | flyoutOverlay | Flyout aside overlay (above nav) |
Theme configuration
Two modes via ShellThemeConfig (discriminated union):
Preset mode — uses a static opt-ui theme:
{ mode: "preset", preset: "default" }Generated mode — produces tokens via the opt-palette engine:
{
mode: "generated",
palette: {
seed: "#0ea5e9",
harmony: "analogous",
radiusScale: "medium",
shadowIntensity: "subtle",
neutralTemperature: "cool",
},
}Theme scope controls where the resolved theme lands:
| Scope | Behavior |
| ---------------- | ---------------------------------------------------------------------- |
| inherit | no harness CSS is emitted; descendants inherit the global opt-ui theme |
| override | generated tokens are scoped under <div data-harness="..."> |
| promote-global | the harness pushes its theme into the global OptThemeProvider stack |
Prerequisites
Components rely on CSS custom properties from @reopt-ai/opt-ui themes:
| Variable | Purpose |
| ---------------------- | -------------------- |
| --opt-bg | Page background |
| --opt-surface | Card / panel surface |
| --opt-border | Border color |
| --opt-text | Primary text |
| --opt-text-secondary | Secondary text |
| --opt-text-tertiary | Muted text |
| --opt-radius | Border radius token |
Peer dependencies
| Package | Required | Notes |
| ------------------------ | ------------------ | -------------------------- |
| react / react-dom | Yes | ^19.0.0 |
| @reopt-ai/opt-palette | Yes | OKLCH color engine |
| @reopt-ai/opt-datagrid | With adapters only | For ShellDataGridAdapter |
| @reopt-ai/opt-editor | With adapters only | For ShellEditorAdapter |
Entry points
| Import path | Use case |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @reopt-ai/opt-shell | Client-side React components, hooks, adapters, managed shell helpers, runtime types |
| @reopt-ai/opt-shell/core | Server-safe factory, policy resolution, recipe metadata, selectRecipe, DTCG token import/export, contract types |
| @reopt-ai/opt-shell/audit | Authoring & audit toolkit: completeness scoring, coverage, pattern search, PR review/SARIF, rollout contract registry. Not part of the production runtime. |
| @reopt-ai/opt-shell/meta | Component doc metadata for the docs registry |
Slot contract
Workspaces are the primary contract:
DashboardWorkspace,ListWorkspace,DetailWorkspace,EditorWorkspace, andLandingWorkspaceare the primary wrappers aroundShellPage.- Workspace wrappers require
headerpluschildrenorcontentat the TypeScript level. The canonicallandingrecipe itself only requirescontentinsrc/recipes.ts, but the currentLandingWorkspacewrapper keeps a header prop for API consistency. filtersbelongs to thelistrecipe. Other recipes should avoid filter chrome even if the current base prop type does not fully prevent it.LandingWorkspaceomitsfiltersandaside; useShellLandingPagewhen you need hero / CTA / stacked public-page chrome instead of workspace slots.- Recipe defaults (width, gap, sticky aside, slot contract) live in
src/recipes.ts
| Recipe | Wrapper | Required recipe slots | Optional recipe slots | Defaults |
| ----------- | -------------------- | --------------------- | --------------------------------------- | ------------------------------------------------ |
| list | ListWorkspace | header, content | toolbar, filters, aside, footer | wide, sticky aside, drawer mobile aside |
| detail | DetailWorkspace | header, content | toolbar, aside, footer | normal, sticky aside, stacked mobile aside |
| editor | EditorWorkspace | header, content | toolbar, aside, footer | wide, non-sticky aside, collapsed mobile aside |
| dashboard | DashboardWorkspace | header, content | toolbar, aside, footer | full, non-sticky aside |
| landing | LandingWorkspace | content | header, footer | full, no aside, no filters |
Fullscreen tools
Tools like theme editors or page builders that don't fit a recipe layout
should use ShellFullscreenToolSurface.
Managed harness definitions
Builder stores user-editable harness drafts as ManagedShellDefinition.
Those definitions normalize:
policy: density, width, motion, state labels, adapter chrome, and theme.recipes: preview slot toggles for all five recipes.fullscreenTool: header/footer toggles for fullscreen tools.
Use normalizeManagedShellDefinition() before persisting arbitrary input, and
resolveManagedShellManifest(baseManifest, definition) to merge an approved
definition back into the runtime manifest. Builder activation still happens
outside this package; opt-shell only provides the normalization and merge
boundary.
Scaffold
bun run --filter @reopt-ai/opt-shell scaffold -- \
--kind workspace --workspace list \
--target app/orders/page.tsx \
--title "Orders" --toolbar --filters --aside --footerThe scaffold command writes the route entry plus .harness-contract.ts,
.harness-preview.fixture.ts, .harness-doc.md, .harness.test.ts, and
.harness-guard-matrix.md. Workspace scaffolding currently accepts
list, detail, editor, and dashboard; landing is a runtime recipe but
is not yet exposed by the scaffold CLI.
AI Agent Protocol
Phase-aware context
generateShellContext(manifest, { phase }) controls what agents see:
| Phase | Shows | Hides |
| ----------- | ------------------------------- | ----------------------- |
| scaffold | Recipe selection, heuristics | Slots, adapters, policy |
| implement | Locked recipe's slots, adapters | Other recipes, policy |
| polish | Policy options, state labels | Recipes, adapters |
| audit | Contract rules, anti-patterns | All creation actions |
| full | Everything | Nothing |
Completeness scoring
import { computeCompletenessScore } from "@reopt-ai/opt-shell/audit";
const score = computeCompletenessScore(findings, totalChecks);
// score.score: 0-100, score.categories: per-category breakdownCategories (weights): layout (20%), recipe-contract (25%), adapter-ownership (15%), state-ux (10%), design-document (10%), scaffold-bundle (10%), accessibility (10%).
MCP Handlers
9 handlers from @reopt-ai/opt-shell/core:
listShellManifests, getShellRecipes, resolveShellPolicyMCP,
getShellRecipeDetail, getShellContractRegistry,
getShellCompletenessScore, getShellCoverage,
searchShellPatternsHandler, exportTokensDTCGHandler.
0.2.0 note:
getStructuredShellContext,recordShellDecision,requestShellApproval,registerShellAgent,detectShellConflicts등 unproven 핸들러는 컨슈머 사용 0건으로 제거됨. 필요하면 issue로 신고.
Pattern search (50+ patterns)
import { searchShellPatterns } from "@reopt-ai/opt-shell/audit";
const result = searchShellPatterns("datagrid list", 5);W3C DTCG token export
import { exportShellTokensDTCG } from "@reopt-ai/opt-shell/core";
const dtcg = exportShellTokensDTCG(resolvedTheme);PR Review & SARIF
import {
findingsToSARIF,
generatePRReviewComments,
} from "@reopt-ai/opt-shell/audit";
const sarif = findingsToSARIF(findings); // SARIF 2.1.0
const comments = generatePRReviewComments(findings); // PR inline commentsESLint Plugin
Enable @reopt/eslint-config/opt-shell-rules:
| Rule | Severity | Description |
| ------------------------- | -------- | ----------------------------------------- |
| no-raw-engine-in-recipe | error | Must use adapter, not raw DataGrid/Editor |
| require-state-boundary | warn | Workspace files need ShellStateBoundary |
| no-hardcoded-width | warn | Use policy.contentWidth, not max-w-* |
