dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH Web 输入框的 Markdown 编辑增强:有序与无序列表续行且组内序号恒连续、Tab 层级缩进、行内代码样式、代码块围栏自动闭合;发送的草稿保持字面 Markdown。
In one sentence: a pure client-side plugin for DSH (DeepSeek Harness) Web that adds Markdown editing aids to the conversation composer — list auto-continuation and renumbering, inline-code styling (backticks hidden once rendered), and code-fence auto-closing with atomic block interactions. Every editing gesture rides on Shift+Enter; Enter keeps DSH's native "submit" semantics and is never intercepted. Apart from the ordered-list renumbering, everything is edit-state visuals and key gestures only — the submitted text always stays literal Markdown, byte-for-byte faithful.
dsh plugin --profile web)The DSH composer is a Lexical plain-text editor that natively offers only "Enter submits / Shift+Enter soft-breaks" — writing Markdown means typing every literal character by hand. This plugin brings the composer's Markdown editing experience up to the level of mature chat products:
• bullet markers, monospace numbering, the inline-code background, and paired ``` fences rendering as a real code block — what you see approaches how the sent message renders;/ and @ menus, @xxx reference chips, image pastes, and IME input all behave exactly as before.Not for: rich-text (non-Markdown) editing, or rewriting/beautifying Markdown at send time — this plugin deliberately performs no send-state transformation.
you type edit-state rendering sent text
─────────────────────────────────────────────────────────────────────
- apple • apple (bullet marker) - apple
⇧↵ (Shift+Enter) - ␣ (next line auto-prefixed) …
1. step one 1. step one (monospace digits) 1. step one
⇧↵ 2. ␣ (auto-incremented, ...) …
`code` code (mono+bg, backticks hidden) `code`
```ts ⇧↵ ┌─────────────────┐ ```
│ ts badge │ (blank)
│ ␣ (caret here) │ ```
└─────────────────┘
By domain:
| Domain | Capabilities |
|---|---|
| Lists | bullet/ordered continuation (any visual line and any nesting depth, including soft-broken and pasted lines), split-at-caret continuation, empty-item exit, the per-group renumbering invariant (delete closes gaps / breaks restart / merges accumulate / nesting-aware), mid-insert shift-down, the level ladder (Tab sinks, Shift+Tab or Backspace-after-the-atom lifts — whole subtree rides along; a top-level lift unlists back to plain text), atomic atom delete (indent included), atomic arrow hops over atoms (indent included), no resting inside an atom, differentiated marker rendering (• dot / monospace digits) |
| Inline code | paired-backtick inner text styled as inline code, backticks hidden the instant the pair closes (zero-advance invisibility, real font metrics kept), revealed while a selection genuinely covers them (select-what-you-see) |
| Code blocks | ``` / ```ts + Shift+Enter skeleton closing, the type-```-at-a-content-line-head flow that preserves the tail, commit-only paired rendering (whole-block code look + floating language badge + zero-height marker lines), one-keystroke unwrap at block boundaries (markers removed, body kept), ↑/↓ skipping the marker lines, Shift+Enter inside a fence inserts a paragraph, fences open at the head of any line (soft-line promotion — a reference chip's embedded newline blocks only its own boundary, the rest still promote, v0.1.3), orphan close-marker cleanup |
0.1.7-rc.x (verified against 0.1.7-rc.2; the composer is a Lexical plain-text editor + @lexical/plain-text, lexical 0.49);^22.19.0 || >=24.0.0 — the same window DSH itself requires; the package's engines field mirrors it;The package declares a dsh.bundle.patch (cordis.patch.yml: one insert row mounting the host half's no-op entry, which makes the plugin appear in "Settings → Plugins").
From a local checkout (the usual development route):
dsh plugin --profile web add link:/absolute/path/to/dsh-composer-markdown
From the npm registry:
dsh plugin --profile web add dsh-composer-markdown
From GitHub (the built client.js is committed, so the plugin loads even when pnpm skips the prepare build script; pin a commit with #<sha> so a later push cannot change what you run):
dsh plugin --profile web add github:chendefine/dsh-composer-markdown
pnpm ≥ 10 blocks a git dependency's
preparescript until allowed. If you want the install to rebuildclient.jsfromsrc/client/, add the key pnpm prints (e.g.dsh-composer-markdown: true) underallowBuildsin the profile'spnpm-workspace.yamland re-run theadd— treat it as permission to run the package's code on your machine at install time.
Or through the DSH plugin marketplace (设置 → DSH插件市场) — tag the repo with the dsh-plugin topic and it is indexed automatically.
After installing, restart the dsh web service and refresh the browser page. With a link: install, edit the sources under src/client/, run node scripts/build.mjs to regenerate client.js — in dev mode HMR hot-reloads the regenerated artifact (no refresh needed; refresh once if HMR is not running).
composer-markdown; disabling restores all native behavior (effective after a refresh);dsh plugin --profile web remove dsh-composer-markdown.Disable/uninstall auto-cleans: the document listeners, the injected style tag, and every code-style format bit in the editor (the draft text content is unchanged).
Every editing gesture rides on Shift+Enter; Enter = native submit, never intercepted; a Shift+Enter that matches no scenario = the native soft break. Tab / Shift+Tab are claimed only when the caret's line is a list item (the level ladder) — everywhere else they stay native (focus traversal and the like). Plain Backspace / Delete / arrow keys are claimed only when they hit exactly the boundaries below — everything else keeps the native per-character behavior.
| Operation | Trigger | Behavior |
|---|---|---|
| Bullet/ordered continuation | Type content after a line-head - / * / N. (any indent depth), press Shift+Enter | The new line below is auto-seeded with the {indent}- / {n+1}. prefix (nesting level preserved), caret right after it; works on any visual line — paragraph heads, soft-broken lines, and multi-line-pasted lines alike |
| Split at caret | Shift+Enter with the caret mid-content of a list item | The line is cut at the caret: everything after it moves down and becomes the new item's content, spliced after the new prefix (byte-faithful); a caret at the line start / inside the marker / at the line end keeps the append-below behavior |
| Mid-insert shift-down | Shift+Enter inserting a new item mid-group | The new line takes n+1 and every member below in the same group shifts +1 (1. 2. 3. inserting between 1 and 2 → 1. 2. 3. 4.); the group stays continuous and duplicate-free; insert and shift share one undo step |
| Renumbering (the invariant) | After any change (delete/insert a line, empty-item exit, paste, undo/redo) | Every ordered group (consecutive ordered lines, one indent, outside fences) is always numbered 1. … n. from its first member: a deleted line closes the gap below; a group split by a plain/blank/bullet line restarts its tail at 1.; deleting the splitting line merges the two runs into one continuous count; deeper indents merely suspend the outer group (a nested sublist never interrupts the outer run — its count resumes when the outer indent returns), nested groups normalize independently. Deliberate trade: a group cannot hold a non-1 start or a manual gap — hand-typed digits snap back (continuity IS the contract) |
| Empty-item exit | Shift+Enter on an item that is only a prefix | The prefix is removed and the empty paragraph kept (an empty line on soft lines) — press twice to cleanly leave the list; the caret stays on the emptied line (v0.1.2) wherever the item sat — trailing, mid-paragraph between items, or the block's first line — never rolled onto the previous item's end nor clamped onto the next line's head |
| Level ladder — sink | Tab with the collapsed caret anywhere on a list item's line (mid-content included) | The item and every deeper line below it (nested sublists, deeper-continuation text — the whole subtree, recursively) each gain 2 spaces of indent; same-level siblings and everything after them are untouched; the caret rides its in-line position; the whole subtree moves in one keystroke, one undo step; the reshuffled ordered groups normalize on the invariant right after (a 2. nested under a parent renumbers to that nested run's 1.). Sinking is capped (v2.9): an item may sit at most ONE level below its parent — the nearest item line above (plain lines skipped, blanks/fences ending the search). An item already one level below its parent, and a list's FIRST item (nothing above to nest under), get a claimed-but-inert no-op on Tab (focus never jumps away mid-list-editing) — the ladder cannot descend without bound |
| Level ladder — lift / unlist | Same positions, Shift+Tab — or Backspace with the collapsed caret right after the list atom (at the content head) | A nested item (indent ≥ 1 space) → the item and its whole subtree each lose min(2, indent) spaces (odd hand-typed indents floor at 0); a top-level item → unlisted: the whole atom dies, the content stays as a plain-text line (the caret parks on that same line, v0.1.2), the subtree still rises one level. The plain line an unlist leaves behind splits an ordered group — the same-indent members below restart at 1. per the invariant. Holding Tab sinks once and then holds at the cap; holding Shift+Tab climbs one rung per repeat — focus never escapes an item line on either key |
| Atomic atom delete | Delete with the collapsed caret right at a list item's line head (before the whole atom) | The indent + marker atom goes in one stroke (␣␣1. — all of it), the line becomes top-level plain text, its own undo step; the indent can never be eaten character by character |
| Atomic arrow hop over atoms | Plain ← / → with the collapsed caret at any list-atom boundary | → at the line head jumps clear over the whole ␣␣1. / ␣␣- (indent included) to the content's first character; ← right after the atom jumps back to the line head; a caret already inside the atom (including inside the indent spaces — a click, a vertical move) exits to the far edge — the keyboard never walks the atom interior. Shift+arrows stay native (a selection can still cover exactly the digit characters for manual renumbering) |
| Differentiated marker rendering | Any visual line headed by - / * / N. (outside fences, empty prefixes included) | Ordered digits switch to the code font; the bullet dash/star renders as • (dot + space as one marker unit); the marker bytes are kept verbatim in the draft/copy/send text; a selection genuinely covering a marker character reveals the raw character |
| No resting inside an atom | A collapsed caret landing inside a list atom (indent spaces included) via any path (↑/↓, mouse click) | The caret is homed to the nearest atom edge (ties snap to the line head); the edges themselves (line head / content head) are legal rest points; selections are unaffected |
`non-empty` whose content stays on one line and whose inner-edge characters are non-blank (full-width space / NBSP included): the inner text gets the inline-code look (code font, light background, rounded corners — reusing the DSH theme tokens); ``-style multi-backtick delimiters are not recognized (naive left-to-right pairing).| Operation | Trigger | Behavior |
|---|---|---|
| Skeleton closing | The caret's line is exactly ``` (optionally with a language id ```ts) and the caret sits inside/after the marker run, Shift+Enter | Inserts the three-line skeleton ``` / empty / ```, caret at the head of the empty line; works when typed at the head of any line (soft-broken and pasted lines included); a caret before the marker run (bare ``` line, offset 0) is plain text and keeps the native soft break |
| Content-preserving flow | Type ``` at the head of a line that already has content (caret still right after it), Shift+Enter | Splits after the ```: the marker line keeps ```, an empty line + closing skeleton is inserted, and everything after ``` on the original line moves below the closing marker (outside the block, byte-faithful); the language-id flow is unaffected (```ts at line end still yields the badged skeleton) |
| Paired rendering | A paired ```…``` region in the draft | The whole region renders as one code block: code background + code font inside (the same theme tokens and geometry as the message-side CodeBlock); the first marker line is zero-height, the language id shows as a floating badge at the block's top-right; the ``` markers are fully imperceptible (invisible, never host the caret); an unclosed ``` is plain text, always — typing ``` mid-draft never swallows the content below into a block |
| One-keystroke unwrap | Backspace / Delete at a block boundary (← at the first body line's head / the paragraph after the block; → at the paragraph before the block / the last body line's end) | One keystroke removes the pair of ``` marker lines: the block un-renders, the body survives verbatim as plain paragraphs, the caret stays put — its own undo step |
| Leaving the block | ↓ at the last body line's end (an empty paragraph grows below when the block ends the draft) | The caret drops to the new paragraph below the block to continue typing plain text; ↑/↓ skip the marker lines at block boundaries; the marker-line eviction lands a text-anchored caret (v0.1.3), so ↑/↓ stay armed from a body edge the caret was homed onto — the box top/bottom is never a keyboard trap |
| New paragraph inside a fence | Shift+Enter inside a fence (the closing line included) | Inserts a new paragraph (not a soft break), split at the caret; plain Enter inside a fence still submits |
<br>);/ or @ trigger menu is open: the Enter family is fully yielded;isComposing / keyCode 229 / a 10 ms window after compositionend — the same guard the DSH input machine itself uses);@ reference chips, image drag-drop/paste, busy/locked states, and Q&A/approval cards are unaffected;preventDefault-ed is yielded to; keys this plugin consumes get stopImmediatePropagation.A DSH plugin has a host (node) half and a browser half; this plugin is the extreme pure-client shape:
┌─ host half (node) ────────────────────────────────────────────┐
│ index.js a no-op carrier: an importable entry row for │
│ the bundle, so the plugin shows up in │
│ "Settings → Plugins" and is toggleable there │
└────────────────────────────────────────────────────────────────┘
┌─ browser half (web) ──────────────────────────────────────────┐
│ client.js the single-file load artifact (the DSH │
│ loader only accepts single-file bundles), │
│ generated from the 15 src/client/ modules by │
│ scripts/build.mjs and committed; registers │
│ via window.__ModuleLoader__.load │
│ ({id, factory}), with an in-bundle │
│ CommonJS-style module registry │
└────────────────────────────────────────────────────────────────┘
constants.js constants + line-grammar regexes + CSS class names
(the stylesheet composes its selectors from them — no drift)
editor.js the host-contract seam: the ONLY module touching the
host's underscore internals
(__lexicalEditor/_nodes/_nodeMap/_selection/_compositionKey)
grammar.js the line-grammar substrate: visual lines, fence intervals
+ committed coverage, the shared caret-line resolution,
the selection-covers reveal rule — derived once per read
doc.js the document read: block/leaf flat geometry + caret/selection
mapping (committed/live flavors) + the husk-block query
fence-plan.js pure fence projections and decisions (line/block roles,
soft-line promotion, fence pairs, orphan cleanup,
atomic keys, caret homing)
code-plan.js the pure inline-code kernels + per-block span/delimiter
projections
enter-plan.js pure Shift+Enter arbitration (one decision tree: fences
first, then lists)
list-plan.js pure list planning (the state-driven renumber walk, marker
glyphs, atomic delete/hop/homing, join/detach, shift-down,
re-anchor)
analysis.js one analyzeDraft(texts): the model + every domain projection
edits.js the edit algebra: the ONLY home of every live-node mutation
(flat-range erase / soft-line promotion / paragraph split /
digit rewrite / format shapes)
style-sheet.js the CSS text (composed from the class constants) + the
style-tag lifecycle
present.js the presentation layer: block marks (fence classes / badge)
+ the ONE glyph-mark engine ({at, class, reveal} table)
+ stripDom
gestures.js the key surface: guards + the gesture policy table
(prepare/apply/history steps) + the one claim path
restyle.js the convergent engine: the ordered STAGES table (normalize →
DOM marks → repairs → caret home → code/marker shapes),
one analysis per pass, at most one structural commit
+ cross-pass memory + loop guard; instance state, never
module globals
index.js the composition root: lifecycle + __internals assembly
Features are not individually plumbed pipelines — they are rows over one set of orthogonal abstractions:
\n; a soft break's \n and a pasted \n are the same thing), fence pairing with committed coverage, and caret-line resolution are derived once in grammar.js; lists, inline code, fences, and Enter arbitration all consume the same model — they cannot drift apart by construction.analyzeDraft(texts) assembles the fence roles, soft-line promotion, inline-code spans/delimiters, list marker glyphs, and the renumber invariant into a single object — every projection sees the same lines and the same fence coverage.edits.js only) are strictly separated; neither the gesture policies nor the convergence engine touch nodes directly.