devockr/idsh--packages-mobile ↗★ 1

@idsh/mobile

优化移动端触控与输入体验的 Web 客户端插件 适合在手机或平板上使用 DSH Web 端的移动端用户

套件
@idsh/mobile
相容性
待驗證
Harness 依賴範圍
^0.2.0-rc.1 || ^0.2.1-alpha.1
Cordis 依賴範圍
^4.0.4 || ^4.0.5-alpha.2
版本
0.0.1
最近更新
2026年10月17日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:devockr/idsh#d72095bd6e4c459f3caee7c024b3f3eac539d65b&path:packages/mobile

description: 'The touch-device half of the Web UI as a client plugin: coarse-pointer CSS, Enter inserting a line break instead of sending, and an iOS home-screen icon.' kind: 'package-bundle'

@idsh/mobile

Summary

Three fixes for using the Web UI on a phone, all of which the self-hosted image got by patching dsh's installed files at build time. Doing them from a client plugin means the image can ship an untouched dsh and install a package instead.

Stock dshWith this package
page panning on touchbrowser defaulthtml { touch-action: pan-x pan-y } under @media (pointer: coarse)
focusing a control on iOSSafari zooms the page, because the control is under 16pxselect, :read-write:not(.xterm-helper-textarea) { font-size: max(16px, 1em) !important }
Enter on a touch keyboardsends the message (there is no Shift key to reach a newline)inserts a line break; the send button sends
home screen icon (iOS)the stock favicon, which iOS ignores — an SVG is not a usable apple-touch-iconthat favicon rasterised to a 512px PNG, white glyph on a mid-tone brand-blue square

The CSS is byte for byte what the image injected into dist/index.html; the composer and icon behaviour is the same intent, reached a different way (see below).

How the Enter rewrite works

The composer is a Lexical editor, and its stock behaviour sends on a plain Enter. Two things follow from that, and the second is the one worth remembering:

  • The editable is found through composedPath(), not closest(): the composer writes contenteditable="" rather than "true", and the editor sits inside a shadow root, where the event target is retargeted to the host.

  • A dispatched keyboard event cannot do this. Untrusted events have no default behaviour, so a synthetic Shift+Enter inserts nothing however it is routed — measured against the running editor. Lexical's insertLineBreak command runs from beforeinput, so the rewrite stops the plain Enter in the capture phase and dispatches

    new InputEvent('beforeinput', {
      inputType: 'insertLineBreak',
      bubbles: true,
      cancelable: true,
    })
    

    which the editor accepts and turns into a real break, as in its DOM.

Why the icon is one tile

apple-touch-icon and iOS have three properties that decide this, all of them measured:

  • iOS ignores an SVG icon, which is why the phone used to keep its own default.
  • It samples the icon when the shortcut is added, so a prefers-color-scheme pair would freeze whichever theme was active at that moment. Apple's own dark-icon treatment re-renders and swaps the background by itself, so one mid-tone tile serves both.
  • It only ever downsamples. 512px covers the largest slot in service (iPad Pro at 167pt × 2 = 334px); 180 is the iPhone slot and soft there, and 1024 measured 47.2KB of data URI against 16.9KB at 512. The artwork comes from the favicon the app already serves, because nothing under a plugin package is reachable by URL.

Install

dsh plugin --profile web add @idsh/mobile

Reload the page. With DevTools open, document.body.dataset.idshMobile reads css+enter coarse:true icon:ok on a phone.

Verified

On the running deployment, in a phone-sized touch context, with the plugin installed:

composer element   DIV contenteditable="true"           ← what composedPath() finds
plain Enter        
  ← the rewrite inserts a real break
rasteriser         180/512/1024px → 4.1KB / 16.9KB / 47.2KB of data URI

Both fixes are also confirmed on a real iPhone: the home-screen shortcut shows the blue tile with the white glyph, a plain Enter in the composer inserts a line break instead of sending, and a plain Enter still reaches the session-search input untouched.

node tools/ui-probe.mts --scenario mobile asserts all of it: the plain Enter never reaches the document (enterEvents: []), both contenteditable spellings are sent insertLineBreak (insertedBreaks), the session-search input keeps its own Enter, and a regression on any of those lands in failures — checked by pointing the scenario at a build that still dispatches the old shift+Enter, where those three assertions fail.