dsh-client-ui-enter-newline
Verifieddsh-client-ui-enter-newline ยท v1.0.0 ยท MIT ยท Web UI
DSH Web client plugin: an "Enter inserts a newline only" checkbox beside the composer's model selector, which suppresses Enter-to-send while checked.
Install
dsh plugin add dsh-client-ui-enter-newline Confirm the layer applied with dsh --profile default --dump-config โ see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-client-ui-enter-newline
A DSH Web client plugin. It adds one checkbox โ Enter ้ฎไป ๆข่ก โ to the left of the model / reasoning-effort selector in the composer toolbar:
| Checkbox | Plain Enter |
Shift+Enter |
Ctrl/Cmd+Enter |
|---|---|---|---|
| unchecked (default) | sends, exactly as shipped | line break | shipped accelerated send |
| checked | line break, submit is suppressed | line break | shipped accelerated send |
The preference is stored in the browser under localStorage['dsh.enterNewlineOnly']
("1" = checked). A missing or unreadable key means unchecked, so the plugin's
default state is the untouched DSH behavior.
Layout
| File | Role |
|---|---|
lib/index.js |
Host half: an empty Cordis entry, so the loader has something to mount. |
lib/client.js |
Browser half: the checkbox component, the preference store, and the Enter guard. |
How it hooks in
- The checkbox is registered into the shipped
conversation.input.leftslot โ the composer toolbar row's leading group. That group renders as a sibling immediately before the trailing group that holdsconversation.input.rightand the model selector, so the control lands directly on the selector's left with the toolbar's own flex spacing. The plugin adds one inline cell; no shipped markup is replaced, reordered, or restyled. The slot is session-scoped and is never hidden by the toolbar's activity expansion, so the control lives exactly as long as the resident composer does. - The Enter guard is a
keydownlistener in the capture phase, registered onwindowand ondocument. The composer's Enter command lives inside Lexical, which listens on the editor root in the bubble phase, so a capturing listener runs strictly earlier. When the checkbox is on and the event is a plain Enter inside the composer's draft host, the listener stops propagation โ Lexical never sees the key, and because the event is notpreventDefault()ed the browser still inserts the line break, which is exactly whatShift+Enterdoes. Every other key, and every Enter while the checkbox is off, is untouched. - The draft host is detected from the live node, not a fixed attribute value: any
element whose
isContentEditableis true (which also coverscontenteditable="plaintext-only") or atextarea, as long as it sits inside a[data-composer-card]. A kernel release that changes how the draft is hosted therefore cannot silently disable the guard, and no editor outside the composer is ever touched. - The preference the guard acts on is read from the rendered control itself
(
checkedon the plugin's own checkbox), not from an in-memory copy, with the page-global record as the fallback for moments when the control is unmounted. The control'scheckedproperty is the value the user is looking at, so nothing that can desynchronize memory โ a hot reload leaving an older bundle execution's listener installed, or a storage write that never landed โ can make the guard disagree with the checkbox on screen. Two historical bugs came from getting this wrong: a module-local flag that went stale across reloads, and a seat check that also excluded anything inside a composer card containing the seat (which is true of the composer's own editor, so the guard skipped exactly the events it exists for). The regression tests intest/guard.test.mjscover both.
Shift/Ctrl/Meta/Alt/AltGr chords and IME composition are never intercepted.
Install
The package declares dsh.bundle.patch, so it installs as an ordinary DSH bundle and
registers its own loader entry โ no hand-edited YAML, no build step on the user's
machine:
dsh plugin --profile <profile> add dsh-client-ui-enter-newline # from npm
dsh plugin --profile <profile> add ./dsh-client-ui-enter-newline-1.0.0.tgz # from a tarball
dsh plugin --profile <profile> add github:<you>/<repo> # from a git host
add appends the bundle to dsh.profile.bundles; dsh plugin --profile <profile> remove dsh-client-ui-enter-newline takes it back out. Check the composed tree before
booting with dsh --profile <profile> --dump-config.
Installing by hand is equally fine: put the package where the profile's module resolution can see it and add the same row the bundle layer adds โ
# <DSH_HOME>/profiles/<profile>/cordis.patch.yml
- insert:
- id: enter-newline
name: 'dsh-client-ui-enter-newline'
A profile that watches its patch file picks the entry up live: the host side becomes an active loader entry and the browser half mounts without restarting DSH. A profile that does not hot-reload its patch layer needs a restart.
Distribute
Publish the built package โ the bundle is plain JavaScript, so there is nothing to
compile, and lib/ ships as-is. Three routes, in order of how little the user has to do:
| Route | User command | Build on install |
|---|---|---|
| npm | dsh plugin add dsh-client-ui-enter-newline |
none |
tarball (pnpm pack) |
dsh plugin add ./dsh-client-ui-enter-newline-1.0.0.tgz |
none |
| git host | dsh plugin add github:you/repo |
prepare must build lib/, and the user must allow it |
Prefer npm or a tarball: pnpm โฅ10 refuses to run a git dependency's prepare script
until the user explicitly allows it in the profile's pnpm-workspace.yaml
(allowBuilds:), and that permission is "let this package run code on my machine at
install time". Distributing built artifacts keeps that decision off the user's plate.
Before publishing, the five fields that decide whether an install works rather than merely succeeds:
main/exportspoint at the built entry (lib/index.js), never at sources;"type": "module";filesincludes the entry andcordis.patch.ymlโ the patch is the bundle layer, so omitting it yields a package that installs but activates nothing;dsh.bundle.patchpoints at that patch file โ without it the package is only a plain dependency anddsh pluginwarns that no layer was activated;versionbumped per semver โ a published version can never be overwritten.
Then:
pnpm pack --dry-run # confirm lib/ + cordis.patch.yml are inside the tarball
pnpm publish # scoped packages: publishConfig.access = public
dsh plugin --profile fresh add ./dsh-client-ui-enter-newline-1.0.0.tgz
dsh --profile fresh --dump-config # expect a "# == dsh-client-ui-enter-newline" layer
Verify in a fresh profile: an install tested in your own profile can pass on dependencies that happen to be there already.
Verify
node test/guard.test.mjs lib/client.js
The check loads the real bundle against a minimal DOM stub and asserts the shipped
behavior, the suppressed behavior, every un-intercepted chord, IME composition,
out-of-composer targets, plaintext-only and detached draft hosts, and the checked
styling on mount. It deliberately cannot prove anything about the live Lexical editor
or the shipped markup โ open the composer and press Enter for that.
Two live probes confirm the wiring without touching the page:
cordis_inspect_queryโ hostConfigโlistConfigswith{"name": "dsh-client-ui-enter-newline"}returns the loader entry.cordis_inspect_queryโ clientSlotsโlistSubTreewith{"root": "conversation.input.left"}listsenter-newline-toggleas an active occupant.
Uninstall
Delete the loader entry above, then the installed package directory. The stored
preference key is inert on its own and can be removed with
localStorage.removeItem('dsh.enterNewlineOnly').
Limitations
- The guard targets the composer's plain
Enter. The primary Send button is a separate control and still sends while the checkbox is on โ by design: the preference is about the keyboard, and a button is an explicit click. - While the checkbox is on, React's synthetic
keydownfor that specific Enter is suppressed along with the composer command. This is intentional (no submit path may remain) and affects no other event. - The guard locates the composer card and the rendered control through the shipped
[data-composer-card]marker plus this plugin's owndata-enter-newline-seat; the draft host itself is recognised viaisContentEditable(ortextarea). A DSH release that renamesdata-composer-cardwould make the plugin stop finding the control, and the guard then falls back to its stored preference โ so the checkbox keeps working, while the "read exactly what the user sees" guarantee no longer holds. Re-check that marker after a kernel upgrade.