dsh-ui-tweaks 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -9,9 +9,13 @@ A [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (DSH)
9
9
  | ![Conversation timeline](assets/timeline.png) | ![Claude Desktop table style](assets/table.png) |
10
10
  | **Conversation timeline**: right-side rail — hover to preview messages, click to jump, scroll-highlighting, auto-dodges a right sidebar | **Table style**: the Claude Desktop look (light-gray rounded cards) |
11
11
  | ![Dialog width](assets/dialog_box.png) | ![Settings panel](assets/settings.png) |
12
- | **Dialog width**: message column, composer and stats line widen together | **Settings panel**: font size / table style / dialog width / timeline |
13
- | ![GitBar](assets/git.png) | |
14
- | **GitBar**: git pills inside the composer tool row (branch after the access-mode control, diff before the model select) — branch management, per-file diff, and commit & push from the diff panel | |
12
+ | **Dialog width**: message column, composer and stats line widen together | **Settings panel**: font & code size / line spacing / table style / dialog width / timeline / GitBar / whale indicator toggles |
13
+ | ![GitBar](assets/git.png) | ![Branch panel](assets/branch.png) |
14
+ | **GitBar**: git pills inside the composer tool row (branch after the access-mode control, diff before the model select) — branch management, per-file diff, and commit & push from the diff panel | **Branch panel**: pops up from the branch pill — local / remote branch lists, click to switch, delete & push to remote, new-branch field at the bottom |
15
+ | ![Diff panel](assets/gitdiff.png) | ![Terminal panel](assets/terminal.png) |
16
+ | **Diff panel**: file list + per-file diff (changed hunks only by default) with a commit area (commit / commit & push) at the bottom; drag to resize | **Terminal panel**: a real PTY terminal (xterm.js over WebSocket) — full interactivity, drag to resize, one-click half-screen |
17
+ | ![Open project](assets/explorer.png) | |
18
+ | **Open project**: the session-header icon menu — open the current project in Explorer / VS Code / IDEA / GoLand / WebStorm / PyCharm; the neighbouring terminal & diff icons toggle their panels | |
15
19
  | ![Archive manager](assets/archive.png) | ![MCP manager](assets/mcp.png) |
16
20
  | **Archive manager**: an Archive page in the Settings dialog listing archived sessions (title / workspace / relative time) with Restore and Delete actions | **MCP manager**: an MCP page in the Settings dialog listing configured MCP servers with live status and full management (Add / Edit / Enable / Disable / Delete / Restart) |
17
21
 
@@ -25,12 +29,14 @@ A [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (DSH)
25
29
  - **Branch pill** — right after the access-mode control; shows the current branch and opens an upward branch panel (local / remote lists, `git switch` on click, new-branch field).
26
30
  - **Diff pill** — right before the model select; shows `+N −M · K files` and opens the right slide-over diff panel (changed-file list + per-file diff, resizable, commit band kept at its foot for 提交 / 提交并推送). The standalone commit pill is gone — commit now happens from the diff panel.
27
31
  - **Squeeze-aware**: the composer tool row is an inline-size container, so when it tightens (e.g. the stretched diff panel pushes the column), the pills degrade like the native chrome — first hiding the "· K files" meta, then collapsing to **icon-only** — never overlapping the access / model controls.
32
+ - **Header utilities** — beside "Session log", an icon group: **Open project** (a menu launching Explorer / VS Code / IDEA / GoLand / WebStorm / PyCharm at the session cwd), a **real PTY terminal** panel (xterm.js over WebSocket, drag to resize, one-click half-screen) and the **diff** panel toggle; the diff icon carries the uncommitted-changes dot.
28
33
  - **Archive manager (toggleable, off by default)** — an **Archive** page in the Settings dialog listing archived sessions (title / workspace / relative time) with per-row **Restore** and **Delete** actions plus batch **Restore all** / **Delete all** buttons.
29
34
  - **Restore** removes a session from the archive set (its log and workspace slot are kept, so the conversation returns to the normal sidebar list).
30
35
  - **Delete** PERMANENTLY deletes the session — the server removes its JSONL log from disk, detaches it from workspace accounting and the archive set, and clears its projection cache (irreversible). Only genuinely **running** sessions are refused; opened-but-idle sessions are also removed from the in-memory store, so the row disappears live.
31
36
  - The list refreshes live via the `host/archived-sessions-changed` event and a session-list re-pull, with no page reload.
32
37
  - **MCP manager (toggleable, off by default)** — an **MCP** page in the Settings dialog listing every configured MCP server (`@deepseek-ai/dsh-mcp-client` loader entries) with its live status, command/url, env vars and registered tools, plus full management: **Add / Edit** (a structured form — instance id, name, stdio or HTTP type, timeout ms, command, args, env — OR raw YAML, both validated), **Enable / Disable / Delete**, and **Restart** (runtime-only). Changes persist to the profile's `cordis.patch.yml` and DSH's built-in patch watcher hot-reloads just that server.
33
38
  - **`/init` slash command (toggleable, off by default)** — type `/init` in the composer (the slash menu shows "Analyze this project and generate an AGENTS.md"), pick a prompt language from the popup (**Chinese / English**), and a complete AGENTS.md bootstrap prompt is submitted into the current session: the agent explores the project on its own (README, manifests, build scripts, key directories), then writes or improves a root `AGENTS.md` addressed to future AI coding agents (overview, common commands, conventions, directory guide, gotchas; existing files are improved in place). Pure client-side contribution; enable it in the UI Tweaks settings section.
39
+ - **Whale indicator (toggleable, off by default)** — the brand whale perched on the composer card's **top-right corner** over drifting blue waves (the Claude Desktop crab spot). It stays in its original colour the whole time: idle it floats still (hover or click it to make it swim — an easter egg); while the model works it swims (bob + sway) until the turn finishes. Working state = the session's `running` flag plus the input machine's claimed/submitting phases, so the swim starts the moment you press Enter. Pure CSS animation on DSH theme tokens; honors `prefers-reduced-motion`.
34
40
 
35
41
  All changes apply **live** — no reload needed. The same values can be hand-edited in the settings document:
36
42
 
@@ -40,8 +46,10 @@ ui-tweaks:
40
46
  tableStyle: claude
41
47
  dialogWidth: 880
42
48
  timelineEnabled: true # defaults to false (off); set true to enable
49
+ gitBarEnabled: true # defaults to false (off); set true to enable GitBar
43
50
  archiveManagerEnabled: true # defaults to false (off); set true to show the Archive page
44
51
  initCommandEnabled: true # defaults to false (off); set true to register the /init slash command
52
+ whaleIndicatorEnabled: true # defaults to false (off); set true to enable the whale indicator
45
53
  ```
46
54
 
47
55
  Settings entry: **Settings → UI Tweaks**.
package/README.md CHANGED
@@ -9,17 +9,21 @@
9
9
  | ![对话时间线](assets/timeline.png) | ![Claude Desktop 表格样式](assets/table.png) |
10
10
  | **对话时间线**:右侧竖轨,悬停展开消息预览、点击跳转、随滚动高亮当前位置,自动避让右侧边栏 | **表格样式**:Claude Desktop 浅灰圆角卡片风格 |
11
11
  | ![对话框宽度](assets/dialog_box.png) | ![设置面板](assets/settings.png) |
12
- | **对话框宽度**:消息列、输入框、统计栏同步变宽 | **设置面板**:字体大小 / 表格样式 / 对话框宽度 / 时间线 / Git 状态栏 |
13
- | ![GitBar](assets/git.png) | |
14
- | **GitBar**:输入框工具行内的 git 状态胶囊(分支在权限旁、差异在模型前),支持分支切换、删除、推送到远程,差异面板内可直接提交 | |
12
+ | **对话框宽度**:消息列、输入框、统计栏同步变宽 | **设置面板**:字体大小 / 代码字号 / 行高 / 表格样式 / 对话框宽度 / 时间线 / Git 状态栏 / 鲸鱼指示器等功能开关 |
13
+ | ![GitBar](assets/git.png) | ![分支面板](assets/branch.png) |
14
+ | **GitBar**:输入框工具行内的 git 状态胶囊(分支在权限旁、差异在模型前),支持分支切换、删除、推送到远程,差异面板内可直接提交 | **分支面板**:点击分支胶囊向上弹出——本地 / 远程分支列表,点击即切换,支持删除、推送远程,底部可新建分支 |
15
+ | ![差异面板](assets/gitdiff.png) | ![终端面板](assets/terminal.png) |
16
+ | **差异面板**:文件列表 + 逐文件 diff(默认只显示有差异的 hunk),底部提交区可提交 / 提交并推送,宽度可拖动 | **终端面板**:真 PTY 终端(xterm.js + WebSocket),完整终端交互,宽度可拖动、一键半屏 |
17
+ | ![打开项目](assets/explorer.png) | |
18
+ | **打开项目**:会话头部的图标菜单——用资源管理器 / VS Code / IDEA / GoLand / WebStorm / PyCharm 打开当前项目;旁边的终端、差异图标一键唤出对应面板 | |
15
19
  | ![归档管理](assets/archive.png) | ![MCP 管理](assets/mcp.png) |
16
20
  | **归档管理**:设置中的「归档」页面,列出所有已归档会话(标题 / 工作区 / 相对时间),支持恢复与彻底删除 | **MCP 管理**:设置中的「MCP 管理」页面,列出所有配置的 MCP 服务器及其运行状态,支持添加 / 编辑 / 启用停用 / 删除 / 重启 |
17
21
 
18
22
  ## 功能
19
23
 
20
24
  - **消息字体大小(px)**:直接输入数字(10–32),作用于消息正文、标题、表格与代码。**代码字号**可单独设置(8–32px 绝对值,默认 13;行内代码按比例跟随)——旧版的百分比配置(`codeFontScale`)仍然兼容,一旦设置新的 px 值即以其为准。
21
- - **表格样式**:可选 `默认` 或 **Claude Desktop** 风格(浅灰圆角单元格卡片、单元格间有间隙、无边框、单元格与行内代码同底色、表头不加粗)。
22
- - **对话框宽度(px)**:直接输入数字(600–1600);消息列、输入框、输入框下方的统计栏(轮数/步数/耗时/tok/s)**三者同步变宽**。
25
+ - **表格样式**:可选 `默认` 或 **Claude Desktop** 风格(浅灰圆角单元格卡片、单元格间有间隙、无边框、单元格与行内代码同底色、表头不加粗)。Claude 风格下表格默认撑满列宽;放不下的宽表保持自然宽度、在表格内横向滚动(悬停出现滚动条),不会被挤压换行或裁掉后面的列。
26
+ - **对话框宽度(px)**:直接输入数字(600–1600);消息列、输入框、输入框下方的统计栏(轮数/步数/耗时/tok/s)**三者同步变宽**,宽表格的两侧「出血」量也随列宽同步计算——表格最多贴到消息区边缘(悬停出滚动条),不会伸出屏幕。
23
27
  - **对话时间线(可开关,默认关闭)**:在消息区右侧显示细竖导航轨——每条用户消息一根指示线,悬停展开面板预览消息、随滚动高亮当前位置、点击平滑跳转到对应消息(自动加载更早历史)。**浅色/深色模式都正常显示**,且**始终贴在消息区右侧**:即使安装了右侧边栏(如 dsh-better-sidebar)并展开,时间线也会自动避让、不会与侧边栏重叠。会话中用户消息少于 2 条时自动隐藏。
24
28
  - **GitBar(默认关闭,可在设置中开启)**:当会话的工作目录是一个 git 仓库时,在**输入框工具行内**显示两颗与输入框原生控件(权限 / 模型选择)同款样式的紧凑胶囊——不再占用输入框上方单独一行,输入区因此更矮:
25
29
  - **分支胶囊**:位于权限(访问模式)控件之后;显示当前分支名(有未提交改动时在标题提示)。点击**向上**弹出分支面板——本地 / 远程分支列表(点击即 `git switch`),底部可**新建分支**(`git switch -c` 并自动切换)。
@@ -35,6 +39,7 @@
35
39
  - **启用 / 停用 / 删除 / 重启**:改动直接写入 profile 的 `cordis.patch.yml`(保持注释与结构),DSH 内置补丁监视器热重载加载器——被改动的那个服务器**实时**启动/停止/重启,不影响其它服务器;重启单独运行时生效、不改配置。环境变量值仅在本机浏览器可见,用于编辑。
36
40
  - 新增后请确认服务器能成功连接(状态为「运行中」并注册了工具);启动失败的实例会显示「错误」并可重启重试。
37
41
  - **`/init` 斜杠命令(可开关,默认关闭)**:在输入框键入 `/init`(斜杠菜单中可见「分析当前项目并生成 AGENTS.md」),回车或点击后弹出语言选择——**中文提示词 / 英文提示词**,选中即向当前会话提交一段完整的 AGENTS.md 引导提示词:代理会自行探索项目(README、清单文件、构建脚本、关键目录),然后在仓库根目录**生成或改进**一份面向未来 AI 编码代理的 `AGENTS.md`(项目简介、常用命令、代码风格约定、目录导览、注意事项;已存在时原地改进不丢内容)。纯客户端实现;在「界面调整」中开启后生效。
42
+ - **鲸鱼指示器(可开关,默认关闭)**:输入框卡片**右上角**停一只品牌小鲸鱼(侧边栏同款 FishLogo,Claude Desktop 小螃蟹的同款位置),身下有**蓝色海浪**缓缓漂移。鲸鱼**常驻原色**(主题主标签色,浅色模式即黑色):**空闲时静静浮在海浪上,悬停或点击它也会游起来(彩蛋)**;**模型工作时开始游泳动画**(原地上下浮动 + 左右轻摆),直到干完活。模型工作状态取自当前会话的 `running` 标志,并叠加输入机的 claimed/submitting 阶段,覆盖「按下回车到代理真正开跑」的间隙。鲸鱼几何为内置的 FishLogo 路径(与 DSH 主程序逐字节一致),挂载时仍会尝试从侧边栏实时徽标抓取最新版以跟随官方换标;动画纯 CSS、颜色走 DSH 主题变量(深浅色模式均正常),`prefers-reduced-motion` 时降级为静止。
38
43
 
39
44
  所有修改**即时生效**,无需刷新。同一份配置也可以直接在设置文档里手改:
40
45
 
@@ -47,6 +52,7 @@ ui-tweaks:
47
52
  gitBarEnabled: true # 默认 false(关闭),设为 true 开启 GitBar
48
53
  archiveManagerEnabled: true # 默认 false(关闭),设为 true 开启「归档」页面
49
54
  initCommandEnabled: true # 默认 false(关闭),设为 true 开启 /init 斜杠命令
55
+ whaleIndicatorEnabled: true # 默认 false(关闭),设为 true 开启鲸鱼指示器
50
56
  # suggestModel: 'provider:model' # 可选:指定生成提交说明的模型
51
57
  ```
52
58
 
@@ -97,6 +103,7 @@ npx -y @deepseek-ai/dsh plugin --profile web add . # 从本目录作为 b
97
103
  - **GitBar**(`src/client/gitbar.tsx`):拆成两个组件,分别挂在 composer 工具行**内部**的 `conversation.input.left`(分支,位于权限控件之后)与 `conversation.input.right`(差异,位于模型选择之前)插槽——输入区不再有独立的一行胶囊。样式与输入框原生控件一致(28px 高、radius 24px、`--dsw-alias-label-secondary` 颜色、hover 用 `--dsw-alias-interactive-bg-hover`)。工具行是 DSH 的 inline-size 容器(`container-type:inline-size`),胶囊用 `@container` 查询做挤压降级:行宽 <700px 隐藏差异的“· K 个文件”元信息,<620px 全部收敛为纯图标,避免与权限 / 模型选择重叠;分支名另加 `text-overflow:ellipsis` 上限。差异面板展开时通过 `#root { margin-right }` 把对话区往左挤,时间线 rail(锚定消息区右缘)因此不被遮挡;面板内的高度分配采用「只给被拖的那一段显式高度、diff 段 `flex:1` 吃掉余量」的方式,配合 45% 上限,拖动永远不会撑破面板。独立 commit 胶囊已移除,提交保留在差异面板底部的提交区。
98
104
  - **归档管理**(服务端 `src/archive.ts` + 浏览器端 `src/client/archive.tsx`):作为 `settings.section` 插槽(设置面板中的「归档」页面)。列表数据直接来自框架标准 hook `useSessions` + `useWorkspaces`(`archivedSessionIds`),无需额外查询;操作走同源路由 `/_dsh/ui-tweaks/archive`。**恢复**把会话 id 从工作区存储域的 `archivedSessionIds` 全局单例中移除(DSH 只暴露单向 `archiveSession`,无公开的取消归档 API,故直接写活体存储域句柄并同步工作区注册表的内存缓存)。**彻底删除**依次:拒绝正在运行的会话(agent `status === 'running'` 才拒绝,空闲会话先 `cancel` + `whenIdle`)→ 用持久化后端自身的 `findLog` 定位并 `rm` 会话日志目录 → 调用公开的 `WorkspaceEntity.detachSession` 摘除工作区记账 → 从归档集合移除并同步注册表内存缓存与 header 索引 → 清理 `session_projcache` → 从内存 SessionStore 摘除该会话(触发 `host/session-removed` 实时消失)。
99
105
  - **MCP 管理**(服务端 `src/mcp.ts` + 浏览器端 `src/client/mcp.tsx`):同源路由 `/_dsh/ui-tweaks/mcp`。**列表**枚举 `ctx.loader.entries()` 中 `@deepseek-ai/dsh-mcp-client` 实例(id / config / fiber 状态:active=2、failed=3 等)并按 `mcp__<serverName>__` 前缀从工具注册表统计工具。**重启**调用 `entry.fiber.restart()`(仅运行时)。**添加 / 编辑 / 删除 / 启用停用**通过 `yaml`(eemeli)的 Document API 直接编辑 profile 的 `cordis.patch.yml`(保留注释与未知补丁结构,原子写 tmp+rename),随后由 DSH 内置的 `watchUserPatches` 热重载监视器重新应用补丁——`cordis-plugin-include` 对根组做**增量** `root.update`,因此只有被改动的 MCP 实例会重启,其它不受影响;环境变量值返回给同源浏览器(本机配置编辑需要),YAML 模式在服务端用 `yaml.parse` + 白名单校验。
106
+ - **鲸鱼指示器**(`src/client/whale.tsx`):挂在 `conversation.input.dock`(输入框卡片上方的整行插槽)。dock 行本身比卡片宽(卡片被 InputBar root 以 `--dsh-composer-card-max-width` 封顶居中、两侧留 `--dsh-composer-side-clearance`),因此鲸鱼行**复刻卡片的几何**——同 max-width、同样居中、同样侧留白——再右对齐 + 6px `translateY`,鲸鱼恰好「骑」在卡片右上角(Claude Desktop 小螃蟹位置),自定义对话框宽度时也自动跟随。开关走与 /init 相同的「按需注册」编排——`whaleIndicatorEnabled` 打开时才注册插槽与样式,关闭即卸载。工作状态 = 会话列表 feed 中当前会话的 `running`(`useSyncExternalStore` 直订)∪ 输入机 `phase` 的 claimed/submitting;鲸鱼 SVG 路径内置为常量,挂载时以 `[class*="brandMark"]` 从侧边栏实时徽标尝试刷新(class 前缀是构建哈希,按后缀匹配以抗版本变化),取不到则回退内置几何。
100
107
 
101
108
  ## 协议
102
109
 
Binary file
Binary file
Binary file
package/assets/git.png CHANGED
Binary file
Binary file
Binary file
Binary file
package/lib/client.js CHANGED
@@ -1903,6 +1903,7 @@ const timeline_tsx_1 = __load_("./timeline.js");
1903
1903
  const gitbar_tsx_1 = __load_("./gitbar.js");
1904
1904
  const archive_tsx_1 = __load_("./archive.js");
1905
1905
  const mcp_tsx_1 = __load_("./mcp.js");
1906
+ const whale_tsx_1 = __load_("./whale.js");
1906
1907
  const NS = 'ui-tweaks';
1907
1908
  const SETTINGS_ROUTE = '/_dsh/ui-tweaks/settings';
1908
1909
  const DEFAULT_FONT_SIZE = 16;
@@ -2010,6 +2011,10 @@ const en = {
2010
2011
  initCommandHint: 'The /init slash command: pick a prompt language and the agent analyzes the project and writes AGENTS.md.',
2011
2012
  initCommandOn: 'On',
2012
2013
  initCommandOff: 'Off',
2014
+ whaleIndicator: 'Whale indicator',
2015
+ whaleIndicatorHint: 'A little whale above the input box: translucent while idle, swimming while the model works.',
2016
+ whaleIndicatorOn: 'On',
2017
+ whaleIndicatorOff: 'Off',
2013
2018
  mcpServerDetail: 'Configured in the profile cordis.patch.yml as @deepseek-ai/dsh-mcp-client instances; add / edit / disable / delete write to that file and apply live.',
2014
2019
  archiveNav: 'Archive',
2015
2020
  archiveTitle: 'Archived sessions',
@@ -2183,6 +2188,10 @@ const zh = {
2183
2188
  initCommandHint: '注册 /init 斜杠命令:选择提示词语言后,让代理分析项目并生成 AGENTS.md。',
2184
2189
  initCommandOn: '开启',
2185
2190
  initCommandOff: '关闭',
2191
+ whaleIndicator: '鲸鱼指示器',
2192
+ whaleIndicatorHint: '输入框上方的小鲸鱼:空闲时半透明静止,模型工作时开始游泳动画。',
2193
+ whaleIndicatorOn: '开启',
2194
+ whaleIndicatorOff: '关闭',
2186
2195
  mcpServerDetail: 'MCP 服务器配置在 profile 的 cordis.patch.yml(@deepseek-ai/dsh-mcp-client 实例);添加 / 编辑 / 停用 / 删除会写入该文件,改动实时生效。',
2187
2196
  archiveNav: '归档',
2188
2197
  archiveTitle: '已归档会话',
@@ -2293,6 +2302,7 @@ function resolveValue(value) {
2293
2302
  archiveManagerEnabled: value?.archiveManagerEnabled ?? false,
2294
2303
  mcpManagerEnabled: value?.mcpManagerEnabled ?? false,
2295
2304
  initCommandEnabled: value?.initCommandEnabled ?? false,
2305
+ whaleIndicatorEnabled: value?.whaleIndicatorEnabled ?? false,
2296
2306
  };
2297
2307
  }
2298
2308
  /** Scale one theme px value proportionally to the chosen base size. */
@@ -2347,6 +2357,10 @@ div[data-slot="conversation.chat.node"] table{
2347
2357
  border-collapse:separate !important;
2348
2358
  border-spacing:3px !important;
2349
2359
  width:100% !important;
2360
+ /* Fill the column like Claude Desktop, but never squeeze below the natural
2361
+ (no-wrap) width: a table too wide to fit keeps its real width and scrolls
2362
+ inside the stock table wrapper instead of hiding columns. */
2363
+ min-width:max-content !important;
2350
2364
  border:none !important;
2351
2365
  font-size:var(--dsw-font-markdown-base-font-size) !important;
2352
2366
  }
@@ -2451,10 +2465,20 @@ function buildRuntimeCss(value) {
2451
2465
  }
2452
2466
  if (value.dialogWidth !== DEFAULT_DIALOG_WIDTH) {
2453
2467
  const width = value.dialogWidth;
2454
- rules.push(`[data-chat-flow]{max-width:${width}px !important}`);
2468
+ // Widen the message column AND tell the stock wide-table bleed math about
2469
+ // it: DSH sizes a wide table's side overhang from --dsh-chat-content-width
2470
+ // (pinned to 748px on the conversation root). Widening the column without
2471
+ // syncing the var pushes every wide table (W − 748)/2 px past the message
2472
+ // area's right edge.
2473
+ rules.push(`[data-chat-flow]{max-width:${width}px !important;--dsh-chat-content-width:${width}px}`);
2455
2474
  // The composer card carries the same column + 32px padding (748→780);
2456
2475
  // widen it through its stable data attribute so the input bar matches.
2457
2476
  rules.push(`[data-composer-card="true"]{max-width:${width + 32}px !important}`);
2477
+ // The conversation root redefines --dsh-composer-card-max-width locally,
2478
+ // so a :root override never reaches inside it; re-declare it on the
2479
+ // composer seat (stable anchor, covers the composer stack incl. plugins)
2480
+ // so width-derived consumers there agree with the widened card.
2481
+ rules.push(`[data-composer-seat]{--dsh-composer-card-max-width:${width + 32}px}`);
2458
2482
  // The conversation stats line under the composer (conversation.composer.dock)
2459
2483
  // keeps its own 748px column; widen it together with the dialog.
2460
2484
  rules.push(`[data-slot="conversation.composer.dock"] > div{max-width:${width + 32}px !important}`);
@@ -2737,6 +2761,9 @@ function SettingsSection({ controller, t }) {
2737
2761
  const setInitCommand = (value) => {
2738
2762
  void controller.set('initCommandEnabled', value).then(() => { setStatus('applied'); }).catch(() => { setStatus('unavailable'); });
2739
2763
  };
2764
+ const setWhaleIndicator = (value) => {
2765
+ void controller.set('whaleIndicatorEnabled', value).then(() => { setStatus('applied'); }).catch(() => { setStatus('unavailable'); });
2766
+ };
2740
2767
  const reset = (field) => {
2741
2768
  void controller.unset(field).then(() => { setStatus('resetDone'); }).catch(() => { setStatus('unavailable'); });
2742
2769
  };
@@ -2763,7 +2790,7 @@ function SettingsSection({ controller, t }) {
2763
2790
  commitFontSize(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.fontSize >= MAX_FONT_SIZE, onClick: () => { stepFontSize(1); }, children: "+" })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-btn' + (resolved.fontSize === DEFAULT_FONT_SIZE ? ' dut-btn-active' : ''), disabled: !writable, onClick: () => { reset('fontSize'); }, children: t('defaultAction') })] })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('lineHeight'), (0, jsx_runtime_1.jsx)(Hint, { text: t('lineHeightHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.lineHeight <= MIN_LINE_HEIGHT, onClick: () => { stepLineHeight(-2); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: MIN_LINE_HEIGHT, max: MAX_LINE_HEIGHT, step: 2, value: lineHeightDraft, disabled: !writable, onChange: (event) => { setLineHeightDraft(event.target.value); }, onBlur: (event) => { commitLineHeight(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
2764
2791
  commitLineHeight(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.lineHeight >= MAX_LINE_HEIGHT, onClick: () => { stepLineHeight(2); }, children: "+" })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-btn' + (resolved.lineHeight === DEFAULT_LINE_HEIGHT ? ' dut-btn-active' : ''), disabled: !writable, onClick: () => { reset('lineHeight'); }, children: t('defaultAction') })] })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('codeFontSize'), (0, jsx_runtime_1.jsx)(Hint, { text: t('codeFontSizeHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.codeFontSize <= MIN_CODE_FONT_SIZE, onClick: () => { stepCodeSize(-1); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: MIN_CODE_FONT_SIZE, max: MAX_CODE_FONT_SIZE, step: 1, value: codeDraft, disabled: !writable, onChange: (event) => { setCodeDraft(event.target.value); }, onBlur: (event) => { commitCodeSize(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
2765
2792
  commitCodeSize(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.codeFontSize >= MAX_CODE_FONT_SIZE, onClick: () => { stepCodeSize(1); }, children: "+" })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-btn' + (resolved.codeFontSize === DEFAULT_CODE_FONT_SIZE ? ' dut-btn-active' : ''), disabled: !writable, onClick: () => { resetCodeSize(); }, children: t('defaultAction') })] })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('tableStyle'), (0, jsx_runtime_1.jsx)(Hint, { text: t('tableStyleHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.tableStyle === 'claude' ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { pickTableStyle('claude'); }, children: t('tableStyleClaude') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.tableStyle === 'default' ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { pickTableStyle('default'); }, children: t('tableStyleDefault') })] }) })] }) })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dut-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-section-label", children: t('sectionLayout') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('dialogWidth'), (0, jsx_runtime_1.jsx)(Hint, { text: t('dialogWidthHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.dialogWidth <= MIN_DIALOG_WIDTH, onClick: () => { stepDialogWidth(-20); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: MIN_DIALOG_WIDTH, max: MAX_DIALOG_WIDTH, step: 20, value: widthDraft, disabled: !writable, onChange: (event) => { setWidthDraft(event.target.value); }, onBlur: (event) => { commitDialogWidth(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
