# Compatibility ## Host requirement This baseline applies to `0.9.0` (unreleased). Release verification results must be recorded separately; the baseline declaration does not assert that behavior checks passed. | Component | Supported baseline | | ------------------------ | ----------------------------------------------------------------------------------- | | DeepSeek Harness host | `0.1.6-alpha.2` exactly | | `engines.dsh` | `0.1.6-alpha.2` | | Development declarations | `0.1.6-alpha.2` | | Cordis | `^4.0.2` | | Node.js | `^22.19.0` or `>=24` | | React | `^18.2.0` | | Browser | Current Chromium-based DSH Web target; other modern browsers retain marker fallback | The release manifest declares `engines.dsh: "0.1.6-alpha.2"`, and every lockstep `@deepseek-ai/dsh-*` peer uses the same exact version. DSH has no external compatibility promise before `0.2.0`, so this plugin does not claim compatibility with a different prerelease. Desktop clients must embed the same DSH host version. When the installed host differs, select the matching plugin release from the README compatibility map or change hosts; forced installation and disabled peer checks are not supported. ## Dependency source Every DSH dependency used for verification must identify the same `0.1.6-alpha.2` source generation. [source-baseline.json](../source-baseline.json) pins official tag `dsh-v0.1.6-alpha.2` at commit `ddefc45fbc7f8e46dd73185e68295696d1297887`. The npm family is available and the registry lockfile targets it; verify the complete peer closure with `pnpm install --frozen-lockfile --strict-peer-dependencies`. A matching version string or a regenerated lockfile is not a behavior-test result. For an unpublished family, use verifiable official source or official artifacts in a disposable directory. Confirm the repository, source commit or artifact URL, package names, and package versions before deriving integrity values. Include the DSH and vendored Cordis package families and the Landlock entry package, and enforce strict peers in the temporary install. Machine-local `file:` URLs, workspace links, and generated overlay lockfiles must never enter the released plugin manifest or checked-in lockfile. Record the actual source and commands in the release verification evidence; an old lockfile or a successful build against a different host does not verify this baseline. The source verification helper provides independent source checks: it checks the pinned checkout commit, clean tracked files, DSH tarball versions, and required direct official packages before preparing a separate plugin directory. It omits the registry lockfile, supplies all provided official packages as temporary development dependencies, and adds tarball overrides. Restore the source manifest after verification and before packaging so those temporary dependency entries do not enter the release. [Development](development.md#official-source-verification) owns the complete procedure. ## Marketplace placement The catalog submission uses **Sessions & Messages** (`session`). The plugin annotates assistant messages, persists drafts per Session, submits one user message through the official composer, and reconciles queue and durable message state; visual decoration supports that message workflow rather than acting as a theme or general appearance extension. The repository-owned `screenshots.json` lists the five curated images that storefronts should present. The catalog entry uses the stable `dsh-annotation.tgz` GitHub Release alias, so installation never depends on a local source build. ## Marketplace update API The plugin configuration card integrates with dsh-market `dsh-market/update-api/v1`, introduced by dsh-market `1.45.0`. This API is optional and currently reports beta stability. The Client discovers capabilities before every first use, validates the schema and all endpoint paths, and only accepts same-origin `/dsh-market/api/v1/*` endpoints. It never imports dsh-market code or calls its legacy private routes. The card checks only the installed `dsh-annotation` package. Update progress comes from the operation endpoint. Force is offered only after `RELEASE_TOO_FRESH` or `VERSION_UNCHANGED`; rollback remains tied to the originating operation; refresh follows `refreshRequired`; restart is visible only when both `features.restart` and `restart.supported` are true. When discovery is unavailable, users are directed to **Settings → Plugin Market**. Client disposal aborts the active request or polling delay and waits for settlement. ## Main Settings integration The Host half registers the `dsh-annotation` namespace through `@deepseek-ai/dsh-settings`. The browser half binds the namespace through `ctx.settingsScope` and contributes a top-level `settings.section` with id `dsh-annotation`, declared by `@deepseek-ai/dsh-client-ui-settings/client`. The main Settings shell owns navigation and renders the complete form under **Annotations**. The plugin does not contribute `plugins.bundle.config`, the built-in `settings.plugins.tab`, or `plugins.item`. The three annotation fields `enabled`, `autoAttach`, and `compactSummary` default to `true`; all 18 [transcript visibility fields](../README.en.md#transcript-visibility) default to `false`. The collapsed compact summary aligns right, sizes to content, and omits the leading icon; saving `compactSummary: false` restores the full-width bar and leading icon without changing annotations, delivery, or editor state. In either mode, an open upward list connects to the bottom summary row at a one-pixel seam with complementary corners. Compact mode expands both pieces to the same 560 px width capped at 100%, while narrow layouts and CSS zoom preserve the cap; the open toolbar grows across that width and keeps actions right-aligned. The attached-count overview is unavailable while the full list or inline editor is visible. The stable chevron wrapper and panel reveal transition honor reduced motion, and Escape returns focus to the summary trigger after closing a list without an editor. The summary has no local-storage usage, recovery export/download, or bulk-clear footer. Local drafts and unfinished editor text still save automatically and recover after refresh; existing data and individual draft deletion with Undo are retained. Stored `localTools` user overrides are ignored without deletion or migration. `summaryMode` is not a setting; any leftover value is ignored rather than interpreted as `compactSummary`. A deployment without a settings provider still runs the annotation command, while the browser keeps all safe defaults and the main Settings section renders no form. The card follows the official plugin-configuration lifecycle: edits are staged, Save writes the Host document with the namespace revision, Discard drops local edits, and Reset clears the user-layer field. Conversation integrations change only after the Host accepts a value. During the rename upgrade, the Host registers the legacy `inline-comments` namespace temporarily, copies its stored user values into `dsh-annotation`, and clears the legacy section only after the new write succeeds. No Settings section is keyed to the legacy namespace, so it renders no duplicate form. ## Protocol, storage, and command compatibility - New submissions only emit protocol v2 (`protocolVersion: 2`, `source: "dsh-annotation"`, `annotation` field, `kind`, and `protocolLocale`). Legacy v1 payloads (`comment` field, no source, no kind, no protocolLocale) are still parsed and converted into the v2 internal model: `kind` is inferred from the content (`note` for non-empty, `highlight-only` for empty) and `protocolLocale` defaults to `en` (the old protocol language); historical messages are never rewritten. - The model protocol follows the DSH locale at submission time: `protocolLocale: "zh"` produces the Chinese template (「注解 N:」), `protocolLocale: "en"` the English one ("Annotation N:"), and every reply must carry the hidden `dsh-annotation-reply` marker before its paragraph. Reply parsing accepts 注解 N:/注解 N:/Annotation N: and legacy label formats; association always rides the hidden stable annotation id. Marker-window boundaries use the same marker removal, newline collapse, and edge normalization as displayed text, so extra blank lines and repeated ordinals across batches stay aligned. If the earliest matching complete label occurs more than once within one window, that marker remains plain even when a later localized label occurs once. - Optional dsh-focus-chat: the adapter never injects or waits for a focus service; it detects the focus view through its public `[data-focus-flow]` DOM root. Without the plugin everything runs unchanged; with it, hidden-node marker/chip measurement pauses, view switches trigger re-measurement, and normal-view duplicates are hidden by message id. Adapter failures only disable focus enhancements. - New messages only emit `dsh-annotation` acknowledgement and `dsh-annotation-reply` markers. The legacy `dsh-inline-comments:` and `dsh-inline-annotations:` prefixes (and their reply-marker variants) remain authoritative reads. - Browser storage uses the `dsh-annotation:v1:` namespace. Legacy `dsh-inline-comments:v1:` and `dsh-inline-annotations:v1:` values are validated, converted, and written to the new key before the legacy keys are removed; a failed migration write leaves the legacy data in place. - The stable submission-derived message id prefix `dsh-inline-annotations:` is retained so persisted retries keep their authoritative queue identity across the rename. - Outbox `attachments` records carry the count and ordered image/file kinds without bytes or temporary upload receipts. Legacy `images` metadata remains readable and requires image attachments on retry. The storage key and `storageVersion: 2` remain unchanged; [Data model](data-model.md#browser-persistence) owns the retry fields and recovery rules. - The legacy internal command names `inline_comments_submit` and `inline_annotations_submit` forward to the new handler through invisible aliases; no second business implementation is retained. ## High-risk integration points The plugin uses two different integration mechanisms: - every existing `conversation.chat.node:assistant-step` entry is decorated in place through `ctx.slots.entries()`. The plugin changes neither its key nor its priority, and it does not register another occupant; - `conversation.chat.node:user` and `conversation.chat.node:steering` are still shadowed at priority `-100` so submitted batches can use a compact timeline row. For assistant rows, the decorator keeps the existing component as the body renderer, composes the existing `inject` face with the annotation face, and restores both fields when the feature is disabled or unloaded. It also watches `slots/changed`, so an assistant renderer registered later, including `dsh-smooth-stream`, is decorated without a same-key registration. DSH `0.1.6-alpha.2` renders HTML as literal text. The decorator removes this plugin's acknowledgement and reply comments, including legacy prefixes, from the text and reasoning blocks sent to the inner renderer. The outer annotation parser and persisted Session content retain the raw markers. Unmarked nodes keep their original references, and the Host's Markdown policy is unchanged. [Decision 0003](decisions/0003-assistant-renderer-decoration.md) defines the streaming and incomplete-marker rules. Source navigation consumes the injected `TurnProcessOwnerProps` fields `foldable`, `open`, and `setOpen`, and recognizes actual folded wrappers through `[data-turn-process-hidden]` or `[hidden="until-found"]`. It expands before measuring only when both signals show a folded source. The quote flash uses positioned DOM rectangles rather than the CSS Custom Highlight registry, and a Session-wide navigation epoch cancels stale local overlays and stale history-load continuations. Reconciliation independently subscribes to Chat nodes from `ctx.uiConversation.binding(binding).target('chat')`, the Inbox projection from `binding.session.projections.faceOf('inbox')`, and the Session snapshot for older-history availability. `SessionSnapshot` does not carry queue membership. The Inbox adapter selects only `next-turn` items and maps their `id` to the controller's `messageId`; `next-step` items never authorize withdrawal. An `undefined` Inbox snapshot means not yet synchronized and must remain unknown rather than become an empty queue. A synchronized snapshot that omits a previously queued id returns the outbox to `accepted` until durable Chat history establishes `sent`. A projection update must reconcile even when the Session and Chat snapshot identities are unchanged. The user and steering renderers pass `openFile` and `openSkill` from their owner props to `projectUserText`, including the overall requirement on annotation submissions. File attachments use the official `FileTypeIcon`. Assistant selection capture and range restoration include Markdown file-link button labels but exclude action buttons, hidden icons, reasoning, and plugin controls. DSH exposes no semantic DOM hook for these links, so the adapter identifies a button by its `title` attribute and both CSS Module local names `fileMention` and `fileLink`, without depending on generated hashes. This is an exact-version DOM dependency that must be rechecked when the renderer changes. Transcript filtering also decorates non-assistant Chat entries and the selected `conversation.view:chat` component. It requires `slots.provideRoot`, framework-bound selector hooks with interchangeable sources, and `locations.getTurn()` array identities that change on content updates, not only membership changes. [Architecture](architecture.md#slot-composition) describes the display-only projection and teardown order. A DSH upgrade is compatible only if `StoredEntry.component`, `StoredEntry.inject`, `ctx.slots.entries()`, `ctx.slots.provideRoot()`, the public owner props, standard Slot hooks, Chat and Inbox projection snapshots, primitives, and queue/session methods used by these integrations remain compatible. The CI type check catches declaration drift; a real Web smoke must catch rendering or lifecycle drift. The internal command is registered through the public command registry. DSH currently has no non-discoverable command flag, so the transport command can appear in slash-command discovery. Invoking it manually without a valid payload fails validation and does not reach the model. Composer attachment relies on the `inputTriggers` service and its scoped `slash/input-begin-command` and `slash/input-consume-token` events, on `CommandClaim.name` and `CommandClaim.attachments`, and on the root `commands/execute(sessionId, line, attachments)` Remote. The claim name identifies the configured command; its token remains zero-width and non-whitespace so annotation-only submissions do not add visible command text. The `binding.session.command` fallback supports attachment-free commands only. An upgrade is compatible only while those bail events keep their claim-and-span semantics, the input machine accepts a claimed token at draft position zero, `claim.submit()` receives ordered `SubmitAttachment[]` values, and the command Remote admits image and file attachments into durable blocks. Slash-command release observes the input state store and installs no global keyboard listeners. Focus restoration locates the Lexical contenteditable through DSH's composer DOM markers and uses browser Selection offsets that exclude the annotation token. It restores only the same editable element with unchanged visible text and no subsequent user focus change; file-reference chips remain editor-owned. The plugin does not import Lexical internals or rewrite the editor document. [Decision 0004](decisions/0004-dsh-0.1.3-composer-and-attachments.md) records these obligations. ## Upgrade checklist 1. Update every direct `@deepseek-ai/dsh-*` development dependency and the complete `@deepseek-ai/dsh` development environment to one release. 2. Regenerate the lockfile and run `pnpm install --frozen-lockfile --strict-peer-dependencies` when that release is on npm. For an unpublished DSH release, follow the [source verification procedure](development.md#install-and-verify) with the complete DSH and vendor families and the Landlock entry package, then run the disposable install with strict peer enforcement without committing its overrides or generated lockfile. 3. Run `pnpm verify`, `pnpm test:browser`, `pnpm test:profile`, and `pnpm test:coverage` against that installed release. The profile smoke requires the built artifacts produced by `pnpm verify` or `pnpm build`. Follow [Packaging](development.md#packaging) to restore the release manifest and produce the tarball from a source verification directory. 4. Install the tarball into a disposable DSH Web profile. 5. Verify finalized Markdown, code, tables, images, reasoning, file mentions, streaming completion, and interruption rendering, plus reply targets after streaming settles. Exercise at least four marker-linked headings separated by normalized blank lines, repeated ordinals from multiple batches, and a duplicated earliest heading followed by a unique translated citation; every unambiguous heading must navigate, while the ambiguous window remains plain. Navigate from a genuinely folded Turn and confirm it expands before measurement, centers the complete quote, fades the quote-range overlay, and cancels an older message's transient overlay without clearing a persistent marker/editor highlight. Select file-link button labels alone and within surrounding text, restore their ranges after remount, and confirm action buttons and hidden icons remain excluded. Exercise `openFile` and `openSkill` in ordinary user/steering text and annotation submission requirements. 6. Verify Inbox-only updates reconcile without Session or Chat changes, undefined projections do not imply departures, synchronized empty projections remove withdrawal, and `next-step` entries never authorize it. Exercise idle, running, blocking confirmation, withdrawal, transport retry, refresh recovery, default automatic attachment, manual attach/detach, Enter submission with composer text, annotation-only submission, mixed image/file submission in original order, retry refusal for missing or mismatched attachments, legacy image-only outbox recovery, slash-command release and the Enter race, and legacy overall-requirement migration. 7. Save the disabled automatic-attachment switch and confirm new annotations remain detached while the paperclip still attaches them manually, and that Lexical focus and the caret return after saving without altering text or reference chips. Confirm a Session switch, text change, or deliberate focus change cancels a pending restore. Then save the disabled plugin switch; confirm official renderers return, controls and highlights disappear, an armed claim detaches without changing visible text, an in-flight submission releases its claim once transport settles, and drafts return after saving the enabled switch. 8. Confirm the Slot ledger has no plugin-owned `assistant-step` entry, the existing assistant component and inject face are decorated exactly once, the two `-100` user/steering entries win their cells, and disable or unload restores the original assistant fields. Repeat with `dsh-smooth-stream` enabled. 9. Open **Settings → Annotations** and confirm one `settings.section:dsh-annotation` page with the three annotation switches `enabled`, `autoAttach`, and `compactSummary` on by default and all 18 transcript-hiding switches off by default. Confirm the transcript controls use a two-column grid at normal width and one column when narrow; verify `grep`, `bash`, and an unknown tool can be hidden independently, and the all-tools switch still hides every tool. Confirm the compact summary is right-aligned, content-fit, and has no leading icon; disable it and Save to restore the full-width bar and leading icon. Confirm there is no local-data setting or usage/export-download/bulk-clear footer, stored `localTools` overrides remain present but inert, local drafts and unfinished editor text recover after refresh without clearing existing data, and individual deletion and Undo still work. Confirm the switches use the official `Switch` primitive, legacy namespaces render nothing, no bundle or built-in-plugin configuration view is registered, and the profile contains exactly one runtime entry named `dsh-annotation`. 10. With dsh-market `1.45.0` or later installed, verify capability discovery, version check, progress, eligible force, rollback, refresh, and capability-gated restart. Remove the public API and confirm no legacy update route is called. 11. Record the verified DSH version in this file and the changelog. ## Browser behavior The CSS Custom Highlight API is an enhancement for persistent annotation ranges. Without `CSS.highlights` and `Highlight`, drafts and sent annotations remain in the composer list and timeline, numbered source markers remain clickable, and navigation still scrolls and renders its independent transient quote-range overlay. `localStorage` availability depends on site permissions and privacy mode. Denial is fail-soft: in-memory drafts work until the page closes, and the UI warns that refresh recovery is unavailable. Enablement, automatic attachment, and compact summary are Host-backed; browser storage is read only for the one-time pre-0.1.3 enablement migration and the per-Session draft records. ## Forward compatibility goal When DSH exposes an additive assistant-body decoration or selection Slot, replace the in-place assistant decorator with that Slot. When a typed annotation conversation node or private Client-to-Host transport becomes public, migrate without changing protocol version 2 unless persisted JSON fields change.