dsh-claude-style 0.7.1 → 0.7.2
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/CHANGELOG.md +36 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/docs/STYLE.md +15 -1
- package/docs/architecture.md +15 -7
- package/lib/client.js +529 -147
- package/lib/index.js +19 -33
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `dsh-claude-style` are documented here, newest first.
|
|
4
4
|
|
|
5
|
+
## [0.7.2] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
[中文](#cn-0.7.2) | [English](#en-0.7.2)
|
|
8
|
+
|
|
9
|
+
<h3 id="cn-0.7.2">新增功能</h3>
|
|
10
|
+
|
|
11
|
+
- **账号位置的头像改为玩家自己的皮肤**:启动器记下了玩家导入的皮肤,宿主半边把那张规范化贴图以只读路由发给浏览器,浏览器按启动器账号列表相同的裁法取头部——脸的 8×8 贴图块按盒子的 1/18 内缩,帽子层铺满整个盒子。头部按**方形**绘制,不用账号头像那条路径的圆形遮罩:它画到盒子边缘,任何圆角都会切掉它的像素。没有头像、头像文件已删、图取不到时显示 Claude 徽标。
|
|
12
|
+
- **权限档位改为跟随宿主目录**:权限控件(输入框分段控件与它的弹层)不再写死四档,段位与行都按宿主的 `permissionPresets` 目录构建——目录里有的档位才画,宿主没提供的整条不出现,第三方插件注册的档位因此成为一等公民:auto mode 插件的 `auto-mode` 会以 **Auto mode** 出现在弹层里,并在分段控件里占用 **Auto** 那一格(部署同时提供宿主内置 Auto review 时,内置档退居弹层、把格子让给部署自己的自动档)。档位名与说明仍由皮肤给(皮肤不认识的档位用宿主自己的名字与说明,机器值不上屏),行保持纯文字——预设声明的 `icon` 也不画,档位列表读起来是一份清单。切换仍走宿主的 `/permission <preset>`。
|
|
13
|
+
|
|
14
|
+
### 体验优化
|
|
15
|
+
|
|
16
|
+
- **工作台首页在还没选工作区时也显示用量面板与权限分段**:刚打开、尚无会话的首页此前只有问候语和输入框;现在用量面板照常出现在两者之间,输入框下方显示 Read / Edit / Auto / Yolo 分段,停在新会话的默认权限上,点它和点输入框一样打开工作区选择。
|
|
17
|
+
- **工作台首页「先选工作区」的虚线框只框住单行输入框**:虚线框此前连同输入框下方的工具行一起框进去,比输入框高出一截;现在虚线画在单行输入框本身,指针移上去时变色的提示不变。
|
|
18
|
+
|
|
19
|
+
### 问题修复
|
|
20
|
+
|
|
21
|
+
- 修复 **选中划过行内代码时代码段显示为一块偏浅的独立高亮**:芯片的半透明底色画在选中高亮之上,把芯片内的选中色混浅了一档;现在芯片内的选中色预先抵消这层罩色,整段选区呈现统一的选中色,聚焦与失焦、深色与浅色下一致。
|
|
22
|
+
- 修复 **窗口较矮时工作台首页被用量面板撑出窗口**:面板放不下时整页向下延伸,问候语被顶出窗口上沿;现在面板在问候语与输入框之间自己滚动,底边渐隐,滚到底时渐隐消失,问候语、上下文行与输入框都留在窗口内。
|
|
23
|
+
|
|
24
|
+
<h3 id="en-0.7.2">New Features</h3>
|
|
25
|
+
|
|
26
|
+
- **The account row's mark becomes the player's own skin**: the launcher stores every imported skin as a normalized texture atlas, and the host half serves it over a read-only route. The browser crops the head the way the launcher's own account list does — the face's 8×8 texel block inset by 1/18 of the box, with the hat layer over the whole box. The head is drawn **square** rather than under the round mask the avatar-photo path uses, because it reaches the box's edges and any rounding would shave its pixels off. With no picture, a deleted file, or a picture that cannot be served, the Claude mark shows.
|
|
27
|
+
- **The permission ladder now follows the host catalog**: the permission control (the composer's segmented group and its popover) no longer hardcodes four tiers. Both the segments and the rows are built from the host's `permissionPresets` catalog, so a deployment offers exactly the tiers it configures and a tier no one serves is absent rather than drawn dead. A tier a third-party plugin registers is therefore a first-class entry: the auto mode plugin's `auto-mode` appears as **Auto mode** in the popover and takes the **Auto** slot of the segmented group (when the deployment also serves the host's built-in Auto review, that one stays in the popover and the slot goes to the deployment's own tier). Names and descriptions still come from the skin — a tier it does not know reads with the host's own name and description, never with its machine id — and the rows stay text-only: a preset's declared `icon` is not drawn either, so the ladder reads as one list. Switching still goes through the host's `/permission <preset>`.
|
|
28
|
+
|
|
29
|
+
### Improvements
|
|
30
|
+
|
|
31
|
+
- **The Studio home shows the usage panel and the permission segments before a workspace is picked**: a fresh home with no session yet showed only the greeting and the input box; the usage panel now sits between them as usual, and the Read / Edit / Auto / Yolo segments show under the input box, resting on the preset a new session starts in — pressing them opens the workspace picker, the same as pressing the input box.
|
|
32
|
+
- **The Studio home's dashed "pick a workspace" ring outlines the single-line input box alone**: the ring used to take in the toolbar row under the input box and stood a row taller than it; it is now drawn on the input box itself, and still changes colour under the pointer.
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
- Fixed **inline code showing a separate, lighter highlight when selected**: the chip's translucent wash painted over the selection and lightened it inside the chip; the in-chip selection paint now pre-compensates for that wash, so a selection crossing inline code is one even colour, focused or blurred, dark or light.
|
|
37
|
+
- Fixed **the usage panel pushing the Studio home past a short window**: when the panel did not fit, the whole page grew downward and the greeting ran off the top; the panel now scrolls between the greeting and the composer with its bottom edge fading out (the fade lifts at the end of the scroll), and the greeting, the context row and the input box stay in the window.
|
|
38
|
+
|
|
39
|
+
**Full Changelog**: [v0.7.1...v0.7.2](https://github.com/Nwflower/dsh-claude-style/compare/v0.7.1...v0.7.2)
|
|
40
|
+
|
|
5
41
|
## [0.7.1] - 2026-09-25
|
|
6
42
|
|
|
7
43
|
[中文](#cn-0.7.1) | [English](#en-0.7.1)
|
package/README.en.md
CHANGED
|
@@ -63,13 +63,13 @@ Keep only one theme enabled at a time. After installation, **restart `dsh web`**
|
|
|
63
63
|
## Features
|
|
64
64
|
|
|
65
65
|
1. **Theme** — applies globally the moment it is installed, with nothing to configure. Light mode uses ivory `#FCFCFB`, dark mode warm black `#141413`, and both share the same ember orange `#D97757` action accent; light/dark follows the system color mode.
|
|
66
|
-
2. **Composer** — the input box is rebuilt from the ground up: a permission segmented control (Read / Edit / Auto / Yolo
|
|
66
|
+
2. **Composer** — the input box is rebuilt from the ground up: a permission segmented control (Read / Edit / Auto / Yolo) with its tier list under it, a model trigger carrying the vendor lockup, and the toolbar and status stats laid out on a single line, with the send and stop buttons unified into 7px rounded rectangles. The stats sentence and the model trigger share the same font size and color. The permission tiers come from the host's own catalog: a deployment draws exactly the tiers it configures, and a tier a third-party plugin registers (the auto mode plugin's `auto-mode`, say) appears in the list and takes the Auto slot; the list stays text-only, so a tier's declared glyph is not drawn.
|
|
67
67
|
3. **Model picker** — a new two-level popover. The first level lists the official service plus the quick providers selected in settings; "More models" opens a second level grouped by provider. Every row carries its vendor lockup and a description, and the footer holds the reasoning-effort slider (stepless — it snaps to the nearest level on release) together with the "More models" entry. The second level aligns its bottom edge with the first, providers already shown on the first level are not repeated, and models without thinking support get no slider. Hovering opens the popover after a 100 ms dwell and closes it after 150 ms; the gap between the two cards does not count as leaving.
|
|
68
68
|
4. **Workspace** — the sidebar's "Workspace" heading becomes an **Active / Archived** segmented control. Active keeps the host's session tree, while Archived is the skin's own flat list (title, time, and per-row unarchive and delete icon buttons). Rows match the host's session rows measurement for measurement (row x=12 / width 251 / height 28 / title x=36).
|
|
69
69
|
5. **Sidebar** — the New session and Plugins rows take Claude's real shape: no background at rest, a background on hover, the "+" of New session set inside a circular chip, and both icons rotating 90° clockwise on hover (four-fold symmetric glyphs, so they land back on themselves). The account drawer and the ban-screen easter egg remain as they were.
|
|
70
|
-
6. **Account area** — the nickname and the picture each follow one fixed order. Nickname: the custom nickname → the signed-in account's name → the HDSL launcher's account name → the last probed system user → the freshly probed system user → `User`. Picture: the signed-in account's avatar → the HDSL launcher's avatar → the Claude mark. The account profile is fetched from the server only at startup, on hot reload, and on sign-in and sign-out — never polled. The HDSL contract is read once by the host half and forwarded over two read-only routes (`GET /dsh-claude-style/hdsl` for the metadata, `GET /dsh-claude-style/hdsl-skin.png` for the avatar image; neither carries the avatar file's absolute path, and both sit behind the host's request fence). A contract version that is missing or unknown voids the whole group, and a deleted avatar file falls back to the mark. The account popover remains the host's own: the host's account row is the entry point, and the popover lists the account header, the other plugins' footer entries, and the host's own Settings / Feedback / Sign out, each keeping its original copy, order, and click behavior. On hosts without an account area (Web), the plugin builds a popover with the same look. The card uses a pure white background and spans the sidebar's width.
|
|
70
|
+
6. **Account area** — the nickname and the picture each follow one fixed order. Nickname: the custom nickname → the signed-in account's name → the HDSL launcher's account name → the last probed system user → the freshly probed system user → `User`. Picture: the signed-in account's avatar → the HDSL launcher's avatar → the Claude mark. The account profile is fetched from the server only at startup, on hot reload, and on sign-in and sign-out — never polled. The HDSL contract is read once by the host half and forwarded over two read-only routes (`GET /dsh-claude-style/hdsl` for the metadata, `GET /dsh-claude-style/hdsl-skin.png` for the avatar image; neither carries the avatar file's absolute path, and both sit behind the host's request fence). A contract version that is missing or unknown voids the whole group, and a deleted avatar file falls back to the mark. What the launcher publishes is a skin sheet rather than a finished avatar, so that picture is cropped into the head first — the face's 8×8 texel block inset by 1/18 of the box with the hat layer over the whole box, drawn square so no rounding shaves its pixels. The account popover remains the host's own: the host's account row is the entry point, and the popover lists the account header, the other plugins' footer entries, and the host's own Settings / Feedback / Sign out, each keeping its original copy, order, and click behavior. On hosts without an account area (Web), the plugin builds a popover with the same look. The card uses a pure white background and spans the sidebar's width.
|
|
71
71
|
7. **Conversation views** — in the desktop Windows titlebar mode, the Conversation / Trajectory control sits centered and always visible on the titlebar row (the same row as the New session button). On wide windows with a short title it stays on the title's row; otherwise it keeps its own row below the title.
|
|
72
|
-
8. **Home layouts** — the new-conversation page comes in two layouts, Studio by default and switchable in the settings page at any time: **Classic** is the centered headline over the composer card, the headline drawn from a pool of greetings for the time of day (a few each for morning, midday, afternoon, evening and night, plus a few for any hour) and redrawn each time the new-conversation page comes back; **Studio** pins the greeting to the top left as one fixed line ("What's up next, <user>?"), docks the composer in the conversation's single-line form at the window's bottom edge with a row of hairline context chips above it, and sets the greeting and a 480px usage panel against the composer's left edge: an Overview tab with six stat cells (sessions, messages, total tokens, active days, peak hour, favorite model) and a twenty-six-week per-day heat grid of square cells in equal columns, a Models tab stacking each day's per-model tokens into one bar over a ranked model list (swatch, name, input and output, share; past six rows it folds behind a "show more" row, and the open list folds back with "show less"), and All / 30d / 7d range pills at the top right, which the peak hour and the multiplier line follow too; once the picked range's total passes one book, a multiplier line appears under the grid, naming one of eleven books (Animal Farm to In Search of Lost Time) drawn afresh each time the new-conversation page comes back. The numbers are rolled up by the plugin's host half: with `dsh-cost-meter` installed it reads that plugin's ledger cache directly (read-only — their file is never written), and without it the host half folds the local session logs itself, caching per log by size and modification time. Both sources answer each model's four buckets and each day's per-model tokens, with one model served by several providers counted as one row; the ledger keeps no hours, so the peak hour is always folded per day from the local session logs; when the roll-up cannot answer, the panel falls back to the projection block each host session row carries. The panel draws its skeleton first and does not shift when the numbers land.
|
|
72
|
+
8. **Home layouts** — the new-conversation page comes in two layouts, Studio by default and switchable in the settings page at any time: **Classic** is the centered headline over the composer card, the headline drawn from a pool of greetings for the time of day (a few each for morning, midday, afternoon, evening and night, plus a few for any hour) and redrawn each time the new-conversation page comes back; **Studio** pins the greeting to the top left as one fixed line ("What's up next, <user>?"), docks the composer in the conversation's single-line form at the window's bottom edge with a row of hairline context chips above it, and sets the greeting and a 480px usage panel against the composer's left edge: an Overview tab with six stat cells (sessions, messages, total tokens, active days, peak hour, favorite model) and a twenty-six-week per-day heat grid of square cells in equal columns, a Models tab stacking each day's per-model tokens into one bar over a ranked model list (swatch, name, input and output, share; past six rows it folds behind a "show more" row, and the open list folds back with "show less"), and All / 30d / 7d range pills at the top right, which the peak hour and the multiplier line follow too; once the picked range's total passes one book, a multiplier line appears under the grid, naming one of eleven books (Animal Farm to In Search of Lost Time) drawn afresh each time the new-conversation page comes back. The numbers are rolled up by the plugin's host half: with `dsh-cost-meter` installed it reads that plugin's ledger cache directly (read-only — their file is never written), and without it the host half folds the local session logs itself, caching per log by size and modification time. Both sources answer each model's four buckets and each day's per-model tokens, with one model served by several providers counted as one row; the ledger keeps no hours, so the peak hour is always folded per day from the local session logs; when the roll-up cannot answer, the panel falls back to the projection block each host session row carries. The panel draws its skeleton first and does not shift when the numbers land. Before a workspace is picked (a fresh start, no session yet), the usage panel and the Read / Edit / Auto / Yolo permission segments still show; the segments rest on the preset a new session starts in, and pressing them opens the workspace picker, the same as pressing the input box, whose dashed "pick a workspace" ring now outlines the single-line input box alone. When the window is too short for the whole panel, the panel scrolls between the greeting and the composer with its bottom edge fading out, and the page no longer runs past the window.
|
|
73
73
|
9. **The pixel crab** — in the Studio layout, Claude Code's pixel crab stands on the new-conversation composer card's top edge, near its right end (the Classic layout has none). When it is clicked or the pointer leaves it (and on its own every half minute or so while the page is open), it plays Claude Code's fishing routine: a half turn and a wink, the rod raised and cast down onto the card's edge, a spell of fishing side-on, and the rod put away as it turns back — about three seconds. With reduced motion requested (Windows' "Show animations in Windows" turned off, for one), it moves only when clicked.
|
|
74
74
|
|
|
75
75
|
## Disabling and uninstalling
|
package/README.md
CHANGED
|
@@ -63,13 +63,13 @@ dsh plugin --profile web add Nwflower/dsh-claude-style # GitHub 源
|
|
|
63
63
|
## 特点
|
|
64
64
|
|
|
65
65
|
1. **主题** —— 安装即全局生效,无需配置。亮色象牙白 `#FCFCFB`、暗色暖黑 `#141413`,两套画布共用同一个操作强调色陶烬橙 `#D97757`;亮暗跟随系统颜色模式切换。
|
|
66
|
-
2. **输入框** —— 输入框整块重做:权限分段控件(只读 /
|
|
66
|
+
2. **输入框** —— 输入框整块重做:权限分段控件(只读 / 编辑 / 自动 / Yolo)与它下方的档位列表、带品牌锁定标的模型触发器、工具栏与状态统计同一行排版,发送键与停止键统一成 7px 圆角。统计句与模型触发器共用一套字号与颜色。权限档位取自宿主的权限目录:宿主配置了哪几档就画哪几档,第三方插件注册的档位(例如 auto mode 插件的 `auto-mode`)会自动出现在列表里并占用「自动」那一格;列表保持纯文字,档位自己声明的图标不画。
|
|
67
67
|
3. **模型选择器** —— 全新的两级弹层:一级列官方服务与设置页勾选的快捷供应商,「更多模型」二级按供应商分组;每行带厂商锁定标与说明文案,底部是推理等级推条(无极滑动,松手对齐最近档位)与「更多模型」入口。二级底边与一级对齐,一级已显示的供应商不再重复;模型不支持思考时不画推条。悬停停留 100ms 打开、离开 150ms 关闭,两卡之间的空隙不算离开。
|
|
68
68
|
4. **工作区** —— 侧栏「工作区」标题改成 **进行中 / 已归档** 分段控件:进行中沿用宿主的会话树,已归档是皮肤自己的平铺列表(标题、时间、每行「取消归档」与「删除」两个图标按钮)。行排版与宿主的会话行逐项一致(行 x=12 / 宽 251 / 高 28 / 标题起点 x=36)。
|
|
69
69
|
5. **侧栏** —— 新会话与插件两行取 Claude 真机形状:默认无底色、hover 才有底色,新会话的「+」套一枚圆形底,两行图标 hover 顺时针转 90°(四重对称图形,转回原位);底部账户抽屉、封号彩蛋一并保留。
|
|
70
|
-
6. **账号区** —— 昵称与头像各走一条固定顺序。昵称:自定义昵称 → 官方账号昵称 → HDSL 启动器昵称 → 上次探测到的系统用户名 → 本次探测到的系统用户名 → `User`;头像:官方账号头像 → HDSL 启动器头像 → Claude 徽标。官方账号资料只在启动、热重载、登录与登出时向服务器读取,不轮询;HDSL 契约由宿主半边读一次后经两条只读路由转发(`GET /dsh-claude-style/hdsl` 回账号元数据、`GET /dsh-claude-style/hdsl-skin.png`
|
|
70
|
+
6. **账号区** —— 昵称与头像各走一条固定顺序。昵称:自定义昵称 → 官方账号昵称 → HDSL 启动器昵称 → 上次探测到的系统用户名 → 本次探测到的系统用户名 → `User`;头像:官方账号头像 → HDSL 启动器头像 → Claude 徽标。官方账号资料只在启动、热重载、登录与登出时向服务器读取,不轮询;HDSL 契约由宿主半边读一次后经两条只读路由转发(`GET /dsh-claude-style/hdsl` 回账号元数据、`GET /dsh-claude-style/hdsl-skin.png` 回头像图片,都不含头像文件的绝对路径,并过宿主的请求栅栏),契约版本读不到或不认识时整组忽略,头像文件被删除时回落到徽标。启动器给的是皮肤贴图而不是成品头像,因此账号位置的头像由贴图裁出:脸部 8×8 贴图块按盒子 1/18 内缩、帽子层铺满整盒,方形绘制不被圆角裁切。账号弹层沿用宿主自带的那一个:宿主的账号行就是入口,弹层里依次是账号头部、其它插件的页脚条目与宿主自己的「设置 / 意见反馈 / 退出登录」,各行的文案、顺序与点击行为保持宿主原样;宿主没有账号区的环境(Web)由插件自建同样外观的弹层。弹层卡片为纯白背景、宽度与侧栏一致。
|
|
71
71
|
7. **对话视图** —— 对话 / 轨迹控件在桌面 Windows 标题栏模式下居中常显在标题栏那一行(与新会话按钮同一行);宽窗口且标题较短时它停在标题那一行,否则留在标题下方自己那一行。
|
|
72
|
-
8. **首页版面** —— 新会话页有两套版面,默认是工作台,在设置页随时切换:**经典**是居中大标题加输入卡片,标题按时段从一组问候里抽一句(早上、午间、下午、晚上、深夜各有几句,另有几句不分时段),每次回到新会话页换一句;**工作台**把标题移到左上并固定为一句「What's up next, 用户名?」,输入框改用对话内的单行样式贴住窗口底部、上方一行细边框上下文胶囊,标题与一块 480px 窄栏用量面板贴着输入框左缘排布:概览页签是六个数字格(会话数、消息数、Token 总量、活跃天数、高峰时段、最常用模型)与 26 周按天热力图(等分列方格),模型页签是每天一根按模型颜色堆叠的柱子加一份模型排行(色块、模型名、输入与输出、占比,超过六行折成「再显示 N 个」,展开后可「收起」),右上角的「全部 / 30 天 / 7 天」切换统计范围(高峰时段与趣味行一并跟随);所选范围内的用量超过一本书时,热力图下方出现倍数趣味行,书目共十一本(《动物农场》到《追忆似水年华》),每次回到新会话页随机换一本。数据由插件的宿主半边汇总:装了 `dsh-cost-meter` 时直接读它的账本缓存(只读,不写对方文件),没有时读本机会话日志自己算,并按日志的文件大小与修改时间增量缓存;两种来源都按模型给出四类 token、按天给出各模型的 token
|
|
72
|
+
8. **首页版面** —— 新会话页有两套版面,默认是工作台,在设置页随时切换:**经典**是居中大标题加输入卡片,标题按时段从一组问候里抽一句(早上、午间、下午、晚上、深夜各有几句,另有几句不分时段),每次回到新会话页换一句;**工作台**把标题移到左上并固定为一句「What's up next, 用户名?」,输入框改用对话内的单行样式贴住窗口底部、上方一行细边框上下文胶囊,标题与一块 480px 窄栏用量面板贴着输入框左缘排布:概览页签是六个数字格(会话数、消息数、Token 总量、活跃天数、高峰时段、最常用模型)与 26 周按天热力图(等分列方格),模型页签是每天一根按模型颜色堆叠的柱子加一份模型排行(色块、模型名、输入与输出、占比,超过六行折成「再显示 N 个」,展开后可「收起」),右上角的「全部 / 30 天 / 7 天」切换统计范围(高峰时段与趣味行一并跟随);所选范围内的用量超过一本书时,热力图下方出现倍数趣味行,书目共十一本(《动物农场》到《追忆似水年华》),每次回到新会话页随机换一本。数据由插件的宿主半边汇总:装了 `dsh-cost-meter` 时直接读它的账本缓存(只读,不写对方文件),没有时读本机会话日志自己算,并按日志的文件大小与修改时间增量缓存;两种来源都按模型给出四类 token、按天给出各模型的 token,同一模型经不同提供方的用量合成一行;账本不记小时,高峰时段始终由本机会话日志按天算出;汇总不可用时面板改用宿主会话列表每行自带的投影值。数字没到之前面板先画骨架,位置在填充前后不变。还没选工作区时(刚打开、尚无会话),用量面板与 Read / Edit / Auto / Yolo 权限分段照常显示,分段停在新会话的默认权限上,点它和点输入框一样打开工作区选择;表示「先选工作区」的虚线框只框住单行输入框本身。窗口高度放不下整块面板时,面板在问候语与输入框之间自己滚动、底边渐隐,页面不会被撑出窗口。
|
|
73
73
|
9. **像素螃蟹** —— 工作台版面的新会话页上,输入卡片上沿、靠右的位置站着 Claude Code 的像素螃蟹(经典版面没有)。点它一下、或指针从它身上移开时(页面开着时也会每隔二三十秒自己来一次),它会照 Claude Code 的样子钓一次鱼:半转身眨眼,举起鱼竿甩到卡片边上,侧身钓一会儿,再收竿转回正面,前后约三秒。系统要求减少动态效果时(例如 Windows 关掉了「在 Windows 中显示动画」),它只在被点击时才动。
|
|
74
74
|
|
|
75
75
|
## 停用与卸载
|
package/docs/STYLE.md
CHANGED
|
@@ -221,7 +221,21 @@ removes the contest.
|
|
|
221
221
|
**The usage panel.** It is a `conversation.input.dock` entry (the host's list
|
|
222
222
|
seat between the greeting and the card; a list seat keys its entries by `id`),
|
|
223
223
|
and it renders only in the hero phase — the host mounts that same seat inside a
|
|
224
|
-
conversation, where the todo, queue and goal bars hang on it.
|
|
224
|
+
conversation, where the todo, queue and goal bars hang on it. The seat is scoped
|
|
225
|
+
to a session, so the cold start screen (no session yet, the card waiting for a
|
|
226
|
+
workspace) has none; there the skin mounts the same component on a
|
|
227
|
+
`display: contents` element of its own (`.dsh-claude-home-seat`) and drops it
|
|
228
|
+
once the host's dock arrives. A window too short for the whole panel shrinks the
|
|
229
|
+
panel, never the page: the greeting keeps the top edge, the chips and the card
|
|
230
|
+
keep the bottom, and the panel scrolls between them with a 32px fade over its
|
|
231
|
+
bottom edge that lifts over the last 32px of travel.
|
|
232
|
+
|
|
233
|
+
The cold start card waits for a workspace: the host marks it with a dashed ring
|
|
234
|
+
and makes every control on it inert, so a press anywhere opens the workspace
|
|
235
|
+
picker. In the studio form the ring sits on the single-line input box itself
|
|
236
|
+
(the host's dashed stroke and colours, flat, no drop shadow), and the permission
|
|
237
|
+
segments fill the card's empty mode strip on the preset a new session starts in
|
|
238
|
+
— disabled, like the rest of the card's controls.
|
|
225
239
|
|
|
226
240
|
The panel is Claude Code's dashboard shape: a flat warm-gray wash (a
|
|
227
241
|
`color-mix` of the label tone at 4% over the card tone, which flips with the
|
package/docs/architecture.md
CHANGED
|
@@ -119,15 +119,15 @@
|
|
|
119
119
|
|
|
120
120
|
## D15. HDSL 账号契约:宿主半边转发,浏览器半边只认一条回退顺序
|
|
121
121
|
|
|
122
|
-
- **背景**:HDSL 启动器用 `HDSL_`
|
|
122
|
+
- **背景**:HDSL 启动器用 `HDSL_` 前缀的环境变量向实例公布「这是谁」:账号显示名、供应商、账号种类、头像来源,以及玩家头像贴图的绝对路径;`HDSL_ACCOUNT_CONTRACT` 是唯一必需键,读不到它整组作废。浏览器半边读不到进程环境,贴图又只有绝对路径,两者都只能由宿主半边转发。启动器的皮肤是一张 64×64(或整数倍)的贴图集,不是成品头像:脸只是其中 (8,8) 起的 8×8 贴图块,帽子层在 (40,8)。
|
|
123
123
|
- **决定**:
|
|
124
|
-
-
|
|
125
|
-
- 新增两条私有路由,与 `/username` 同级过 `refusalOf` 栅栏:`GET /dsh-claude-style/hdsl` 回账号元数据,**不含**头像文件的绝对路径;`GET /dsh-claude-style/hdsl-skin.png`
|
|
126
|
-
- 读环境走 harness 自带的 `@deepseek-ai/dsh-launch-environment`(`launchEnvironmentOf(ctx)`)。解析方式与 schemastery 相同:`createRequire(process.argv[1]).resolve()` 拿到真实路径后动态 `import`。这是 AGENTS 记录在案的第二条例外,理由与 D10 相同——`link:` 安装的插件真实路径在 profile 树之外,普通 import 解析不到,而解析不到时皮肤必须照常加载。
|
|
124
|
+
- 宿主半边读 harness 启动时填好的启动环境快照(`ctx.launchEnvironment`,也就是 `launchEnvironmentOf(ctx)` 内部读的那个槽位),只信 `process` 与 `user-env` 两层(项目目录的 `.env` 会随仓库被克隆,没有资格声明玩家是谁),契约版本不等于 `1` 时回 `{ contract: false }`,其余字段一律不看。**不 import** harness 自带的启动环境包:`link:` 安装的插件解析不到它(与 `resolveSchemaFactory` 同一类问题),而那个包要读的正是这个槽位;宿主没填槽位就是没有契约。
|
|
125
|
+
- 新增两条私有路由,与 `/username` 同级过 `refusalOf` 栅栏:`GET /dsh-claude-style/hdsl` 回账号元数据,**不含**头像文件的绝对路径;`GET /dsh-claude-style/hdsl-skin.png` 回贴图字节——路径只来自环境,永远不来自请求,读不到就回 404 让浏览器回退。契约在进程生命周期内不变,宿主半边读一次并记住。
|
|
127
126
|
- 浏览器半边把昵称与头像各收敛成一条回退顺序:昵称 = 自定义昵称 → 官方账号昵称 → HDSL 昵称 → 探测昵称缓存 → 探测昵称 → `User`;头像 = 官方账号头像 → HDSL 头像 → Claude 徽标(样式表里的底色标记)。官方账号资料与 HDSL 契约各自推给 `src/context/host.js` 的身份存储,两个消费点(欢迎语、账号行)只读这一处。
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
127
|
+
- 头像不是直接贴上去的:启动器给的是贴图集,浏览器按启动器账号列表同样的裁法取头部(脸的 8×8 贴图块按盒子 1/18 内缩、帽子层铺满整盒)画进 64×64 画布再缩到账号位的尺寸,方形绘制不被圆角裁切;贴图只以裁出的头部出现,加载完成前、图取不到、契约没带图、文件被删时都显示徽标。画完主动叫一次 pass:贴图加载不改 DOM,没有 pass 会自己来。
|
|
128
|
+
- **理由**:契约读一次就够,所以路由只转发、不轮询;把两条链收在一处,欢迎语、账号行、封号彩蛋不会各自漂移;缓存探测到的系统用户名,是为了刷新页面时欢迎语不再从 `User` 跳一次;裁脸几何与启动器账号列表一致,同一个玩家在启动器与皮肤里长得一样。
|
|
129
|
+
- **代价**:桌面端宿主的账号行由宿主自己渲染,插件只在自建账号行上画头像,所以 HDSL 头像在桌面端只作用于插件自己的表面;只有玩家自选的本地图片能画,启动器内置的默认形象(steve/alex 等)没有像素可用,回退到徽标;契约在进程生命周期内不变,玩家中途换皮肤要下次启动才生效。
|
|
130
|
+
- **重审条件**:HDSL 契约递增版本(其 v2 计划里有按指纹取图的媒体通道与账号 UUID)时,取图改为优先走该通道、以指纹作缓存键,按玩家存储的数据改用 UUID 作键;宿主提供「显示名 / 头像」的统一服务时,改接那一处,删掉本插件的转发路由。
|
|
131
131
|
|
|
132
132
|
## D16. 弹层共用两条基准:一个停留时长,一次只开一张卡
|
|
133
133
|
|
|
@@ -140,3 +140,11 @@
|
|
|
140
140
|
- **理由**:停留时长是同一个交互问题(路过还是停留),分处写必然漂移;互斥此前成对硬编码,新增弹层时要记得改 N 处,登记表把它变成一次登记。
|
|
141
141
|
- **代价**:登记表是模块级状态,特性卸载必须 `unregisterPopover`,否则旧一代的关闭函数留在表里。
|
|
142
142
|
- **重审条件**:宿主提供统一的弹层管理(全局互斥或焦点陷阱)时,改接宿主的机制,删掉本插件的登记表。
|
|
143
|
+
|
|
144
|
+
## D17. 权限档位以宿主目录为准,皮肤只做表现层
|
|
145
|
+
|
|
146
|
+
- **背景**:权限控件(输入框分段控件与它的弹层)此前把四档写死在两张常量表里,只对内置 `auto`(Auto review)做一次目录判定。于是第三方插件注册的权限档位看不见也点不到——例如 auto mode 插件的 `auto-mode`(宿主 `permissionPresets` 目录里的一等条目:确定性规则 + 两阶段分类器),而且会话已经处于该档位时,触发键上会显示机器值 `auto-mode`。宿主的目录是「当前可选档位」的权威:`PermissionCatalog.options` 由宿主按贡献顺序给出,`PresetOption` 只有 `value` / `name` / `description`(部署打上 automode 提供的补丁或宿主将来原生支持时另有 `icon`)。
|
|
147
|
+
- **决定**:段位与弹层行都按宿主目录构建——目录里有的档位才画,宿主没提供的整条不出现(不再有「画出来再隐藏」);`PERMISSION_SEGMENTS` 只描述**格子**,每格列出可绑定的档位、按先后取第一个宿主提供的(部署自己的自动档优先于宿主内置的 Auto review),一格都绑不上就不画。档位名与说明来自皮肤的 `PERMISSION_PRESETS`,皮肤不认识的档位用目录自带的 `name`/`description`,永不把机器值上屏(宿主的 `custom` 用宿主自己的词)。行的内容是纯文字:档位声明的 `icon`(部署方写在预设上的 SVG 路径,宿主原生会忽略、由第三方补丁开启)**不画**——同一条列表里只有第三方档位带图标会读成两种东西,档位之间的区别交给名字与说明。切换仍走宿主的 `/permission <preset>`(与 automode 的 `/auto` 是同一条写入路径)。
|
|
148
|
+
- **理由**:目录变化时皮肤不再漏档位;第三方档位成为一等公民而皮肤不需要认识它;名字由皮肤给、档位由宿主提供,两件事各自独立,接入新档位不用改皮肤代码,也不必让第三方为图标去打宿主 `node_modules` 的补丁。
|
|
149
|
+
- **代价**:档位集合变化时要重建行与段位(此前只改显隐);皮肤的表现表是一张白名单,不认识的档位会以宿主给的名字出现(可能不够 Claude 风味)但不会消失。
|
|
150
|
+
- **重审条件**:宿主原生支持预设 `icon`、且界面上有了统一的档位图形语言时,再考虑把图标画回来。
|
package/lib/client.js
CHANGED
|
@@ -260,40 +260,51 @@ window.__ModuleLoader__.load({
|
|
|
260
260
|
'Spinning', 'Twisting', 'Unfurling', 'Unravelling', 'Vibing', 'Whirring'
|
|
261
261
|
]
|
|
262
262
|
|
|
263
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Claude-flavored presentation of the permission presets, keyed by preset
|
|
265
|
+
* id. The host's catalog decides WHICH presets a deployment offers — a
|
|
266
|
+
* third-party plugin's ride in it, the auto mode plugin's `auto-mode` among
|
|
267
|
+
* them — and this table decides how a known one reads. A preset the table
|
|
268
|
+
* does not know falls back to the name the catalog carries, so nothing the
|
|
269
|
+
* host offers is ever hidden and a machine id is never shown.
|
|
270
|
+
*/
|
|
271
|
+
var PERMISSION_PRESETS = {
|
|
272
|
+
'read-only': { label: 'Read only', desc: '仅读取文件与分析,不修改代码' },
|
|
273
|
+
'workspace-write': { label: 'Accept edits', desc: '允许编辑工作区文件' },
|
|
274
|
+
'auto-mode': { label: 'Auto mode', desc: '规则放行常规操作,其余由分类器裁决' },
|
|
275
|
+
'auto': { label: 'Auto review', desc: '无沙箱运行,调用前由模型审查' },
|
|
276
|
+
'danger-full-access': { label: 'Full access', desc: '自动执行,无需反复确认' }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The control's segments, in slot order. Each slot lists the presets it may
|
|
281
|
+
* bind to, best first: the deployment's own auto tier wins the slot over the
|
|
282
|
+
* host's built-in Auto review, and a slot none of whose presets the host
|
|
283
|
+
* offers is not drawn at all.
|
|
284
|
+
*/
|
|
264
285
|
var PERMISSION_SEGMENTS = [
|
|
265
|
-
{ label: 'Read',
|
|
266
|
-
{ label: 'Edit',
|
|
267
|
-
{ label: 'Auto',
|
|
268
|
-
{ label: 'Yolo',
|
|
286
|
+
{ label: 'Read', presets: ['read-only'] },
|
|
287
|
+
{ label: 'Edit', presets: ['workspace-write'] },
|
|
288
|
+
{ label: 'Auto', presets: ['auto-mode', 'auto'] },
|
|
289
|
+
{ label: 'Yolo', presets: ['danger-full-access'] }
|
|
269
290
|
]
|
|
270
291
|
|
|
271
|
-
/**
|
|
272
|
-
var
|
|
273
|
-
{
|
|
274
|
-
preset: 'read-only',
|
|
275
|
-
label: 'Read only',
|
|
276
|
-
desc: '仅读取文件与分析,不修改代码'
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
preset: 'workspace-write',
|
|
280
|
-
label: 'Accept edits',
|
|
281
|
-
desc: '允许编辑工作区文件'
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
preset: 'auto',
|
|
285
|
-
label: 'Auto review',
|
|
286
|
-
desc: '无沙箱运行,调用前由模型审查'
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
preset: 'danger-full-access',
|
|
290
|
-
label: 'Full access',
|
|
291
|
-
desc: '自动执行,无需反复确认'
|
|
292
|
-
}
|
|
293
|
-
]
|
|
292
|
+
/** Popover row order; a preset the host offers but this list does not know follows in catalog order. */
|
|
293
|
+
var PERMISSION_ORDER = ['read-only', 'workspace-write', 'auto-mode', 'auto', 'danger-full-access']
|
|
294
294
|
|
|
295
|
-
/**
|
|
296
|
-
|
|
295
|
+
/**
|
|
296
|
+
* What the control draws before the host's first catalog read settles: the
|
|
297
|
+
* shipped built-ins, with the auto slot left out the way the shipped picker
|
|
298
|
+
* renders nothing until its own catalog arrives.
|
|
299
|
+
*/
|
|
300
|
+
var PERMISSION_SHIPPED_PRESETS = ['read-only', 'workspace-write', 'danger-full-access']
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Names for host values that are never switch targets. `custom` is the
|
|
304
|
+
* host's own word for knob settings that match no preset, so the trigger
|
|
305
|
+
* reads that rather than the machine value.
|
|
306
|
+
*/
|
|
307
|
+
var PERMISSION_CURRENT_LABELS = { custom: 'Custom' }
|
|
297
308
|
|
|
298
309
|
/** Skin-owned class names, so nothing couples to hashed CSS-module classes. */
|
|
299
310
|
var SEGMENTS_CLASS = 'dsh-claude-segments'
|
|
@@ -423,11 +434,16 @@ window.__ModuleLoader__.load({
|
|
|
423
434
|
var AUTO_POPOVER_ALL = 'all'
|
|
424
435
|
var AUTO_POPOVER_SCOPES = [AUTO_POPOVER_OFF, AUTO_POPOVER_ACCOUNT, AUTO_POPOVER_ALL]
|
|
425
436
|
var DEFAULT_AUTO_POPOVER = AUTO_POPOVER_ALL
|
|
426
|
-
/** Route that resolves the
|
|
437
|
+
/** Route that resolves the name this instance runs as, once; never polled. */
|
|
427
438
|
var USERNAME_ROUTE = '/dsh-claude-style/username'
|
|
428
439
|
/** Route that forwards the HDSL launcher's account contract; never polled. */
|
|
429
440
|
var HDSL_ROUTE = '/dsh-claude-style/hdsl'
|
|
430
|
-
/**
|
|
441
|
+
/**
|
|
442
|
+
* The player's own avatar, forwarded by the host half; 404 falls back to the
|
|
443
|
+
* mark. What the route serves is the launcher's normalized skin atlas, not a
|
|
444
|
+
* finished avatar, so the account row crops the head out of it
|
|
445
|
+
* (src/overrides/account/rows.js).
|
|
446
|
+
*/
|
|
431
447
|
var HDSL_SKIN_ROUTE = '/dsh-claude-style/hdsl-skin.png'
|
|
432
448
|
/** Longest accepted custom username; mirrored by lib/index.js. */
|
|
433
449
|
var USERNAME_MAX = 64
|
|
@@ -1101,6 +1117,30 @@ window.__ModuleLoader__.load({
|
|
|
1101
1117
|
" color: #000000 !important;",
|
|
1102
1118
|
"}",
|
|
1103
1119
|
"",
|
|
1120
|
+
"/* Inline-code chips: Blink paints an inline box's own background ABOVE the",
|
|
1121
|
+
" selection highlight, so the chip's 5% wash (tokens.css",
|
|
1122
|
+
" --dsh-claude-inline-code-bg) tints whatever the selection paints and a",
|
|
1123
|
+
" selected chip reads as a separate, lighter block. These paints are the",
|
|
1124
|
+
" channel-wise inverse of that overlay — dark composites 5% white over the",
|
|
1125
|
+
" paint, light 5% black — so after the wash blends over them the result lands",
|
|
1126
|
+
" back on the two selection colours above. The chip's 1px border is outside",
|
|
1127
|
+
" ::selection's reach and keeps its own colour. */",
|
|
1128
|
+
"body[data-dsh-claude-style] code:not(pre code)::selection {",
|
|
1129
|
+
" background-color: rgb(40, 94, 206) !important;",
|
|
1130
|
+
"}",
|
|
1131
|
+
"",
|
|
1132
|
+
"body[data-dsh-claude-style][data-dsh-window-blur] code:not(pre code)::selection {",
|
|
1133
|
+
" background-color: rgb(196, 196, 195) !important;",
|
|
1134
|
+
"}",
|
|
1135
|
+
"",
|
|
1136
|
+
"body[data-dsh-claude-style]:not([data-ds-dark-theme]) code:not(pre code)::selection {",
|
|
1137
|
+
" background-color: rgb(54, 107, 219) !important;",
|
|
1138
|
+
"}",
|
|
1139
|
+
"",
|
|
1140
|
+
"body[data-dsh-claude-style]:not([data-ds-dark-theme])[data-dsh-window-blur] code:not(pre code)::selection {",
|
|
1141
|
+
" background-color: rgb(209, 209, 208) !important;",
|
|
1142
|
+
"}",
|
|
1143
|
+
"",
|
|
1104
1144
|
"body[data-dsh-claude-style] :is(button, input, textarea, select, [role=\"button\"], [role=\"tab\"], [role=\"treeitem\"]) {",
|
|
1105
1145
|
" outline: none;",
|
|
1106
1146
|
"}",
|
|
@@ -1836,6 +1876,28 @@ window.__ModuleLoader__.load({
|
|
|
1836
1876
|
"body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [class*=\"composerStack\"]:not([data-composer-variant=\"hero\"]) [data-composer-card] [data-input-scroll] {",
|
|
1837
1877
|
" box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) !important;",
|
|
1838
1878
|
"}",
|
|
1879
|
+
"/* No workspace picked yet: the host marks the card as a workspace picker with a",
|
|
1880
|
+
" dashed ring over its whole box. In the single-line form the card is the input",
|
|
1881
|
+
" box plus the toolbar row on the canvas, so the ring moves onto the input box",
|
|
1882
|
+
" itself — the host's dashed stroke and colours, hover included — and the",
|
|
1883
|
+
" card's own ring is dropped. The input box stays flat, the way the host's",
|
|
1884
|
+
" picker card drops its elevation. */",
|
|
1885
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][class*=\"_cardWorkspaceTrigger\"]::after,",
|
|
1886
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not([data-composer-variant=\"hero\"]) [data-composer-card][class*=\"_cardWorkspaceTrigger\"]::after {",
|
|
1887
|
+
" display: none !important;",
|
|
1888
|
+
"}",
|
|
1889
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][class*=\"_cardWorkspaceTrigger\"] [data-input-scroll],",
|
|
1890
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not([data-composer-variant=\"hero\"]) [data-composer-card][class*=\"_cardWorkspaceTrigger\"] [data-input-scroll] {",
|
|
1891
|
+
" border-style: dashed !important;",
|
|
1892
|
+
" border-color: var(--dsw-alias-border-l4) !important;",
|
|
1893
|
+
" box-shadow: none !important;",
|
|
1894
|
+
" cursor: pointer !important;",
|
|
1895
|
+
" transition: border-color 100ms ease !important;",
|
|
1896
|
+
"}",
|
|
1897
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][class*=\"_cardWorkspaceTrigger\"]:hover [data-input-scroll],",
|
|
1898
|
+
"body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not([data-composer-variant=\"hero\"]) [data-composer-card][class*=\"_cardWorkspaceTrigger\"]:hover [data-input-scroll] {",
|
|
1899
|
+
" border-color: var(--dsw-alias-state-business-primary) !important;",
|
|
1900
|
+
"}",
|
|
1839
1901
|
"body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"]:focus-within [data-input-scroll],",
|
|
1840
1902
|
"body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not([data-composer-variant=\"hero\"]) [data-composer-card]:focus-within [data-input-scroll] {",
|
|
1841
1903
|
" border-color: rgba(20, 20, 19, 0.18) !important;",
|
|
@@ -3194,14 +3256,6 @@ window.__ModuleLoader__.load({
|
|
|
3194
3256
|
" box-shadow: 0 1px 2px rgba(20, 20, 19, 0.08);",
|
|
3195
3257
|
"}",
|
|
3196
3258
|
"",
|
|
3197
|
-
"/* Rows whose preset the host's permission catalog does not carry (the Auto",
|
|
3198
|
-
" review tier without the auto-review integration) are taken out of the",
|
|
3199
|
-
" control entirely. */",
|
|
3200
|
-
"body[data-dsh-claude-style] .dsh-claude-segment[data-hidden],",
|
|
3201
|
-
"body[data-dsh-claude-style] .dsh-claude-perm-popover .dsh-claude-popover-item[data-hidden] {",
|
|
3202
|
-
" display: none !important;",
|
|
3203
|
-
"}",
|
|
3204
|
-
"",
|
|
3205
3259
|
"/* ---------- Claude Code layout: in-conversation permission popover ---------- */",
|
|
3206
3260
|
"body[data-dsh-claude-style] .dsh-claude-perm-container {",
|
|
3207
3261
|
" position: relative !important;",
|
|
@@ -3478,6 +3532,35 @@ window.__ModuleLoader__.load({
|
|
|
3478
3532
|
" display: none !important;",
|
|
3479
3533
|
"}",
|
|
3480
3534
|
"",
|
|
3535
|
+
"/* The launcher's own skin, when the instance was started with one: the head is",
|
|
3536
|
+
" a canvas cropped out of the texture the host serves (src/overrides/account/",
|
|
3537
|
+
" rows.js), so it is the picture, not the mark, that fills the box. The mark is",
|
|
3538
|
+
" dropped only while that canvas is mounted — a skin whose bytes never arrived",
|
|
3539
|
+
" leaves the attribute off and the mark in place. The pixels are a Minecraft",
|
|
3540
|
+
" head's 8×8 face, so they are scaled without smoothing.",
|
|
3541
|
+
" The box is square, not the circle the photo path uses: the head is drawn to",
|
|
3542
|
+
" the box's edges (the hat layer covers it, the face is inset by 1/18), so any",
|
|
3543
|
+
" rounding at all shaves the head's own pixels — the circle cut the four",
|
|
3544
|
+
" corners off. The launcher's own account list shows the head square for the",
|
|
3545
|
+
" same reason. */",
|
|
3546
|
+
"body[data-dsh-claude-style] .dsh-claude-account-avatar[data-dsh-claude-skin] {",
|
|
3547
|
+
" position: relative !important;",
|
|
3548
|
+
" background-image: none !important;",
|
|
3549
|
+
" border-radius: 0 !important;",
|
|
3550
|
+
"}",
|
|
3551
|
+
"body[data-dsh-claude-style] .dsh-claude-account-avatar[data-dsh-claude-skin]::before {",
|
|
3552
|
+
" display: none !important;",
|
|
3553
|
+
"}",
|
|
3554
|
+
"body[data-dsh-claude-style] .dsh-claude-account-skin {",
|
|
3555
|
+
" position: absolute !important;",
|
|
3556
|
+
" inset: 0 !important;",
|
|
3557
|
+
" width: 100% !important;",
|
|
3558
|
+
" height: 100% !important;",
|
|
3559
|
+
" border-radius: inherit !important;",
|
|
3560
|
+
" pointer-events: none !important;",
|
|
3561
|
+
" image-rendering: pixelated;",
|
|
3562
|
+
"}",
|
|
3563
|
+
"",
|
|
3481
3564
|
"/* The drawer's rows had no hover feedback at all: the only",
|
|
3482
3565
|
" `.dsh-claude-popover-item:hover` rule in the stylesheet is scoped to the model",
|
|
3483
3566
|
" picker. Give the account drawer the same affordance as the rest of the skin. */",
|
|
@@ -5506,9 +5589,18 @@ window.__ModuleLoader__.load({
|
|
|
5506
5589
|
" margin-bottom: 30px;",
|
|
5507
5590
|
"}",
|
|
5508
5591
|
"",
|
|
5509
|
-
"
|
|
5592
|
+
"/* The skin's own seat on the cold start screen, where the host renders no dock",
|
|
5593
|
+
" (src/overrides/home-layout.js); like the host's anchor it generates no box. */",
|
|
5594
|
+
"body[data-dsh-claude-style] .dsh-claude-home-seat {",
|
|
5595
|
+
" display: contents;",
|
|
5596
|
+
"}",
|
|
5597
|
+
"",
|
|
5598
|
+
"body[data-dsh-claude-style][data-dsh-claude-home-layout=\"studio\"] [class*=\"_composerHero\"] > :is([data-slot=\"conversation.input.dock\"], .dsh-claude-home-seat) > * {",
|
|
5510
5599
|
" order: 1;",
|
|
5511
|
-
"
|
|
5600
|
+
" /* A window too short for the whole panel shrinks the panel instead of the",
|
|
5601
|
+
" page: the greeting stays on the top edge, the context row and the card on",
|
|
5602
|
+
" the bottom, and the panel scrolls inside the room between them. */",
|
|
5603
|
+
" flex: 0 1 auto;",
|
|
5512
5604
|
" min-height: 0;",
|
|
5513
5605
|
" /* The panel is the narrow dashboard card, left-aligned on the composer's own",
|
|
5514
5606
|
" left edge; the free width to its right stays empty. The width is stated",
|
|
@@ -5584,7 +5676,7 @@ window.__ModuleLoader__.load({
|
|
|
5584
5676
|
" display: none;",
|
|
5585
5677
|
"}",
|
|
5586
5678
|
"",
|
|
5587
|
-
"body[data-dsh-claude-style][data-dsh-claude-home-layout=\"studio\"] [class*=\"_composerHero\"] > [data-slot=\"conversation.input.dock\"] > .dsh-claude-home-panel {",
|
|
5679
|
+
"body[data-dsh-claude-style][data-dsh-claude-home-layout=\"studio\"] [class*=\"_composerHero\"] > :is([data-slot=\"conversation.input.dock\"], .dsh-claude-home-seat) > .dsh-claude-home-panel {",
|
|
5588
5680
|
" display: flex;",
|
|
5589
5681
|
"}",
|
|
5590
5682
|
"",
|
|
@@ -5601,6 +5693,27 @@ window.__ModuleLoader__.load({
|
|
|
5601
5693
|
" the label tone over the card tone, which flips with the theme on its own. */",
|
|
5602
5694
|
" background: color-mix(in srgb, var(--dsw-alias-label-primary, #141413) 4%, var(--dsw-alias-bg-layer-2, #fbfbf9));",
|
|
5603
5695
|
" overflow: auto;",
|
|
5696
|
+
" /* Claude Code fades its dashboard into the context row when the window cuts",
|
|
5697
|
+
" it short. The mask's transparent 32px foot hangs below the box at rest, so",
|
|
5698
|
+
" a panel that fits is untouched; while the panel scrolls, the scroll-linked",
|
|
5699
|
+
" animation pulls the foot up over the bottom edge, and lowers it again over",
|
|
5700
|
+
" the last 32px of travel, so the final row reads clean at the end. A panel",
|
|
5701
|
+
" that does not overflow has an inactive scroll timeline, and the resting",
|
|
5702
|
+
" value holds. */",
|
|
5703
|
+
" -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent);",
|
|
5704
|
+
" mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent);",
|
|
5705
|
+
" -webkit-mask-repeat: no-repeat;",
|
|
5706
|
+
" mask-repeat: no-repeat;",
|
|
5707
|
+
" -webkit-mask-size: 100% calc(100% + 32px);",
|
|
5708
|
+
" mask-size: 100% calc(100% + 32px);",
|
|
5709
|
+
" animation: dsh-claude-home-panel-fade linear both;",
|
|
5710
|
+
" animation-timeline: scroll(self);",
|
|
5711
|
+
" animation-range: calc(100% - 32px) 100%;",
|
|
5712
|
+
"}",
|
|
5713
|
+
"",
|
|
5714
|
+
"@keyframes dsh-claude-home-panel-fade {",
|
|
5715
|
+
" from { -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }",
|
|
5716
|
+
" to { -webkit-mask-size: 100% calc(100% + 32px); mask-size: 100% calc(100% + 32px); }",
|
|
5604
5717
|
"}",
|
|
5605
5718
|
"",
|
|
5606
5719
|
"body[data-dsh-claude-style] .dsh-claude-home-panel-head {",
|
|
@@ -8209,46 +8322,120 @@ window.__ModuleLoader__.load({
|
|
|
8209
8322
|
var permLabel = null
|
|
8210
8323
|
var permPopover = null
|
|
8211
8324
|
var permHoverIntent = null
|
|
8325
|
+
/** The pick path the rows call back into; the rows are rebuilt, the path is not. */
|
|
8326
|
+
var permPick = null
|
|
8212
8327
|
|
|
8213
8328
|
/**
|
|
8214
|
-
*
|
|
8215
|
-
*
|
|
8216
|
-
*
|
|
8217
|
-
*
|
|
8218
|
-
* first catalog read.
|
|
8329
|
+
* The host's permission catalog: every preset this deployment offers, in
|
|
8330
|
+
* the host's order, or null before the first read settles. It is the
|
|
8331
|
+
* authority on what is switchable — a third-party plugin's preset rides
|
|
8332
|
+
* in it — while PERMISSION_PRESETS decides how a known one reads.
|
|
8219
8333
|
*/
|
|
8220
|
-
var
|
|
8334
|
+
var catalogOptions = null
|
|
8335
|
+
/**
|
|
8336
|
+
* The preset a new session starts in (the catalog's `defaultPreset`: the
|
|
8337
|
+
* configured default, or the one the host infers). The cold start screen
|
|
8338
|
+
* has no session to read a preset from, so its segments show this one.
|
|
8339
|
+
* null until a catalog read settled.
|
|
8340
|
+
*/
|
|
8341
|
+
var defaultPreset = null
|
|
8342
|
+
/** Whether the last pass drew the segments on the cold start screen. */
|
|
8343
|
+
var coldStartShown = false
|
|
8221
8344
|
/** The first catalog failure; thrown on the next sync to retire the feature. */
|
|
8222
8345
|
var autoPresetError = null
|
|
8223
8346
|
var catalogFiber = null
|
|
8224
8347
|
var catalogChangedDisposer = null
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8348
|
+
/** What the popover and the segment group currently render, so a change rebuilds them. */
|
|
8349
|
+
var renderedRows = ''
|
|
8350
|
+
var renderedRowsFor = null
|
|
8351
|
+
var renderedSegments = ''
|
|
8352
|
+
|
|
8353
|
+
/** The catalog's entry for one preset, or null. */
|
|
8354
|
+
function catalogOption(preset) {
|
|
8355
|
+
if (catalogOptions === null) return null
|
|
8356
|
+
for (var i = 0; i < catalogOptions.length; i++) {
|
|
8357
|
+
var option = catalogOptions[i]
|
|
8358
|
+
if (option !== null && typeof option === 'object' && option.value === preset) return option
|
|
8359
|
+
}
|
|
8360
|
+
return null
|
|
8229
8361
|
}
|
|
8230
8362
|
|
|
8231
8363
|
/**
|
|
8232
|
-
*
|
|
8233
|
-
*
|
|
8234
|
-
*
|
|
8364
|
+
* Whether the host offers a preset. Until the first read settles the
|
|
8365
|
+
* shipped built-ins stand in, which is what the control drew before the
|
|
8366
|
+
* catalog existed; a preset the catalog does not carry is not drawn at
|
|
8367
|
+
* all, so a deployment that configures fewer presets gets fewer rows
|
|
8368
|
+
* rather than dead ones.
|
|
8235
8369
|
*/
|
|
8236
|
-
function
|
|
8237
|
-
if (
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8370
|
+
function presetOffered(preset) {
|
|
8371
|
+
if (catalogOptions === null) return PERMISSION_SHIPPED_PRESETS.indexOf(preset) !== -1
|
|
8372
|
+
return catalogOption(preset) !== null
|
|
8373
|
+
}
|
|
8374
|
+
|
|
8375
|
+
/** The name a preset reads as: the skin's table first, then the host's own name. */
|
|
8376
|
+
function presetLabel(preset) {
|
|
8377
|
+
var known = PERMISSION_PRESETS[preset]
|
|
8378
|
+
if (known !== undefined) return known.label
|
|
8379
|
+
var option = catalogOption(preset)
|
|
8380
|
+
if (option !== null && typeof option.name === 'string' && option.name !== '') return option.name
|
|
8381
|
+
var current = PERMISSION_CURRENT_LABELS[preset]
|
|
8382
|
+
return current === undefined ? preset : current
|
|
8383
|
+
}
|
|
8384
|
+
|
|
8385
|
+
/** The one line under that name, from the same two sources. */
|
|
8386
|
+
function presetDesc(preset) {
|
|
8387
|
+
var known = PERMISSION_PRESETS[preset]
|
|
8388
|
+
if (known !== undefined) return known.desc
|
|
8389
|
+
var option = catalogOption(preset)
|
|
8390
|
+
return option !== null && typeof option.description === 'string' ? option.description : ''
|
|
8391
|
+
}
|
|
8392
|
+
|
|
8393
|
+
/** Every preset the control offers, in the order its rows list them. */
|
|
8394
|
+
function offeredPresets() {
|
|
8395
|
+
var i
|
|
8396
|
+
if (catalogOptions === null) return PERMISSION_SHIPPED_PRESETS.slice()
|
|
8397
|
+
var offered = []
|
|
8398
|
+
for (i = 0; i < catalogOptions.length; i++) {
|
|
8399
|
+
var option = catalogOptions[i]
|
|
8400
|
+
if (option === null || typeof option !== 'object') continue
|
|
8401
|
+
if (typeof option.value !== 'string' || option.value === '') continue
|
|
8402
|
+
if (offered.indexOf(option.value) === -1) offered.push(option.value)
|
|
8403
|
+
}
|
|
8404
|
+
// The skin's order for the presets it knows; anything else the host
|
|
8405
|
+
// offers follows in the host's own order.
|
|
8406
|
+
var ordered = []
|
|
8407
|
+
for (i = 0; i < PERMISSION_ORDER.length; i++) {
|
|
8408
|
+
if (offered.indexOf(PERMISSION_ORDER[i]) !== -1) ordered.push(PERMISSION_ORDER[i])
|
|
8409
|
+
}
|
|
8410
|
+
for (i = 0; i < offered.length; i++) {
|
|
8411
|
+
if (ordered.indexOf(offered[i]) === -1) ordered.push(offered[i])
|
|
8412
|
+
}
|
|
8413
|
+
return ordered
|
|
8414
|
+
}
|
|
8415
|
+
|
|
8416
|
+
/** The segments to draw: each slot bound to the first of its presets the host offers. */
|
|
8417
|
+
function resolvedSegments() {
|
|
8418
|
+
var out = []
|
|
8419
|
+
for (var i = 0; i < PERMISSION_SEGMENTS.length; i++) {
|
|
8420
|
+
var slot = PERMISSION_SEGMENTS[i]
|
|
8421
|
+
for (var j = 0; j < slot.presets.length; j++) {
|
|
8422
|
+
if (!presetOffered(slot.presets[j])) continue
|
|
8423
|
+
out.push({ label: slot.label, preset: slot.presets[j] })
|
|
8424
|
+
break
|
|
8425
|
+
}
|
|
8241
8426
|
}
|
|
8427
|
+
return out
|
|
8242
8428
|
}
|
|
8243
8429
|
|
|
8244
8430
|
/**
|
|
8245
|
-
* Read the host's permission catalog
|
|
8246
|
-
*
|
|
8247
|
-
* same catalog, so it is the authority on
|
|
8248
|
-
*
|
|
8249
|
-
*
|
|
8250
|
-
*
|
|
8251
|
-
*
|
|
8431
|
+
* Read the host's permission catalog into `catalogOptions`, and which
|
|
8432
|
+
* preset a new session starts in into `defaultPreset`. The shipped
|
|
8433
|
+
* picker builds its rows from the same catalog, so it is the authority on
|
|
8434
|
+
* what is switchable — including presets a third-party plugin registered.
|
|
8435
|
+
* A rejected read retries like the account profile's reads (the client
|
|
8436
|
+
* connection may still be coming up at install); once the retries are
|
|
8437
|
+
* exhausted the failure is remembered and thrown on the next sync, which
|
|
8438
|
+
* retires this feature and hands the shipped access button back (D12).
|
|
8252
8439
|
*/
|
|
8253
8440
|
var AUTO_PRESET_RETRY_MS = [1000, 5000]
|
|
8254
8441
|
var autoPresetRetries = 0
|
|
@@ -8280,22 +8467,17 @@ window.__ModuleLoader__.load({
|
|
|
8280
8467
|
if (read !== autoPresetRead) return
|
|
8281
8468
|
if (result === null || typeof result !== 'object' || result.ok !== true ||
|
|
8282
8469
|
result.value === null || typeof result.value !== 'object' ||
|
|
8283
|
-
!Array.isArray(result.value.options)) {
|
|
8470
|
+
!Array.isArray(result.value.options) || typeof result.value.defaultPreset !== 'string') {
|
|
8284
8471
|
autoPresetError = new Error('permission: unexpected permissionPresets catalog shape')
|
|
8285
8472
|
ui.schedule()
|
|
8286
8473
|
return
|
|
8287
8474
|
}
|
|
8288
8475
|
autoPresetRetries = 0
|
|
8289
8476
|
autoPresetError = null
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
live = true
|
|
8295
|
-
break
|
|
8296
|
-
}
|
|
8297
|
-
}
|
|
8298
|
-
autoPresetLive = live
|
|
8477
|
+
// The whole option list is kept, not just whether one preset is in
|
|
8478
|
+
// it: the rows and the segments are built from what the host serves.
|
|
8479
|
+
catalogOptions = result.value.options.slice()
|
|
8480
|
+
defaultPreset = result.value.defaultPreset
|
|
8299
8481
|
ui.schedule()
|
|
8300
8482
|
}, function () {
|
|
8301
8483
|
if (read !== autoPresetRead) return
|
|
@@ -8338,14 +8520,19 @@ window.__ModuleLoader__.load({
|
|
|
8338
8520
|
}
|
|
8339
8521
|
}
|
|
8340
8522
|
|
|
8341
|
-
|
|
8523
|
+
/**
|
|
8524
|
+
* Build the segment group from the resolved slots. `specs` is what
|
|
8525
|
+
* resolvedSegments() returned, so a slot the host cannot serve is simply
|
|
8526
|
+
* absent instead of drawn dead.
|
|
8527
|
+
*/
|
|
8528
|
+
function buildSegments(onPick, specs) {
|
|
8342
8529
|
var group = document.createElement('div')
|
|
8343
8530
|
group.className = SEGMENTS_CLASS
|
|
8344
8531
|
group.setAttribute('role', 'radiogroup')
|
|
8345
8532
|
group.setAttribute('aria-label', 'Permission')
|
|
8346
8533
|
group.setAttribute('data-composer-segments', '')
|
|
8347
|
-
for (var i = 0; i <
|
|
8348
|
-
var spec =
|
|
8534
|
+
for (var i = 0; i < specs.length; i++) {
|
|
8535
|
+
var spec = specs[i]
|
|
8349
8536
|
var item = document.createElement('button')
|
|
8350
8537
|
item.type = 'button'
|
|
8351
8538
|
item.className = SEGMENT_CLASS
|
|
@@ -8376,9 +8563,77 @@ window.__ModuleLoader__.load({
|
|
|
8376
8563
|
permPopover.removeAttribute('data-open')
|
|
8377
8564
|
}
|
|
8378
8565
|
|
|
8566
|
+
/**
|
|
8567
|
+
* One popover row for one preset: the name and the line under it from the
|
|
8568
|
+
* skin's table (the host's own copy for a preset the table does not know),
|
|
8569
|
+
* and the active check. No glyph: the tiers read as one list, and a
|
|
8570
|
+
* preset's own `icon` — which the host ignores natively — is left out so
|
|
8571
|
+
* the rows stay consistent with each other.
|
|
8572
|
+
*/
|
|
8573
|
+
function buildPermRow(preset) {
|
|
8574
|
+
var item = document.createElement('button')
|
|
8575
|
+
item.type = 'button'
|
|
8576
|
+
item.className = 'dsh-claude-popover-item'
|
|
8577
|
+
item.setAttribute('role', 'menuitem')
|
|
8578
|
+
item.setAttribute('data-preset', preset)
|
|
8579
|
+
|
|
8580
|
+
var col = document.createElement('div')
|
|
8581
|
+
col.style.cssText = 'display:flex; flex-direction:column; gap:2px; flex:1; text-align:left; min-width:0;'
|
|
8582
|
+
|
|
8583
|
+
var itemTitle = document.createElement('span')
|
|
8584
|
+
itemTitle.style.cssText = 'font-weight:500; font-size:13px; line-height:16px;'
|
|
8585
|
+
itemTitle.textContent = presetLabel(preset)
|
|
8586
|
+
|
|
8587
|
+
var itemDesc = document.createElement('span')
|
|
8588
|
+
itemDesc.style.cssText = 'font-size:11px; line-height:14px; color:var(--dsw-alias-label-tertiary);'
|
|
8589
|
+
itemDesc.textContent = presetDesc(preset)
|
|
8590
|
+
|
|
8591
|
+
col.appendChild(itemTitle)
|
|
8592
|
+
col.appendChild(itemDesc)
|
|
8593
|
+
item.appendChild(col)
|
|
8594
|
+
|
|
8595
|
+
var check = document.createElement('span')
|
|
8596
|
+
check.className = 'dsh-claude-perm-check'
|
|
8597
|
+
check.style.cssText = 'font-size:12px; color:var(--dsw-alias-brand-primary, #d97757); margin-left:8px; display:none;'
|
|
8598
|
+
check.textContent = '✓'
|
|
8599
|
+
item.appendChild(check)
|
|
8600
|
+
|
|
8601
|
+
item.addEventListener('click', function (e) {
|
|
8602
|
+
e.stopPropagation()
|
|
8603
|
+
closePermMenu()
|
|
8604
|
+
if (permPick !== null) permPick(preset)
|
|
8605
|
+
})
|
|
8606
|
+
|
|
8607
|
+
return item
|
|
8608
|
+
}
|
|
8609
|
+
|
|
8610
|
+
/**
|
|
8611
|
+
* Rebuild the popover's rows when the set of offered presets changes.
|
|
8612
|
+
* Rows come from the host's catalog, so a preset a plugin registers while
|
|
8613
|
+
* the page stays open appears without a reload, and one it withdraws
|
|
8614
|
+
* disappears; an unchanged set leaves the DOM alone (the pass runs on
|
|
8615
|
+
* every mutation).
|
|
8616
|
+
*
|
|
8617
|
+
* The rendered signature is kept WITH the element it was rendered into: a
|
|
8618
|
+
* new popover starts empty (the hero view takes the old one out of the
|
|
8619
|
+
* tree), so a signature that merely matches the previous one must not
|
|
8620
|
+
* skip filling this one.
|
|
8621
|
+
*/
|
|
8622
|
+
function syncPermRows() {
|
|
8623
|
+
if (permPopover === null) return
|
|
8624
|
+
var wanted = offeredPresets()
|
|
8625
|
+
var signature = wanted.join('|')
|
|
8626
|
+
if (signature === renderedRows && renderedRowsFor === permPopover) return
|
|
8627
|
+
renderedRows = signature
|
|
8628
|
+
renderedRowsFor = permPopover
|
|
8629
|
+
while (permPopover.firstChild !== null) permPopover.removeChild(permPopover.firstChild)
|
|
8630
|
+
for (var i = 0; i < wanted.length; i++) permPopover.appendChild(buildPermRow(wanted[i]))
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8379
8633
|
registerPopover('permission', closePermMenu)
|
|
8380
8634
|
|
|
8381
8635
|
function buildPermTriggerAndPopover(onPick) {
|
|
8636
|
+
permPick = onPick
|
|
8382
8637
|
var container = document.createElement('div')
|
|
8383
8638
|
container.className = 'dsh-claude-perm-container'
|
|
8384
8639
|
|
|
@@ -8403,46 +8658,6 @@ window.__ModuleLoader__.load({
|
|
|
8403
8658
|
popover.className = 'dsh-claude-perm-popover'
|
|
8404
8659
|
popover.setAttribute('role', 'menu')
|
|
8405
8660
|
|
|
8406
|
-
for (var i = 0; i < PERMISSION_OPTIONS.length; i++) {
|
|
8407
|
-
var opt = PERMISSION_OPTIONS[i]
|
|
8408
|
-
var item = document.createElement('button')
|
|
8409
|
-
item.type = 'button'
|
|
8410
|
-
item.className = 'dsh-claude-popover-item'
|
|
8411
|
-
item.setAttribute('role', 'menuitem')
|
|
8412
|
-
item.setAttribute('data-preset', opt.preset)
|
|
8413
|
-
|
|
8414
|
-
var col = document.createElement('div')
|
|
8415
|
-
col.style.cssText = 'display:flex; flex-direction:column; gap:2px; flex:1; text-align:left; min-width:0;'
|
|
8416
|
-
|
|
8417
|
-
var itemTitle = document.createElement('span')
|
|
8418
|
-
itemTitle.style.cssText = 'font-weight:500; font-size:13px; line-height:16px;'
|
|
8419
|
-
itemTitle.textContent = opt.label
|
|
8420
|
-
|
|
8421
|
-
var itemDesc = document.createElement('span')
|
|
8422
|
-
itemDesc.style.cssText = 'font-size:11px; line-height:14px; color:var(--dsw-alias-label-tertiary);'
|
|
8423
|
-
itemDesc.textContent = opt.desc
|
|
8424
|
-
|
|
8425
|
-
col.appendChild(itemTitle)
|
|
8426
|
-
col.appendChild(itemDesc)
|
|
8427
|
-
item.appendChild(col)
|
|
8428
|
-
|
|
8429
|
-
var check = document.createElement('span')
|
|
8430
|
-
check.className = 'dsh-claude-perm-check'
|
|
8431
|
-
check.style.cssText = 'font-size:12px; color:var(--dsw-alias-brand-primary, #d97757); margin-left:8px; display:none;'
|
|
8432
|
-
check.textContent = '✓'
|
|
8433
|
-
item.appendChild(check)
|
|
8434
|
-
|
|
8435
|
-
item.addEventListener('click', (function (preset) {
|
|
8436
|
-
return function (e) {
|
|
8437
|
-
e.stopPropagation()
|
|
8438
|
-
closePermMenu()
|
|
8439
|
-
onPick(preset)
|
|
8440
|
-
}
|
|
8441
|
-
})(opt.preset))
|
|
8442
|
-
|
|
8443
|
-
popover.appendChild(item)
|
|
8444
|
-
}
|
|
8445
|
-
|
|
8446
8661
|
function openPerm() {
|
|
8447
8662
|
if (permHoverIntent) permHoverIntent.cancel()
|
|
8448
8663
|
closeOtherPopovers('permission')
|
|
@@ -8513,15 +8728,13 @@ window.__ModuleLoader__.load({
|
|
|
8513
8728
|
|
|
8514
8729
|
function updatePermState(preset) {
|
|
8515
8730
|
if (!permLabel || !permPopover) return
|
|
8516
|
-
//
|
|
8517
|
-
//
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
}
|
|
8524
|
-
}
|
|
8731
|
+
// The rows follow the host's catalog: one it does not serve is not
|
|
8732
|
+
// drawn at all, and one it starts serving appears.
|
|
8733
|
+
syncPermRows()
|
|
8734
|
+
// The running preset reads as its Claude-flavored name; a value no
|
|
8735
|
+
// preset carries (the host's `custom`) reads as the host's own word for
|
|
8736
|
+
// it rather than as the machine value.
|
|
8737
|
+
var matchedLabel = preset === null ? 'Accept edits' : presetLabel(preset)
|
|
8525
8738
|
// Same-value guard: this runs on every pass, and an identical
|
|
8526
8739
|
// textContent write still replaces the text node — a mutation that
|
|
8527
8740
|
// schedules the next pass, so the page never went idle.
|
|
@@ -8530,8 +8743,7 @@ window.__ModuleLoader__.load({
|
|
|
8530
8743
|
var items = permPopover.querySelectorAll('[data-preset]')
|
|
8531
8744
|
for (var j = 0; j < items.length; j++) {
|
|
8532
8745
|
var it = items[j]
|
|
8533
|
-
var
|
|
8534
|
-
var isCurrent = rowPreset === preset
|
|
8746
|
+
var isCurrent = it.getAttribute('data-preset') === preset
|
|
8535
8747
|
var check = it.querySelector('.dsh-claude-perm-check')
|
|
8536
8748
|
if (check) {
|
|
8537
8749
|
check.style.display = isCurrent ? 'inline' : 'none'
|
|
@@ -8541,7 +8753,6 @@ window.__ModuleLoader__.load({
|
|
|
8541
8753
|
} else {
|
|
8542
8754
|
it.removeAttribute('data-active')
|
|
8543
8755
|
}
|
|
8544
|
-
syncRowVisibility(it, rowPreset)
|
|
8545
8756
|
}
|
|
8546
8757
|
}
|
|
8547
8758
|
|
|
@@ -8618,16 +8829,29 @@ window.__ModuleLoader__.load({
|
|
|
8618
8829
|
existingSegments[es0].remove()
|
|
8619
8830
|
}
|
|
8620
8831
|
segments = null
|
|
8832
|
+
coldStartShown = false
|
|
8621
8833
|
return
|
|
8622
8834
|
}
|
|
8623
8835
|
|
|
8836
|
+
var session = currentSession(ctx)
|
|
8624
8837
|
var trigger = findAccessTrigger()
|
|
8625
|
-
|
|
8626
|
-
|
|
8838
|
+
// The cold start screen (no session yet) renders no access button: the
|
|
8839
|
+
// host leaves the card's mode strip empty and turns the whole card into
|
|
8840
|
+
// the workspace pick target. The segments take that strip and show the
|
|
8841
|
+
// preset the new session will start in; they are disabled there, so a
|
|
8842
|
+
// press falls through to the card and opens the workspace picker like
|
|
8843
|
+
// every other control on it.
|
|
8844
|
+
var coldStart = trigger === null && isHero && session === null
|
|
8845
|
+
var host = coldStart
|
|
8846
|
+
? document.querySelector('[data-composer-card][class*="_cardWorkspaceTrigger"] [class*="_modes"]')
|
|
8847
|
+
: trigger === null ? null : trigger.parentElement
|
|
8848
|
+
// The default can change in Settings without a catalog event, so each
|
|
8849
|
+
// return to the cold start screen reads it again.
|
|
8850
|
+
if (coldStart && !coldStartShown && catalogOptions !== null) probeAutoPreset()
|
|
8851
|
+
coldStartShown = coldStart
|
|
8627
8852
|
if (host === null) return
|
|
8628
8853
|
|
|
8629
|
-
var
|
|
8630
|
-
var preset = session === null ? null : currentPreset(session)
|
|
8854
|
+
var preset = coldStart ? defaultPreset : session === null ? null : currentPreset(session)
|
|
8631
8855
|
|
|
8632
8856
|
if (isHero) {
|
|
8633
8857
|
for (var i = 0; i < existingPermContainers.length; i++) {
|
|
@@ -8641,19 +8865,26 @@ window.__ModuleLoader__.load({
|
|
|
8641
8865
|
permLabel = null
|
|
8642
8866
|
permPopover = null
|
|
8643
8867
|
|
|
8868
|
+
// The slots follow the host's catalog: the deployment's own auto tier
|
|
8869
|
+
// takes the Auto slot when it is offered, and a slot with no offered
|
|
8870
|
+
// preset is not drawn. Rebuild when the binding changed; the shipped
|
|
8871
|
+
// group is reused otherwise, so a pass leaves the DOM alone.
|
|
8872
|
+
var segmentSpecs = resolvedSegments()
|
|
8873
|
+
var segmentSignature = segmentSpecs.map(function (spec) { return spec.label + '=' + spec.preset }).join('|')
|
|
8644
8874
|
if (existingSegments.length > 1) {
|
|
8645
8875
|
for (var s = 1; s < existingSegments.length; s++) existingSegments[s].remove()
|
|
8646
8876
|
}
|
|
8647
|
-
if (existingSegments.length === 1 && host.contains(existingSegments[0])) {
|
|
8877
|
+
if (segmentSignature === renderedSegments && existingSegments.length === 1 && host.contains(existingSegments[0])) {
|
|
8648
8878
|
segments = existingSegments[0]
|
|
8649
8879
|
} else {
|
|
8880
|
+
renderedSegments = segmentSignature
|
|
8650
8881
|
for (var s2 = 0; s2 < existingSegments.length; s2++) existingSegments[s2].remove()
|
|
8651
|
-
segments = buildSegments(pick)
|
|
8882
|
+
segments = buildSegments(pick, segmentSpecs)
|
|
8652
8883
|
host.insertBefore(segments, host.firstChild)
|
|
8653
8884
|
}
|
|
8654
8885
|
for (var j = 0; j < segments.children.length; j++) {
|
|
8655
8886
|
var item = segments.children[j]
|
|
8656
|
-
|
|
8887
|
+
if (item.disabled !== coldStart) item.disabled = coldStart
|
|
8657
8888
|
if (item.getAttribute('data-preset') === preset) {
|
|
8658
8889
|
item.setAttribute('data-active', '')
|
|
8659
8890
|
item.setAttribute('aria-checked', 'true')
|
|
@@ -11821,8 +12052,8 @@ window.__ModuleLoader__.load({
|
|
|
11821
12052
|
/**
|
|
11822
12053
|
* The profile picture's address, or null when there is none usable. It
|
|
11823
12054
|
* comes from the account service or from the plugin's own HDSL route, so
|
|
11824
|
-
* only http(s) and that route are accepted, and it is
|
|
11825
|
-
*
|
|
12055
|
+
* only http(s) and that route are accepted, and it is set as a property —
|
|
12056
|
+
* never written into markup.
|
|
11826
12057
|
*/
|
|
11827
12058
|
function accountPhotoUrl(raw) {
|
|
11828
12059
|
if (raw === HDSL_SKIN_ROUTE) return raw
|
|
@@ -11835,11 +12066,110 @@ window.__ModuleLoader__.load({
|
|
|
11835
12066
|
}
|
|
11836
12067
|
}
|
|
11837
12068
|
|
|
12069
|
+
/** The head's canvas, and what a pass needs to decide whether to keep it. */
|
|
12070
|
+
var headCanvas = null
|
|
12071
|
+
/** A load has been started; the route is read once per page, so is a failure. */
|
|
12072
|
+
var headRequested = false
|
|
12073
|
+
var headFailed = false
|
|
12074
|
+
|
|
12075
|
+
/**
|
|
12076
|
+
* Draw the head out of a launcher skin, the way the launcher's own
|
|
12077
|
+
* account list draws it: the front face of the head texel block, inset by
|
|
12078
|
+
* 1/18 of the box, then the hat layer over the whole box. The atlas may
|
|
12079
|
+
* be stored at any integer multiple of 64 (the launcher normalizes to
|
|
12080
|
+
* 64×64 and keeps an already-larger import at its size), so every texel
|
|
12081
|
+
* block is measured by that multiple.
|
|
12082
|
+
*
|
|
12083
|
+
* @returns whether a head was drawn.
|
|
12084
|
+
*/
|
|
12085
|
+
function drawLauncherHead(canvas, image) {
|
|
12086
|
+
var box = canvas.width
|
|
12087
|
+
var scale = image.naturalWidth / 64
|
|
12088
|
+
var context = canvas.getContext('2d')
|
|
12089
|
+
if (context === null || scale < 1 || scale !== Math.floor(scale)) return false
|
|
12090
|
+
var offset = Math.round(box / 18)
|
|
12091
|
+
context.clearRect(0, 0, box, box)
|
|
12092
|
+
// The face is a 8×8 texel block drawn inside the inset; the pixels are
|
|
12093
|
+
// already at the right size, so smoothing would only blur them.
|
|
12094
|
+
context.imageSmoothingEnabled = false
|
|
12095
|
+
context.drawImage(image, 8 * scale, 8 * scale, 8 * scale, 8 * scale, offset, offset, box - 2 * offset, box - 2 * offset)
|
|
12096
|
+
context.drawImage(image, 40 * scale, 8 * scale, 8 * scale, 8 * scale, 0, 0, box, box)
|
|
12097
|
+
return true
|
|
12098
|
+
}
|
|
12099
|
+
|
|
12100
|
+
/**
|
|
12101
|
+
* Load the launcher's atlas once. The canvas is built off-screen: the
|
|
12102
|
+
* avatar element a pass hands in may be a different one by the time the
|
|
12103
|
+
* picture lands, and a failure only means the mark keeps the circle.
|
|
12104
|
+
*/
|
|
12105
|
+
function loadLauncherHead() {
|
|
12106
|
+
headRequested = true
|
|
12107
|
+
var image = new Image()
|
|
12108
|
+
image.decoding = 'async'
|
|
12109
|
+
image.addEventListener('load', function () {
|
|
12110
|
+
if (image.naturalWidth !== image.naturalHeight || image.naturalWidth < 64) {
|
|
12111
|
+
headFailed = true
|
|
12112
|
+
wake()
|
|
12113
|
+
return
|
|
12114
|
+
}
|
|
12115
|
+
if (headCanvas === null) {
|
|
12116
|
+
headCanvas = document.createElement('canvas')
|
|
12117
|
+
headCanvas.className = 'dsh-claude-account-skin'
|
|
12118
|
+
headCanvas.width = 64
|
|
12119
|
+
headCanvas.height = 64
|
|
12120
|
+
headCanvas.setAttribute('aria-hidden', 'true')
|
|
12121
|
+
}
|
|
12122
|
+
if (!drawLauncherHead(headCanvas, image)) headFailed = true
|
|
12123
|
+
wake()
|
|
12124
|
+
})
|
|
12125
|
+
image.addEventListener('error', function () {
|
|
12126
|
+
headFailed = true
|
|
12127
|
+
wake()
|
|
12128
|
+
})
|
|
12129
|
+
image.src = HDSL_SKIN_ROUTE
|
|
12130
|
+
}
|
|
12131
|
+
|
|
12132
|
+
/** Ask for the pass that mounts the head (the picture changes no DOM). */
|
|
12133
|
+
function wake() {
|
|
12134
|
+
if (typeof options.onChange === 'function') options.onChange()
|
|
12135
|
+
}
|
|
12136
|
+
|
|
12137
|
+
/** Drop the account profile's photo, if one is mounted. */
|
|
12138
|
+
function clearAccountPhoto(avatarEl) {
|
|
12139
|
+
var photo = avatarEl.querySelector('.dsh-claude-account-photo')
|
|
12140
|
+
if (photo === null) return
|
|
12141
|
+
avatarEl.removeChild(photo)
|
|
12142
|
+
if (avatarEl.hasAttribute('data-dsh-claude-photo')) avatarEl.removeAttribute('data-dsh-claude-photo')
|
|
12143
|
+
}
|
|
12144
|
+
|
|
12145
|
+
/** Take the head's canvas back out of a circle the photo path owns again. */
|
|
12146
|
+
function detachLauncherHead(avatarEl) {
|
|
12147
|
+
if (headCanvas !== null && headCanvas.parentElement === avatarEl) avatarEl.removeChild(headCanvas)
|
|
12148
|
+
if (avatarEl.hasAttribute('data-dsh-claude-skin')) avatarEl.removeAttribute('data-dsh-claude-skin')
|
|
12149
|
+
}
|
|
12150
|
+
|
|
12151
|
+
/**
|
|
12152
|
+
* Paint the player's own head. What the launcher serves is the normalized
|
|
12153
|
+
* skin atlas — a sheet of body parts, not a face — so it is cropped into
|
|
12154
|
+
* a canvas and never handed to the `<img>` the photo path uses. Until the
|
|
12155
|
+
* head is drawn, and when it cannot be, the mark keeps the circle.
|
|
12156
|
+
*/
|
|
12157
|
+
function syncLauncherHead(avatarEl) {
|
|
12158
|
+
if (!headRequested) loadLauncherHead()
|
|
12159
|
+
if (headCanvas === null || headFailed) return
|
|
12160
|
+
if (headCanvas.parentElement !== avatarEl) {
|
|
12161
|
+
if (headCanvas.parentElement !== null) headCanvas.parentElement.removeChild(headCanvas)
|
|
12162
|
+
avatarEl.appendChild(headCanvas)
|
|
12163
|
+
}
|
|
12164
|
+
if (!avatarEl.hasAttribute('data-dsh-claude-skin')) avatarEl.setAttribute('data-dsh-claude-skin', '')
|
|
12165
|
+
}
|
|
12166
|
+
|
|
11838
12167
|
/**
|
|
11839
12168
|
* Paint (or clear) the picture inside the avatar circle. The address comes
|
|
11840
12169
|
* from the identity chain (src/context/host.js): the account's own avatar,
|
|
11841
|
-
* then the HDSL launcher's, then nothing — and
|
|
11842
|
-
* stylesheet draws shows through.
|
|
12170
|
+
* then the HDSL launcher's (drawn as a cropped head), then nothing — and
|
|
12171
|
+
* the brand mark the stylesheet draws shows through. The account's avatar
|
|
12172
|
+
* is a real `<img>` layered over that
|
|
11843
12173
|
* mark rather than a CSS background: the host's own avatar `<img>` carries
|
|
11844
12174
|
* `referrerPolicy="no-referrer"`, which is what the picture host expects,
|
|
11845
12175
|
* and a background cannot drop the referrer. A picture that fails to load
|
|
@@ -11848,10 +12178,16 @@ window.__ModuleLoader__.load({
|
|
|
11848
12178
|
function syncAccountAvatar(avatarEl) {
|
|
11849
12179
|
if (avatarEl === null) return
|
|
11850
12180
|
var src = accountPhotoUrl(resolveAvatarUrl())
|
|
12181
|
+
if (src === HDSL_SKIN_ROUTE) {
|
|
12182
|
+
// The launcher's picture only ever shows as the cropped head.
|
|
12183
|
+
clearAccountPhoto(avatarEl)
|
|
12184
|
+
syncLauncherHead(avatarEl)
|
|
12185
|
+
return
|
|
12186
|
+
}
|
|
12187
|
+
detachLauncherHead(avatarEl)
|
|
11851
12188
|
var photo = avatarEl.querySelector('.dsh-claude-account-photo')
|
|
11852
12189
|
if (src === null) {
|
|
11853
|
-
|
|
11854
|
-
if (avatarEl.hasAttribute('data-dsh-claude-photo')) avatarEl.removeAttribute('data-dsh-claude-photo')
|
|
12190
|
+
clearAccountPhoto(avatarEl)
|
|
11855
12191
|
return
|
|
11856
12192
|
}
|
|
11857
12193
|
if (photo === null) {
|
|
@@ -12698,6 +13034,11 @@ window.__ModuleLoader__.load({
|
|
|
12698
13034
|
var hostMenu = createHostAccountMenu({ close: closePopover })
|
|
12699
13035
|
var rows = createAccountRows({
|
|
12700
13036
|
hostMenu: hostMenu,
|
|
13037
|
+
// Drawing the launcher's head is asynchronous and changes no DOM the
|
|
13038
|
+
// observer can see, so the picture asks for the pass that paints it.
|
|
13039
|
+
onChange: function () {
|
|
13040
|
+
if (typeof ui.schedule === 'function') ui.schedule()
|
|
13041
|
+
},
|
|
12701
13042
|
openBan: function () {
|
|
12702
13043
|
// Leave the surface up: the overlay is a full-window surface, so what
|
|
12703
13044
|
// is behind it does not matter, and the footer is left as the user
|
|
@@ -15043,7 +15384,10 @@ window.__ModuleLoader__.load({
|
|
|
15043
15384
|
* the hero region that accepts a new entry (the brand mark, workspace and
|
|
15044
15385
|
* preset seats are `single` and occupied), and it is scoped to a session, so
|
|
15045
15386
|
* on the cold start screen — no session object yet — the host does not render
|
|
15046
|
-
* it at all
|
|
15387
|
+
* it at all. There the skin mounts the same panel itself: a React root on a
|
|
15388
|
+
* `display: contents` element of its own at the end of the hero stack, which
|
|
15389
|
+
* the stylesheet sorts into the dock's place. It goes away as soon as the
|
|
15390
|
+
* host renders the dock.
|
|
15047
15391
|
*
|
|
15048
15392
|
* This fragment is the shell: the seat registration, the head (the
|
|
15049
15393
|
* Overview/Models tabs and the All/30d/7d range pills), the tab switch and
|
|
@@ -15069,6 +15413,9 @@ window.__ModuleLoader__.load({
|
|
|
15069
15413
|
*/
|
|
15070
15414
|
var bookPick = Math.random()
|
|
15071
15415
|
var slotsFiber = null
|
|
15416
|
+
var ReactDOM = require('react-dom/client')
|
|
15417
|
+
/** The cold start screen's panel: `{ stack, element, root }`, or null. */
|
|
15418
|
+
var coldSeat = null
|
|
15072
15419
|
|
|
15073
15420
|
var usage = createHomeUsage(ctx)
|
|
15074
15421
|
var overview = createHomeOverview()
|
|
@@ -15219,11 +15566,44 @@ window.__ModuleLoader__.load({
|
|
|
15219
15566
|
})
|
|
15220
15567
|
}
|
|
15221
15568
|
|
|
15569
|
+
function unmountColdSeat() {
|
|
15570
|
+
if (coldSeat === null) return
|
|
15571
|
+
coldSeat.root.unmount()
|
|
15572
|
+
if (coldSeat.element.parentElement !== null) coldSeat.element.parentElement.removeChild(coldSeat.element)
|
|
15573
|
+
coldSeat = null
|
|
15574
|
+
}
|
|
15575
|
+
|
|
15576
|
+
/**
|
|
15577
|
+
* Mount the panel on the cold start screen, and drop it once the host's
|
|
15578
|
+
* dock is there to carry it (or the page leaves the studio hero). The
|
|
15579
|
+
* element is appended to the host's stack and never moves a host node;
|
|
15580
|
+
* a re-render that replaces the stack leaves the old copy behind, so it
|
|
15581
|
+
* is re-mounted on the new stack.
|
|
15582
|
+
*/
|
|
15583
|
+
function syncColdSeat() {
|
|
15584
|
+
var stack = document.body.hasAttribute(HOME_HERO_ATTR)
|
|
15585
|
+
? document.querySelector('[class*="_composerStack"][class*="_composerHero"]')
|
|
15586
|
+
: null
|
|
15587
|
+
if (stack === null || stack.querySelector(':scope > [data-slot="' + DOCK_SLOT + '"]') !== null) {
|
|
15588
|
+
unmountColdSeat()
|
|
15589
|
+
return
|
|
15590
|
+
}
|
|
15591
|
+
if (coldSeat !== null && coldSeat.stack === stack && coldSeat.element.parentElement === stack) return
|
|
15592
|
+
unmountColdSeat()
|
|
15593
|
+
var element = document.createElement('div')
|
|
15594
|
+
element.className = 'dsh-claude-home-seat'
|
|
15595
|
+
stack.appendChild(element)
|
|
15596
|
+
var root = ReactDOM.createRoot(element)
|
|
15597
|
+
root.render(React.createElement(HomeUsagePanel))
|
|
15598
|
+
coldSeat = { stack: stack, element: element, root: root }
|
|
15599
|
+
}
|
|
15600
|
+
|
|
15222
15601
|
/** Each pass reads the preference and the phase; only a change re-renders. */
|
|
15223
15602
|
function sync() {
|
|
15224
15603
|
var next = readPrefs().homeLayout
|
|
15225
15604
|
if (next !== layout) {
|
|
15226
15605
|
setLayout(next)
|
|
15606
|
+
syncColdSeat()
|
|
15227
15607
|
return
|
|
15228
15608
|
}
|
|
15229
15609
|
// The hero/active phase flips without any preference change (opening a
|
|
@@ -15237,6 +15617,7 @@ window.__ModuleLoader__.load({
|
|
|
15237
15617
|
if (hero) bookPick = Math.random()
|
|
15238
15618
|
usage.notify()
|
|
15239
15619
|
}
|
|
15620
|
+
syncColdSeat()
|
|
15240
15621
|
if (next === HOME_LAYOUT_STUDIO) {
|
|
15241
15622
|
usage.load(false)
|
|
15242
15623
|
usage.loadList()
|
|
@@ -15249,6 +15630,7 @@ window.__ModuleLoader__.load({
|
|
|
15249
15630
|
setLayout(readPrefs().homeLayout)
|
|
15250
15631
|
|
|
15251
15632
|
return function () {
|
|
15633
|
+
unmountColdSeat()
|
|
15252
15634
|
usage.stop()
|
|
15253
15635
|
document.body.removeAttribute(HOME_LAYOUT_ATTR)
|
|
15254
15636
|
document.body.removeAttribute(HOME_HERO_ATTR)
|
package/lib/index.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { readFileSync, readdirSync, rmSync, statSync } from 'node:fs'
|
|
32
32
|
import { homedir, userInfo } from 'node:os'
|
|
33
33
|
import { dirname, join, resolve, sep } from 'node:path'
|
|
34
|
-
import { fileURLToPath
|
|
34
|
+
import { fileURLToPath } from 'node:url'
|
|
35
35
|
import { createUsage } from './usage.js'
|
|
36
36
|
|
|
37
37
|
export const name = 'dsh-claude-style'
|
|
@@ -71,6 +71,12 @@ const HDSL_PATH = `${ROUTE_PREFIX}/hdsl`
|
|
|
71
71
|
const HDSL_SKIN_PATH = `${ROUTE_PREFIX}/hdsl-skin.png`
|
|
72
72
|
/** The contract version this half understands; any other value voids the whole group. */
|
|
73
73
|
const HDSL_CONTRACT = '1'
|
|
74
|
+
/**
|
|
75
|
+
* The context slot the harness's boot fills with this launch's environment
|
|
76
|
+
* snapshot (`@deepseek-ai/dsh-launch-environment` defines the same key, and its
|
|
77
|
+
* `launchEnvironmentOf(ctx)` reads exactly this slot before falling back).
|
|
78
|
+
*/
|
|
79
|
+
const DSH_LAUNCH_ENVIRONMENT_KEY = 'launchEnvironment'
|
|
74
80
|
/**
|
|
75
81
|
* The layers allowed to declare who the player is, in the launcher's own trust
|
|
76
82
|
* order. A project directory's `.env` travels with a cloned repository, so it
|
|
@@ -265,33 +271,6 @@ function buildPrefsSchema(Schema) {
|
|
|
265
271
|
})
|
|
266
272
|
}
|
|
267
273
|
|
|
268
|
-
/**
|
|
269
|
-
* The `launchEnvironmentOf` function of the harness's own launch-environment
|
|
270
|
-
* library, or null when it cannot be resolved.
|
|
271
|
-
*
|
|
272
|
-
* Same reason as {@link resolveSchemaFactory}: a plugin installed by link
|
|
273
|
-
* resolves its realpath outside the profile tree, so a plain import of a
|
|
274
|
-
* harness-provided library fails outright. The library is ESM, so the anchor
|
|
275
|
-
* resolves the real file and this half imports that path.
|
|
276
|
-
*
|
|
277
|
-
* @returns the accessor, or null.
|
|
278
|
-
*/
|
|
279
|
-
async function resolveLaunchEnvironmentOf() {
|
|
280
|
-
try {
|
|
281
|
-
const { createRequire } = await import('node:module')
|
|
282
|
-
const anchor = typeof process.argv[1] === 'string' && process.argv[1] !== '' ? process.argv[1] : process.execPath
|
|
283
|
-
const resolved = createRequire(anchor).resolve('@deepseek-ai/dsh-launch-environment')
|
|
284
|
-
const module = await import(pathToFileURL(resolved).href)
|
|
285
|
-
if (typeof module?.launchEnvironmentOf === 'function') return module.launchEnvironmentOf
|
|
286
|
-
} catch { /* the anchor carries no launch environment: try normal resolution */ }
|
|
287
|
-
try {
|
|
288
|
-
const module = await import('@deepseek-ai/dsh-launch-environment')
|
|
289
|
-
return typeof module?.launchEnvironmentOf === 'function' ? module.launchEnvironmentOf : null
|
|
290
|
-
} catch {
|
|
291
|
-
return null
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
274
|
/**
|
|
296
275
|
* The HDSL account contract of this launch, read once.
|
|
297
276
|
*
|
|
@@ -299,6 +278,13 @@ async function resolveLaunchEnvironmentOf() {
|
|
|
299
278
|
* every request re-serves it. `skinFile` is the player's avatar path: it stays
|
|
300
279
|
* inside this half and is never serialized to the browser.
|
|
301
280
|
*
|
|
281
|
+
* The snapshot is read through the slot the harness itself fills
|
|
282
|
+
* (`ctx.launchEnvironment` — the first thing `launchEnvironmentOf()` reads),
|
|
283
|
+
* rather than by importing `@deepseek-ai/dsh-launch-environment`: importing a
|
|
284
|
+
* harness-provided package fails outright for a `link:`-installed plugin (the
|
|
285
|
+
* same resolution problem `resolveSchemaFactory` documents), and a host that
|
|
286
|
+
* fills no slot simply has no contract to read.
|
|
287
|
+
*
|
|
302
288
|
* @param ctx - host plugin context.
|
|
303
289
|
* @returns `{ read() }`, whose promise resolves to the contract or to
|
|
304
290
|
* `{ contract: false }` when HDSL did not launch this instance.
|
|
@@ -307,14 +293,14 @@ function createHdslAccount(ctx) {
|
|
|
307
293
|
let reading = null
|
|
308
294
|
const read = () => {
|
|
309
295
|
if (reading !== null) return reading
|
|
310
|
-
reading =
|
|
311
|
-
|
|
312
|
-
let env
|
|
296
|
+
reading = Promise.resolve().then(() => {
|
|
297
|
+
let env = null
|
|
313
298
|
try {
|
|
314
|
-
env =
|
|
299
|
+
env = ctx.get(DSH_LAUNCH_ENVIRONMENT_KEY)
|
|
315
300
|
} catch {
|
|
316
|
-
|
|
301
|
+
env = null
|
|
317
302
|
}
|
|
303
|
+
if (env === null || env === undefined || typeof env.getFrom !== 'function') return { contract: false }
|
|
318
304
|
const value = (name) => {
|
|
319
305
|
try {
|
|
320
306
|
return env.getFrom(name, HDSL_LAYERS)?.value
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-style",
|
|
3
3
|
"description": "Claude Code Desktop theme for DeepSeek Harness (dsh) web GUI — visual & interaction overhaul",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"icon": "lib/claude-mark.svg",
|