# Changelog

Notable changes to `@2nd1st/dsh-plugin-open-app` (published as `dsh-plugin-open-app`
through 0.1.0). This project follows
[semantic versioning](https://semver.org/), and while the major version is `0` it tracks a
fast-moving host: anything here may change between minor releases.

## 0.1.2 — 2026-08-16

Nothing here changes what the plugin does — apart from this file and the version bump, the
whole diff since `v0.1.1` is `README.md`, 175 insertions and 140 deletions in one file, and
`lib/` is untouched. What changes is the page other people see.

### Changed

- **The npm page catches up with the README.** npm renders the README that was inside the
  tarball at publish time, and `0.1.1` was cut at `b7d53d8` — one commit before the rewrite
  landed. So the package page kept showing the old shape: a lede, a screenshot, a preview
  warning and two sections of design narrative first, with the install command 6,848
  characters in, at line 96. That is far past the fold, and the reader who most needs it is
  usually an assistant reading top-down rather than a human skimming for a code fence.
  In this version's README that command is at character 927, line 16 — a facts table on the
  first screen (package, license, the one-step install command, the engine floor, which dsh
  surface it needs, where the changelog is) with a complete runnable Install section
  immediately under it, then the predictable skeleton: install, requirements, configuration,
  usage, troubleshooting, how it works, the deep sections, known limitations, development.
  Nothing was dropped. The container prompt, the six unpublished seams the plugin reaches
  through and the known limitations are the most valuable things in the file and they stay
  in full, moved below the operational sections rather than split out — `files` ships only
  the README, so a `docs/` page would be invisible to exactly the npm reader this is for.
  New rather than moved: a troubleshooting table of eight symptoms indexing the sections
  that already explain them, a requirements section that states the engine floor once, and a
  development section holding the linked-checkout install.

- **The Platform row states the supported side only.** It read "the TUI has no view slots to
  take". That is probably true and it is not a reading — what is measured here is that the
  package declares `platform: "web"` and every slot the browser half takes is a web slot,
  which is a fact about what this plugin asks for, not about what the TUI publishes. Nobody
  here has enumerated the TUI's slots, so the row no longer claims to have:
  *dsh **web** (`dsh web`) — every surface it takes is a web slot*.

- **The Requires row names pnpm.** `dsh plugin` is a thin forwarder: it runs
  `spawnSync("pnpm", …)` in the profile directory, so the one-step install this README leads
  with needs pnpm on `PATH`, and without it stops with exit 127 at
  `dsh: pnpm not found on PATH — install pnpm to manage profile plugins`.
  Nothing changed to make that true — it is
  dsh's own prerequisite rather than this plugin's, and that is exactly why it went
  unlisted: the machine this README was written on has a global pnpm, so the first step
  never failed here. It fails on a machine that has none, which is every reader who has not
  installed one. The Requires row states it and the requirements section explains whose
  prerequisite it is. No minimum version is given, because none has been measured.

- **The command examples name a real profile instead of a placeholder.** They read
  `--profile <profile>`, which invites a name of your own, and exactly one name works. dsh
  ships two profile templates — `web` is `dsh-base` + `dsh-web-app`, `headless` is
  `dsh-base` + `dsh-headless` — and a name matching neither is initialized from the default
  bundle list, `dsh-base` alone. Only `dsh-web-app` carries the `webserver` row, while the
  host half declares `inject = ['webServer', 'systemPrompt']`, so anywhere else the plugin
  simply never loads — and yet `dsh plugin add` still exits 0 and still appends the bundle
  to the tree: installed, configured, and silent. The examples now say `web`. The restart
  line was wrong on its own account besides — `dsh web --profile <name>` answers
  `error: unknown option '--profile'` for every name, `web` included, because `dsh web` is
  already the alias for that profile — so it is now bare `dsh web`.

## 0.1.1 — 2026-08-15

### Changed

- **The package is `@2nd1st/dsh-plugin-open-app`.** Same repository, same plugin, a scoped
  name — which is also what the browser half now registers itself as (dsh's client-modules
  registry looks a bundle up by the name it resolved the package under, and a bundle that
  answers to anything else takes the whole plugin tree down with it). `0.1.0` went out
  under the bare `dsh-plugin-open-app`; that name will be deprecated on npm with a pointer
  here. To move an existing install: `dsh plugin --profile <p> remove dsh-plugin-open-app`,
  then add the scoped one.

### Added

- **Installing it is one step now.** The package declares `dsh.bundle`, so
  `dsh plugin --profile <p> add @2nd1st/dsh-plugin-open-app` both installs it and appends
  it to `dsh.profile.bundles` — its own `cordis.patch.yml` becomes a layer of your profile,
  carrying the `open-app` row and an `mcp-oma` row (dsh's MCP client on the same engine).
  The second step everyone used to do by hand — pasting an `- insert:` block into the
  profile — is gone, and with it the reason a plugin could sit installed but inert. Bundle
  layers apply below your own patch file, so both rows stay configurable by id.

  If you already pasted those rows, delete them: a patch `insert` appends rather than
  replaces, and dsh refuses to boot a tree with two rows of one id
  (`duplicate loader entry id: open-app`) rather than running the plugin twice.

- **Open in Apps** — every inline app card now carries the way onward as a real button:
  a pill in dsh's own primary-action shape (the composer's Send, one notch smaller),
  centred in a strip under the frame, that enters the app's container — its workspace, its
  conversation, its tab. It sits below the frame rather than floating over it, because an
  app keeps its own controls at its own edges. The small *Open app* link in the card's
  corner stays where it was.

### Fixed

- **App mode never appeared for anyone whose default preset is 创造模式.** The container's
  label is a copy of the deployment's default preset, and a preset is composed once per
  process: dsh's `cordis` preset registers Host Cordis inspect providers into a
  process-global registry, so its duplicate throws `Host Cordis inspect provider "Service"
  is already registered` and `agentPreset.select` refuses with `agent-preset-invalid` —
  which the browser half returned from without a word, leaving every container on the
  default preset and the header reading *Creator mode*. The refusal is now said out loud
  and repaired: the host half re-authors the preset from the first preset the deployment
  lists that is neither the default nor this one, and the same container gets the label on
  a retry. Nothing mounts at boot to find this out — mounting our copy first would make
  *us* the process's registrant of those global providers and break the user's own
  ordinary sessions, so the only mount is the one that was going to happen anyway.

- **The app directory went stale the moment the model built something.** The registry the
  Apps UI reads was one `/bootstrap` at page load, and its only refresh was the *Refresh*
  button, so an app created in the conversation was missing from *All apps* and from the
  ordinary session's Apps tab until somebody pressed it (measured on the rig: model builds
  `mood-tracker`, the directory still says *3 apps*, while the host half's own `/bootstrap`
  — which calls the engine on every request and was never the stale half — already listed
  four). It refreshes on mount now, so opening the directory is what re-reads it, and a
  card rendering an app the registry has never heard of re-reads it too — that card is the
  plugin's one in-band notice that the model just made one.
