Project Plugin Surfaces
Create a project plugin emptyCanvas surface: view claims, slot ownership, launcher recovery, runtime styling, and state lifecycle.
A project plugin can give the project's empty canvas its own starting screen. This is useful when a repository has become a small application for its team: a release desk, a content studio, a dataset browser, or an operations board. People enter through the work they need to do, while Daintree continues to provide terminals, worktrees, and review around it.
The implemented surface is emptyCanvas: the content-grid region shown when no panels are open. It does not create a persistent home route, replace the project switcher, or install a default layout. projectHome and defaultLayout are not accepted manifest slots.
Claim the empty canvas
First declare a normal panel and a matching view. Then add this small object to that project plugin's contributes block:
"surfaces": {
"emptyCanvas": { "viewId": "overview" }
} overview must be a declared view id; that view must have a matching panel id. The panel cannot have hasPty: true, because PTY kinds render through the terminal host rather than the view module. Surface claims are only valid with top-level "scope": "project". The schema rejects a dangling view, a PTY view claim, an unsupported slot, and a claim from an installed plugin.
Use the same view as an ordinary panel if the workflow should stay available while terminals are open. A surface is an entry point into the project, and its panel counterpart can be the place someone continues working.
Keep the launcher within reach
Daintree adds a Launcher control in the upper-right corner of the surface frame. It switches to the stock launcher; the control then shows the plugin panel's name so the user can switch back. This control belongs to the host. Leave that corner usable and do not duplicate the escape control inside your plugin.
The surrounding chrome remains available. The project switcher, sidebar, and worktree dashboard continue to work. If the plugin unloads or its view cannot resolve, the stock surface remains available. A view error is handled by the normal plugin error boundary, including retry.
If two plugins claim the same slot in one project, the first claim owns it. Daintree reports the collision with both names. The other plugin still loads its remaining contributions, and its claim is queued: if the current owner unloads, the next claimant takes over. For predictable ownership, keep one deliberate claim for each slot in the repository.
Design a useful starting screen
A starting screen earns its place by answering what someone can do next. Show a small amount of real context and give it a clear action: drafts awaiting review, a selected dataset, the last validation result, or the release currently being prepared. Link to a detail panel when the work needs more space.
- Start with available data. Read from the repository or an existing service. Display loading, empty, unavailable, and failed states separately; an unavailable worktree read is not evidence that the project has no worktrees.
- Keep actions specific. “Review the release notes” or “Validate this dataset” tells people what a button will do. Declare commands so the same task is discoverable in the palette.
- Make writes reviewable. Show the destination and intended change before triggering a mutation. Check the action result and handle a denied capability or confirmation requirement explicitly.
- Expect a narrow pane. The view may also be opened as a panel. Prefer container queries and a layout that remains usable beside a terminal.
- Use accessible controls. Provide keyboard navigation, visible focus, labels, and a useful state when a network connection is unavailable.
These are authoring choices, not additional automatic host behavior. A release board does not gain deployment authority merely by occupying the canvas. Its worker still uses the same host API and capabilities as the rest of the plugin.
Style with the host theme
Plugin views can use Tailwind utility classes without compiling their own Tailwind stylesheet. Daintree compiles the classes at runtime against its theme and scopes the generated rules to plugin view roots. Use semantic classes such as bg-surface-panel, text-text-primary, and border-border-default. The stock palette, dark:, prose, and @apply are outside this contract.
Keep conditional utility names as complete strings. For portals, spread PanelViewProps.styleRootAttributes onto the portalled container so it receives the same scoped styling. Normal descendants are already inside the host's marked root.
Any plain CSS you add must be scoped to your own root; place component rules in @layer components so utility overrides retain their intended priority. Do not ship a second Tailwind build or preflight. The Vite preset rejects those configurations. Read the view styling contract for the full token vocabulary and supported patterns.
State and lifecycle
A rendered view is temporary. It can unmount when the user switches to the launcher, opens other panels, changes projects, or reloads the plugin. Tie DOM observers, request cancellation, and UI subscriptions to disposeSignal. Keep long-running jobs in the plugin worker and manage them deliberately.
For ordinary panel instances, initialArgs is the saved state snapshot at mount and optional persistState merges small JSON patches into the panel record. The 64 KB limit applies to the resulting bag; a return value of true means accepted and scheduled, not synchronously written to disk. A synthetic surface is not a normal persisted panel record, so use host.storage for state that must survive surface remounts and do not assume persistState is present.
Project settings should contain shared configuration. The local settings scope is suitable for a machine's interpreter path or a project-specific credential, and stays under the user's Daintree data directory. Plain storage and panel state are not encrypted. See Project Plugin Trust & Management.
Implementation and tests
- Manifest schema: surface slot validation and panel/view cross-references.
- PluginSurfaceRegistry and its tests: project isolation, first ownership, and promotion after unload.
- ProjectSurfaceFrame and its tests: the host-owned launcher switch and recovery behavior.
- PanelViewProps and the Vite preset: lifecycle, state, portal styling, and bundle boundaries.