DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Rail Tones — DSH Plugin for DeepSeek Harness
← Plugins
R

dsh-rail-tones

Rail Tones

Session navigation bar sounds: play pentatonic cue sounds based on session position when hovering over, dragging, clicking markers on, or scrolling through the official Turn Rail.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:oqozzzz/dsh-rail-tones#73aa999be8f1c85f639e1eccda9b8f614a432a04
READMECompatibilityVersions

Compatibility and provenance

Rail Tones is published as dsh-rail-tones and currently resolves to version 0.2.7. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/13/2026

Versions

0.2.7stable
10/13/2026
0.2.3stable
10/10/2026
0.1.12stable
10/6/2026

Related plugins

Loading related plugins…

Latest
0.2.7
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/13/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in

Related plugins

More verified plugins in ui-customization.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Experimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profileExperimental Web profile layer for Agent Teams Remote and UI pluginsAb Chartdsh-ab-chartChart rendering tool with the browser card that draws its calls, in one publishable plugin packageClient Ui Skill Explorer@linxin666/dsh-client-ui-skill-explorerDSH skill center: browse loaded skills by source (bundled / project / user / custom / runtime), enable or disable, create and delete, in a web GUI panel.

README

dsh-rail-tones

会话导航条音效 —— 为 DeepSeek Harness Web GUI / DSH Desktop 复刻「在会话导航条上竖向滑动或点击刻度时,播放音阶提示音」这一功能。

  • 音高 = 位置:导轨内容位置 0→1 映射到大调五声音阶 2 个八度(A3…F#5,10 个音级),越高越靠后。
  • 四种触发:光标沿导轨竖向划过(不需要按键)、按住拖动、点击刻度、在导轨上滚轮翻阅。
  • 零素材、零依赖、零宿主服务:Web Audio 实时合成;不 require 任何 @deepseek-ai/* 客户端包;宿主半边为空。
  • 只读观察:只用 document 上的捕获阶段 + passive 委托监听,不写 DOM、不 preventDefault、不 stopPropagation,官方跳转 / 悬停预览 / 键盘聚焦行为完全不变。

当前版本 v0.2.7;版本历史见 CHANGELOG.md。

安装

# <DSH 安装目录> = DeepSeek Harness 的安装位置,CLI 在其 resources\runtime\cli\bin\dsh.cmd
#                 (该 bin 已在 PATH 里的话,直接写 dsh 即可)
# <插件目录>     = 本仓库在你机器上的绝对路径
& '<DSH 安装目录>\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop add link:<插件目录>

安装后重启 DSH Desktop(插件名单在实例启动时读取)。

使用

  1. 打开任意会话(真实提问 ≥ 2 轮才会出现官方导轨)。
  2. 光标沿导航条竖向划过(不按键)→ 随位置发声;按住拖动同理。
  3. 点击某个刻度 → 发出该刻度对应的音(跳转行为不变)。
  4. 指针停在导轨上滚动滚轮 → 刻度滚动经过指针时发声。
  5. 设置 → 导航条音效:开关、音量(0–100%,0% 无声、100% 最大)、音效选择(点击展开列表选择音色:正弦音 / 钢琴 / 清音 / MiMo)、试听。开关关闭后立即生效(试听同样静音),音效选择点选即生效、刷新后保持。

音符以「刻度」为单位:事件先吸附到指针所在(或最近)刻度的中线,再换算音高。 因此同一个刻度上,光标划过、按住滑动、点击、滚轮得到的是同一个音。

同一个音级只响一次:音高把整场会话分成 10 档(2 个八度五声音阶)。只要指针还在 同一档里 —— 哪怕手抖出几十个 pointermove、哪怕宿主在重渲染、哪怕刻度节点被虚拟 滚动回收 —— 都不会再发声;只有音级变化、或做出明确手势(按下 / 键盘回车激活) 才会再响一声。指针离开导轨超过 0.3 秒后再次划入同一音级,会重新发声。

两道防抖保险(v0.1.9):

  • 刻度吸附滞回:相邻刻度必须「明显更近」(4px 余量)才切换,指针在相邻刻度分界线 上抖动不会来回跳档;
  • 离开延迟确认:任何「指针离开导轨」的信号(包括宿主重渲染导致的瞬时异常目标、 蹭到悬停预览气泡)都要持续满 300ms 才清空去重锚点 —— 瞬时抖动不再被误判成 「离开后又进入」而重放同一音。

这也意味着:会话很长时,一个音级覆盖很多个刻度,同一档内滑动是安静的 —— 这是 「音高表示会话位置」的必然结果,不是丢音。

音频上下文需要一次用户手势才会启动:第一次点击/按键之后,纯划过(悬停)才有声音; 在此之前划过是静音的(浏览器自动播放策略,非故障)。

改动后如何生效

client.js 是手写的客户端产物,没有构建步骤。宿主在实例启动时对每个 client.js 做快照并按下发版本号(mtime/ctime/size)提供;HMR 只在这一行被重新发布后才换 URL。 所以改完 client.js 之后:

  1. 先刷新页面试试;
  2. 没变化就重启 DSH Desktop(最可靠);
  3. 用 设置 → 导航条音效 卡片右下角的 dsh-rail-tones vX.Y.Z,或 __dshRailTones.status().version 确认浏览器里真正跑的是哪一版 —— 版本号没变就说明 新产物还没下发,此时讨论行为没有意义。

验证

# 语法与单元测试(纯逻辑 + 清单契约)
node --check client.js
node --check index.js
node --test test/

# 宿主是否已收录该客户端行:`dsh plugin` 是否写入了依赖与 bundle
Select-String -Path "$env:USERPROFILE\.dsh\profiles\desktop\package.json" -Pattern 'dsh-rail-tones'

# 插件自报(宿主半边注册的只读路由,无需 DevTools、无需页面)
curl.exe -s http://127.0.0.1:19387/rail-tones/status
Get-Content "$env:USERPROFILE\.dsh\rail-tones-report.json" -Raw

/rail-tones/status 的返回直接回答「重启后插件到底有没有起来」:

  • 404 —— 这一行在启动时根本没有被组装(插件没加载,跟卡片注册无关);
  • 200 + report: null —— 宿主半边起来了,但浏览器半边还没上报(客户端没挂载);
  • 200 + report —— 浏览器半边挂载了,看这几个字段:
    • settingsFailure —— registerSettingsSection() 整体抛异常时的异常原文(含栈顶三行);
    • settings.react / settings.reactError —— 取 React 是否成功、失败原文;
    • settings.slots / settings.error —— ctx.slots 是否就绪 / 具体错误;
    • settings.injected / settings.registered —— 槽位注入与卡片注册的结果;
    • settings.retriesLeft —— 还在重试时剩余次数;
    • report.payload.reason —— 这次上报的触发点:mount / settings-registered / settings-threw(注册链抛错,仅首次失败报一次)/ settings-failed / settings-gave-up(重试耗尽)/ manual;
    • rails —— 当时识别到的导轨(未接触过导轨时为空数组)。

三者互斥地指向不同的修复方向:settingsFailure 非空 = 注册流程本身炸了; settings.react === false = 拿不到 React;settings.slots === false = 服务还没就绪; injected: true, registered: false = 槽位在但注册被拒。

浏览器半边挂载时、以及槽位注册成功/失败时都会自动上报一次; __dshRailTones.report('手动') 可随时再报一次。 这两条路由只读、只在本机回环上、只服务于诊断;~/.dsh/rail-tones-report.json 删掉也无影响。

注意:dsh web 对未认证请求返回 401(“dsh web authentication required”)的路由是页面与 应用接口;插件自己注册的路由不在那道栅栏内,可以直接 curl。

浏览器 DevTools 控制台:

__dshRailTones.status()
// { version, instances, enabled, volume, voice, notesPlayed, mutedCount, lastNoteHz, lastTick,
//   railSeen, audioState,
//   settings: { registered, attempts, error },
//   dedup: { accepted, skippedByDegree, lastDegree, lastTickIndex,
//            lastPosition, lastSkip, resets, lastReset, offRailPending, railKnown } }
__dshRailTones.log(10)        // 最近 10 次调度:{ at, hz, source, tick, degree, played, egg, strum }
__dshRailTones.rails()        // 页面上每个「含刻度按钮的 nav」→ { used, ticks, width, height }
__dshRailTones.play(0.5)      // 手动发一声(会话中段)
__dshRailTones.setEnabled(false)
__dshRailTones.setVolume(0.3)
__dshRailTones.setVoice('mimo')    // 注册表内任意 id('sine' | 'piano' | 'chime' | 'mimo'),返回生效值

排查时看这几处:

  • version / 设置卡片右下角的版本号 —— 不是最新版就说明新产物还没下发(重启 DSH Desktop);
  • instances —— 正常为 1(挂载时会主动销毁上一份残留实例);
  • settings.registered —— 设置卡片没出现时看这里:false + error 就是槽位注册失败的原因;
  • mutedCount / log() 里的 played: false —— 「没声音」先看这里:调度被音频层丢弃 (音量为 0、上下文不可用或还卡在 suspended)。mutedCount 持续增长而扬声器无声 → 查 audioState 是否为 suspended(做一次点击/按键手势即可唤醒);
  • dedup —— 抖动时 skippedByDegree 应持续增长而 notesPlayed 不动;resets 应保持为 0 (>0 说明去重锚点被清过,lastReset.reason 指明是哪条路径清的)。

railSeen: false 说明还没命中过导轨(会话轮次不足或指针未接触导轨)。 rails() 里 used: false 的条目是被排除的 nav(例如恰好也是 <nav> 的外层包裹元素)—— 它们不会发声;如果这里出现了两条 used: true,说明界面上确实有两条导轨(例如同屏两个会话视图)。

实现要点

关注点做法
刻度识别nav 内含 button[data-index](官方 TurnMark),不依赖任何哈希 CSS 类名
槽位服务时序客户端这一行在组合脚本里排在前面,apply 执行时 ctx.slots 可能是 undefined(模块级 inject: ['slots'] 在客户端这条路径上不保证等到服务就绪);ctx.get('locale') 未就绪时返回的也是 undefined 而不是 null,所有服务判定必须同时挡住两者(v0.1.9 只判 !== null,2026-10-06 在真实会话中炸过一次)。因此用 ctx.inject(['slots'], cb) 作用域注入等服务就绪,且任何失败(slots === false 或整条抛错)都按 400ms × 最多 60 次有界重试,放弃时报 settings-gave-up
导轨唯一性只认直接拥有刻度的 nav(tick.closest('nav') === nav):外壳恰好也是 <nav> 时不会把它当成第二条导轨,避免整块面板乱发声
刻度吸附所有触发都先 tickAt(nav, y) 取指针所在/最近的刻度,用刻度中线换算位置 → 同一刻度上四种手势得到同一个音;另加 4px 滞回:另一个刻度明显更近才切换,分界线抖动不跳档
触发方式pointermove:光标划过(hover,不需要按键)与按住拖动都发声;pointerdown:点击发声;click.detail === 0:键盘激活发声;wheel:滚动停止 90ms 后取离指针最近的刻度
预览气泡hover 只在导轨滚动条区域内发声(scroller.contains(target)),鼠标移到刻度预览气泡上时不发声;且「离开导轨」统一走 300ms 延迟确认才清去重锚点,重渲染瞬时异常 / 蹭气泡不会导致重放
滚动容器缓存WeakMap<nav, scroller>:命中一次后复用,避免高额 pointermove 触发 getComputedStyle 遍历
位置换算刻度祖先中唯一的可滚动元素(overflow-y:auto 且 scrollHeight > clientHeight)作为虚拟滚动容器,(y - rectTop + scrollTop) / scrollHeight
虚拟滚动不缓存刻度节点,每次事件重新查询;滚动时 data-index 只用于宿主自身渲染
音高映射默认(含 sine/piano/chime):degree = round(position × 9),hz = 220 × 2^((SCALE[d%5] + 12·⌊d/5⌋)/12),SCALE = [0,2,4,7,9]。MiMo:音色自带 pitchAt(context)(注册表条目里的映射优先),按原文 s(d,m) —— d = 刻度下标(data-index)、m = 刻度总数,C = round(d/max(1,m-1)×18)、f = NOTES[C%5] × 2^(⌊C/5⌋−1),NOTES = [261.63,293.66,329.63,392,440](C4 D4 E4 G4 A4)

MiMo 音色(v0.2.6:逐行移植,含彩蛋与刮弦音)

移植自小米 MiMo 桌面端(resources/app.asar → out/renderer/assets/SessionSpine-*.js 里那个返回 {hover, hit, endEngagement, resetEgg} 的 IIFE)。v0.2.4 的「复刻」有 5 处偏差导致听感相差甚远 (音高被压进最低 3 个半音、速度门槛方向写反、点击走了弱音档、响度放大 2.93×、按 10 档音级去重), v0.2.5 全部按原文改正:

要素mimo 原文本项目(v0.2.6)
波形F.type = "triangle"同
包络setValueAtTime(1e-4) → exponentialRamp(峰值, +8ms) → exponentialRamp(1e-4, +衰减)同
弱音档(hover)峰值 0.13、衰减 0.42s、停振 +0.46s时长同;峰值 v0.2.7 起 = 0.6 × 音量(响度适配,见「响度」行)
强音档(hit)峰值 0.3、衰减 0.9s、停振 +0.95s时长同;峰值 = 0.88 × 音量(与 sine 等峰)—— 点击刻度 / 键盘激活 / 试听走这一档
音高表[261.63,293.66,329.63,392,440](C4 D4 E4 G4 A4)同
档位C = round(d / max(1, m-1) × 18),d = 刻度下标、m = 刻度总数,f = NOTES[C%5] × 2^(⌊C/5⌋−1)同(mimoFrequencyAt(step, count))—— 5 刻度时 130.8 / 261.6 / 440 / 880 / 1568 Hz,整条 18 档阶梯铺满导轨
滑动门槛进入导轨 <100ms 静音;取样窗口 80ms;窗口内速度 < 600px/s(慢)才放行,快速甩动静音同(createMimoGate,常量 100 / 80 / 600);v0.2.6 增强:快速甩动不再静音,改播单频刮弦音(见下)
去重粒度每个刻度各响一声(c !== w.current)同(dedupKeyFor:mimo 按刻度下标,其它音色仍按 10 档音级)
响度绝对增益 0.13 / 0.3(mimo 本体没有音量滑杆)v0.2.7 响度适配:生效峰值抬到与其它音色接近 —— 强音档 = sine 的 PEAK_FACTOR(0.88)、弱音档 0.6(≈ 其 7 成),单频/刮弦/彩蛋/快速甩动全路径一致;两档仍保留「点击比划过响」的层级(约 +3.4dB)。音色/包络/音高不变,只抬电平

点击为什么是强音:原文 hit 里被 H = () => !1 封掉的只是彩蛋短句分支,紧随其后的 p(s(d,m), !0) 是正常路径 —— 点击刻度本来就该响 0.9s 的强音。v0.2.4 误判成「点击也是弱音」, v0.2.5 已改正:按下 / 键盘激活 / 试听 → 强音档;划过 / 拖拽移动 / 滚轮 → 弱音档。

刻度总数在虚拟滚动下的估计:原文用消息数组长度(s.length)。本项目从 DOM 取刻度总数, 虚拟滚动时只有已渲染的刻度在场,于是用 max(渲染刻度数, 最大 data-index + 1) 作为 m —— 渲染窗口与宿主一致,指针滑到窗口底部时能落到与之匹配的档位。

彩蛋旋律(v0.2.6,逐字移植原文 R/x/Y/k/j 与 S/U/V)

划过导轨时掷骰:每轮互动只掷一次(离开导轨重新获得资格,原文 endEngagement 复位 k)、 冷却 180s(上次彩蛋结束或被打断起算)、概率 5% —— 命中后从 5 首曲子里随机选一首, 整曲由后续手势逐音奏出(原文语义:每次划过 / 点击推进一个音符,不是后台自动播放), 播完整曲自动结束并重计冷却;离开导轨不打断进行中的曲子,卸载(刷新/禁用)才 resetEgg。 __dshRailTones.status().egg 可看 {active, songIndex, noteIndex, loops, rolled, lastFinishedAt}。

#曲子bpm音数时长
1致爱丽丝(开头 E5 ♯D5 E5 ♯D5 E5 B4 D5 C5 A4)764019.7s
2F# 小调练习曲(含 2 个三音和弦)1507720.4s
3卡农式主题(原文 g/P 两段交错组合)12010520.0s
4波莱罗片段723420.0s
5欢乐颂变奏728220.2s

刮弦合成器与「快速滑动」增强(v0.2.6)

playMimoStrum 逐字移植原文 i(freqs, strong, ring):每个频率叠 5 个分音 (基频三角波 + 2/3/4/6 次正弦泛音,增益 [1, .42, .22, .12, .06]),master 峰值按 1/√频率数 归一(和弦不削波)并串 lowpass(4200Hz 在 0.85F 处指数扫到 1400Hz), 总时长 F = clamp(0.25 + ring, 0.4, 2.6),首分音失谐 1.5 音分。彩蛋激活期间由它奏出旋律音。

与原文的一处有意差异:mimo 对「窗口内速度 ≥600px/s」的划过是静音的;本项目把快速 甩动改派为单频刮弦音(ring 用原文默认值 1.1、弱音档形状经 lowpass 扫频), 飞速翻阅导轨时是一串刮弦而非死寂 —— 属于用户明确要求的增强,不想要可把 createMimoGate 的快速分支改回 'mute'。快速帧不掷骰也不推进彩蛋(原文根本不会调 hover(),彩蛋语义不变)。

依旧不移植:原文被 H = () => !1 封死的 q/w() 短句路径 —— shipped 代码不可达,移植它反而偏离真实行为。

设置与存储

设置项存 localStorage['dsh-rail-tones:v1'] = {"enabled":true,"volume":0.5,"voice":"sine"}(每个浏览器各自一份;隐私模式下退化为仅本次会话有效)。存储不可用或内容损坏时回退到默认值,绝不抛错。

音量范围:0 … 100%(MAX_VOLUME = 1;0% 为无声,100% 为最大声音。v0.1.13 起由 0–200% 收窄为 0–100%)。 读取、写入、__dshRailTones.setVolume()、滑杆四处都按同一上限钳制,存量里越界的旧值读取时会被钳到 100%。 包络峰值为 音量 × 0.88,因此 100% 时是 0.88(较此前 0–200% 区间翻倍:旧 200% 峰值为 0.44),仍在满幅之内、不会削波。

音色(v0.2.0 起):voice ∈ {"sine","piano","chime","mimo"},默认 "sine"。白名单派生自模块里的 VOICE_OPTIONS 注册表 (每项 {id, labelKey, descKey, synth},条目直接持有合成器;可选 pitchAt 覆盖音高映射),读取与写入都按注册表钳制,非法/缺失回退默认; 存量数据没有 voice 字段时自动落默认音色(向后兼容)。 设置页「音效选择」点击展开候选列表(v0.2.1 起,遍历注册表渲染),__dshRailTones.setVoice('piano') 切换同一状态。 新增音效 = 注册表加一条 + 中英文案各一条,UI 与存储代码零改动(v0.2.4 起注册表为单一来源,条目自带合成器); 查不到合成器的未知 id 在引擎里兜底默认音色、绝不抛错 —— 这是给后续自定义音效预留的扩展点。

卸载

& '<DSH 安装目录>\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop remove dsh-rail-tones

删除后重启 DSH Desktop 即完全恢复原状(无残留文件、无宿主配置)。

项目结构

文件说明
client.js全部功能:手写客户端产物,无构建步骤
index.js宿主半边:只做两条只读诊断路由(/rail-tones/status、/rail-tones/report)
test/rail-tones.test.mjsnode --test 用例:纯逻辑、清单契约、设置注册、监听层与 apply 级接线
package.jsondsh.bundle.patch + dsh.client{platform:'web', immediately, inject} + ./client 导出;零依赖
cordis.patch.yml向 profile 的配置树插入本插件那一行
CHANGELOG.md版本历史

改动 client.js 后如何生效,见上文 改动后如何生效。

已知边界

  • 开发与验证环境为 DSH Desktop 0.2.0-rc.2(Electron 44 / Chromium 152);其它宿主版本不保证。
  • 会话真实提问少于 2 轮时官方不渲染导轨,此时不会有任何声音(设计如此)。
  • 滚轮发声在音频上下文尚未被用户手势激活时会静默跳过(浏览器自动播放策略;滚轮事件本身会尝试唤醒上下文,唤醒后正常)。
  • 音色有正弦音 / 钢琴 / 清音 / MiMo 四种,设置页可切换;如需新增音色,按 client.js 里 VOICE_OPTIONS 注册表上方的注释两步走即可(加条目 + 文案)。音色条目还可自带 pitchAt(context) 覆盖音高映射(MiMo 就是这么做的)。
  • 与官方导轨的耦合点是「nav + button[data-index] + 可滚动祖先」这三个语义特征;DSH 未来大改版会让插件无声降级(不报错)。

许可

MIT,见 LICENSE。

合成
正弦(默认):单个 sine 振荡器 + 6ms 线性起音 + 180ms 指数衰减,峰值 = 音量 × 0.88。钢琴(v0.2.0 起可选):6 个 sine 分音并联进一条 master gain(权重按 Σ 归一 → 不削波)、频率带轻微失谐(f·n·(1+0.0002·n²))、高次分音按各自 τ 更快衰减(先亮后暗),master 峰值 = 音量 × 0.88 × 0.8 ≈ 0.70。清音(v0.2.2 起可选):主音(基频 + 1.5× 钟类分音)+ 上方五度 40ms 引导音的「叮-咚」软钟音,30ms 软起音、850ms 指数衰减(−20dB ≈ 0.22s),参数逆向自对 mimo 桌面端提示音的实测比对与逐帧频率跟踪(见 compare/ 两份报告)。MiMo(v0.2.5 起可选):逐行移植 mimo 的发声函数 —— 单个 triangle 三角波振荡器 + 1e-4→8ms 指数升到峰值→指数衰减回 1e-4;hover 弱音档衰减 0.42s、点击强音档衰减 0.9s(停振各留 0.05s);生效峰值 v0.2.7 起为弱 0.6 / 强 0.88(响度适配,见下),不再是原文裸增益 0.13/0.3。v0.2.6 起再加 playMimoStrum(原文 i() 的刮弦合成器:5 分音 + lowpass 4200→1400 扫频 + 1/√len 归一)—— 由彩蛋旋律与「快速滑动」增强路径共用
节奏控制闸门 = 去重键:key 没变就不发声(mimo 音色 key = 刻度下标,其它音色 key = 10 档音级,见 dedupKeyFor)。指针停在原处(哪怕抖出几十个 pointermove、哪怕宿主重渲染、哪怕刻度节点被虚拟滚动回收)都不会重复响;key 变了才响,离开导轨后再划入同一 key 也会响。另有最小间隔 70ms。注意:监听层靠 env.signal() 的返回值更新锚点,适配器必须 return controller.signal(...) —— v0.1.11 修掉了这里漏掉的 return:此前锚点从不更新,这道闸门自 v0.1.5 引入起就是死的;监听层自身那道闸门也必须用同一个 env.keyFor,否则 mimo 会被 10 档音级提前拦掉
明确手势pointerdown(按下)与键盘激活(click.detail === 0)带 repeat:即使与上一声同音级也再响一次作为确认,仍受 70ms 最小间隔约束(v0.1.10 起 repeat 才真的透传到调度器;此前在 apply 的适配器里被丢掉,250ms 内的点击是静音的)
槽位注册slots.register() 在槽位尚未被父级声明时会抛错(slot "…" is not declared)。启动早于设置页时不能一次注册就放弃:内层按 400ms 有界重试(inject 45 次、register 12 次),成功即停,owner 折叠时清定时器;结果记在 status().settings。status.error 只在注册成功时清空 —— 失败原文若被后续步骤覆盖,自报里就只剩 registered:false,无从定位(v0.1.9 踩过)
单实例市场禁用/启用走热挂载(不刷新页面)。挂载前先 dispose() 掉 window.__dshRailTonesActive 上残留的上一个实例,避免同一指针动作被多份监听各响一遍
自证status() 暴露 version / instances / settings{registered,attempts,error,retriesLeft} / mutedCount / dedup{…};log() 返回最近 20 次调度明细(含 played 生效标记);设置卡片右下角显示版本号;宿主半边提供 GET /rail-tones/status + POST /rail-tones/report(并镜像到 ~/.dsh/rail-tones-report.json),让「启动时到底有没有挂载」在终端可读
自动播放策略首次 pointerdown/keydown 预热 AudioContext;state !== 'running' 时尝试 resume()(滚轮事件也会补试一次),仍非 running 则丢弃该次调度(played: false + mutedCount +1)而非堆叠在冻结的 currentTime 上 —— 否则上下文恢复瞬间整段时间窗的音符会齐鸣(v0.2.3 修)
生命周期监听器、定时器、音频上下文全部在 ctx.effect 内注册并在卸载时清理
失败模式任何异常只 console.warn 一次;导轨结构变化时软降级为无声,不影响宿主