- **The inline view keys stopped being a boot-time set.** The same refresh re-asks the
  engine which `open_*` tools it publishes and claims any new ones, so the plugin's key set
  is never older than the app list beside it. (What still waits for a dsh restart is dsh's
  own tool table — see Known limitations in the README for why, and why `open_app` covers
  it either way.)

## 0.1.0 — 2026-08-15

First public release. It carries every iteration the plugin went through in development;
nothing before this tag was published.

### Added

- **Apps section in the sidebar** — *All apps* (everything the engine has), *App Store*
  (where new ones come from), and one node per pinned app.
- **App containers.** Clicking an app node opens that app's own dsh workspace and a session
  inside it, with the app's UI in an **Apps** tab registered ahead of Chat, and the whole
  conversation about the app kept with it. The workspace registration is the durable
  binding — clearing the browser's container map re-adopts the same directory, the same
  workspace and the conversation already accounted under it.
- **The agent's presence** — a one-line strip under the app: the tool being called, the
  reply arriving, what was last said, and, unmissably, a question waiting for you or a
  failure. Click it to peek at the last reply in place; the whole conversation is one tab
  away. The row sits in dsh's own content column and the model's markdown is read out flat,
  since nothing there can typeset it.
- **App mode** — a container's agent gets a system-prompt section of its own: who it is
  here, the app's declaration card (collections, row shape, declared functions), a ban on
  `open_app` / `app_html` / `open_*` — the panel is already showing the app, and the widget
  HTML would be re-sent on every later turn of a durable conversation (measured on the rig:
  23.1K input tokens on a first turn without the call, 46.6K with) — and a one-sentence
  reply contract machined to fit the strip. The section renders only for sessions living in
  an app directory, so an ordinary chat session carries no trace of the plugin. Containers
  also run on an `app-mode` agent preset, copied once from the deployment's default, so dsh
  names the mode where dsh names modes.
- **An opening line.** A brand-new container is asked one question — "What is in the app
  right now?" — which ends the blank-session state (dsh gives a session with no messages no
  view ring) and draws the strip's first receipt. `installMessage: ''` opts out.
- **App workspaces stay out of your way.** They are filtered out of the workspace tree, the
  New Session picker and dsh's own "most recent workspace", their sessions are hidden with
  them, and an implicit New Session standing inside a container goes to your own most recent
  workspace instead of the app's.
- **App → app stays inside the model.** *Open* from the App Store enters the other app's
  container instead of navigating the store's own frame (engine `?nav=intent`).
- **Inline app rendering.** In an ordinary chat session, an `open_app` — or per-app
  `open_*` — tool call renders as the running app in place of the tool card.
- **A whitelisted host-side proxy** for the three read-only engine tools the Apps UI needs
  (`list_apps`, `app_store_list`, `get_app`), since the engine answers its own origin only.
  Everything that writes keeps going over MCP, where the host's permission prompts are.
- **Configuration** for the engine's base URL, the MCP `serverName` the inline views key
  on, the apps root, the container prompt and the opening line. Every setting is optional.
- `docs/status-strip-states.html` — a design board rendering every state of the agent strip
  from the same values the live component uses, including the two states a running harness
  will not hold still for: a question waiting, and a turn that failed.

### Requires

- dsh with a web profile. The plugin takes only published slots (`conversation.view`,
  `conversation.input.dock`, `sidebar.footer.action`, `shell.overlay`,
  `tool.call.toolview`) and shadows nothing; the six places it reaches past a published
  seam are listed in the README, with what each degrades to.
- open-mcp-apps v0.5.1 or newer for the full experience (`?chrome=0`, `?nav=intent`,
  standalone root `overflow-y:auto` all shipped in it). Against an older engine the
  plugin degrades as described under Known limitations in the README.