2766
- commitDialogWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.dialogWidth >= MAX_DIALOG_WIDTH, onClick: () => { stepDialogWidth(20); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 880 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(880); }, children: [t('presetWide'), " \u00B7 880"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 1024 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(1024); }, children: [t('presetWideXl'), " \u00B7 1024"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 748 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(748); }, children: [t('presetDefault'), " \u00B7 748"] })] }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dut-panel dut-grid", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-section-label", children: t('sectionFeatures') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('timeline'), (0, jsx_runtime_1.jsx)(Hint, { text: t('timelineHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(true); }, children: t('timelineOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(false); }, children: t('timelineOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('gitBar'), (0, jsx_runtime_1.jsx)(Hint, { text: t('gitBarHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.gitBarEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setGitBar(true); }, children: t('gitBarOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.gitBarEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setGitBar(false); }, children: t('gitBarOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('archiveManager'), (0, jsx_runtime_1.jsx)(Hint, { text: t('archiveManagerHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.archiveManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setArchiveManager(true); }, children: t('archiveManagerOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.archiveManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setArchiveManager(false); }, children: t('archiveManagerOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('mcpManager'), (0, jsx_runtime_1.jsx)(Hint, { text: t('mcpManagerHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.mcpManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setMcpManager(true); }, children: t('mcpManagerOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.mcpManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setMcpManager(false); }, children: t('mcpManagerOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('initCommand'), (0, jsx_runtime_1.jsx)(Hint, { text: t('initCommandHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.initCommandEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setInitCommand(true); }, children: t('initCommandOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.initCommandEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setInitCommand(false); }, children: t('initCommandOff') })] }) })] }) })] })] }));
2793
+ commitDialogWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.dialogWidth >= MAX_DIALOG_WIDTH, onClick: () => { stepDialogWidth(20); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 880 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(880); }, children: [t('presetWide'), " \u00B7 880"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 1024 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(1024); }, children: [t('presetWideXl'), " \u00B7 1024"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 748 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(748); }, children: [t('presetDefault'), " \u00B7 748"] })] }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dut-panel dut-grid", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-section-label", children: t('sectionFeatures') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('timeline'), (0, jsx_runtime_1.jsx)(Hint, { text: t('timelineHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(true); }, children: t('timelineOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(false); }, children: t('timelineOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('gitBar'), (0, jsx_runtime_1.jsx)(Hint, { text: t('gitBarHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.gitBarEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setGitBar(true); }, children: t('gitBarOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.gitBarEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setGitBar(false); }, children: t('gitBarOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('archiveManager'), (0, jsx_runtime_1.jsx)(Hint, { text: t('archiveManagerHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.archiveManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setArchiveManager(true); }, children: t('archiveManagerOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.archiveManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setArchiveManager(false); }, children: t('archiveManagerOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('mcpManager'), (0, jsx_runtime_1.jsx)(Hint, { text: t('mcpManagerHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.mcpManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setMcpManager(true); }, children: t('mcpManagerOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.mcpManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setMcpManager(false); }, children: t('mcpManagerOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('initCommand'), (0, jsx_runtime_1.jsx)(Hint, { text: t('initCommandHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.initCommandEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setInitCommand(true); }, children: t('initCommandOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.initCommandEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setInitCommand(false); }, children: t('initCommandOff') })] }) })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dut-label", children: [t('whaleIndicator'), (0, jsx_runtime_1.jsx)(Hint, { text: t('whaleIndicatorHint') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.whaleIndicatorEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setWhaleIndicator(true); }, children: t('whaleIndicatorOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.whaleIndicatorEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setWhaleIndicator(false); }, children: t('whaleIndicatorOff') })] }) })] }) })] })] }));
2767
2794
  }
