简体中文 · English · Back to README

# DSH Studio design and plugin boundaries ## Goals DSH Studio provides Desktop, Web, and TUI over one pinned DSH runtime. The surfaces share sessions, Profiles, plugin contracts, and local capabilities, while each package carries only the interaction layer it needs. Lightweight deployments do not have to install Electron. Design principles: - Reuse DSH Profile, Loader, locale, settings, and ThemeService contracts. - Desktop is the full distribution; Web and TUI can be packaged separately. - Keep one Host and one permission boundary for each capability. - Human and Agent plugin actions share the same preview and commit transaction. - Synchronize upstream features without replacing the DSH Studio UI or themes. ## Surface architecture ```mermaid flowchart TB CLI["dsh-studio"] --> Desktop["desktop\nElectron + Web runtime"] CLI --> Web["web\nHTTP + Web runtime"] CLI --> TUI["tui\ndsh-TUI renderer"] Desktop --> Core["Pinned DSH runtime"] Web --> Core TUI --> Core Core --> Profiles["Profile + Loader"] Profiles --> Plugins["DSH Studio and third-party plugins"] Plugins --> Host["Workspace · PTY · Git · Browser"] ``` `dsh-studio` only selects an interaction surface. Runtime capabilities remain under DSH Profile and Loader management, so separate packages never create a second plugin system. ## Distribution boundaries | Package | Includes | Excludes | | --- | --- | --- | | Full/Desktop | Electron, Web runtime, TUI, Node, bundled plugins, unified CLI | Nothing | | Web-only | HTTP/Web runtime, Node, Web-compatible plugins, unified CLI | Electron and native window features | | TUI-only | dsh-TUI renderer, Node, TUI-compatible plugins, unified CLI | Electron and browser UI | Desktop itself uses the Web UI, so DSH Studio does not ship a degraded "Desktop-only" package. Web-only and TUI-only remove Electron; TUI-only is the smallest supported distribution. ## Bundled plugins and upstreams | Plugin | Relationship | DSH Studio boundary | | --- | --- | --- | | `@dsh-studio/desktop` | Native | Unified entry, window, menu, bridge, and bundled-plugin registration | | `@dsh-studio/capabilities` | Pins and adapts [`DSH-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar) | DSH Studio Host capability gateway: PTY, Files, Git, WorkTrees, Workspaces, jobs, and Agent tools | | `@dsh-studio/sidebar` | Downstream Better Sidebar UI adapter | Reuses the Host while retaining DSH Studio layout, icons, themes, Review, and comments | | `@dsh-studio/panel-controls` | Downstream implementation of the `dsh-web-panel` interaction model | Unified Terminal dock without a separate Web Terminal install | | `@dsh-studio/pinned-summary` | Native | Session summary, half-height card, and content-gutter management | | `@dsh-studio/plugin-marketplace` | DSH Studio's canonical catalog and transaction implementation | One Loader, candidate staging, low-risk direct install, optional isolated preview, risk approval, TOFU source lock, and recovery | | `@dsh-studio/skins` | Downstream implementation of the `dsh-skins` ThemeService model | One skin id set, Host persistence, Web/Desktop CSS, and TUI palette adapters | | `@dsh-studio/vision` | Adapts [`dsh-vision`](https://github.com/william-jin-cmu/dsh-vision) | Cross-surface `view_image` Host tool with cloud/local OCR fallback; DeepSeek V4 is admitted at the final image-capability check and its native attachments are described before the pinned text-only adapter, while DSH owns paste, thumbnails, and submission through its native attachment rail; reuses DSH credentials and settings | | `dsh-cc-tui` | Pins [`dsh-TUI`](https://github.com/ccch1mneyyy/dsh-TUI) | Upstream owns terminal rendering, session interaction, commands, and terminal compatibility | | `@dsh-studio/tui` | Downstream Profile adapter for `dsh-TUI` | Unified `dsh-studio tui`, DSH Studio TUI identity, defaults, packaging, and DSH data boundary | Downstream plugins periodically inspect upstream features and adapt them to the current DSH contracts. Upstream code, the DSH Studio UI, and final permission boundaries remain separate layers. `@dsh-studio/skins` is the only skin-definition module for all three surfaces. Web and Desktop adapt the catalog to DSH CSS tokens; TUI adapts the same ids to the upstream native `/theme` palettes. TUI retains upstream hot switching and its picker, then mirrors the choice into the shared `skins.json` on the next launch. There is no second theme loader. ## Workbench kernel contracts The right-panel workbench converges open semantics and state scoping onto the shared kernel contract `@dsh-studio/shared/workbench-contracts`, carried by the four runtime services in `plugins/workbench` (all implemented below). Persistence slice vocabulary remains in shared contracts, but there is no standalone `workbench.state` runtime service. The old scattered open/layout/state entry points are gone: - `SurfaceRegistry`: the one surface registry per region (center/left/right); registration declares region ownership and lifecycle — consumers never build a second table. - `OpenPipeline`: the single open-decision pipeline. intent (`preview`/`pin`/`background`) × `resolveOpenPlan` ⇒ area, replaceable preview, and activation. The focus invariant (an open never moves keyboard focus) is upheld by the pipeline itself, not by each caller. - `LayoutService`: cross-plugin layout negotiation. The sidebar width policy stays in the sidebar domain (persisted cap 4096, live viewport capped at 75%); the service only receives final footprints and coordinates regional footprint plus overlay mounting through `ensureLayoutDom` (`@dsh-studio/shared/layout-dom`). - Persistence is not exposed as a standalone `workbench.state` service. Shared `StateSliceDefinition` retains the schema/version vocabulary, while actual writes go through `persistVia` onto host-owned backends. Each domain keeps its `workspace`/`session`/`global` bucketing contract; center-surface queues bucket by cwd because their objects are workspace-bound. - `WorkspaceEvents`: the workspace/session identity event source. `onWorkspaceChanged` / `onSessionChanged` are driven by one identity pump (the runtime `currentProvideInfo` projection); workspace fires before session. GitWatch/websocket freshness events stay in the source-control domain and are deliberately not folded into this service. Upstream DOM probes still live in exactly one module per plugin (`dsh-dom.ts` for the sidebar). ## Plugin installation transaction ```mermaid stateDiagram-v2 [*] --> CatalogReady CatalogReady --> Planned: plan Planned --> Staging: execute Staging --> Applied: direct / atomic swap Staging --> Previewing: explicit preview Previewing --> Discarded: discard Previewing --> Applied: apply Staging --> Cancelled: cancel Applied --> Planned: update / enable / disable / uninstall Applied --> Undoable: keep previous profile Undoable --> Applied: undo Planned --> AwaitingInput: provide required material AwaitingInput --> Staging: provide ``` `installed` and `enabled` are separate states. `plan` only resolves the source, pins the commit, and validates the manifest, compatibility, and risk. `execute` uses the same candidate-staging implementation for both modes. A low-risk plan with no required material or confirmation may atomically replace the live Profile directly; other plans can explicitly start an isolated preview before applying. The Agent and UI share one Loader, transaction owner, source lock, recovery, and Undo path. ## Marketplace implementation The complete P0/P1/P2 marketplace behavior is specified in [Plugin marketplace redesign](./plugin-marketplace-redesign.md). The runtime uses one canonical catalog schema and one `MarketplaceCommand` model: `refresh`, `plan`, `execute`, `pack`, `apply`, `discard`, `cancel`, `provide`, and `undo`. Catalog and direct repository candidates are resolved to exact source facts: GitHub commit pins, exact npm versions, or HTTPS tarballs with SHA-256 digests. The Host validates the manifest, DSH bundle contract, compatibility, license, lifecycle scripts, required configuration, and risk before candidate staging. Low-risk plans can atomically install directly; preview remains an explicit isolated-runtime option. UI and Agent consume the same Host transaction, source locks, confirmation decisions, progress, recovery, and Undo path. Removed registry readers and `inspect`/`prepare`/legacy `preview` command aliases are not part of this contract. This repository is verified by source and isolated fixture tests only; this checkout does not install or run it. ## Left-rail architecture The facts, deep-module seam, semantic commands, project-icon resolution, and physical Worktree deletion rules for the Project → Worktree → Session rail are documented in the [left-rail architecture](./left-rail-architecture.en.md). That document freezes architecture only; implementation has not started. ## Security boundaries - Web binds to loopback by default; LAN exposure requires trusted authorities. - The cwd of every Files, PTY, and Git request is validated against a server-side workspace scope registry (registered workspace roots ∪ live session cwds; unregistered directories get `forbidden`); paths are then fenced to the session subtree — reads anchor on the server-resolved repository root so subdirectory sessions keep working. The same-origin loopback fence is transport hygiene, not authentication. - Local `view_image` reads are bound to the active Session workspace; remote vision requests go only to the user-configured endpoint. - Desktop/Web image paste, thumbnails, and submission remain owned by DSH's attachment store and native attachment rail; `@dsh-studio/vision` augments the final DeepSeek V4 image-admission capability check and describes those native attachments before the pinned text-only adapter serializes the request. - Marketplace candidate, current, and previous states remain separate. - A source receives a TOFU lock on first use; later commit changes need review. - The Electron bridge exists only on Desktop; Web does not emulate its rights. - TUI starts only on a real TTY and retains the active DSH Profile's sandbox and approval policies. ## Naming and data root User-facing names are **DSH Studio**, **DSH Studio Web**, and **DSH Studio TUI**. Internal package ids and the bundle id remain stable. All three surfaces use `~/.dsh-studio`, keep their compositions in separate Profiles, and share sessions, credentials, skins, and plugin caches. `DSH_STUDIO_HOME` is the common override. `DSH_STUDIO_CHANNEL=stable|dev` selects the sibling default roots `~/.dsh-studio` and `~/.dsh-studio-dev` so an installed Desktop can run beside a source verification instance. The Web and TUI `--data` flags override only the current process. See [installation, operations, and troubleshooting](./usage.en.md). ## Data flow & persistence Client components do not own server data; fetching and persistence go through the pinned DSH runtime's existing pipes, and components only consume zustand stores and runtime caches. The semantics live in `.workflow/specs/` (S1–S3) and are enforced by `scripts/guards/*.mjs`: ```text client store / component │ render queue subscribe ▼ ▼ zustand store (pure memory) ──────────► shared/runtime RevisionedStore / GenerationGate │ persistVia(store,{table,sanitize,merge,debounceMs}) ▼ shared persistVia facade ──────────────► host-owned backend │ ├── ui-chrome table (capabilities ui-chrome.get/put JSON) │ ├── settings namespace (replace/mutate) │ └── nodeFs (atomic file writes) ▼ transport: capabilities JSON / ui-chrome / settings / WS / IPC ▼ cordis service (capabilities, terminal, marketplace, …) ▼ data-root ~/.dsh-studio / ~/.dsh-studio-dev (per DSH_STUDIO_CHANNEL) ``` - **One fetch shape:** RPC caching uses the shared/runtime RevisionedStore family, keyed by cwd/scope, with soft refresh and precise mutation invalidation; command-style mutations converge on a single lane wrapped in a store action; event pushes (e.g. marketplace changed) are triggered by a host state transition and each store subscription revalidates. No hand-written loading/error/data triplets, no scattered bare `callCapabilitiesApi`. - **One persistence path:** everything persists through `persistVia` onto a host-owned backend. `localStorage`/`sessionStorage` are only a legacy migration source (e.g. `comments-migration.ts` reads them once into an ui-chrome table) — never a runtime write channel. - **One runtime cache shape:** surfaces in the same workspace share a single ScopedRuntimeRegistry instance; they do not each build their own cache. ## Unified hover comments File viewing and diff views share ONE hover-comment interaction (R2): a hovering gutter `+` opens an inline composer (Enter commits / Shift+Enter newline / Esc dismisses) that writes to the unified batch store (`diff-comments-store` v2; anchor path+startLine/endLine+contentHash, resolve lifecycle) or "reference in chat" lightweight composer injection. Interaction uses the official `@pierre/diffs` hooks (renderGutterUtility / onLineEnter) — no DOM scraping; the legacy diff bottom form is removed. Markdown preview keeps selection-references (no stable line numbers); its source view is a code view and supports line comments.