dsh-claude-style 0.9.0 → 0.10.0
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 +38 -0
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/docs/STYLE.md +62 -0
- package/docs/architecture.md +25 -9
- package/host/index.js +77 -21
- package/lib/client.js +1208 -117
- package/lib/deepy/building.png +0 -0
- package/lib/deepy/compacting.png +0 -0
- package/lib/deepy/conducting.png +0 -0
- package/lib/deepy/drag.png +0 -0
- package/lib/deepy/error.png +0 -0
- package/lib/deepy/happy.png +0 -0
- package/lib/deepy/idle-look.png +0 -0
- package/lib/deepy/idle-spout.png +0 -0
- package/lib/deepy/idle.png +0 -0
- package/lib/deepy/music.png +0 -0
- package/lib/deepy/notification.png +0 -0
- package/lib/deepy/poke-left.png +0 -0
- package/lib/deepy/poke-right.png +0 -0
- package/lib/deepy/sleeping.png +0 -0
- package/lib/deepy/thinking.png +0 -0
- package/lib/deepy/tickle.png +0 -0
- package/lib/deepy/typing.png +0 -0
- package/lib/deepy/waking.png +0 -0
- package/lib/model-descriptions.json +13 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `dsh-claude-style` are documented here, newest first.
|
|
4
4
|
|
|
5
|
+
## [0.10.0] - 2026-09-28
|
|
6
|
+
|
|
7
|
+
[中文](#cn-0.10.0) | [English](#en-0.10.0)
|
|
8
|
+
|
|
9
|
+
<h3 id="cn-0.10.0">新增功能</h3>
|
|
10
|
+
|
|
11
|
+
- **DeepSeek 品牌下,输入框上站着像素小鲸鱼 Deepy**:品牌选 DeepSeek 时,螃蟹换成小鲸鱼 Deepy,动画逐帧取自 Deepy 主题包(每帧 50 毫秒)。它在首页(经典与工作台两种版面)站在输入卡片上沿、靠右;进入对话后站在整块输入区的上沿,要你批准、回答问题或审阅计划时站到那块面板上。只有主对话区有它,侧栏里的子代理对话没有。它的动作跟着智能体走:没事时呼吸、眨眼,偶尔东张西望或喷水;模型思考时冒思考云;写回答或调用工具时敲代码,两个会话同时在干活时戴耳机,三个以上戴安全帽砌砖;一个子代理在跑时戴耳机,两个以上指挥小鲸鱼分身;压缩上下文时吸入碎片;等你操作时摇感叹号铃铛;工具调用或这一轮失败时冒烟、眼冒金星;一轮或一次压缩结束时空翻撒彩纸。首页上它看整个工作区:按同时在干活的会话数挑动作,有会话在等你时摇铃,别的会话在后台做完时庆祝。闲下来后一分钟没动鼠标、没按键就睡着,再动一下就惊醒。点脸或尾巴会被戳得晃动,连点四下被挠痒痒,按住往外拖被拎起来。系统要求减少动态效果时每个状态停在一张代表帧上,点它才动。本机实测,小鲸鱼动着时页面主线程每秒多用约 13 毫秒(没有它时约 1–3 毫秒),换帧不触发皮肤的整页刷新,页面切到后台时停止换帧。插件新增公开路由 `GET /dsh-claude-style/deepy/<动作>.png` 提供帧图(共约 0.4 MB,浏览器在某个动作第一次出现时才下载);更新插件后要重启宿主,小鲸鱼才会出现。
|
|
12
|
+
|
|
13
|
+
### 体验优化
|
|
14
|
+
|
|
15
|
+
- **品牌标识的「关闭」改名为 DeepSeek,整套配色换成 DeepSeek 蓝色系**:设置页「修改品牌标识」原来的「关闭」现在明确写作 DeepSeek(设置值由 `off` 改为 `deepseek`,已经存下的 `off` 照样读作 DeepSeek)。选它时强调色由陶烬橙换成 DeepSeek 的品牌蓝 `#4D6BFE`,主按钮、开关、勾选、焦点框、链接与轮次状态行都跟着变;亮色画布从偏黄的象牙白换成带一点天蓝的亮白 `#F7FAFF`(侧栏 `#F3F7FE`),暗色画布从暖黑换成蓝黑 `#13161D`,边框、各级灰色文字、弹层与菜单卡片、搜索面板、分段控件、表头与滚动条一并换成冷色,用户消息气泡带一点蓝,行内代码用正文颜色。侧栏与首页照旧显示宿主自己的 DeepSeek 鲸鱼与字标,并画成 DeepSeek 蓝;账号行没有头像时、进行中与结束轮次的状态行,原来的 Claude 标也换成 DeepSeek 的鲸鱼标(进行中时摇摆着游动)。Claude 与 Anthropic 两档的样子不变。
|
|
16
|
+
- **已归档列表里删除被拒时弹出提示说明原因**:此前点「删除」被拒绝时界面毫无反应,只在控制台记一条警告。现在会弹出宿主自己的 Toast:会话仍被应用占用时提示「会话仍被本应用占用,重启后再删除」,其余拒绝提示「删除失败:原因」,行保持原样。
|
|
17
|
+
|
|
18
|
+
<h3 id="en-0.10.0">New Features</h3>
|
|
19
|
+
|
|
20
|
+
- **Deepy the pixel whale on the composer under the DeepSeek brand**: with the DeepSeek brand chosen, the crab gives way to Deepy, whose frames come from the Deepy theme pack one for one (50 ms a frame). On the home page (Classic and Studio alike) it stands on the composer card's top edge near its right end; in a conversation it stands on top of the whole input area, and while you are asked to approve something, answer a question or review a plan it stands on that panel. Only the main conversation carries it; the sidebar's subagent chats do not. What it does follows the agent: between jobs it breathes and blinks, now and then looking around or spouting; a thought cloud while the model thinks; typing while it writes an answer or calls tools, headphones when two sessions work at once and a hard hat from three; headphones for one running subagent and a squad of little whale clones to conduct for two or more; inhaling the fragments during a context compaction; an exclamation bell while something waits on you; smoke and dizzy stars when a tool call or the turn fails; a flip and confetti when a turn or a compaction finishes. On the home page it reads the whole workspace: how many sessions are working picks the animation, a session waiting on you rings the bell, and one finishing in the background is celebrated. A minute with no work and no pointer movement or key presses puts it to sleep, and the next one wakes it with a start. Clicking its face or tail pokes it, four quick clicks tickle it, and pressing and pulling lifts it. With reduced motion requested each state holds one frame, and it moves only when clicked. Measured here, the animating whale adds about 13 ms of main-thread work a second (the page idles at 1–3 ms without it); a frame change wakes none of the skin's page passes, and frames stop while the page is in the background. A new public route, `GET /dsh-claude-style/deepy/<animation>.png`, serves the frames (about 0.4 MB in all, each animation fetched the first time it plays); after updating the plugin, restart the host for the whale to appear.
|
|
21
|
+
|
|
22
|
+
### Improvements
|
|
23
|
+
|
|
24
|
+
- **The brand mark's "Off" is now DeepSeek, with the whole palette in DeepSeek blue**: the settings page's Brand mark row now names its first choice DeepSeek (the setting's value changes from `off` to `deepseek`, and a stored `off` still reads as DeepSeek). It trades the ember orange accent for DeepSeek's brand blue `#4D6BFE` across primary buttons, switches, checks, focus rings, links and the turn status line; the yellowish ivory light canvas gives way to a white with a touch of sky blue, `#F7FAFF` (sidebar `#F3F7FE`), and the warm-black dark canvas to a blue-black `#13161D`, with the hairlines, grey inks, popover and menu cards, search palette, segmented controls, table headers and scrollbars turning cool, user bubbles taking a touch of blue and inline code set in the body ink. The sidebar and the home page keep the host's own DeepSeek whale and wordmark, painted DeepSeek blue; the account row's picture without an avatar and the status line of running and ended turns trade the Claude mark for DeepSeek's whale (which sways as it swims while the turn runs). The Claude and Anthropic choices look as before.
|
|
25
|
+
- **A Toast now explains a refused delete on the archived list**: a delete the host half refuses used to do nothing visible, leaving one console warning. The host's own Toast now rises: when the conversation is still held open by the app it reads "The conversation is still held open by this app; restart it, then delete again", and every other refusal reads "Delete failed: \<reason\>". The row stays put.
|
|
26
|
+
|
|
27
|
+
**Full Changelog**: [v0.9.1...v0.10.0](https://github.com/Nwflower/dsh-claude-style/compare/v0.9.1...v0.10.0)
|
|
28
|
+
|
|
29
|
+
## [0.9.1] - 2026-09-27
|
|
30
|
+
|
|
31
|
+
[中文](#cn-0.9.1) | [English](#en-0.9.1)
|
|
32
|
+
|
|
33
|
+
<h3 id="cn-0.9.1">问题修复</h3>
|
|
34
|
+
|
|
35
|
+
- 修复 **已归档列表里的会话删不掉**:点「删除」后宿主半边只删存储目录、不动归档记录,存储目录已经不在的会话更是直接被拒——列表那一行永远留着,重载页面后还在。现在删除成功时宿主半边在同一请求里把该会话从归档集合里移除;归档集合里存储目录已消失的条目(幽灵)也按删除成功处理并移出归档集合,浏览器随即重拉一次会话列表基线,已删除的会话不会再靠旧摘要顶在已归档列表或会话树里。旧版宿主半边对目录缺失的会话回 404 时,浏览器同样移除该行。
|
|
36
|
+
|
|
37
|
+
<h3 id="en-0.9.1">Bug Fixes</h3>
|
|
38
|
+
|
|
39
|
+
- **Fixed archived sessions refusing to disappear when deleted**: the delete button removed the stored directory while the archive record kept the id, and a session whose directory was already gone was refused outright — the row stayed on the list and survived a page reload. A successful delete now also drops the session from the archive set in the same request, an archive entry whose stored directory has vanished is answered as deleted and leaves the set, and the browser re-pulls the session baseline right after, so a deleted session no longer lingers on the archived list or in the session tree on a stale summary. Against an older host half that still answers 404 for a missing directory, the browser removes the row just the same.
|
|
40
|
+
|
|
41
|
+
**Full Changelog**: [v0.9.0...v0.9.1](https://github.com/Nwflower/dsh-claude-style/compare/v0.9.0...v0.9.1)
|
|
42
|
+
|
|
5
43
|
## [0.9.0] - 2026-09-26
|
|
6
44
|
|
|
7
45
|
[中文](#cn-0.9.0) | [English](#en-0.9.0)
|
package/README.en.md
CHANGED
|
@@ -62,15 +62,16 @@ Keep only one theme enabled at a time. After installation, **restart `dsh web`**
|
|
|
62
62
|
|
|
63
63
|
## Features
|
|
64
64
|
|
|
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.
|
|
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. The settings page's Brand mark row offers DeepSeek / Claude / Anthropic: DeepSeek turns the whole palette blue — the accent becomes DeepSeek's brand blue `#4D6BFE` (buttons, switches, checks, links and the status line follow), the yellowish ivory light canvas gives way to a white with a touch of sky blue, `#F7FAFF` (sidebar `#F3F7FE`), the warm-black dark canvas to a blue-black `#13161D`, the hairlines and grey inks turn cool and user bubbles take a touch of blue; the sidebar and the home page keep the host's own DeepSeek whale and wordmark, painted DeepSeek blue, and the account row's picture without an avatar and a running turn's status line take DeepSeek's whale mark as well. The choice used to be labelled "Off", and a value stored under that name reads as DeepSeek.
|
|
66
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. Switching tiers slides the highlight from the old tier to the new one, and every segmented control on the settings page does the same.
|
|
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
|
-
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). Switching between the two views slides the highlight from one segment to the other.
|
|
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). A refused delete raises the host's Toast with the reason: a conversation still held open by the app asks for a restart first, and every other refusal names the reason. Rows match the host's session rows measurement for measurement (row x=12 / width 251 / height 28 / title x=36). Switching between the two views slides the highlight from one segment to the other.
|
|
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. On the desktop the brand row sits 10px higher than the host draws it, level with the web build. While the pointer is over the sidebar, the brand mark at its top left fades into a search box (the collapsed rail has none); pressing it, or the host's search shortcut (Ctrl+K on the desktop, Ctrl+Alt+K on the web), fades in a search palette near the top of the window, and the Workspace heading's own search icon is gone: it searches past sessions (by title, workspace name and what was said in them: every message between you and the model counts, a few Chinese characters from the middle of a sentence match, case does not matter, and the matching line shows under the row with the match in bold; the first time the palette opens after the host starts, every session is read once, which takes a dozen seconds or so), projects, plugins, the skills the current session offers and keyboard shortcuts, with All / Sessions / Projects / Plugins / Skills / Shortcuts across the top, and with the box empty it lists the five most recent sessions and the New session, Plugins, Settings and Keyboard shortcuts actions. ↑↓ moves, Enter opens, Tab and Shift+Tab switch the category, Esc closes; a session opens, a project starts a new session in that workspace, a plugin opens its details on the plugins page, a skill puts `/name ` at the head of the current session's input, and a shortcut opens the host's shortcut list filtered to that entry.
|
|
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
|
|
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. Switching views slides the highlight from the old tab to the new one. For running, stopped and failed turns the status line follows the end of the turn's work (the footer and queued messages sit below it) and looks like Claude Code's: while running, a turning spark, then elapsed time · output tokens · what the model is doing (waiting for the model, thinking, thought for N s, writing, preparing a tool call, running tools); once stopped or failed, a still spark and "Stopped / Failed · duration · output tokens". A turn that finishes normally keeps the host's own look and place.
|
|
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 takes the main canvas's fill (ivory in light mode, sky white under the DeepSeek brand) and spans the sidebar's width. Without an avatar the picture is the brand mark: the Claude mark under the Claude and Anthropic brands, DeepSeek's whale under the DeepSeek brand.
|
|
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. Switching views slides the highlight from the old tab to the new one. For running, stopped and failed turns the status line follows the end of the turn's work (the footer and queued messages sit below it) and looks like Claude Code's: while running, a turning spark, then elapsed time · output tokens · what the model is doing (waiting for the model, thinking, thought for N s, writing, preparing a tool call, running tools); once stopped or failed, a still spark and "Stopped / Failed · duration · output tokens". Under the DeepSeek brand the spark is DeepSeek's whale mark, which sways as it swims while the turn runs. A turn that finishes normally keeps the host's own look and place.
|
|
72
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 sixty-five books (Tao Te Ching 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. The workspace picker is drawn as Claude Code's folder menu: a narrow card of plain text rows set closer together, with no folder glyph in front of each folder and no plus sign on "Add workspace…"; the current workspace keeps the accent check.
|
|
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, frame for frame from Claude Code's own animation: a blink, the rod raised and cast down past the card's edge, a hop into a side-on stance for a spell of fishing, and the rod reeled in as it turns back — about three and a half seconds. With reduced motion requested (Windows' "Show animations in Windows" turned off, for one), it moves only when clicked.
|
|
74
|
+
10. **Deepy the pixel whale** — under the DeepSeek brand the crab gives way to Deepy, a pixel whale drawn on a 52×52 grid whose every frame comes from the Deepy theme pack's animations, 50 ms a frame. On the home page (both layouts) it stands on the composer card's top edge near its right end; in a conversation it stands on top of the whole input area (the composer card together with the todo, queue and goal cards above it), and while you are asked to approve something, answer a question or review a plan it stands on the panel that takes the card's place. Only the main conversation carries it; the sidebar's subagent chats do not. What it does follows the agent's work: between jobs it breathes, flicks its tail and blinks, and every half minute or so looks around or spouts water; while the model reasons or has not started answering, a thought cloud floats over it; while the model writes an answer or calls tools it types at a keyboard, puts on headphones when two sessions work at once and a hard hat from three; while subagents run it wears headphones for one and conducts a squad of little whale clones with its tail for two or more; a context compaction has it inhale the fragments; while you are asked for something it rings an exclamation bell; a failed tool call or a failed turn gives it a fright, smoke and dizzy stars; a turn or a compaction that finishes gets a flip and confetti (a finish that comes right after a failure is celebrated once the fright has played out). On the home page it reads the whole workspace: how many sessions are working picks the animation, a session waiting on you rings the bell, and one finishing in the background is celebrated. After a minute with no work and no pointer movement or key press it puts on a nightcap and falls asleep, and wakes with a start at the next one. Clicking its face or its tail pokes it into a jelly wobble, four quick clicks tickle it, and pressing it and pulling lifts it into a dangle until you let go. With reduced motion requested, each state holds one frame, and it moves only when clicked. The frames weigh about 0.4 MB, so they stay out of the page script: the plugin's host half serves them over a public route, `GET /dsh-claude-style/deepy/<animation>.png`, and the browser fetches an animation's frames the first time it plays. Against a host half that predates the route (the plugin updated without restarting the host), the frames do not load, the whale stays away and the console says so once.
|
|
74
75
|
|
|
75
76
|
## Disabling and uninstalling
|
|
76
77
|
|
package/README.md
CHANGED
|
@@ -62,15 +62,16 @@ dsh plugin --profile web add Nwflower/dsh-claude-style # GitHub 源
|
|
|
62
62
|
|
|
63
63
|
## 特点
|
|
64
64
|
|
|
65
|
-
1. **主题** —— 安装即全局生效,无需配置。亮色象牙白 `#FCFCFB`、暗色暖黑 `#141413`,两套画布共用同一个操作强调色陶烬橙 `#D97757
|
|
65
|
+
1. **主题** —— 安装即全局生效,无需配置。亮色象牙白 `#FCFCFB`、暗色暖黑 `#141413`,两套画布共用同一个操作强调色陶烬橙 `#D97757`;亮暗跟随系统颜色模式切换。设置页「修改品牌标识」有 DeepSeek / Claude / Anthropic 三档:选 DeepSeek 时整套配色换成蓝色系——强调色换成 DeepSeek 的品牌蓝 `#4D6BFE`(按钮、开关、勾选、链接与状态行都跟着变),亮色画布从偏黄的象牙白换成带一点天蓝的亮白 `#F7FAFF`(侧栏 `#F3F7FE`),暗色画布从暖黑换成蓝黑 `#13161D`,边框与灰色文字一并换成冷色,用户消息气泡带一点蓝;侧栏与首页保留宿主自己的 DeepSeek 鲸鱼与字标,画成 DeepSeek 蓝,账号行没有头像时、进行中轮次的状态行也改用 DeepSeek 的鲸鱼标。这一档以前叫「关闭」,存下的旧值照样读作 DeepSeek。
|
|
66
66
|
2. **输入框** —— 输入框整块重做:权限分段控件(只读 / 编辑 / 自动 / Yolo)与它下方的档位列表、带品牌锁定标的模型触发器、工具栏与状态统计同一行排版,发送键与停止键统一成 7px 圆角。统计句与模型触发器共用一套字号与颜色。权限档位取自宿主的权限目录:宿主配置了哪几档就画哪几档,第三方插件注册的档位(例如 auto mode 插件的 `auto-mode`)会自动出现在列表里并占用「自动」那一格;列表保持纯文字,档位自己声明的图标不画。切换档位时,高亮块从原档位平滑滑到新档位;设置页里的各组分段控件也是如此。
|
|
67
67
|
3. **模型选择器** —— 全新的两级弹层:一级列官方服务与设置页勾选的快捷供应商,「更多模型」二级按供应商分组;每行带厂商锁定标与说明文案,底部是推理等级推条(无极滑动,松手对齐最近档位)与「更多模型」入口。二级底边与一级对齐,一级已显示的供应商不再重复;模型不支持思考时不画推条。悬停停留 100ms 打开、离开 150ms 关闭,两卡之间的空隙不算离开。
|
|
68
|
-
4. **工作区** —— 侧栏「工作区」标题改成 **进行中 / 已归档**
|
|
68
|
+
4. **工作区** —— 侧栏「工作区」标题改成 **进行中 / 已归档** 分段控件:进行中沿用宿主的会话树,已归档是皮肤自己的平铺列表(标题、时间、每行「取消归档」与「删除」两个图标按钮)。删除被拒时弹出宿主的 Toast 说明原因:会话仍被应用占用时提示重启后再删除,其余拒绝提示失败原因。行排版与宿主的会话行逐项一致(行 x=12 / 宽 251 / 高 28 / 标题起点 x=36)。切换两种视图时,高亮块在两段之间平滑滑动。
|
|
69
69
|
5. **侧栏** —— 新会话与插件两行取 Claude 真机形状:默认无底色、hover 才有底色,新会话的「+」套一枚圆形底,两行图标 hover 顺时针转 90°(四重对称图形,转回原位);底部账户抽屉、封号彩蛋一并保留。桌面端的品牌行比宿主原样上移 10px,不再比 Web 端低出一截。鼠标移到侧栏上时,左上角的品牌标志淡出、换成一个搜索框(侧栏收起成窄条时没有),点它或按宿主的搜索快捷键(桌面端 Ctrl+K,Web 端 Ctrl+Alt+K)在窗口上方淡入搜索面板,「工作区」标题行原来的搜索图标不再显示:搜历史会话(按标题、所属工作区名和对话内容:你和模型说过的每句话都算,句子中间的几个中文字也能搜到,不分大小写,行下显示命中的那一句并把命中的字加粗;宿主启动后第一次打开面板时要把全部会话读一遍,约十几秒)、项目、插件、当前会话可用的 Skill 与快捷键,顶部「全部 / 会话 / 项目 / 插件 / Skill / 快捷键」切换分类,搜索框为空时列出最近的五个会话与「新会话 / 插件 / 设置 / 键盘快捷键」几个操作。↑↓ 选择、Enter 打开、Tab 与 Shift+Tab 切换分类、Esc 关闭;选中会话即打开,选中项目在该工作区开新会话,选中插件打开它在插件页的详情,选中 Skill 把 `/名称 ` 放到当前会话输入框开头,选中快捷键打开宿主的快捷键列表并筛到这一条。
|
|
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
|
-
7. **对话视图** —— 对话 / 轨迹控件在桌面 Windows 标题栏模式下居中常显在标题栏那一行(与新会话按钮同一行);宽窗口且标题较短时它停在标题那一行,否则留在标题下方自己那一行。切换视图时,高亮块从原标签平滑滑到新标签。进行中、已停止与失败的轮次,状态行跟在这一轮工作的末尾(页脚与排队中的消息在它下面),画成 Claude Code 的样子:进行中是转动的火花标记加「用时 · 输出 tokens · 当前动作」(等待模型、思考中、思考了 N 秒、输出中、准备调用工具、运行工具中),停止或失败后火花静止、文字为「已停止 / 处理失败 · 用时 · 输出 tokens
|
|
70
|
+
6. **账号区** —— 昵称与头像各走一条固定顺序。昵称:自定义昵称 → 官方账号昵称 → HDSL 启动器昵称 → 上次探测到的系统用户名 → 本次探测到的系统用户名 → `User`;头像:官方账号头像 → HDSL 启动器头像 → Claude 徽标。官方账号资料只在启动、热重载、登录与登出时向服务器读取,不轮询;HDSL 契约由宿主半边读一次后经两条只读路由转发(`GET /dsh-claude-style/hdsl` 回账号元数据、`GET /dsh-claude-style/hdsl-skin.png` 回头像图片,都不含头像文件的绝对路径,并过宿主的请求栅栏),契约版本读不到或不认识时整组忽略,头像文件被删除时回落到徽标。启动器给的是皮肤贴图而不是成品头像,因此账号位置的头像由贴图裁出:脸部 8×8 贴图块按盒子 1/18 内缩、帽子层铺满整盒,方形绘制不被圆角裁切。账号弹层沿用宿主自带的那一个:宿主的账号行就是入口,弹层里依次是账号头部、其它插件的页脚条目与宿主自己的「设置 / 意见反馈 / 退出登录」,各行的文案、顺序与点击行为保持宿主原样;宿主没有账号区的环境(Web)由插件自建同样外观的弹层。弹层卡片与主画布同色(亮色下为象牙白,DeepSeek 品牌下为天蓝亮白)、宽度与侧栏一致。没有头像时显示品牌标:Claude 与 Anthropic 两档是 Claude 徽标,DeepSeek 档是 DeepSeek 的鲸鱼标。
|
|
71
|
+
7. **对话视图** —— 对话 / 轨迹控件在桌面 Windows 标题栏模式下居中常显在标题栏那一行(与新会话按钮同一行);宽窗口且标题较短时它停在标题那一行,否则留在标题下方自己那一行。切换视图时,高亮块从原标签平滑滑到新标签。进行中、已停止与失败的轮次,状态行跟在这一轮工作的末尾(页脚与排队中的消息在它下面),画成 Claude Code 的样子:进行中是转动的火花标记加「用时 · 输出 tokens · 当前动作」(等待模型、思考中、思考了 N 秒、输出中、准备调用工具、运行工具中),停止或失败后火花静止、文字为「已停止 / 处理失败 · 用时 · 输出 tokens」;DeepSeek 品牌下火花换成 DeepSeek 的鲸鱼标,进行中时它摇摆着游动。正常结束的一轮保留宿主原来的样子与位置。
|
|
72
72
|
8. **首页版面** —— 新会话页有两套版面,默认是工作台,在设置页随时切换:**经典**是居中大标题加输入卡片,标题按时段从一组问候里抽一句(早上、午间、下午、晚上、深夜各有几句,另有几句不分时段),每次回到新会话页换一句;**工作台**把标题移到左上并固定为一句「What's up next, 用户名?」,输入框改用对话内的单行样式贴住窗口底部、上方一行细边框上下文胶囊,标题与一块 480px 窄栏用量面板贴着输入框左缘排布:概览页签是六个数字格(会话数、消息数、Token 总量、活跃天数、高峰时段、最常用模型)与 26 周按天热力图(等分列方格),模型页签是每天一根按模型颜色堆叠的柱子加一份模型排行(色块、模型名、输入与输出、占比,超过六行折成「再显示 N 个」,展开后可「收起」),右上角的「全部 / 30 天 / 7 天」切换统计范围(高峰时段与趣味行一并跟随);所选范围内的用量超过一本书时,热力图下方出现倍数趣味行,书目共六十五本(《道德经》到《追忆似水年华》),每次回到新会话页随机换一本。数据由插件的宿主半边汇总:装了 `dsh-cost-meter` 时直接读它的账本缓存(只读,不写对方文件),没有时读本机会话日志自己算,并按日志的文件大小与修改时间增量缓存;两种来源都按模型给出四类 token、按天给出各模型的 token,同一模型经不同提供方的用量合成一行;账本不记小时,高峰时段始终由本机会话日志按天算出;汇总不可用时面板改用宿主会话列表每行自带的投影值。数字没到之前面板先画骨架,位置在填充前后不变。还没选工作区时(刚打开、尚无会话),用量面板与 Read / Edit / Auto / Yolo 权限分段照常显示,分段停在新会话的默认权限上,点它和点输入框一样打开工作区选择;表示「先选工作区」的虚线框只框住单行输入框本身。窗口高度放不下整块面板时,面板在问候语与输入框之间自己滚动、底边渐隐,页面不会被撑出窗口。工作区选择菜单照 Claude Code 的文件夹菜单绘制:窄卡片、纯文字行排得更紧,每行前面的文件夹图标与「添加工作区…」前的加号都不画,当前工作区仍以强调色勾号标出。
|
|
73
73
|
9. **像素螃蟹** —— 工作台版面的新会话页上,输入卡片上沿、靠右的位置站着 Claude Code 的像素螃蟹(经典版面没有)。点它一下、或指针从它身上移开时(页面开着时也会每隔二三十秒自己来一次),它会照 Claude Code 的样子钓一次鱼:眨眨眼,举起鱼竿甩过卡片边沿,跳一下侧过身钓一会儿,再收竿转回正面,逐帧照搬 Claude Code 的动画,前后约三秒半。系统要求减少动态效果时(例如 Windows 关掉了「在 Windows 中显示动画」),它只在被点击时才动。
|
|
74
|
+
10. **像素小鲸鱼 Deepy** —— 品牌选 DeepSeek 时,螃蟹换成小鲸鱼 Deepy(画在 52×52 的像素格上,每一帧照搬 Deepy 主题包的动图,每帧 50 毫秒)。它在首页(两种版面都有)站在输入卡片上沿、靠右的位置;进入对话后站在整块输入区的上沿(输入卡片连同上面的待办、排队、目标卡片),要你批准、回答问题或审阅计划时,站到替换输入卡片的那块面板上。只有主对话区有它,侧栏里的子代理对话没有。它的动作跟着智能体的工作走:没事时慢慢呼吸、甩尾、眨眼,每隔二三十秒东张西望或开心喷水一次;模型在想或还没开始回答时头顶冒思考云;模型写回答、调用工具时趴在键盘后敲代码,同时有两个会话在干活时戴上耳机摇摆,三个以上戴安全帽砌砖;有子代理在跑时,一个戴耳机、两个以上用尾巴打拍子带小鲸鱼分身;压缩上下文时把碎片吸进肚子;等你批准或回答时摇感叹号铃铛;工具调用失败或这一轮失败时受惊冒烟、眼冒金星;一轮或一次压缩顺利结束时空翻撒彩纸(紧跟在失败之后的完成,等冒烟演完再庆祝)。首页上它看的是整个工作区:有几个会话在干活就按几个会话挑动作,有会话在等你时摇铃,别的会话在后台做完时庆祝。闲下来后一分钟没动鼠标、没按键时它会戴上睡帽睡着,再动一下就惊醒。点它的脸或尾巴会被戳得果冻一样晃,连点四下被挠痒痒,按住往外拖会被拎起来晃荡,松手放回。系统要求减少动态效果时,每个状态只停在一张代表帧上,点它才会动。动图帧图共约 0.4 MB,不打进页面脚本:插件的宿主半边经公开路由 `GET /dsh-claude-style/deepy/<动作>.png` 提供,浏览器在某个动作第一次出现时才下载它的帧图;宿主半边还是旧版(更新插件后没有重启宿主)时读不到帧图,小鲸鱼不出现,控制台留一条提示。
|
|
74
75
|
|
|
75
76
|
## 停用与卸载
|
|
76
77
|
|
package/docs/STYLE.md
CHANGED
|
@@ -21,6 +21,37 @@ Rules:
|
|
|
21
21
|
- Never pure white, never pure black, never cool grays.
|
|
22
22
|
- Accent usage stays under ~10% of visible elements.
|
|
23
23
|
|
|
24
|
+
**The DeepSeek brand's palettes.** The DeepSeek brand turns both palettes
|
|
25
|
+
blue. The single accent is DeepSeek's brand blue `#4D6BFE` — the colour of its
|
|
26
|
+
whale logo and of Deepy — with a darker step for text links on the light canvas
|
|
27
|
+
and a lighter one on the dark; the sidebar's whale and wordmark and the hero's
|
|
28
|
+
fish take it as their ink. The neutrals are the host's own cool family (its
|
|
29
|
+
`neutral-bluish` ramp) under a sky-tinted white in light and a blue-black in
|
|
30
|
+
dark. User bubbles take a blue tint as the host's own do, and inline code keeps
|
|
31
|
+
the body ink as the host draws it. The skin's Claude marks (the account row's
|
|
32
|
+
picture without an avatar, the turn status line's spark) become DeepSeek's
|
|
33
|
+
whale (`src/assets/brand/deepseek-mark.svg`, the host's `FISH_LOGO_PATH`); the
|
|
34
|
+
violet top rung of the reasoning slider stays.
|
|
35
|
+
|
|
36
|
+
| Token | Claude light | DeepSeek light | Claude dark | DeepSeek dark | Use |
|
|
37
|
+
|---|---|---|---|---|---|
|
|
38
|
+
| accent | `#D97757` | `#4D6BFE` | `#D97757` | `#4D6BFE` | brand primary, primary / info fills, checks, business primary (dark `#6A84FF`) |
|
|
39
|
+
| accent hover | `#C6613F` | `#3A57E8` | `#E08A6D` | `#6A84FF` | primary / info button hover |
|
|
40
|
+
| link | `#C6613F` / `#A94F2F` | `#3B56D9` / `#2C43B8` | `#E08A6D` / `#F0A488` | `#8FA4FF` / `#B0C0FF` | text link at rest / under the pointer |
|
|
41
|
+
| markdown link | `#184F95` | `#3B56D9` | `#8AB4F8` | `#8FA4FF` | `--dsh-claude-link` |
|
|
42
|
+
| brand art | `#141413` | `#4D6BFE` | `#FAF9F5` | `#4D6BFE` | sidebar whale and wordmark, `--dsh-claude-logo-ink` |
|
|
43
|
+
| canvas | `#FCFCFB` | `#F7FAFF` | `#141413` | `#13161D` | bg base / layer 1, `--dsh-claude-canvas` |
|
|
44
|
+
| sidebar | `#FBFBF9` | `#F3F7FE` | `#141413` | `#13161D` | sidebar, `--dsh-claude-sidebar-canvas` |
|
|
45
|
+
| layers | `#FBFBF9` / `#F9F9F6` | `#F3F7FE` / `#EFF4FD` | `#242320` / `#2E2C29` | `#212631` / `#2A303C` | layer 2 / layer 3 |
|
|
46
|
+
| raised card | canvas | canvas | `#1E1E1D` | `#1B1F28` | popovers, menus, search palette, `--dsh-claude-raised` |
|
|
47
|
+
| hairlines | `#E8E6DC` / `#DEDCD2` / `#D0CDC1` | `#E1E8F4` / `#D6DFEE` / `#C7D2E6` | `#242320` / `#2E2C29` / `#3A3833` | `#212631` / `#2A303C` / `#363D4B` | border l1 / l2 / l3 |
|
|
48
|
+
| solid hover | `#F0EFE9` | `#E9EFF9` | `#2E2C29` | `#2A303C` | the host's solid hover chip |
|
|
49
|
+
| chip | `#F6F6F4` | `#EEF3FB` | selector | selector | segmented control and switch at rest, `--dsh-claude-chip` |
|
|
50
|
+
| table head | `#F0F0EF` | `#EAF0F9` | `#242320` | `#212631` | `--dsh-claude-table-head` |
|
|
51
|
+
| inks | `#141413` / `#6E6A60` / `#8F8A7E` / `#A6A094` | `#0F1115` / `#61666B` / `#81858C` / `#ADB2B8` | `#FAF9F5` / `#B0AEA5` / `#8F8D84` / `#6B6A65` | `#EEF1F8` / `#AEB5C4` / `#8A92A3` / `#666E7E` | label primary / secondary / tertiary / caption |
|
|
52
|
+
| user bubble | hover wash | `#E6EDFD` | host's | `#232A3A` | `--dsw-specific-bubble` |
|
|
53
|
+
| hover wash | `rgba(0, 0, 0, 0.08)` | `rgba(38, 49, 72, 0.08)` | `rgba(255, 255, 255, 0.08)` | `rgba(255, 255, 255, 0.08)` | `--dsh-claude-hover-bg` |
|
|
54
|
+
|
|
24
55
|
**Host token bindings.** The skin supplies its palette through the host's own
|
|
25
56
|
alias tokens, so a host control that reads more than one token for one surface
|
|
26
57
|
has to be given all of them. A filled primary button is that case: the host takes
|
|
@@ -377,6 +408,37 @@ face front. With reduced motion requested, only a click plays it: the pointer
|
|
|
377
408
|
passing by and the idle timer leave it still. Only the crab takes the pointer;
|
|
378
409
|
the room the rod swings through does not.
|
|
379
410
|
|
|
411
|
+
## Deepy · 小鲸鱼
|
|
412
|
+
|
|
413
|
+
Under the DeepSeek brand the crab gives way to Deepy, the pixel whale of the
|
|
414
|
+
Deepy theme pack: a 52×52 grid of logical pixels drawn at 2px a pixel (a 104px
|
|
415
|
+
square), its ground line — row 48.5, the middle of its shadow — on the top edge
|
|
416
|
+
of what it stands on, its box 8px in from the right end of the card it stands
|
|
417
|
+
over. On the home page that is the composer card (both layouts); in a
|
|
418
|
+
conversation it is the whole input area, and the panel that replaces the card
|
|
419
|
+
while the reader is asked for something (those panels start 6–8px above their
|
|
420
|
+
card). Every frame lasts 50ms. The sheets carry five device pixels to a logical
|
|
421
|
+
pixel and are scaled down smoothly, so the whale stays crisp from 1× to 2×
|
|
422
|
+
screens without `image-rendering: pixelated`, which would drop rows on a
|
|
423
|
+
downscale. The body is Deepy's blue `#4E6FFF` with a navy `#142660` outline and
|
|
424
|
+
a white belly in both themes; on the dark canvas the outline and the soft shadow
|
|
425
|
+
recede and the blue body carries the shape. Only the resting body (columns
|
|
426
|
+
12–44, rows 30–48) takes the pointer. With reduced motion requested each state
|
|
427
|
+
holds its still frame; a click or a pull still plays its reaction.
|
|
428
|
+
|
|
429
|
+
| State | Animation | Still frame |
|
|
430
|
+
|---|---|---|
|
|
431
|
+
| idle, every 20–40 s a look around or a spout | `idle`, `idle-look`, `idle-spout` | 0 |
|
|
432
|
+
| the model reasons or has not answered | `thinking` | 20 |
|
|
433
|
+
| the model writes or tools run: 1 / 2 / 3+ sessions at work | `typing` / `music` / `building` | 16 / 0 / 0 |
|
|
434
|
+
| subagents running: 1 / 2+ | `music` / `conducting` | 0 / 6 |
|
|
435
|
+
| a compaction runs | `compacting` | 20 |
|
|
436
|
+
| an approval, a question or a plan review waits | `notification` | 12 |
|
|
437
|
+
| a tool call or a turn failed (4.8 s) | `error` | 24 |
|
|
438
|
+
| a turn or a compaction finished (5.2 s) | `happy` | 44 |
|
|
439
|
+
| a quiet minute (no work, no pointer move or key) / the next pointer move or key | `sleeping` / `waking` | 10 / 29 |
|
|
440
|
+
| a click on the face / the tail, four quick clicks, a pull | `poke-left` / `poke-right`, `tickle`, `drag` | 0 |
|
|
441
|
+
|
|
380
442
|
## Implementation notes
|
|
381
443
|
|
|
382
444
|
- Every rule is scoped under `body[data-dsh-claude-style]`.
|
package/docs/architecture.md
CHANGED
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
- 构建与源码:D1 拼接构建 · D5 模型文案是数据 · D18 源码布局与共用部件层 · D4 输入框样式门控
|
|
14
14
|
- 宿主边界:D2 只做浏览器半边 · D3 宿主选择器纪律 · D19 宿主契约与构建编号 · D10 设置传输 · D11 私有路由与字符串安全
|
|
15
15
|
- 运行时:D6 单一调度器 · D13 功能契约 · D12 快速失败与功能隔离 · D9 样式性能
|
|
16
|
-
- 功能:D14 账号表面 · D15 HDSL 契约 · D16 弹层基准 · D17 权限档位 · D20 滑动高亮 · D21 已归档列表 · D22 搜索面板 · D23 轮次状态行
|
|
16
|
+
- 功能:D14 账号表面 · D15 HDSL 契约 · D16 弹层基准 · D17 权限档位 · D20 滑动高亮 · D21 已归档列表 · D22 搜索面板 · D23 轮次状态行 · D24 小鲸鱼 Deepy
|
|
17
17
|
- 分发:D7 行为层与皮肤同包 · D8 多主题
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## D1. 零构建工具链:构建期逐字拼接成单文件
|
|
22
22
|
|
|
23
|
-
- **决定**:`scripts/build.mjs` 按 `FRAGMENTS` / `STYLE_FILES` 的顺序把 `src/` 下的 JS 片段与样式表拼成 `lib/client.js`。所有片段共用一个工厂作用域:不写 import/export,顶层名字在整个产物里唯一;React 由构建头部经加载器 `require('react')` 取得,其他宿主包在用到它的功能里直接 `require`。片段用现代语法(`const` / `let`、箭头函数、可选链),基础缩进 4 空格。品牌 SVG、螃蟹的帧条 PNG
|
|
24
|
-
- **构建检查**:`src/` 下每个 `.js` / `.css` 都在两份清单里;`%%TOKEN%%` 全部替换;输入框样式门控(D4);`:has()` 位置(D9);产物能被 `vm.Script` 解析;模型文案文档校验(D5)。产物写入构建编号(D19)。
|
|
23
|
+
- **决定**:`scripts/build.mjs` 按 `FRAGMENTS` / `STYLE_FILES` 的顺序把 `src/` 下的 JS 片段与样式表拼成 `lib/client.js`。所有片段共用一个工厂作用域:不写 import/export,顶层名字在整个产物里唯一;React 由构建头部经加载器 `require('react')` 取得,其他宿主包在用到它的功能里直接 `require`。片段用现代语法(`const` / `let`、箭头函数、可选链),基础缩进 4 空格。品牌 SVG、螃蟹的帧条 PNG 与厂商锁定标在构建时内联,字体、模型文案与小鲸鱼的帧图(D24,构建时复制到 `lib/deepy/`)走宿主半边的路由。
|
|
24
|
+
- **构建检查**:`src/` 下每个 `.js` / `.css` 都在两份清单里;`%%TOKEN%%` 全部替换;输入框样式门控(D4);`:has()` 位置(D9);产物能被 `vm.Script` 解析;模型文案文档校验(D5);小鲸鱼的帧图与 `DEEPY_SHEETS` 表一一对应、每行的帧数、裁切框与静止帧合法(D24)。产物写入构建编号(D19)。
|
|
25
25
|
- **理由**:DSH 的插件加载器没有相对 require,也没有资产 URL,打包器产出的分块与资产引用无处安放。
|
|
26
26
|
- **代价**:顶层名字共用一个作用域,靠命名约定避免冲突(D18);没有按需裁剪,产物体积靠自律控制。
|
|
27
27
|
- **重审条件**:加载器原生支持 ES module 相对导入与资产 URL。
|
|
28
28
|
|
|
29
29
|
## D2. 只做浏览器半边的皮肤,不改宿主
|
|
30
30
|
|
|
31
|
-
- **决定**:一切效果靠 CSS 覆盖与客户端 DOM 覆盖实现,不修改 DSH 引擎、apiproxy 与官方 UI 包。宿主半边(`host
|
|
31
|
+
- **决定**:一切效果靠 CSS 覆盖与客户端 DOM 覆盖实现,不修改 DSH 引擎、apiproxy 与官方 UI 包。宿主半边(`host/`)只做浏览器做不到的事:提供模型文案、字体、小鲸鱼的帧图、系统用户名、HDSL 契约、用量汇总、删除会话这些路由(D11),以及设置表单用的 `Config`(D10)。
|
|
32
32
|
- **理由**:主题是皮肤,宿主升级时要能快速跟进。
|
|
33
33
|
- **代价**:依赖宿主带哈希的类名与界面结构,宿主改版可能打断选择器,由 D3 / D19 的纪律与 smoke、probe 兜住。
|
|
34
34
|
- **重审条件**:宿主开放主题 API,或为需要覆盖的区域提供插槽时,逐步迁移过去。
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
|
|
66
66
|
## D6. 单一调度器
|
|
67
67
|
|
|
68
|
-
- **决定**:`src/core/scheduler.js` 持有唯一的 MutationObserver(`<body>` 子树,属性只看 `aria-label` / `aria-selected`),用 requestAnimationFrame 把变化合并成每帧一次刷新,按功能顺序调用各功能的 `sync()`。全局的指针、键盘、焦点、滚动与尺寸监听也只注册在这里,经功能句柄上的钩子分发(D13
|
|
68
|
+
- **决定**:`src/core/scheduler.js` 持有唯一的 MutationObserver(`<body>` 子树,属性只看 `aria-label` / `aria-selected`),用 requestAnimationFrame 把变化合并成每帧一次刷新,按功能顺序调用各功能的 `sync()`。全局的指针、键盘、焦点、滚动与尺寸监听也只注册在这里,经功能句柄上的钩子分发(D13)。指针移动只经 `onActivity` 告诉功能「读者在」,钩子只记下时刻,一次扫过不触发刷新。键盘路径只认用户的按键:皮肤自己代发的键盘事件带标记(账号表面对宿主菜单派发的 Escape 带 `__dshHostMenuEscape`),调度器见到标记即跳过——那次代发只送达宿主的菜单,不得顺带触发全局 Esc 路由。刷新状态在调度器开头声明:订阅偏好时,已就绪的设置表单会同步通知并触发 `schedule()`。
|
|
69
69
|
- **理由**:各功能自挂观察者与监听会互相干扰、重复扫描、难以卸载干净。
|
|
70
70
|
- **代价**:每个 `sync()` 必须能快速提前返回,并且只在值变化时写 DOM(同值写入也会让元素样式失效);页面持续变化时每帧都有一次刷新,其开销见 D9。
|
|
71
71
|
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
## D11. 私有路由借宿主的请求栅栏;外来字符串只以文本上屏
|
|
99
99
|
|
|
100
100
|
- **决定**:
|
|
101
|
-
- 宿主半边的私有路由(`/username`、`/usage`、`/session-search`、`/hdsl`、`/hdsl-skin.png`、`POST /session-delete`)处理前先调 `connection.requestRejection(req)`,拒绝即回 401/403;宿主没有该服务时,插件自带的替身只放行回环请求(回环 Host、无跨站标记、Origin 与 Host
|
|
102
|
-
- 删除会话另加四道:只接受 POST;id 必须符合宿主的会话 id 格式;正在打开的会话拒绝;目录解析后必须留在会话根目录内。删除发生在宿主半边,浏览器只提交一个 id
|
|
101
|
+
- 宿主半边的私有路由(`/username`、`/usage`、`/session-search`、`/hdsl`、`/hdsl-skin.png`、`POST /session-delete`)处理前先调 `connection.requestRejection(req)`,拒绝即回 401/403;宿主没有该服务时,插件自带的替身只放行回环请求(回环 Host、无跨站标记、Origin 与 Host 一致)。模型文案、字体与小鲸鱼的帧图是公开的静态资产;帧图的文件名只允许小写单词加连字符(`/deepy/<名字>.png`),读的只有 `lib/deepy/` 一个目录。
|
|
102
|
+
- 删除会话另加四道:只接受 POST;id 必须符合宿主的会话 id 格式;正在打开的会话拒绝;目录解析后必须留在会话根目录内。删除发生在宿主半边,浏览器只提交一个 id。删除成功时宿主半边同时经 `workspaceRegistry.unarchiveSession` 把该 id 从归档集合里移除;归档集合里存储目录已消失的条目(幽灵)同样按删除成功应答并移出归档集合,否则归档列表会一直留着这行。会话根目录读不了是故障,按 500 应答。
|
|
103
103
|
- 用量汇总只读:第三方插件的账本只读不写,自己的缓存放在 `$DSH_HOME/cache/dsh-claude-style/`。
|
|
104
104
|
- 客户端:来自设置、账号服务、系统或其他插件的字符串只用 `textContent` 或元素属性写入;图标复制节点,不重新解析标记。
|
|
105
105
|
- **理由**:插件路由不在宿主自己的栅栏之内;`dsh web` 绑定 `0.0.0.0` 时局域网可直接访问,而页面能驱动执行 shell 命令的智能体。
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
|
|
123
123
|
- **决定**:
|
|
124
124
|
- `src/entry.js` 的 FEATURES 表(`{ name, handle?, install }`)同时决定安装顺序与刷新顺序:刷新顺序就是安装顺序中句柄带 `sync` 的那些。
|
|
125
|
-
- 调度器只认功能句柄上的可选钩子(类型定义在 scheduler.js 开头):`sync` / `owns` + `close('outside')` / `onPointerDown` / `close('escape')` / `close('composer')` / `onInput` / `onFocusIn` / `reposition('viewport' | 'composer')` / `onCopyChange` / `onKey`;没实现的钩子直接跳过,每个功能保留自己的关闭方式。
|
|
125
|
+
- 调度器只认功能句柄上的可选钩子(类型定义在 scheduler.js 开头):`sync` / `owns` + `close('outside')` / `onPointerDown` / `close('escape')` / `close('composer')` / `onInput` / `onFocusIn` / `reposition('viewport' | 'composer')` / `onCopyChange` / `onKey` / `onActivity`;没实现的钩子直接跳过,每个功能保留自己的关闭方式。
|
|
126
126
|
- `retire` 按 name 或 handle 匹配:只匹配到 handle 时只停止 sync、不拆安装(设置页的 `settingsNav`)。
|
|
127
127
|
- 功能拆出的部分写成顶层工厂 `createX(...)`:状态留在工厂自己的闭包里,访问器与回调经参数传入,不伸手进别的闭包;名字以功能开头(`createAccountProfile`)。
|
|
128
128
|
- **理由**:加一个功能只需在 FEATURES 表加一行、在句柄上实现钩子;调度器不认识任何具体功能,没有要手工同步的清单。
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
|
|
196
196
|
## D21. 已归档列表跟随宿主的两份客户端数据
|
|
197
197
|
|
|
198
|
-
- **决定**:侧栏「已归档」是插件自己的平铺列表,订阅宿主 `workspaces.list`(归档集合)与 `sessions.list`(标题、时间、来源),两者都就绪后按宿主「仅已归档」的规则重算:只收已有会话摘要、非子代理、非空白占位的归档会话,按更新时间排序,内容不变时不重建。宿主自带的筛选状态存在 ui-workspace 私有的视图 store 里,不是客户端服务,只能靠点它的菜单改,因此不用。取消归档走 `workspaces.unarchiveSession`;删除走宿主半边的私有路由(D11
|
|
198
|
+
- **决定**:侧栏「已归档」是插件自己的平铺列表,订阅宿主 `workspaces.list`(归档集合)与 `sessions.list`(标题、时间、来源),两者都就绪后按宿主「仅已归档」的规则重算:只收已有会话摘要、非子代理、非空白占位的归档会话,按更新时间排序,内容不变时不重建。宿主自带的筛选状态存在 ui-workspace 私有的视图 store 里,不是客户端服务,只能靠点它的菜单改,因此不用。取消归档走 `workspaces.unarchiveSession`;删除走宿主半边的私有路由(D11),宿主半边在删除目录的同时把 id 移出归档集合,浏览器删除成功后重拉一次会话列表基线,让已删除会话的旧摘要立刻离开投影、不再顶着会话树里的行;其余失败记录警告。点击已归档行显示宿主的同一条提示:用宿主的 Toast 组件与 `workspace` 文案表里的 `toast.archivedNotOpenable`(宿主的提示通道是私有的)。
|
|
199
199
|
- **代价**:宿主「仅已归档」的规则变化时需要同步。
|
|
200
200
|
- **重审条件**:宿主把视图筛选或提示通道开放为客户端服务时改用宿主的。
|
|
201
201
|
|
|
@@ -224,3 +224,19 @@
|
|
|
224
224
|
- **理由**:状态行在工作过程的末尾,正在发生的事、停在哪里、为何失败都与它的状态挨在一起,和 Claude Code 一样;借用宿主的控件与数据,完成、停止、失败与折叠都还是宿主的行为。
|
|
225
225
|
- **代价**:依赖对话列是纵向 flex、行上的 `data-chat-flow-kind` / `data-chat-turn` 与聊天快照的现状;读屏与 Tab 键仍按文档顺序先经过这个控件;历史里有停止或失败的轮次时,它之后的每一行都带一个内联顺序值。
|
|
226
226
|
- **重审条件**:宿主提供可替换的轮次状态插槽,或把这个控件移到这一轮末尾时,改用宿主的。
|
|
227
|
+
|
|
228
|
+
## D24. 小鲸鱼 Deepy:宿主的状态、宿主半边供图、自己的节点上换帧
|
|
229
|
+
|
|
230
|
+
- **决定**:
|
|
231
|
+
- 品牌偏好的第三档叫 DeepSeek(值 `deepseek`;早先这一档叫「关闭」,存下的 `off` 读作 DeepSeek)。这一档保留宿主自己的品牌区(两个 Claude 变体都不匹配)并画成 DeepSeek 蓝,亮暗两套配色都换成蓝色系,皮肤自己画的 Claude 标(账号行的兜底头像、轮次状态行的火花)换成宿主的鲸鱼标(`FISH_LOGO_PATH`,构建时内联),输入框上的螃蟹换成小鲸鱼 Deepy。配色的切换只靠令牌:宿主的别名令牌与皮肤自己的 `--dsh-claude-canvas`、`--dsh-claude-raised`、`--dsh-claude-logo-ink` 等令牌在 Claude 的亮暗两块里给出原值,DeepSeek 的亮暗两块(多一个品牌属性,优先级更高)整组重写;各功能样式表里原来写死的画布、卡片、边框与强调色一律改读这些令牌,所以 Claude 与 Anthropic 两档的取值不变(smoke 的默认用例核对)。
|
|
232
|
+
- 站位:首页站在输入卡片上沿(composer 功能读出的首页卡片,与螃蟹同一处,两种版面都有)。对话页只认主对话区(`[data-phase="active"]` 里的 `[data-conversation-session]`),站在输入区 composerStack 的上沿;宿主的 composer 链选出替代面板时(ui-renderer 把 `[data-chain-overlay-fallback="conversation.composer"]` 内联写成 `display: none`,面板紧跟其后挂载),站到那块面板上沿。站着的宿主元素打 `data-dsh-claude-deepy-anchor`,样式表给它 `position: relative` 作小鲸鱼的定位容器,换位置或离开时撤掉。
|
|
233
|
+
- 状态只读宿主的客户端数据:`uiSession.sessionStatus`(是否在跑、等待你的那一个交互)、`sessions.list`(哪些是顶层会话,各自的子代理目录 `subagentCatalog`)、聊天快照(打开的轮次、正在输出的一步的最后一块、进行中的工具调用),每轮刷新、会话状态与聊天快照的订阅通知时重读。聊天快照在跟随这个会话期间订阅:宿主只为订阅者(或正在显示的聊天视图)构建聊天目标,只读不订阅时在轨迹视图下读不到内容。一次性的时刻(轮次结束的原因、失败的工具结果、压缩开始与结束)订阅这个会话的事件流 `sessions.binding(id).eventSource`,只处理订阅之后追加的事件;聊天快照在自动压缩结束之前不显示它,事件流是唯一能看到压缩进行中的地方。正在进行的压缩按事件流的整个窗口算(开始了、还没结束的),开始跟随时与窗口整体重新下发时(例如重新连接)重算,其间由追加的事件更新。首页读整个工作区:会话状态里新出现的 `completionUnread` 就是「后台做完」的时刻。
|
|
234
|
+
- 状态到动画按 Deepy 为 Clawd on Desk 做的主题映射。优先级从高到低为:点击与拖动的反应、出错、等待你、压缩、完成、子代理、工作、思考、空闲;工作按同时在跑的顶层会话数分三档,子代理按数目分两档。一个状态上台后一秒内不被同级或更低的状态换下;出错与完成各停两轮动画(4.8 秒、5.2 秒),较低的时刻在较高的时刻结束后接着演。空闲时每二三十秒随机东张西望或喷水一次;空闲满一分钟、其间也没有 `onActivity`(D6)就睡着(这一分钟从读者的最后一次活动与最后一个非空闲状态中较晚的那个算起),下一次活动先演惊醒;`onActivity` 只记下时刻与「该醒了」,惊醒由小鲸鱼自己的下一次计时演出。
|
|
235
|
+
- 帧图:每个动画一张精灵图——帧裁到能装下全部帧的最小整数逻辑像素框,八帧一行,每个逻辑像素 5 个像素,无损的调色板 PNG——放在 `src/assets/mascot/deepy/`,由 `src/constants.js` 的 `DEEPY_SHEETS` 登记帧数、裁切框与减少动态效果时的静止帧。构建复制到 `lib/deepy/`,宿主半边在公开路由 `/dsh-claude-style/deepy/<名字>.png` 提供(D11),浏览器在地址里带上构建编号,所以可以长期缓存。某个动画第一次要演时才加载并解码,解码完成之前画面上的动画照演;加载失败时这个动画缺席,控制台说一次。
|
|
236
|
+
- 播放:小鲸鱼挂在页面上时一个计时器每 50 毫秒走一步(减少动态效果时 250 毫秒,页面隐藏时 1 秒),换帧只写小鲸鱼自己节点上列与行两个自定义属性(`style` 属性不在调度器的观察范围里),不唤醒刷新。
|
|
237
|
+
- **理由**:
|
|
238
|
+
- 帧图共约 0.4 MB,内联进产物会让每个品牌的每次页面加载都多解析这么多样式文本;从宿主半边按需取,只有 DeepSeek 品牌、只有演到的动画才下载。
|
|
239
|
+
- 站在输入区上沿:待办、排队与目标卡片叠在输入卡片上面,站在卡片上会盖住它们右端的按钮;等待批准时输入卡片被隐藏,站到面板上,摇铃正好出现在要你操作的地方。
|
|
240
|
+
- 时刻读事件流:轮次结束的原因、工具失败与压缩都原样出现在事件里,只算订阅之后追加的,打开历史会话不会误报。
|
|
241
|
+
- **代价**:依赖 composer 链的替代方式(内联 `display: none` 与紧随其后的面板)、composerStack 的类名片段与会话状态、事件流的现状;订阅聊天目标让宿主在轨迹视图下也为这个会话构建聊天视图;宿主半边是旧版时(更新插件后没有重启宿主)帧图读不到,小鲸鱼不出现。持续的动画每 50 毫秒改一次自己节点的样式,只牵动这一个元素的样式重算与一小块重绘。
|
|
242
|
+
- **重审条件**:宿主提供放置状态角色的插槽或统一的「会话活动」服务时改用宿主的;加载器支持资产 URL 时,帧图改随客户端包提供。
|
package/host/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host half of dsh-claude-style.
|
|
3
3
|
*
|
|
4
|
-
* The skin's effect is browser-only. This half exists for the
|
|
4
|
+
* The skin's effect is browser-only. This half exists for the things a
|
|
5
5
|
* browser-only plugin cannot do:
|
|
6
6
|
*
|
|
7
7
|
* 1. Serve the model copy document. That table is DATA, not code — it ships
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* 3. Resolve the OS user once for the browser half. The username route answers
|
|
22
22
|
* a single GET and the browser caches it; it runs the host's own request
|
|
23
23
|
* fence first — see refusalOf().
|
|
24
|
+
* 4. Serve Deepy's animation sheets. The DeepSeek brand's pixel whale plays
|
|
25
|
+
* about 0.4 MB of sprite sheets, too much to inline into the bundle, so
|
|
26
|
+
* they ship as files beside it and the browser fetches each one the
|
|
27
|
+
* first time its animation plays.
|
|
24
28
|
*
|
|
25
29
|
* The registrations are defensive. A host without a web server, or with the
|
|
26
30
|
* route prefix already taken, or with the settings service absent, must still
|
|
@@ -56,6 +60,15 @@ const FONT_FILES = {
|
|
|
56
60
|
'AnthropicSansWebText.ttf': 'font/ttf',
|
|
57
61
|
'AnthropicSerifWebText.ttf': 'font/ttf',
|
|
58
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Deepy's animation sheets, served under `${ROUTE_PREFIX}/deepy/` from the
|
|
65
|
+
* build output in `lib/deepy/`. The file name is the whole request contract
|
|
66
|
+
* and its shape admits no separator and no dot segment, so nothing outside
|
|
67
|
+
* that directory is reachable; a name with no sheet is a 404. The browser
|
|
68
|
+
* half puts its build id in the query, so a long cache never serves a sheet
|
|
69
|
+
* from another build.
|
|
70
|
+
*/
|
|
71
|
+
const DEEPY_FILE = /^[a-z]+(?:-[a-z]+)*\.png$/
|
|
59
72
|
/** One-shot host OS user route; the browser half caches the response. */
|
|
60
73
|
const USERNAME_PATH = `${ROUTE_PREFIX}/username`
|
|
61
74
|
/**
|
|
@@ -91,7 +104,10 @@ const HDSL_LAYERS = ['process', 'user-env']
|
|
|
91
104
|
* controller archives and unarchives, and the agent protocol's session delete is
|
|
92
105
|
* the host delegating to an ACP agent that owns the storage. The archived row's
|
|
93
106
|
* delete button therefore comes here, where the stored session directory under
|
|
94
|
-
* the harness home
|
|
107
|
+
* the harness home is removed and the id is dropped from the workspace
|
|
108
|
+
* registry's archive set — the stored-directory miss included, so an archive
|
|
109
|
+
* entry whose storage is already gone leaves the set instead of pinning its
|
|
110
|
+
* row to the list. The path is also spelled in
|
|
95
111
|
* src/constants.js (SESSION_DELETE_ROUTE) for the browser half; keep the two in
|
|
96
112
|
* step.
|
|
97
113
|
*/
|
|
@@ -451,13 +467,37 @@ function readRequestBody(req, limit) {
|
|
|
451
467
|
})
|
|
452
468
|
}
|
|
453
469
|
|
|
470
|
+
/**
|
|
471
|
+
* Drop one id from the workspace registry's archive set, durably.
|
|
472
|
+
*
|
|
473
|
+
* The registry is read at request time: it mounts with the workspace domain and
|
|
474
|
+
* can complete after this plugin. A host without one has no archive set to
|
|
475
|
+
* update — and no archived list to delete from — while a failing unarchive
|
|
476
|
+
* propagates: the route answers 500, the row stays, and the next delete
|
|
477
|
+
* retries through the miss branch.
|
|
478
|
+
*/
|
|
479
|
+
async function unarchiveSession(ctx, sessionId) {
|
|
480
|
+
const registry = ctx.get?.('workspaceRegistry')
|
|
481
|
+
if (typeof registry?.unarchiveSession !== 'function') return
|
|
482
|
+
await registry.unarchiveSession(sessionId)
|
|
483
|
+
}
|
|
484
|
+
|
|
454
485
|
/**
|
|
455
486
|
* Delete one stored session: the directory named by the id under one of the
|
|
456
|
-
* sessions root's working-directory directories
|
|
487
|
+
* sessions root's working-directory directories, and the id's entry in the
|
|
488
|
+
* workspace registry's archive set.
|
|
457
489
|
*
|
|
458
490
|
* The id never reaches a path unchecked — it must match the harness's own
|
|
459
491
|
* shape, the lookup is by exact name, and the resolved directory must stay
|
|
460
492
|
* inside the sessions root.
|
|
493
|
+
*
|
|
494
|
+
* The archive set can name a session whose stored directory is already gone —
|
|
495
|
+
* an earlier removal took the directory while the registry entry stayed, and
|
|
496
|
+
* the stale session summary keeps its row on the archived list. The miss
|
|
497
|
+
* answers success too: the registry's unarchive runs no existence check and
|
|
498
|
+
* resolves without writing for an id that is not archived, so the entry leaves
|
|
499
|
+
* the set either way and the row is gone for good instead of coming back on
|
|
500
|
+
* the next reload.
|
|
461
501
|
*/
|
|
462
502
|
async function deleteSession(ctx, req, res) {
|
|
463
503
|
const refused = refusalOf(ctx, req)
|
|
@@ -483,34 +523,41 @@ async function deleteSession(ctx, req, res) {
|
|
|
483
523
|
}
|
|
484
524
|
const root = resolve(sessionsRoot(ctx))
|
|
485
525
|
let dir = null
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
526
|
+
// The root listing is the storage's own answer, so a failure there is a
|
|
527
|
+
// fault and propagates to the route's 500 answer; a candidate that stats as
|
|
528
|
+
// absent is the OS's "not under this entry" — the expected state the scan
|
|
529
|
+
// moves past.
|
|
530
|
+
for (const entry of readdirSync(root)) {
|
|
531
|
+
const candidate = join(root, entry, sessionId)
|
|
532
|
+
let stat
|
|
533
|
+
try {
|
|
534
|
+
stat = statSync(candidate)
|
|
535
|
+
} catch {
|
|
536
|
+
continue
|
|
537
|
+
}
|
|
538
|
+
if (stat.isDirectory()) {
|
|
539
|
+
dir = candidate
|
|
540
|
+
break
|
|
499
541
|
}
|
|
500
|
-
} catch {
|
|
501
|
-
dir = null
|
|
502
542
|
}
|
|
503
|
-
if (dir === null
|
|
504
|
-
|
|
543
|
+
if (dir === null) {
|
|
544
|
+
await unarchiveSession(ctx, sessionId)
|
|
545
|
+
sendJson(res, 200, { ok: true, ghost: true })
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
if (!resolve(dir).startsWith(root + sep)) {
|
|
549
|
+
sendJson(res, 403, { ok: false, error: 'session directory outside the sessions root' })
|
|
505
550
|
return
|
|
506
551
|
}
|
|
507
552
|
try {
|
|
508
553
|
rmSync(dir, { recursive: true, force: true })
|
|
509
554
|
} catch (error) {
|
|
555
|
+
// A storage fault answers 500 with the OS error — nothing is swallowed.
|
|
510
556
|
sendJson(res, 500, { ok: false, error: String(error?.message ?? error) })
|
|
511
557
|
return
|
|
512
558
|
}
|
|
513
|
-
|
|
559
|
+
await unarchiveSession(ctx, sessionId)
|
|
560
|
+
sendJson(res, 200, { ok: true, ghost: false })
|
|
514
561
|
}
|
|
515
562
|
|
|
516
563
|
/**
|
|
@@ -522,6 +569,7 @@ export function apply(ctx) {
|
|
|
522
569
|
// The copy document is build output beside the client bundle in lib/.
|
|
523
570
|
const file = join(here, '..', 'lib', COPY_FILE)
|
|
524
571
|
const fontsDir = join(here, '..', 'fonts')
|
|
572
|
+
const deepyDir = join(here, '..', 'lib', 'deepy')
|
|
525
573
|
|
|
526
574
|
/**
|
|
527
575
|
* Answer one request under the route prefix with a static file.
|
|
@@ -585,6 +633,14 @@ export function apply(ctx) {
|
|
|
585
633
|
})
|
|
586
634
|
return
|
|
587
635
|
}
|
|
636
|
+
const sheet = sub.startsWith('/deepy/') ? sub.slice('/deepy/'.length) : ''
|
|
637
|
+
if (DEEPY_FILE.test(sheet)) {
|
|
638
|
+
sendFile(res, req.method, join(deepyDir, sheet), {
|
|
639
|
+
'content-type': 'image/png',
|
|
640
|
+
'cache-control': 'public, max-age=31536000, immutable',
|
|
641
|
+
})
|
|
642
|
+
return
|
|
643
|
+
}
|
|
588
644
|
res.writeHead(404)
|
|
589
645
|
res.end()
|
|
590
646
|
},
|