2768
2795
  /**
2769
2796
  * The /init bootstrap prompts, submitted verbatim into the current session on
@@ -2961,6 +2988,35 @@ function apply(ctx) {
2961
2988
  sync();
2962
2989
  return controller.subscribe(sync);
2963
2990
  }, 'dsh-ui-tweaks: /init command');
2991
+ // Whale working indicator: the brand whale centered above the composer
2992
+ // card — translucent and still while idle, swimming while the model works.
2993
+ // The dock entry is registered only while the whaleIndicatorEnabled toggle
2994
+ // is on, so off costs nothing; styles ride the same toggle so an unused
2995
+ // keyframes rule never lingers in <head>.
2996
+ ctx.effect(() => {
2997
+ let disposeEntry;
2998
+ let disposeStyles;
2999
+ const sync = () => {
3000
+ const enabled = controller.getSnapshot().value?.whaleIndicatorEnabled === true;
3001
+ if (enabled && disposeEntry === undefined) {
3002
+ disposeStyles = (0, whale_tsx_1.installWhaleStyles)();
3003
+ disposeEntry = ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
3004
+ name: 'conversation.input.dock',
3005
+ id: 'whale',
3006
+ order: 30,
3007
+ inject: () => ({ sessionsService: ctx.sessions }),
3008
+ }, whale_tsx_1.WhaleIndicator));
3009
+ }
3010
+ else if (!enabled && disposeEntry !== undefined) {
3011
+ disposeEntry();
3012
+ disposeEntry = undefined;
3013
+ disposeStyles?.();
3014
+ disposeStyles = undefined;
3015
+ }
3016
+ };
3017
+ sync();
3018
+ return controller.subscribe(sync);
3019
+ }, 'dsh-ui-tweaks: whale indicator');
2964
3020
  }
2965
3021
  };
2966
3022
  __modules["./mcp.js"] = function(module, exports, require, __load_) {
@@ -3783,6 +3839,138 @@ function TimelineRail({ useProjection, sessionId, sessionsService, controller, t
3783
3839
  }) }) }) }), bubble !== null && show ? ((0, jsx_runtime_1.jsxs)("div", { className: "dutl-bubble", style: bubbleStyle, role: "tooltip", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dutl-bubble-head", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dutl-bubble-user", children: [(0, jsx_runtime_1.jsx)("span", { className: "dutl-bubble-dot" }), t('roleUser')] }), (0, jsx_runtime_1.jsx)("span", { className: "dutl-bubble-time", children: formatTime(bubble.entry.time) })] }), (0, jsx_runtime_1.jsx)("div", { className: "dutl-bubble-text", children: bubble.entry.text === '' ? t('noText') : bubble.entry.text })] })) : null] }), document.body);
3784
3840
  }
3785
3841
  };
3842
+ __modules["./whale.js"] = function(module, exports, require, __load_) {
3843
+ "use strict";
3844
+ Object.defineProperty(exports, "__esModule", { value: true });
3845
+ exports.WHALE_CSS = void 0;
3846
+ exports.installWhaleStyles = installWhaleStyles;
3847
+ exports.WhaleIndicator = WhaleIndicator;
3848
+ const jsx_runtime_1 = require("react/jsx-runtime");
3849
+ /**
3850
+ * dsh-ui-tweaks — whale working indicator (browser half).
3851
+ *
3852
+ * A little brand whale (the sidebar's FishLogo mark) perched on the composer
3853
+ * card's top-right corner over drifting blue waves — Claude Desktop's little
3854
+ * crab, but a whale — resident the whole time the feature is on. Idle: the
3855
+ * whale keeps its original colour and floats still on the waves; hovering or
3856
+ * clicking it swims it too (easter egg). Working: it swims until the model
3857
+ * finishes.
3858
+ *
3859
+ * - Mounted in `conversation.input.dock` (the full-width row stacked by the
3860
+ * composer column right above the card), registered only while the
3861
+ * `whaleIndicatorEnabled` toggle is on — off costs nothing. The dock row is
3862
+ * WIDER than the card (the card is capped at
3863
+ * `--dsh-composer-card-max-width` and centered with
3864
+ * `--dsh-composer-side-clearance` side padding by the InputBar root), so
3865
+ * the row replicates that exact geometry — same max-width, centered, same
3866
+ * side padding — before right-aligning; a 6px `translateY` then straddles
3867
+ * the card's top edge. The extra 14px right inset centers the whale over
3868
+ * the send button (34px round button, 8px tool-row padding → its center
3869
+ * sits 25px from the card's right edge; the 22px whale's right edge at
3870
+ * 14px puts its center at 25px).
3871
+ * - "Working" = the current session's `running` flag on the sessions list
3872
+ * feed, OR the input machine's claimed/submitting phases (the window
3873
+ * between pressing Enter and the agent actually starting to run).
3874
+ * - The whale geometry is the stock FishLogo path embedded as a constant, so
3875
+ * the indicator never depends on DOM timing; at mount we still try to lift
3876
+ * a fresher path from the live sidebar brand mark (`[class*="brandMark"]`)
3877
+ * so a DSH logo update is followed without a plugin release.
3878
+ * @module dsh-ui-tweaks/client/whale
3879
+ */
3880
+ const react_1 = require("react");
3881
+ /**
3882
+ * Stock FishLogo whale geometry (single path, viewBox 23.16 × 17.04, painted
3883
+ * `currentColor`) — lifted from the DSH web bundle's brand mark.
3884
+ */
3885
+ const STOCK_WHALE_PATH = 'M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018ZM11.1749 14.4736C9.15936 12.889 8.18184 12.3675 7.77832 12.39C7.40081 12.4125 7.46881 12.8445 7.55182 13.126C7.63882 13.404 7.75182 13.5955 7.91033 13.8396C8.01983 14.0011 8.09533 14.2411 7.80083 14.4216C7.15181 14.8231 6.02327 14.2866 5.97027 14.2601C4.65673 13.4865 3.5587 12.4655 2.78467 11.069C2.03715 9.72493 1.60314 8.28289 1.53164 6.74384C1.51264 6.37233 1.62214 6.24082 1.99215 6.17332C2.47916 6.08332 2.98118 6.06432 3.46769 6.13582C5.52476 6.43633 7.27581 7.35586 8.74385 8.8129C9.58188 9.64243 10.2159 10.634 10.8689 11.6025C11.5634 12.631 12.3105 13.611 13.262 14.4146C13.598 14.6961 13.866 14.9101 14.1225 15.0681C13.349 15.1546 12.058 15.1731 11.1749 14.4746L11.1749 14.4736ZM12.141 8.25988C12.141 8.09488 12.273 7.96338 12.439 7.96338C12.4765 7.96338 12.5105 7.97088 12.541 7.98188C12.5825 7.99688 12.6205 8.01938 12.6505 8.05338C12.7035 8.10588 12.7335 8.18088 12.7335 8.25988C12.7335 8.42489 12.6015 8.55639 12.4355 8.55639C12.2695 8.55639 12.141 8.42489 12.141 8.25988ZM15.1415 9.79893C14.949 9.87793 14.7565 9.94544 14.5715 9.95294C14.2845 9.96794 13.9715 9.85143 13.8015 9.70893C13.5375 9.48742 13.3485 9.36342 13.2695 8.97691C13.2355 8.8119 13.2545 8.55639 13.2845 8.40989C13.3525 8.09438 13.277 7.89187 13.0545 7.70787C12.8735 7.55786 12.643 7.51636 12.39 7.51636C12.2955 7.51636 12.209 7.47486 12.1445 7.44136C12.039 7.38886 11.9519 7.25735 12.035 7.09585C12.0615 7.04335 12.19 6.91584 12.22 6.89334C12.5635 6.69784 12.9595 6.76184 13.326 6.90834C13.6655 7.04735 13.9225 7.30236 14.292 7.66287C14.6695 8.09838 14.7375 8.21838 14.9525 8.54539C15.1225 8.8009 15.277 9.06341 15.3831 9.36392C15.4471 9.55142 15.3641 9.70493 15.1415 9.79893Z';
3886
+ /** Whale width in px; height follows the logo's 23.16 : 17.04 aspect. */
3887
+ const WHALE_WIDTH = 22;
3888
+ /** Whale viewBox, kept in sync with the stock FishLogo geometry. */
3889
+ const WHALE_VIEW_W = 23.16;
3890
+ const WHALE_VIEW_H = 17.04;
3891
+ // ---------------------------------------------------------------------------
3892
+ // Styles — resident (translucent, still) vs working (opaque, swimming).
3893
+ // ---------------------------------------------------------------------------
3894
+ exports.WHALE_CSS = `
3895
+ .duwi-row{box-sizing:border-box;width:100%;max-width:calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance, 0px));margin:0 auto;padding:0 calc(var(--dsh-composer-side-clearance, 0px) + 14px) 0 var(--dsh-composer-side-clearance, 0px);display:flex;align-items:flex-end;justify-content:flex-end;height:27px;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
3896
+ .duwi-stack{display:flex;flex-direction:column;align-items:center;transform:translateY(6px)}
3897
+ .duwi-whale{display:block;color:var(--dsw-alias-label-primary);transform-origin:50% 60%;pointer-events:auto;cursor:pointer}
3898
+ .duwi-row.duwi-swim .duwi-whale,.duwi-whale:hover{animation:duwi-swim 1.9s ease-in-out infinite}
3899
+ .duwi-waves{display:block;width:26px;height:9px;margin-top:-3px;overflow:hidden}
3900
+ .duwi-wave-back{stroke:#3d8bfd;opacity:.35}
3901
+ .duwi-wave-front{stroke:#3d8bfd;opacity:.8}
3902
+ .duwi-wave-front,.duwi-wave-back{animation:duwi-drift-front 1.9s linear infinite}
3903
+ .duwi-wave-back{animation-duration:3.4s;animation-direction:reverse}
3904
+ @keyframes duwi-swim{
3905
+ 0%,100%{transform:translateY(0) rotate(-4deg)}
3906
+ 50%{transform:translateY(-3.5px) rotate(4deg)}
3907
+ }
3908
+ @keyframes duwi-drift-front{
3909
+ from{transform:translateX(0)}
3910
+ to{transform:translateX(-8px)}
3911
+ }
3912
+ @media (prefers-reduced-motion:reduce){
3913
+ .duwi-row.duwi-swim .duwi-whale,.duwi-whale:hover{animation:none}
3914
+ .duwi-wave-front,.duwi-wave-back{animation:none}
3915
+ }
3916
+ `;
3917
+ /** Idempotent installer for the indicator styles (same pattern as the other features). */
3918
+ function installWhaleStyles() {
3919
+ const id = 'dsh-ui-tweaks-whale';
3920
+ const existing = document.querySelector(`style[data-plugin-css="${id}"]`);
3921
+ if (existing !== null)
3922
+ return () => { };
3923
+ const style = document.createElement('style');
3924
+ style.dataset.plugin = 'dsh-ui-tweaks';
3925
+ style.dataset.pluginCss = id;
3926
+ style.textContent = exports.WHALE_CSS;
3927
+ document.head.appendChild(style);
3928
+ return () => { style.remove(); };
3929
+ }
3930
+ // ---------------------------------------------------------------------------
3931
+ // Live whale geometry refresh.
3932
+ // ---------------------------------------------------------------------------
3933
+ /**
3934
+ * Lift the whale path from the live sidebar brand mark (rendered into
3935
+ * `sidebar.brand.mark`, wrapped in a CSS-modules `*_brandMark` span). The
3936
+ * class prefix is a build hash, so match on the stable suffix of the class
3937
+ * attribute; miss (sidebar collapsed-away, future markup change) keeps the
3938
+ * embedded stock geometry.
3939
+ */
3940
+ function liftLiveWhalePath() {
3941
+ const path = document.querySelector('[class*="brandMark"] svg path');
3942
+ const d = path?.getAttribute('d');
3943
+ return typeof d === 'string' && d !== '' ? d : undefined;
3944
+ }
3945
+ function WhaleIndicator({ sessionId, input, sessionsService }) {
3946
+ // Prefer the live sidebar whale when one is on screen; fall back to stock.
3947
+ const [path, setPath] = (0, react_1.useState)(STOCK_WHALE_PATH);
3948
+ (0, react_1.useEffect)(() => {
3949
+ setPath(liftLiveWhalePath() ?? STOCK_WHALE_PATH);
3950
+ }, []);
3951
+ // Idle easter egg: a click swims the whale for a couple of cycles.
3952
+ const [peek, setPeek] = (0, react_1.useState)(false);
3953
+ const peekTimer = (0, react_1.useRef)(undefined);
3954
+ (0, react_1.useEffect)(() => () => { window.clearTimeout(peekTimer.current); }, []);
3955
+ const swimFor = () => {
3956
+ setPeek(true);
3957
+ window.clearTimeout(peekTimer.current);
3958
+ peekTimer.current = window.setTimeout(() => { setPeek(false); }, 3800);
3959
+ };
3960
+ // The current session's `running` flag, straight off the sessions list feed.
3961
+ const list = sessionsService.list;
3962
+ const subscribe = (0, react_1.useMemo)(() => list.subscribe.bind(list), [list]);
3963
+ const running = (0, react_1.useSyncExternalStore)(subscribe, () => {
3964
+ const snapshot = list.getSnapshot();
3965
+ const id = sessionId ?? snapshot.current;
3966
+ return id !== undefined && (snapshot.byId[id]?.running ?? false);
3967
+ }, () => false);
3968
+ // `claimed`/`submitting` bridge the gap between Enter and the agent start.
3969
+ const submitting = input.phase === 'claimed' || input.phase === 'submitting';
3970
+ const swim = running || submitting || peek;
3971
+ return ((0, jsx_runtime_1.jsx)("div", { className: swim ? 'duwi-row duwi-swim' : 'duwi-row', "aria-hidden": "true", children: (0, jsx_runtime_1.jsxs)("div", { className: "duwi-stack", children: [(0, jsx_runtime_1.jsx)("svg", { className: "duwi-whale", width: WHALE_WIDTH, height: WHALE_WIDTH * WHALE_VIEW_H / WHALE_VIEW_W, viewBox: `0 0 ${WHALE_VIEW_W} ${WHALE_VIEW_H}`, fill: "none", onClick: swimFor, children: (0, jsx_runtime_1.jsx)("path", { d: path, fill: "currentColor" }) }), (0, jsx_runtime_1.jsxs)("svg", { className: "duwi-waves", viewBox: "0 0 26 9", fill: "none", children: [(0, jsx_runtime_1.jsx)("path", { className: "duwi-wave-back", d: "M-8 4 Q-4 5.8 0 4 T8 4 T16 4 T24 4 T32 4 T40 4", strokeWidth: "1.4", strokeLinecap: "round" }), (0, jsx_runtime_1.jsx)("path", { className: "duwi-wave-front", d: "M-8 5.5 Q-4 3.3 0 5.5 T8 5.5 T16 5.5 T24 5.5 T32 5.5 T40 5.5", strokeWidth: "1.4", strokeLinecap: "round" })] })] }) }));
3972
+ }
3973
+ };
3786
3974
  function __resolve(from, request) {
3787
3975
  if (!request.startsWith(".")) return request;
3788
3976
  var parts = from.slice(2).split("/"); parts.pop();