dsh-gufa-code 0.0.0-stage → 1.0.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.
Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -2
  3. package/client.js +11548 -0
  4. package/cordis.patch.yml +8 -0
  5. package/docs/icon-180.png +0 -0
  6. package/docs/icon.svg +18 -0
  7. package/docs/market-submission.md +32 -0
  8. package/docs/screenshot-editor.jpg +0 -0
  9. package/docs/screenshot-ide.jpg +0 -0
  10. package/host/assets.js +31 -0
  11. package/host/chat.js +523 -0
  12. package/host/context.js +438 -0
  13. package/host/dev.js +35 -0
  14. package/host/fs.js +306 -0
  15. package/host/git.js +532 -0
  16. package/host/project.js +305 -0
  17. package/host/ssh.js +54 -0
  18. package/host/terminal-pty.js +237 -0
  19. package/host/terminal.js +947 -0
  20. package/icon.svg +18 -0
  21. package/index.js +63 -0
  22. package/locale/en.json +121 -0
  23. package/locale/zh.json +121 -0
  24. package/package.json +66 -4
  25. package/scripts/build-client.mjs +38 -0
  26. package/scripts/selftest-client.mjs +457 -0
  27. package/scripts/selftest-host.mjs +197 -0
  28. package/scripts/selftest-http.mjs +284 -0
  29. package/scripts/selftest-router.mjs +101 -0
  30. package/scripts/selftest-terminal-client.mjs +314 -0
  31. package/scripts/selftest-terminal-pty.mjs +190 -0
  32. package/scripts/selftest-terminal.mjs +351 -0
  33. package/scripts/test-chat-render.mjs +702 -0
  34. package/scripts/test-chat.mjs +761 -0
  35. package/scripts/test-editor.mjs +516 -0
  36. package/scripts/verify-workspace-sessions.mjs +135 -0
  37. package/src/client/00-core.js +1686 -0
  38. package/src/client/10-icons.js +205 -0
  39. package/src/client/20-editor.js +1318 -0
  40. package/src/client/30-terminal.js +1510 -0
  41. package/src/client/40-chat.js +1184 -0
  42. package/src/client/46-gitgraph.js +278 -0
  43. package/src/client/50-panels.js +916 -0
  44. package/src/client/55-backdrop.js +508 -0
  45. package/src/client/60-dock.js +662 -0
  46. package/src/client/85-shortcuts.js +516 -0
  47. package/src/client/90-app.js +2272 -0
  48. package/src/client/99-main.js +449 -0
  49. package/vendor/monaco/LICENSE +21 -0
  50. package/vendor/monaco/ThirdPartyNotices.txt +448 -0
  51. package/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  52. package/vendor/monaco/vs/base/worker/workerMain.js +31 -0
  53. package/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
  54. package/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
  55. package/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
  56. package/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
  57. package/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
  58. package/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
  59. package/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
  60. package/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
  61. package/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  62. package/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
  63. package/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
  64. package/vendor/monaco/vs/basic-languages/css/css.js +12 -0
  65. package/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
  66. package/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
  67. package/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  68. package/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
  69. package/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
  70. package/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
  71. package/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  72. package/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
  73. package/vendor/monaco/vs/basic-languages/go/go.js +10 -0
  74. package/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
  75. package/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
  76. package/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
  77. package/vendor/monaco/vs/basic-languages/html/html.js +10 -0
  78. package/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
  79. package/vendor/monaco/vs/basic-languages/java/java.js +10 -0
  80. package/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
  81. package/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
  82. package/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  83. package/vendor/monaco/vs/basic-languages/less/less.js +11 -0
  84. package/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
  85. package/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
  86. package/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
  87. package/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
  88. package/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  89. package/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
  90. package/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
  91. package/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
  92. package/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
  93. package/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
  94. package/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
  95. package/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  96. package/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
  97. package/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
  98. package/vendor/monaco/vs/basic-languages/php/php.js +10 -0
  99. package/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
  100. package/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
  101. package/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
  102. package/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
  103. package/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
  104. package/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
  105. package/vendor/monaco/vs/basic-languages/python/python.js +10 -0
  106. package/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
  107. package/vendor/monaco/vs/basic-languages/r/r.js +10 -0
  108. package/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
  109. package/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
  110. package/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
  111. package/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  112. package/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
  113. package/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
  114. package/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
  115. package/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
  116. package/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
  117. package/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
  118. package/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
  119. package/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
  120. package/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
  121. package/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
  122. package/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
  123. package/vendor/monaco/vs/basic-languages/st/st.js +10 -0
  124. package/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
  125. package/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  126. package/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
  127. package/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
  128. package/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
  129. package/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
  130. package/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
  131. package/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
  132. package/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
  133. package/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
  134. package/vendor/monaco/vs/editor/editor.main.css +8 -0
  135. package/vendor/monaco/vs/editor/editor.main.js +798 -0
  136. package/vendor/monaco/vs/language/css/cssMode.js +13 -0
  137. package/vendor/monaco/vs/language/css/cssWorker.js +77 -0
  138. package/vendor/monaco/vs/language/html/htmlMode.js +13 -0
  139. package/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
  140. package/vendor/monaco/vs/language/json/jsonMode.js +19 -0
  141. package/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
  142. package/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
  143. package/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
  144. package/vendor/monaco/vs/loader.js +11 -0
  145. package/vendor/monaco/vs/nls.messages.de.js +21 -0
  146. package/vendor/monaco/vs/nls.messages.es.js +21 -0
  147. package/vendor/monaco/vs/nls.messages.fr.js +19 -0
  148. package/vendor/monaco/vs/nls.messages.it.js +19 -0
  149. package/vendor/monaco/vs/nls.messages.ja.js +21 -0
  150. package/vendor/monaco/vs/nls.messages.ko.js +19 -0
  151. package/vendor/monaco/vs/nls.messages.ru.js +21 -0
  152. package/vendor/monaco/vs/nls.messages.zh-cn.js +21 -0
  153. package/vendor/monaco/vs/nls.messages.zh-tw.js +19 -0
  154. package/vendor/xterm/LICENSE +21 -0
  155. package/vendor/xterm/LICENSE.addon-fit +19 -0
  156. package/vendor/xterm/addon-fit.js +2 -0
  157. package/vendor/xterm/xterm.css +285 -0
  158. package/vendor/xterm/xterm.js +2 -0
@@ -0,0 +1,1686 @@
1
+ /**
2
+ * 00-core:G 命名空间、网络请求、i18n、主题、样式、基础控件。
3
+ * 构建时本文件最先拼进 client.js 的 factory 内。
4
+ */
5
+ const GUFA_NS = 'gufa-code'
6
+
7
+ /** 全局命名空间:所有零件通过 G.xxx 共享能力与组件。 */
8
+ const G = {}
9
+ G.NS = GUFA_NS
10
+ G.PANEL_ID = 'gufa-code'
11
+ /** 从哪个工作区点进来的(工作区菜单/节点按钮写入,App 启动时消费)。 */
12
+ G.pendingEntryWorkspace = null
13
+ G.ctx = null
14
+ G.h = h
15
+ G.React = React
16
+ G.hooks = { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect }
17
+
18
+ /* ── 事件总线(零件之间解耦:SSH → 终端 发送命令等) ───────────────────── */
19
+ G.bus = (function createBus() {
20
+ const listeners = new Map()
21
+ return {
22
+ on(type, fn) {
23
+ if (!listeners.has(type)) listeners.set(type, new Set())
24
+ listeners.get(type).add(fn)
25
+ return () => listeners.get(type)?.delete(fn)
26
+ },
27
+ emit(type, detail) {
28
+ for (const fn of listeners.get(type) ?? []) {
29
+ try {
30
+ fn(detail)
31
+ } catch (error) {
32
+ console.error('[gufa-code] bus listener failed', error)
33
+ }
34
+ }
35
+ },
36
+ }
37
+ })()
38
+
39
+ /* ── i18n:字典走 Client locale 服务;未注册时回落到内置中文 ─────────────── */
40
+ const DICT_ZH = {
41
+ 'panel.label': '古法编程',
42
+ 'panel.enterHint': '一键进入古法编程 IDE',
43
+ 'dock.title': '古法 AI',
44
+ 'dock.sub': 'DSH 会话',
45
+ 'dock.live': '已连接 DSH 会话',
46
+ 'dock.newSession': '新建会话',
47
+ 'dock.sessions': '切换会话(当前工作区)',
48
+ 'dock.sessionsTitle': '切换会话',
49
+ 'shortcuts.title': '古法编程快捷键',
50
+ 'shortcuts.save': '保存当前文件',
51
+ 'shortcuts.clearTerminal': '清除终端内容',
52
+ 'shortcuts.toggleTerminal': '显示 / 隐藏终端',
53
+ 'shortcuts.toggleSidebar': '显示 / 隐藏左侧栏',
54
+ 'shortcuts.closeTab': '关闭当前标签页',
55
+ 'shortcuts.explorer': '聚焦资源管理器',
56
+ 'shortcuts.git': '聚焦源代码管理',
57
+ 'shortcuts.ssh': '聚焦 SSH 主机',
58
+ 'shortcuts.newTerminal': '新建终端',
59
+ 'shortcuts.help': '打开 / 关闭本快捷键列表',
60
+ 'shortcuts.editorTitle': '编辑器(Monaco / VS Code 同款键位)',
61
+ 'editorKeys.comment': '切换行注释',
62
+ 'editorKeys.blockComment': '切换块注释',
63
+ 'editorKeys.nextOccurrence': '选中下一个相同内容(多光标)',
64
+ 'editorKeys.allOccurrences': '选中全部相同内容',
65
+ 'editorKeys.cursorAboveBelow': '向上 / 向下添加光标',
66
+ 'editorKeys.undoCursor': '撤销上一个光标',
67
+ 'editorKeys.moveLine': '上移 / 下移当前行',
68
+ 'editorKeys.copyLine': '向上 / 向下复制当前行',
69
+ 'editorKeys.deleteLine': '删除当前行',
70
+ 'editorKeys.insertLine': '在下方 / 上方插入新行',
71
+ 'editorKeys.indent': '增加 / 减少缩进',
72
+ 'editorKeys.columnSelect': '列选择(鼠标拖拽)',
73
+ 'editorKeys.find': '查找 / 替换',
74
+ 'editorKeys.goToLine': '跳转到行',
75
+ 'editorKeys.format': '格式化文档',
76
+ 'editorKeys.rename': '重命名符号',
77
+ 'dock.fullConversation': '打开完整对话',
78
+ 'dock.collapse': '收起 AI 面板',
79
+ 'dock.missingTitle': '未连接到 DSH 会话',
80
+ 'dock.missingHint': '未找到官方会话面板(可能没有选中会话)。点击「新建会话」,或「打开完整对话」后重试。',
81
+ 'dock.missingNotice': '未连接到 DSH 会话,暂用内置轻量对话',
82
+ 'dock.retry': '重试连接',
83
+ 'app.title': '古法编程',
84
+ 'app.subtitle': 'GUFA CODE',
85
+ 'app.newProject': '新建项目',
86
+ 'app.newWorkspace': '新建工作区',
87
+ 'app.switchWorkspace': '切换工作区',
88
+ 'app.noWorkspace': '选择工作区',
89
+ 'session.otherWorkspaces': '其他工作区',
90
+ 'workspace.title': '工作区',
91
+ 'workspace.current': '当前',
92
+ 'workspace.empty': '还没有工作区,点下方「新建工作区」开始',
93
+ 'workspace.unavailable': '当前环境未提供工作区服务;可以用「打开目录」方式进入',
94
+ 'workspace.create': '新建工作区',
95
+ 'app.openProject': '打开项目',
96
+ 'app.refresh': '刷新',
97
+ 'app.exit': '返回经典模式',
98
+ 'app.exitHint': '退出古法编程,回到 DSH 经典界面',
99
+ 'app.openFolder': '打开文件夹',
100
+ 'app.noProject': '尚未打开项目',
101
+ 'app.noProjectHint': '打开一个文件夹,或新建一个项目,开始古法编程。',
102
+ 'app.projectName': '项目名',
103
+ 'app.parentFolder': '父目录',
104
+ 'app.cancel': '取消',
105
+ 'app.confirm': '确定',
106
+ 'app.create': '创建',
107
+ 'app.loading': '加载中…',
108
+ 'app.error': '出错了',
109
+ 'app.retry': '重试',
110
+ 'app.hostStale': '插件已升级:当前 Host 仍是旧版本。AI 对话与编辑器已可用;请退出并重新打开 DeepSeek Harness 以启用真实终端。',
111
+ 'app.backdropOn': '开启梁圣背景图',
112
+ 'app.backdropOff': '关闭梁圣背景图',
113
+ 'app.backdropMissing': '未检测到梁圣皮肤背景(在设置中开启皮肤后可用)',
114
+ 'app.backdropStrength': '背景浓度(无级)',
115
+ 'app.backdropStrengthHint': '拖动到任意浓度;双击恢复跟随皮肤档位',
116
+ 'app.chooseFolder': '选择文件夹',
117
+ 'app.manualPath': '或直接输入路径',
118
+ 'app.pathPlaceholder': '例如 /Users/me/project',
119
+ 'activity.explorer': '资源管理器',
120
+ 'activity.git': '源代码管理',
121
+ 'activity.ssh': 'SSH 远程',
122
+ 'activity.terminal': '终端',
123
+ 'activity.chat': '古法 AI 助手',
124
+ 'activity.collapseChat': '收起 AI 面板',
125
+ 'activity.expandChat': '展开 AI 面板',
126
+ 'explorer.empty': '目录为空',
127
+ 'explorer.newFile': '新建文件',
128
+ 'explorer.newFolder': '新建文件夹',
129
+ 'explorer.rename': '重命名',
130
+ 'explorer.copy': '复制',
131
+ 'explorer.paste': '粘贴',
132
+ 'explorer.revealInFinder': '打开所在文件夹',
133
+ 'explorer.compareSelected': '对比',
134
+ 'explorer.changedDir': '目录内有改动',
135
+ 'explorer.delete': '删除',
136
+ 'explorer.fileName': '文件名(可含子路径)',
137
+ 'explorer.folderName': '文件夹名',
138
+ 'explorer.deleteConfirm': '确定删除 {name}?',
139
+ 'explorer.refresh': '刷新目录',
140
+ 'explorer.loading': '读取目录…',
141
+ 'explorer.openProjectFirst': '请先打开项目',
142
+ 'git.branch': '分支',
143
+ 'git.changes': '更改',
144
+ 'git.noRepo': '当前项目不是 Git 仓库',
145
+ 'git.noChanges': '工作区干净',
146
+ 'git.stage': '暂存',
147
+ 'git.unstage': '取消暂存',
148
+ 'git.stageAll': '全部暂存',
149
+ 'git.commitPlaceholder': '提交信息(Ctrl+Enter 提交)',
150
+ 'git.commit': '提交',
151
+ 'git.stageAll': '全部暂存',
152
+ 'git.unstageAll': '全部取消暂存',
153
+ 'git.noStagedHint': '没有已暂存的改动:点改动行右侧的 + 暂存,或点标题栏的「全部暂存」后再提交',
154
+ 'git.pushHint': '点击推送到远端',
155
+ 'git.pullHint': '点击从远端拉取',
156
+ 'git.push': '推送',
157
+ 'git.pushNoUpstream': '推送(首次会建立上游)',
158
+ 'git.aheadCount': '待推送',
159
+ 'git.behindCount': '待拉取',
160
+ 'git.pull': '拉取',
161
+ 'git.noUpstreamHint': '当前分支还没有上游:点标题栏 ↑ 推送一次即可自动创建上游(git push -u)',
162
+ 'git.pushDone': '已推送(如需首次建立上游会自动 -u)',
163
+ 'git.discardFile': '还原该文件的改动',
164
+ 'git.autoStageHint': '提交时会自动暂存所有未暂存改动(也可以先手动选择要暂存的文件)',
165
+ 'git.commitNeedsMessage': '请先填写提交信息,再按 ⌘⇧↵ 提交',
166
+ 'git.commitDone': '提交成功',
167
+ 'git.more': '更多操作',
168
+ 'git.viewDiff': '查看改动对比',
169
+ 'git.copyPath': '复制文件路径',
170
+ 'graph.open': '打开 Git Graph',
171
+ 'graph.title': 'Git Graph',
172
+ 'graph.refresh': '刷新提交图',
173
+ 'graph.toggleAll': '切换显示范围',
174
+ 'graph.allBranches': '全部分支',
175
+ 'graph.currentBranch': '仅当前分支',
176
+ 'graph.noProject': '请先打开项目',
177
+ 'graph.noCommits': '还没有提交',
178
+ 'graph.copyHash': '复制完整 hash',
179
+ 'graph.copied': '已复制',
180
+ 'git.branchesTitle': '分支',
181
+ 'git.branchSwitchHint': '点击切换 / 新建分支',
182
+ 'git.switchBranch': '切换到该分支',
183
+ 'git.currentBranch': '当前分支',
184
+ 'git.branchCurrent': '当前',
185
+ 'git.branchNamePlaceholder': '新分支名',
186
+ 'git.branchCreate': '新建并切换',
187
+ 'git.undoLastCommit': '撤回上次提交(保留改动)',
188
+ 'diff.prev': '上一处差异',
189
+ 'diff.next': '下一处差异',
190
+ 'diff.revertFile': '还原文件',
191
+ 'diff.stageFile': '暂存文件',
192
+ 'git.discardAll': '撤销所有未暂存改动',
193
+ 'git.discardConfirm': '再点一次确认撤销(未跟踪文件不会被删除)',
194
+ 'git.discardDone': '已撤销所有改动(未跟踪文件保留)',
195
+ 'git.pullDone': '拉取完成',
196
+ 'git.committed': '已提交',
197
+ 'git.refresh': '刷新',
198
+ 'git.staged': '已暂存',
199
+ 'git.unstaged': '未暂存',
200
+ 'git.untracked': '未跟踪',
201
+ 'git.noRepoHint': '初始化:git init',
202
+ 'ssh.hosts': 'SSH 主机',
203
+ 'ssh.noHosts': '~/.ssh/config 中没有主机配置',
204
+ 'ssh.connect': '连接',
205
+ 'ssh.connectHint': '点击主机将在终端中执行 ssh 连接',
206
+ 'ssh.configHint': '在 ~/.ssh/config 中添加 Host 配置后刷新',
207
+ 'ssh.refresh': '刷新',
208
+ 'terminal.new': '新建终端',
209
+ 'terminal.close': '关闭终端',
210
+ 'terminal.exited': '进程已退出',
211
+ 'terminal.session': '终端',
212
+ 'terminal.title': '终端',
213
+ 'editor.noFile': '选择或新建一个文件开始编辑',
214
+ 'editor.noFileHint': '左侧资源管理器中打开文件,或按「新建文件」',
215
+ 'editor.saveConflict': '文件已在磁盘上被修改,请重新打开后再保存',
216
+ 'editor.binary': '二进制文件,无法以文本编辑',
217
+ 'editor.tooLarge': '文件过大,无法打开',
218
+ 'editor.loading': '加载编辑器…',
219
+ 'editor.save': '保存',
220
+ 'editor.close': '关闭',
221
+ 'chat.title': '古法 AI',
222
+ 'chat.newChat': '新建对话',
223
+ 'chat.history': '历史对话',
224
+ 'chat.noHistory': '暂无历史对话',
225
+ 'chat.deleteChat': '删除对话',
226
+ 'chat.inputPlaceholder': '问问 AI,或让它解释左侧代码…(Enter 发送,Shift+Enter 换行)',
227
+ 'chat.send': '发送',
228
+ 'chat.stop': '停止',
229
+ 'chat.you': '你',
230
+ 'chat.assistant': '古法 AI',
231
+ 'chat.thinking': '思考中…',
232
+ 'chat.emptyTitle': '古法 AI 助手',
233
+ 'chat.emptyHint': '我在这里辅助你手写代码:解释、找错、补注释、给思路。',
234
+ 'chat.attachFile': '附带当前文件',
235
+ 'chat.insert': '插入到编辑器',
236
+ 'chat.copy': '复制',
237
+ 'chat.copied': '已复制',
238
+ 'chat.error': '请求失败',
239
+ 'chat.quickExplain': '解释当前代码',
240
+ 'chat.quickBug': '查找潜在问题',
241
+ 'chat.quickComment': '补充注释',
242
+ 'chat.quickRefactor': '重构建议',
243
+ 'chat.contextLabel': '上下文',
244
+ 'chat.ctxFile': '当前文件',
245
+ 'chat.ctxSelection': '选中代码',
246
+ 'chat.clear': '清空对话',
247
+ 'chat.model': '模型',
248
+ 'status.ln': '行',
249
+ 'status.col': '列',
250
+ 'status.saved': '已保存',
251
+ 'status.dirty': '未保存',
252
+ 'status.noGit': '非 Git 仓库',
253
+ 'status.lines': '行',
254
+ 'status.encoding': 'UTF-8',
255
+ 'status.spaces': '空格: 2',
256
+ 'common.refresh': '刷新',
257
+ 'common.cancel': '取消',
258
+ 'common.confirm': '确定',
259
+ 'common.delete': '删除',
260
+ 'common.rename': '重命名',
261
+ 'common.close': '关闭',
262
+ 'common.retry': '重试',
263
+ 'common.error': '出错了',
264
+ 'common.loading': '加载中…',
265
+ 'common.empty': '空',
266
+ 'common.save': '保存',
267
+ }
268
+
269
+ const DICT_EN = {
270
+ 'panel.label': 'Gufa Code',
271
+ 'panel.enterHint': 'Open the Gufa Code IDE',
272
+ 'dock.title': 'Gufa AI',
273
+ 'dock.sub': 'DSH session',
274
+ 'dock.live': 'Connected to the DSH session',
275
+ 'dock.newSession': 'New session',
276
+ 'dock.sessions': 'Switch session (current workspace)',
277
+ 'dock.sessionsTitle': 'Switch session',
278
+ 'shortcuts.title': 'Gufa Code shortcuts',
279
+ 'shortcuts.save': 'Save current file',
280
+ 'shortcuts.clearTerminal': 'Clear terminal',
281
+ 'shortcuts.toggleTerminal': 'Toggle terminal panel',
282
+ 'shortcuts.toggleSidebar': 'Toggle sidebar',
283
+ 'shortcuts.closeTab': 'Close current tab',
284
+ 'shortcuts.explorer': 'Focus explorer',
285
+ 'shortcuts.git': 'Focus source control',
286
+ 'shortcuts.ssh': 'Focus SSH hosts',
287
+ 'shortcuts.newTerminal': 'New terminal',
288
+ 'shortcuts.help': 'Show / hide this shortcut list',
289
+ 'shortcuts.editorTitle': 'Editor (Monaco / VS Code keybindings)',
290
+ 'editorKeys.comment': 'Toggle line comment',
291
+ 'editorKeys.blockComment': 'Toggle block comment',
292
+ 'editorKeys.nextOccurrence': 'Add next occurrence to multi-cursor',
293
+ 'editorKeys.allOccurrences': 'Select all occurrences',
294
+ 'editorKeys.cursorAboveBelow': 'Add cursor above / below',
295
+ 'editorKeys.undoCursor': 'Undo last cursor',
296
+ 'editorKeys.moveLine': 'Move line up / down',
297
+ 'editorKeys.copyLine': 'Copy line up / down',
298
+ 'editorKeys.deleteLine': 'Delete line',
299
+ 'editorKeys.insertLine': 'Insert line below / above',
300
+ 'editorKeys.indent': 'Indent / outdent line',
301
+ 'editorKeys.columnSelect': 'Column selection (mouse drag)',
302
+ 'editorKeys.find': 'Find / replace',
303
+ 'editorKeys.goToLine': 'Go to line',
304
+ 'editorKeys.format': 'Format document',
305
+ 'editorKeys.rename': 'Rename symbol',
306
+ 'dock.fullConversation': 'Open full conversation',
307
+ 'dock.collapse': 'Collapse AI panel',
308
+ 'dock.missingTitle': 'Not connected to a DSH session',
309
+ 'dock.missingHint': 'The official conversation panel was not found (maybe no session is selected). Press “New session”, or “Open full conversation” and retry.',
310
+ 'dock.missingNotice': 'Not connected to a DSH session — using the built-in lightweight chat',
311
+ 'dock.retry': 'Retry connection',
312
+ 'app.title': 'Gufa Code',
313
+ 'app.subtitle': 'CLASSIC CODING',
314
+ 'app.newProject': 'New project',
315
+ 'app.openProject': 'Open project',
316
+ 'app.refresh': 'Refresh',
317
+ 'app.exit': 'Classic mode',
318
+ 'app.exitHint': 'Leave Gufa Code and return to the classic DSH UI',
319
+ 'app.openFolder': 'Open folder',
320
+ 'app.noProject': 'No project opened',
321
+ 'app.noProjectHint': 'Open a folder or create a project to start classic coding.',
322
+ 'app.projectName': 'Project name',
323
+ 'app.parentFolder': 'Parent folder',
324
+ 'app.cancel': 'Cancel',
325
+ 'app.confirm': 'OK',
326
+ 'app.create': 'Create',
327
+ 'app.loading': 'Loading…',
328
+ 'app.error': 'Something went wrong',
329
+ 'app.retry': 'Retry',
330
+ 'app.hostStale': 'Plugin upgraded: the Host is still an older build. The editor and AI chat already work; quit and reopen DeepSeek Harness to enable the real terminal.',
331
+ 'app.backdropOn': 'Show the Liang skin backdrop',
332
+ 'app.backdropOff': 'Hide the Liang skin backdrop',
333
+ 'app.backdropMissing': 'Liang skin backdrop not detected (enable the skin in Settings)',
334
+ 'app.backdropStrength': 'Backdrop intensity (stepless)',
335
+ 'app.backdropStrengthHint': 'Drag to any intensity; double-click to follow the skin again',
336
+ 'app.chooseFolder': 'Choose folder',
337
+ 'app.manualPath': 'Or type a path',
338
+ 'app.pathPlaceholder': 'e.g. /Users/me/project',
339
+ 'activity.explorer': 'Explorer',
340
+ 'activity.git': 'Source control',
341
+ 'activity.ssh': 'SSH',
342
+ 'activity.terminal': 'Terminal',
343
+ 'activity.chat': 'Gufa AI',
344
+ 'activity.collapseChat': 'Collapse AI panel',
345
+ 'activity.expandChat': 'Expand AI panel',
346
+ 'explorer.empty': 'Folder is empty',
347
+ 'explorer.newFile': 'New file',
348
+ 'explorer.newFolder': 'New folder',
349
+ 'explorer.rename': 'Rename',
350
+ 'explorer.copy': 'Copy',
351
+ 'explorer.paste': 'Paste',
352
+ 'explorer.revealInFinder': 'Open Containing Folder',
353
+ 'explorer.compareSelected': 'Compare',
354
+ 'explorer.changedDir': 'Contains changes',
355
+ 'explorer.delete': 'Delete',
356
+ 'explorer.fileName': 'File name (may include sub path)',
357
+ 'explorer.folderName': 'Folder name',
358
+ 'explorer.deleteConfirm': 'Delete {name}?',
359
+ 'explorer.refresh': 'Refresh folder',
360
+ 'explorer.loading': 'Reading folder…',
361
+ 'explorer.openProjectFirst': 'Open a project first',
362
+ 'git.branch': 'Branch',
363
+ 'git.changes': 'Changes',
364
+ 'git.noRepo': 'This project is not a Git repository',
365
+ 'git.noChanges': 'Working tree clean',
366
+ 'git.stage': 'Stage',
367
+ 'git.unstage': 'Unstage',
368
+ 'git.stageAll': 'Stage all',
369
+ 'git.commitPlaceholder': 'Commit message (Ctrl+Enter to commit)',
370
+ 'git.commit': 'Commit',
371
+ 'git.stageAll': 'Stage all changes',
372
+ 'git.unstageAll': 'Unstage all changes',
373
+ 'git.noStagedHint': 'Nothing staged yet: click + on a change row, or use "Stage all" in the panel header before committing',
374
+ 'git.pushHint': 'Click to push to remote',
375
+ 'git.pullHint': 'Click to pull from remote',
376
+ 'git.push': 'Push',
377
+ 'git.pushNoUpstream': 'Push (creates upstream on first push)',
378
+ 'git.aheadCount': 'to push',
379
+ 'git.behindCount': 'to pull',
380
+ 'git.pull': 'Pull',
381
+ 'git.noUpstreamHint': 'No upstream for this branch yet: click ↑ in the header to push once (auto git push -u)',
382
+ 'git.pushDone': 'Pushed',
383
+ 'git.discardFile': 'Revert this file',
384
+ 'git.autoStageHint': 'Committing auto-stages all unstaged changes',
385
+ 'git.commitNeedsMessage': 'Write a commit message first, then press ⌘⇧↵',
386
+ 'git.commitDone': 'Committed',
387
+ 'git.more': 'More actions',
388
+ 'git.viewDiff': 'View diff',
389
+ 'git.copyPath': 'Copy file path',
390
+ 'graph.open': 'Open Git Graph',
391
+ 'graph.title': 'Git Graph',
392
+ 'graph.refresh': 'Refresh graph',
393
+ 'graph.toggleAll': 'Toggle scope',
394
+ 'graph.allBranches': 'All branches',
395
+ 'graph.currentBranch': 'Current branch only',
396
+ 'graph.noProject': 'Open a project first',
397
+ 'graph.noCommits': 'No commits yet',
398
+ 'graph.copyHash': 'Copy full hash',
399
+ 'graph.copied': 'Copied',
400
+ 'git.branchesTitle': 'Branches',
401
+ 'git.branchSwitchHint': 'Click to switch / create branch',
402
+ 'git.switchBranch': 'Switch to this branch',
403
+ 'git.currentBranch': 'Current branch',
404
+ 'git.branchCurrent': 'current',
405
+ 'git.branchNamePlaceholder': 'New branch name',
406
+ 'git.branchCreate': 'Create & switch',
407
+ 'git.undoLastCommit': 'Undo last commit (keep changes)',
408
+ 'diff.prev': 'Previous change',
409
+ 'diff.next': 'Next change',
410
+ 'diff.revertFile': 'Revert file',
411
+ 'diff.stageFile': 'Stage file',
412
+ 'git.discardAll': 'Discard all unstaged changes',
413
+ 'git.discardConfirm': 'Click again to confirm (untracked files are kept)',
414
+ 'git.discardDone': 'All changes discarded (untracked files kept)',
415
+ 'git.pullDone': 'Pull complete',
416
+ 'git.committed': 'Committed',
417
+ 'git.refresh': 'Refresh',
418
+ 'git.staged': 'Staged',
419
+ 'git.unstaged': 'Unstaged',
420
+ 'git.untracked': 'Untracked',
421
+ 'git.noRepoHint': 'Initialize: git init',
422
+ 'ssh.hosts': 'SSH hosts',
423
+ 'ssh.noHosts': 'No hosts in ~/.ssh/config',
424
+ 'ssh.connect': 'Connect',
425
+ 'ssh.connectHint': 'Click a host to run ssh in the terminal',
426
+ 'ssh.configHint': 'Add Host entries in ~/.ssh/config, then refresh',
427
+ 'ssh.refresh': 'Refresh',
428
+ 'terminal.new': 'New terminal',
429
+ 'terminal.close': 'Close terminal',
430
+ 'terminal.exited': 'Process exited',
431
+ 'terminal.session': 'Terminal',
432
+ 'terminal.title': 'TERMINAL',
433
+ 'editor.noFile': 'Open or create a file to start editing',
434
+ 'editor.noFileHint': 'Pick a file in the explorer on the left, or press “New file”',
435
+ 'editor.saveConflict': 'File changed on disk; reopen before saving',
436
+ 'editor.binary': 'Binary file cannot be edited as text',
437
+ 'editor.tooLarge': 'File too large to open',
438
+ 'editor.loading': 'Loading editor…',
439
+ 'editor.save': 'Save',
440
+ 'editor.close': 'Close',
441
+ 'chat.title': 'Gufa AI',
442
+ 'chat.newChat': 'New chat',
443
+ 'chat.history': 'History',
444
+ 'chat.noHistory': 'No history yet',
445
+ 'chat.deleteChat': 'Delete chat',
446
+ 'chat.inputPlaceholder': 'Ask the AI, or have it explain the code on the left… (Enter to send, Shift+Enter for newline)',
447
+ 'chat.send': 'Send',
448
+ 'chat.stop': 'Stop',
449
+ 'chat.you': 'You',
450
+ 'chat.assistant': 'Gufa AI',
451
+ 'chat.thinking': 'Thinking…',
452
+ 'chat.emptyTitle': 'Gufa AI assistant',
453
+ 'chat.emptyHint': 'I help you write code by hand: explain, spot bugs, add comments, suggest ideas.',
454
+ 'chat.attachFile': 'Attach current file',
455
+ 'chat.insert': 'Insert into editor',
456
+ 'chat.copy': 'Copy',
457
+ 'chat.copied': 'Copied',
458
+ 'chat.error': 'Request failed',
459
+ 'chat.quickExplain': 'Explain this code',
460
+ 'chat.quickBug': 'Find issues',
461
+ 'chat.quickComment': 'Add comments',
462
+ 'chat.quickRefactor': 'Refactor ideas',
463
+ 'chat.contextLabel': 'Context',
464
+ 'chat.ctxFile': 'Current file',
465
+ 'chat.ctxSelection': 'Selection',
466
+ 'chat.clear': 'Clear chat',
467
+ 'chat.model': 'Model',
468
+ 'status.ln': 'Ln',
469
+ 'status.col': 'Col',
470
+ 'status.saved': 'Saved',
471
+ 'status.dirty': 'Unsaved',
472
+ 'status.noGit': 'No Git repo',
473
+ 'status.lines': 'lines',
474
+ 'status.encoding': 'UTF-8',
475
+ 'status.spaces': 'Spaces: 2',
476
+ 'common.refresh': 'Refresh',
477
+ 'common.cancel': 'Cancel',
478
+ 'common.confirm': 'OK',
479
+ 'common.delete': 'Delete',
480
+ 'common.rename': 'Rename',
481
+ 'common.close': 'Close',
482
+ 'common.retry': 'Retry',
483
+ 'common.error': 'Something went wrong',
484
+ 'common.loading': 'Loading…',
485
+ 'common.empty': 'Empty',
486
+ 'common.save': 'Save',
487
+ }
488
+
489
+ G.translate = function translate(key, params) {
490
+ const raw = DICT_ZH[key] ?? key
491
+ if (!params) return raw
492
+ return raw.replace(/\{(\w+)\}/g, (match, name) => (name in params ? String(params[name]) : match))
493
+ }
494
+ G.t = G.translate
495
+
496
+ /** 订阅 locale 变化并重新渲染,返回翻译函数。 */
497
+ G.useT = function useT() {
498
+ const [, setRevision] = useState(0)
499
+ useEffect(() => {
500
+ const locale = G.ctx === null ? undefined : G.ctx.get('locale')
501
+ if (locale === undefined || locale === null || typeof locale.subscribe !== 'function') return undefined
502
+ return locale.subscribe(() => setRevision((value) => value + 1))
503
+ }, [])
504
+ return G.translate
505
+ }
506
+
507
+ /* ── 古法编程模式开关(全局:覆盖层显示 / 隐藏) ───────────────────────── */
508
+ const MODE_KEY = 'gufa-code:mode:v1'
509
+ function readModePreference() {
510
+ try {
511
+ return localStorage.getItem(MODE_KEY) === '1'
512
+ } catch {
513
+ return false
514
+ }
515
+ }
516
+ let gufaModeVisible = readModePreference()
517
+ const gufaModeListeners = new Set()
518
+
519
+ G.mode = {
520
+ get() {
521
+ return gufaModeVisible
522
+ },
523
+ set(value) {
524
+ const next = value === true
525
+ if (next === gufaModeVisible) return
526
+ gufaModeVisible = next
527
+ try {
528
+ if (window.__gufaModeLogs === undefined) window.__gufaModeLogs = 20
529
+ if (window.__gufaModeLogs > 0) {
530
+ window.__gufaModeLogs -= 1
531
+ void G.api('dev.log', { message: `mode-set ${next}` }).catch(() => {})
532
+ }
533
+ } catch {
534
+ /* ignore */
535
+ }
536
+ try {
537
+ localStorage.setItem(MODE_KEY, next ? '1' : '0')
538
+ } catch {
539
+ /* 隐私模式忽略 */
540
+ }
541
+ for (const listener of [...gufaModeListeners]) {
542
+ try {
543
+ listener(next)
544
+ } catch (error) {
545
+ console.error('[gufa-code] mode listener failed', error)
546
+ }
547
+ }
548
+ },
549
+ toggle() {
550
+ G.mode.set(!gufaModeVisible)
551
+ },
552
+ subscribe(listener) {
553
+ gufaModeListeners.add(listener)
554
+ return () => gufaModeListeners.delete(listener)
555
+ },
556
+ }
557
+
558
+ /** 进入古法编程:显示覆盖层,并确保主视图是 DSH 会话(这样官方对话已挂载,可停靠进右侧)。 */
559
+ G.enterGufa = function enterGufa() {
560
+ G.mode.set(true)
561
+ const layout = G.ctx === null || G.ctx === undefined ? undefined : G.ctx.get('layout')
562
+ try {
563
+ layout?.selectPanel?.(null)
564
+ } catch (error) {
565
+ console.error('[gufa-code] 切换到会话主视图失败', error)
566
+ }
567
+ }
568
+
569
+ /** 订阅模式开关的 hook。 */
570
+ G.useMode = function useMode() {
571
+ const [visible, setVisible] = useState(G.mode.get)
572
+ useEffect(() => G.mode.subscribe(setVisible), [])
573
+ return visible
574
+ }
575
+
576
+ /* ── 网络 ─────────────────────────────────────────────────────────────── */
577
+ G.api = async function api(method, body, signal) {
578
+ const response = await fetch(`/gufa-code/api/${method}`, {
579
+ method: 'POST',
580
+ headers: { 'content-type': 'application/json' },
581
+ body: JSON.stringify(body ?? {}),
582
+ signal,
583
+ })
584
+ let payload
585
+ try {
586
+ payload = await response.json()
587
+ } catch {
588
+ throw new Error(`请求失败(HTTP ${response.status})`)
589
+ }
590
+ if (!response.ok || payload.ok !== true) {
591
+ const error = new Error(payload?.error?.message ?? `请求失败(HTTP ${response.status})`)
592
+ error.code = payload?.error?.code
593
+ throw error
594
+ }
595
+ return payload.value
596
+ }
597
+
598
+ /** SSE 流式请求:onEvent 收到服务端事件对象,返回 abort 函数。 */
599
+ G.stream = function stream(method, body, onEvent, onEnd) {
600
+ const controller = new AbortController()
601
+ let completed = false
602
+ const finish = () => {
603
+ if (completed) return
604
+ completed = true
605
+ if (typeof onEnd === 'function') {
606
+ try {
607
+ onEnd()
608
+ } catch (error) {
609
+ console.error('[gufa-code] stream onEnd failed', error)
610
+ }
611
+ }
612
+ }
613
+ void (async () => {
614
+ try {
615
+ const response = await fetch(`/gufa-code/api/${method}`, {
616
+ method: 'POST',
617
+ headers: { 'content-type': 'application/json' },
618
+ body: JSON.stringify(body ?? {}),
619
+ signal: controller.signal,
620
+ })
621
+ if (!response.ok || response.body === null) {
622
+ let message = `请求失败(HTTP ${response.status})`
623
+ try {
624
+ const payload = await response.json()
625
+ message = payload?.error?.message ?? message
626
+ } catch {
627
+ /* 保留默认信息 */
628
+ }
629
+ onEvent({ type: 'error', message })
630
+ return
631
+ }
632
+ const reader = response.body.getReader()
633
+ const decoder = new TextDecoder()
634
+ let buffer = ''
635
+ for (;;) {
636
+ const { value, done } = await reader.read()
637
+ if (done) break
638
+ buffer += decoder.decode(value, { stream: true })
639
+ let index
640
+ while ((index = buffer.indexOf('\n\n')) >= 0) {
641
+ const chunk = buffer.slice(0, index)
642
+ buffer = buffer.slice(index + 2)
643
+ for (const line of chunk.split('\n')) {
644
+ if (!line.startsWith('data: ')) continue
645
+ try {
646
+ onEvent(JSON.parse(line.slice(6)))
647
+ } catch {
648
+ /* 忽略坏帧 */
649
+ }
650
+ }
651
+ }
652
+ }
653
+ finish()
654
+ } catch (error) {
655
+ if (controller.signal.aborted) return
656
+ onEvent({ type: 'error', message: error instanceof Error ? error.message : String(error) })
657
+ finish()
658
+ }
659
+ })()
660
+ return () => controller.abort()
661
+ }
662
+
663
+ G.asset = (path) => `/gufa-code/asset/${path}`
664
+ /** 解析真实 Host 的 HTTP 基址:桌面壳页面在 dsh-app://,需用 __DSH_TRANSPORT__.streamBaseUrl。 */
665
+ G.serverBase = function serverBase() {
666
+ try {
667
+ const transport = globalThis.__DSH_TRANSPORT__
668
+ if (transport !== null && transport !== undefined && typeof transport.streamBaseUrl === 'string' && transport.streamBaseUrl !== '') {
669
+ return transport.streamBaseUrl
670
+ }
671
+ } catch {
672
+ /* ignore */
673
+ }
674
+ return typeof document !== 'undefined' && typeof document.baseURI === 'string' ? document.baseURI : location.href
675
+ }
676
+
677
+ /** 终端 WS 地址(桌面壳下用真实 Host 基址解析,浏览器下与 document.baseURI 一致)。 */
678
+ G.wsUrl = function wsUrl(query, path) {
679
+ const target = typeof path === 'string' && path !== '' ? path : '/gufa-code/ws'
680
+ let url
681
+ try {
682
+ const base = G.serverBase()
683
+ url = target.startsWith('/') ? new URL(target, base) : new URL(target.slice(0), base)
684
+ } catch {
685
+ url = new URL(`${location.origin}${target}`)
686
+ }
687
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
688
+ url.search = query
689
+ return url.href
690
+ }
691
+
692
+ /* ── 主题 ─────────────────────────────────────────────────────────────── */
693
+ G.theme = function theme() {
694
+ return document.body.hasAttribute('data-ds-dark-theme') ? 'dark' : 'light'
695
+ }
696
+
697
+ G.useTheme = function useTheme() {
698
+ const [value, setValue] = useState(G.theme)
699
+ useEffect(() => {
700
+ const observer = new MutationObserver(() => setValue(G.theme()))
701
+ observer.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })
702
+ return () => observer.disconnect()
703
+ }, [])
704
+ return value
705
+ }
706
+
707
+ /* ── 不透明配色(皮肤可能把主题 token 改成半透明/透明,IDE 需要实底) ─────── */
708
+
709
+ /** 解析 CSS 颜色为 [r,g,b,a];无法解析返回 null。 */
710
+ G.parseCssColor = function parseCssColor(value) {
711
+ if (typeof value !== 'string') return null
712
+ const text = value.trim().toLowerCase()
713
+ if (text === '') return null
714
+ if (text === 'transparent') return [0, 0, 0, 0]
715
+ const hex = /^#([0-9a-f]{3,8})$/.exec(text)
716
+ if (hex !== null) {
717
+ const raw = hex[1]
718
+ if (raw.length === 3) return [Number.parseInt(raw[0] + raw[0], 16), Number.parseInt(raw[1] + raw[1], 16), Number.parseInt(raw[2] + raw[2], 16), 1]
719
+ if (raw.length === 6) return [Number.parseInt(raw.slice(0, 2), 16), Number.parseInt(raw.slice(2, 4), 16), Number.parseInt(raw.slice(4, 6), 16), 1]
720
+ if (raw.length === 8) return [Number.parseInt(raw.slice(0, 2), 16), Number.parseInt(raw.slice(2, 4), 16), Number.parseInt(raw.slice(4, 6), 16), Number.parseInt(raw.slice(6, 8), 16) / 255]
721
+ return null
722
+ }
723
+ const channel = (raw) => (raw.endsWith('%') ? Math.round((Number.parseFloat(raw) / 100) * 255) : Math.round(Number.parseFloat(raw)))
724
+ const rgb = /^rgba?\(\s*([\d.]+%?)[\s,]+([\d.]+%?)[\s,]+([\d.]+%?)(?:[\s,/]+([\d.]+%?))?\s*\)$/.exec(text)
725
+ if (rgb !== null) {
726
+ const alphaRaw = rgb[4]
727
+ const alpha = alphaRaw === undefined ? 1 : alphaRaw.endsWith('%') ? Number.parseFloat(alphaRaw) / 100 : Number.parseFloat(alphaRaw)
728
+ return [channel(rgb[1]), channel(rgb[2]), channel(rgb[3]), Math.max(0, Math.min(1, alpha))]
729
+ }
730
+ const srgb = /^color\(\s*srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+))?\s*\)$/.exec(text)
731
+ if (srgb !== null) {
732
+ return [Math.round(Number.parseFloat(srgb[1]) * 255), Math.round(Number.parseFloat(srgb[2]) * 255), Math.round(Number.parseFloat(srgb[3]) * 255), srgb[4] === undefined ? 1 : Number.parseFloat(srgb[4])]
733
+ }
734
+ return null
735
+ }
736
+
737
+ /** 相对亮度(0–1,用于判断背景深浅)。 */
738
+ G.luminance = function luminance(color) {
739
+ const c = color ?? [0, 0, 0]
740
+ const channel = (value) => {
741
+ const v = Math.max(0, Math.min(255, value)) / 255
742
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
743
+ }
744
+ return 0.2126 * channel(c[0]) + 0.7152 * channel(c[1]) + 0.0722 * channel(c[2])
745
+ }
746
+
747
+ /** 把半透明前景色合成到不透明背景色上,返回 [r,g,b,a=1]。 */
748
+ G.compositeColor = function compositeColor(foreground, background) {
749
+ const fg = foreground ?? [0, 0, 0, 0]
750
+ const bg = background ?? [0, 0, 0, 1]
751
+ const alpha = fg[3] ?? 1
752
+ return [
753
+ Math.round(fg[0] * alpha + bg[0] * (1 - alpha)),
754
+ Math.round(fg[1] * alpha + bg[1] * (1 - alpha)),
755
+ Math.round(fg[2] * alpha + bg[2] * (1 - alpha)),
756
+ 1,
757
+ ]
758
+ }
759
+
760
+ /**
761
+ * 计算 IDE 用的不透明配色:
762
+ * 皮肤(例如 liang-intensity-skin)会把 --dsw-alias-bg-* 改成半透明/透明以透出壁纸,
763
+ * 这里把主题 token 依次合成到不透明页面底色上,得到 bg/layer/layer2/overlay/sidebar 实色。
764
+ * @param {HTMLElement|null} container 用于探测计算样式的挂载点(须在文档内)
765
+ * @returns {{bg:string, layer:string, layer2:string, overlay:string, sidebar:string}}
766
+ */
767
+ G.resolveSolidPalette = function resolveSolidPalette(container) {
768
+ const dark = G.theme() === 'dark'
769
+ const fallbackPage = dark ? [26, 27, 31, 1] : [250, 250, 252, 1]
770
+ const fallbackSurface = dark ? [45, 46, 51, 1] : [255, 255, 255, 1]
771
+ const toRgb = (color) => `rgb(${color[0]}, ${color[1]}, ${color[2]})`
772
+ const usable = typeof window !== 'undefined' && typeof window.getComputedStyle === 'function' && container !== null && container !== undefined
773
+ if (!usable) {
774
+ const bg = fallbackPage
775
+ const layer = G.compositeColor(fallbackSurface, bg)
776
+ const layer2 = G.compositeColor(fallbackSurface, layer)
777
+ return { bg: toRgb(bg), layer: toRgb(layer), layer2: toRgb(layer2), overlay: toRgb(layer2), sidebar: toRgb(layer), text: toRgb(dark ? [236, 238, 241, 1] : [24, 26, 30, 1]), textDim: toRgb(dark ? [166, 170, 176, 1] : [92, 98, 106, 1]), accent: toRgb(dark ? [65, 118, 230, 1] : [181, 43, 36, 1]), isDark: dark }
778
+ }
779
+ let page = fallbackPage
780
+ const probe = document.createElement('div')
781
+ probe.setAttribute('aria-hidden', 'true')
782
+ probe.style.cssText = 'position:absolute;left:-9999px;top:0;width:0;height:0;pointer-events:none;opacity:0'
783
+ let palette = null
784
+ try {
785
+ container.appendChild(probe)
786
+ const read = (token) => {
787
+ probe.style.backgroundColor = ''
788
+ probe.style.backgroundColor = `var(${token})`
789
+ return window.getComputedStyle(probe).backgroundColor
790
+ }
791
+ const bodyColor = G.parseCssColor(window.getComputedStyle(document.body).backgroundColor)
792
+ if (bodyColor !== null && bodyColor[3] >= 0.999) page = bodyColor
793
+ const token = (name, fallback) => {
794
+ const parsed = G.parseCssColor(read(name))
795
+ return parsed === null ? fallback : parsed
796
+ }
797
+ const bg = G.compositeColor(token('--dsw-alias-bg-base', page), page)
798
+ const layer = G.compositeColor(token('--dsw-alias-bg-layer-1', fallbackSurface), bg)
799
+ const layer2 = G.compositeColor(token('--dsw-alias-bg-layer-2', fallbackSurface), layer)
800
+ const overlayShade = dark ? [255, 255, 255, 0.08] : [0, 0, 0, 0.05]
801
+ const overlay = G.compositeColor(overlayShade, layer2)
802
+ const sidebar = G.compositeColor(token('--dsw-specific-sidebar-fill', [0, 0, 0, 0]), bg)
803
+ const text = G.compositeColor(token('--dsw-alias-label-primary', dark ? [236, 238, 241, 1] : [24, 26, 30, 1]), layer2)
804
+ const textDim = G.compositeColor(token('--dsw-alias-label-secondary', dark ? [166, 170, 176, 1] : [92, 98, 106, 1]), layer2)
805
+ const accentToken = token('--dsw-alias-state-business-primary', token('--dsw-alias-brand-primary', dark ? [65, 118, 230, 1] : [181, 43, 36, 1]))
806
+ const accent = G.compositeColor(accentToken, bg)
807
+ const isDark = G.luminance(bg) < 0.5
808
+ palette = { bg: toRgb(bg), layer: toRgb(layer), layer2: toRgb(layer2), overlay: toRgb(overlay), sidebar: toRgb(sidebar), text: toRgb(text), textDim: toRgb(textDim), accent: toRgb(accent), isDark }
809
+ } catch {
810
+ palette = null
811
+ } finally {
812
+ try {
813
+ probe.remove()
814
+ } catch {
815
+ /* ignore */
816
+ }
817
+ }
818
+ if (palette !== null) return palette
819
+ const bg = fallbackPage
820
+ const layer = G.compositeColor(fallbackSurface, bg)
821
+ const layer2 = G.compositeColor(fallbackSurface, layer)
822
+ return { bg: toRgb(bg), layer: toRgb(layer), layer2: toRgb(layer2), overlay: toRgb(layer2), sidebar: toRgb(layer), text: toRgb(dark ? [236, 238, 241, 1] : [24, 26, 30, 1]), textDim: toRgb(dark ? [166, 170, 176, 1] : [92, 98, 106, 1]), accent: toRgb(dark ? [65, 118, 230, 1] : [181, 43, 36, 1]), isDark: dark }
823
+ }
824
+
825
+ /** IDE 根错误边界:渲染异常时显示可读错误并允许退回经典模式(同时写入诊断日志)。 */
826
+ const GUFA_COMPONENT_BASE = typeof React !== 'undefined' && React !== null && typeof React.Component === 'function' ? React.Component : class {}
827
+ G.GufaErrorBoundary = class GufaErrorBoundary extends GUFA_COMPONENT_BASE {
828
+ constructor(props) {
829
+ super(props)
830
+ this.state = { error: null }
831
+ }
832
+
833
+ static getDerivedStateFromError(error) {
834
+ return { error }
835
+ }
836
+
837
+ componentDidCatch(error, info) {
838
+ try {
839
+ const detail = `${String(error && error.stack ? error.stack : error).slice(0, 1500)} | componentStack=${String(info && info.componentStack ? info.componentStack : '').slice(0, 600)}`
840
+ void G.api('dev.log', { message: `gufa-app-error ${detail}` }).catch(() => {})
841
+ } catch {
842
+ /* ignore */
843
+ }
844
+ }
845
+
846
+ render() {
847
+ const error = this.state.error
848
+ if (error === null || error === undefined) return this.props.children
849
+ return React.createElement(
850
+ 'div',
851
+ { className: 'gufa-error-fallback' },
852
+ React.createElement('div', { className: 'gufa-error-fallback-title' }, '古法编程渲染出错'),
853
+ React.createElement('pre', { className: 'gufa-error-fallback-detail' }, String(error && error.message ? error.message : error).slice(0, 1200)),
854
+ React.createElement('div', { className: 'gufa-error-fallback-actions' },
855
+ React.createElement(G.Button, { onClick: () => { try { G.mode.set(false) } catch { /* ignore */ } } }, G.t('app.exit')),
856
+ React.createElement(G.Button, { onClick: () => { this.setState({ error: null }) } }, '重试'),
857
+ ),
858
+ )
859
+ }
860
+ }
861
+
862
+ /* ── 调色板 store(供 Monaco 等需要实时配色的组件订阅) ───────────────── */
863
+ const gufaPaletteListeners = new Set()
864
+ G.paletteStore = {
865
+ value: null,
866
+ set(next) {
867
+ if (this.value === next) return
868
+ try {
869
+ if (this.value !== null && JSON.stringify(this.value) === JSON.stringify(next)) return
870
+ } catch {
871
+ /* 比较失败则照常发布 */
872
+ }
873
+ this.value = next
874
+ try {
875
+ if (window.__gufaStoreLogs === undefined) window.__gufaStoreLogs = 20
876
+ if (window.__gufaStoreLogs > 0) {
877
+ window.__gufaStoreLogs -= 1
878
+ void G.api('dev.log', { message: `palette-store set listeners=${gufaPaletteListeners.size} isDark=${next !== null && next !== undefined && next.isDark === true} bg=${next !== null && next !== undefined ? String(next.bg) : '?'}` }).catch(() => {})
879
+ }
880
+ } catch {
881
+ /* ignore */
882
+ }
883
+ for (const listener of [...gufaPaletteListeners]) {
884
+ try {
885
+ listener(next)
886
+ } catch (error) {
887
+ console.error('[gufa-code] palette listener failed', error)
888
+ }
889
+ }
890
+ },
891
+ subscribe(listener) {
892
+ gufaPaletteListeners.add(listener)
893
+ // 迟到的订阅者(组件挂载顺序晚于首次发布)立即拿到当前配色,否则会一直停留在 null
894
+ if (this.value !== null && this.value !== undefined) {
895
+ try {
896
+ listener(this.value)
897
+ } catch (error) {
898
+ console.error('[gufa-code] palette subscriber failed', error)
899
+ }
900
+ }
901
+ return () => gufaPaletteListeners.delete(listener)
902
+ },
903
+ }
904
+
905
+ /** 按扩展名推断 Monaco language(Git Graph 打开提交内文件时用,覆盖常用类型)。 */
906
+ G.languageOfPath = function languageOfPath(path) {
907
+ const name = String(path ?? '').split(/[\\/]/).pop() ?? ''
908
+ const lower = name.toLowerCase()
909
+ if (lower === 'dockerfile') return 'dockerfile'
910
+ if (lower === 'makefile') return 'makefile'
911
+ const dot = lower.lastIndexOf('.')
912
+ if (dot <= 0) return 'plaintext'
913
+ const map = {
914
+ js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
915
+ ts: 'typescript', tsx: 'typescript', mts: 'typescript', cts: 'typescript',
916
+ json: 'json', jsonc: 'json', json5: 'json', md: 'markdown', markdown: 'markdown',
917
+ css: 'css', scss: 'scss', less: 'less', html: 'html', htm: 'html', vue: 'html', svelte: 'html', xml: 'xml', svg: 'xml',
918
+ yaml: 'yaml', yml: 'yaml', toml: 'ini', ini: 'ini', cfg: 'ini', conf: 'ini',
919
+ py: 'python', sh: 'shell', bash: 'shell', zsh: 'shell', fish: 'shell', ps1: 'powershell',
920
+ lua: 'lua', java: 'java', c: 'c', h: 'c', cpp: 'cpp', cc: 'cpp', hpp: 'cpp', cs: 'csharp', go: 'go',
921
+ rs: 'rust', rb: 'ruby', php: 'php', sql: 'sql', swift: 'swift', kt: 'kotlin', dart: 'dart',
922
+ }
923
+ return map[lower.slice(dot + 1)] ?? 'plaintext'
924
+ }
925
+
926
+ /* ── 编辑器问题标记(语法错误/警告),供资源管理器 / 标签页 / 状态栏显示 ── */
927
+ const gufaProblemListeners = new Set()
928
+ G.problemsStore = {
929
+ value: {},
930
+ set(next) {
931
+ const value = next !== null && typeof next === 'object' ? next : {}
932
+ try {
933
+ if (JSON.stringify(this.value) === JSON.stringify(value)) return
934
+ } catch {
935
+ /* 比较失败照常发布 */
936
+ }
937
+ this.value = value
938
+ for (const listener of [...gufaProblemListeners]) {
939
+ try {
940
+ listener(value)
941
+ } catch (error) {
942
+ console.error('[gufa-code] problems listener failed', error)
943
+ }
944
+ }
945
+ },
946
+ subscribe(listener) {
947
+ gufaProblemListeners.add(listener)
948
+ if (this.value !== null && this.value !== undefined) {
949
+ try {
950
+ listener(this.value)
951
+ } catch (error) {
952
+ console.error('[gufa-code] problems subscriber failed', error)
953
+ }
954
+ }
955
+ return () => gufaProblemListeners.delete(listener)
956
+ },
957
+ }
958
+
959
+ /** 订阅当前编辑器的问题集合:{ [相对路径]: { errors, warnings } }。 */
960
+ G.useProblems = function useProblems() {
961
+ const [problems, setProblems] = useState(() => G.problemsStore.value)
962
+ useEffect(() => G.problemsStore.subscribe(setProblems), [])
963
+ return problems
964
+ }
965
+
966
+ /** 按语言给默认缩进(detectIndentation 会优先跟随文件内容)。 */
967
+ G.indentForLanguage = function indentForLanguage(language) {
968
+ const id = typeof language === 'string' ? language.toLowerCase() : ''
969
+ if (id === 'makefile') return { tabSize: 4, insertSpaces: false }
970
+ if (id === 'go' || id === 'rust' || id === 'c' || id === 'cpp' || id === 'csharp' || id === 'java' || id === 'kotlin' || id === 'swift' || id === 'scala' || id === 'php' || id === 'python') {
971
+ return { tabSize: 4, insertSpaces: true }
972
+ }
973
+ if (id === 'ruby' || id === 'lua' || id === 'shell') return { tabSize: 2, insertSpaces: true }
974
+ return { tabSize: 2, insertSpaces: true }
975
+ }
976
+
977
+ /** 订阅当前实色调色板(随滑动变祖档位 / 明暗切换实时更新)。 */
978
+ G.usePalette = function usePalette() {
979
+ const [palette, setPalette] = useState(() => G.paletteStore.value)
980
+ useEffect(() => G.paletteStore.subscribe(setPalette), [])
981
+ return palette
982
+ }
983
+
984
+ /** 纯函数暴露(测试/控制台排查)。 */
985
+ G.__palette = { parseCssColor: G.parseCssColor, compositeColor: G.compositeColor, resolveSolidPalette: G.resolveSolidPalette, luminance: G.luminance, store: G.paletteStore }
986
+
987
+ /* ── 小工具 ───────────────────────────────────────────────────────────── */
988
+ G.cls = (...values) => values.filter(Boolean).join(' ')
989
+ G.basename = (path) => String(path ?? '').split(/[\\/]/).filter(Boolean).at(-1) ?? ''
990
+ G.dirname = (path) => {
991
+ const parts = String(path ?? '').split('/')
992
+ parts.pop()
993
+ return parts.join('/')
994
+ }
995
+ G.joinPath = (base, name) => {
996
+ if (!base) return name
997
+ if (!name) return base
998
+ return `${base.replace(/\/+$/, '')}/${name.replace(/^\/+/, '')}`
999
+ }
1000
+ G.formatBytes = (size) => {
1001
+ if (typeof size !== 'number') return ''
1002
+ if (size < 1024) return `${size} B`
1003
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
1004
+ return `${(size / 1024 / 1024).toFixed(1)} MB`
1005
+ }
1006
+ G.shortPath = (path, max = 42) => {
1007
+ const value = String(path ?? '')
1008
+ return value.length > max ? `…${value.slice(-(max - 1))}` : value
1009
+ }
1010
+ /** 尝试从错误里取出人类可读信息。 */
1011
+ G.errorText = (error) => {
1012
+ if (error === null || error === undefined) return ''
1013
+ if (typeof error === 'string') return error
1014
+ if (typeof error.message === 'string') return error.message
1015
+ return String(error)
1016
+ }
1017
+
1018
+ /* ── 样式 ─────────────────────────────────────────────────────────────── */
1019
+ G.CSS = `
1020
+ .gufa-overlay-root{position:fixed;inset:0;z-index:1;display:flex;box-sizing:border-box;background:var(--gufa-solid-bg, var(--dsw-alias-bg-base));pointer-events:auto;overflow:hidden}
1021
+ .gufa-backdrop{position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none}
1022
+ .gufa-backdrop-page{position:absolute;inset:0;transition:background-color 200ms linear}
1023
+ .gufa-backdrop-glow{position:absolute;inset:0;pointer-events:none;
1024
+ background:
1025
+ radial-gradient(ellipse 58% 72% at 78% 44%, rgba(205,142,82,0.32), rgba(120,72,42,0.12) 55%, transparent 76%),
1026
+ radial-gradient(ellipse 60% 50% at 92% 88%, rgba(150,88,52,0.22), transparent 70%),
1027
+ linear-gradient(90deg, transparent 52%, rgba(70,44,28,0.28) 100%)}
1028
+ .gufa-backdrop-particles{position:absolute;inset:0;pointer-events:none;width:100%;height:100%}
1029
+ .gufa-backdrop-portrait{position:absolute;top:50%;right:0;display:block;height:100%;width:auto;max-width:none;object-fit:contain;object-position:right center;
1030
+ transform-origin:100% 50%;pointer-events:none;transition:opacity 180ms linear;
1031
+ -webkit-mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.18) 7%, rgba(0,0,0,.6) 18%, #000 34%, #000 100%);
1032
+ mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.18) 7%, rgba(0,0,0,.6) 18%, #000 34%, #000 100%)}
1033
+ .gufa-backdrop-fade{position:absolute;inset:0;pointer-events:none;
1034
+ background:linear-gradient(90deg,
1035
+ var(--gufa-solid-bg, #17191e) 0%,
1036
+ color-mix(in srgb, var(--gufa-solid-bg, #17191e) 88%, transparent) 24%,
1037
+ color-mix(in srgb, var(--gufa-solid-bg, #17191e) 46%, transparent) 48%,
1038
+ color-mix(in srgb, var(--gufa-solid-bg, #17191e) 12%, transparent) 72%,
1039
+ transparent 100%)}
1040
+ .gufa-backdrop-portrait[data-leaving]{transition:opacity 520ms ease}
1041
+ .gufa-strength{display:flex;align-items:center;gap:6px;height:26px;padding:0 8px;border-radius:7px;border:1px solid var(--gufa-border);
1042
+ background:var(--gufa-layer2);color:var(--gufa-text-dim);-webkit-app-region:no-drag;flex:0 0 auto}
1043
+ /* IDE 模式下:官方 MenuSurface/Menu 弹层通过 portal 挂到 body 且自身没有 z-index,
1044
+ 会被全屏覆盖层(shell.overlay 的层叠上下文)盖住——表现为弹窗被聊天框/覆盖层遮挡。
1045
+ 给这些语义节点一个高于覆盖层的层级(覆盖层 z-index 20、浮动会话 2147483040)。 */
1046
+ html[data-gufa-usage] .gufa-sidepanel .gufa-panel-body{padding-bottom:calc(var(--gufa-usage-height, 0px) + 4px)}
1047
+ [data-gufa-usage-dock="1"]{pointer-events:auto!important}
1048
+ /* 停靠的侧栏 footer 里隐藏我们自己的「返回经典模式」按钮,避免与标题栏重复 */
1049
+ [data-gufa-foot-dock="1"] .gufa-footer-action{display:none!important}
1050
+ [data-gufa-foot-dock="1"]{padding:0 2px!important}
1051
+ html[data-gufa-ide] [data-menu-material],
1052
+ html[data-gufa-ide] [data-menu-backing],
1053
+ html[data-gufa-ide] [role="menu"],
1054
+ html[data-gufa-ide] [role="listbox"],
1055
+ html[data-gufa-ide] [role="tooltip"],
1056
+ html[data-gufa-ide] [role="dialog"] {
1057
+ z-index: 2147483060 !important;
1058
+ }
1059
+ .gufa-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:38px;height:22px;padding:0 7px;border-radius:6px;
1060
+ border:0.5px solid var(--gufa-border-strong);background:var(--gufa-solid-layer, rgba(255,255,255,0.06));
1061
+ color:var(--gufa-solid-text, var(--dsw-alias-label-primary));font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
1062
+ .gufa-shortcuts-dialog{width:min(540px,94vw)}
1063
+ .gufa-shortcuts-dialog .gufa-modal-body{max-height:min(60vh,520px);overflow:auto;padding:6px 14px}
1064
+ .gufa-session-backdrop{position:absolute;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;padding:20px;
1065
+ background:color-mix(in srgb, #000 42%, transparent)}
1066
+ .gufa-session-dialog{width:min(520px,92vw);color:var(--gufa-solid-text, var(--dsw-alias-label-primary));
1067
+ background:linear-gradient(160deg, var(--gufa-solid-layer2, var(--dsw-alias-bg-layer-2)) 0%, var(--gufa-solid-layer, var(--dsw-alias-bg-layer-1)) 100%)}
1068
+ .gufa-session-dialog .gufa-modal-body{max-height:min(52vh,460px);overflow:auto;gap:4px;padding:10px 12px}
1069
+ .gufa-session-row{display:flex;align-items:center;gap:8px;width:100%;min-width:0;height:34px;padding:0 10px;border:0;border-radius:8px;
1070
+ background:transparent;color:inherit;cursor:pointer;font-size:12.5px;text-align:left;white-space:nowrap;flex:0 0 auto}
1071
+ .gufa-session-row:hover{background:color-mix(in srgb, var(--gufa-solid-text, #888) 9%, transparent)}
1072
+ .gufa-session-row[data-current]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1073
+ .gufa-session-group{display:flex;flex-direction:column;padding:2px 4px}
1074
+ .gufa-session-group-head{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;border:0;border-radius:7px;
1075
+ background:transparent;color:var(--gufa-text-dim);cursor:pointer;font:inherit;font-size:11.5px;text-align:left}
1076
+ .gufa-session-group-head:hover{background:var(--gufa-hover)}
1077
+ .gufa-session-group-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--gufa-text);font-weight:600}
1078
+ .gufa-session-group-count{font-size:10.5px;opacity:.6;font-variant-numeric:tabular-nums}
1079
+ .gufa-session-group[data-current] .gufa-session-group-head{color:var(--gufa-text)}
1080
+ .gufa-session-group .gufa-session-row{margin-left:10px}
1081
+ .gufa-session-workspace{font-size:11px;color:var(--gufa-text-dim);max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1082
+ .gufa-session-row-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis}
1083
+ .gufa-session-row-when{flex:0 0 auto;font-size:10.5px;color:var(--gufa-solid-text-dim, var(--dsw-alias-label-secondary));font-variant-numeric:tabular-nums}
1084
+ .gufa-strength-range{-webkit-appearance:none;appearance:none;width:86px;height:3px;border-radius:2px;margin:0;cursor:pointer;outline:none;
1085
+ background:color-mix(in srgb, var(--gufa-text) 22%, transparent)}
1086
+ /* IDE 模式下:官方 MenuSurface/Menu 弹层通过 portal 挂到 body 且自身没有 z-index,
1087
+ 会被全屏覆盖层(shell.overlay 的层叠上下文)盖住——表现为弹窗被聊天框/覆盖层遮挡。
1088
+ 给这些语义节点一个高于覆盖层的层级(覆盖层 z-index 20、浮动会话 2147483040)。 */
1089
+ html[data-gufa-usage] .gufa-sidepanel .gufa-panel-body{padding-bottom:calc(var(--gufa-usage-height, 0px) + 4px)}
1090
+ [data-gufa-usage-dock="1"]{pointer-events:auto!important}
1091
+ /* 停靠的侧栏 footer 里隐藏我们自己的「返回经典模式」按钮,避免与标题栏重复 */
1092
+ [data-gufa-foot-dock="1"] .gufa-footer-action{display:none!important}
1093
+ [data-gufa-foot-dock="1"]{padding:0 2px!important}
1094
+ html[data-gufa-ide] [data-menu-material],
1095
+ html[data-gufa-ide] [data-menu-backing],
1096
+ html[data-gufa-ide] [role="menu"],
1097
+ html[data-gufa-ide] [role="listbox"],
1098
+ html[data-gufa-ide] [role="tooltip"],
1099
+ html[data-gufa-ide] [role="dialog"] {
1100
+ z-index: 2147483060 !important;
1101
+ }
1102
+ .gufa-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:38px;height:22px;padding:0 7px;border-radius:6px;
1103
+ border:0.5px solid var(--gufa-border-strong);background:var(--gufa-solid-layer, rgba(255,255,255,0.06));
1104
+ color:var(--gufa-solid-text, var(--dsw-alias-label-primary));font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
1105
+ .gufa-shortcuts-dialog{width:min(540px,94vw)}
1106
+ .gufa-shortcuts-dialog .gufa-modal-body{max-height:min(60vh,520px);overflow:auto;padding:6px 14px}
1107
+ .gufa-session-backdrop{position:absolute;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;padding:20px;
1108
+ background:color-mix(in srgb, #000 42%, transparent)}
1109
+ .gufa-session-dialog{width:min(520px,92vw);color:var(--gufa-solid-text, var(--dsw-alias-label-primary));
1110
+ background:linear-gradient(160deg, var(--gufa-solid-layer2, var(--dsw-alias-bg-layer-2)) 0%, var(--gufa-solid-layer, var(--dsw-alias-bg-layer-1)) 100%)}
1111
+ .gufa-session-dialog .gufa-modal-body{max-height:min(52vh,460px);overflow:auto;gap:4px;padding:10px 12px}
1112
+ .gufa-session-row{display:flex;align-items:center;gap:8px;width:100%;min-width:0;height:34px;padding:0 10px;border:0;border-radius:8px;
1113
+ background:transparent;color:inherit;cursor:pointer;font-size:12.5px;text-align:left;white-space:nowrap;flex:0 0 auto}
1114
+ .gufa-session-row:hover{background:color-mix(in srgb, var(--gufa-solid-text, #888) 9%, transparent)}
1115
+ .gufa-session-row[data-current]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1116
+ .gufa-session-group{display:flex;flex-direction:column;padding:2px 4px}
1117
+ .gufa-session-group-head{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;border:0;border-radius:7px;
1118
+ background:transparent;color:var(--gufa-text-dim);cursor:pointer;font:inherit;font-size:11.5px;text-align:left}
1119
+ .gufa-session-group-head:hover{background:var(--gufa-hover)}
1120
+ .gufa-session-group-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--gufa-text);font-weight:600}
1121
+ .gufa-session-group-count{font-size:10.5px;opacity:.6;font-variant-numeric:tabular-nums}
1122
+ .gufa-session-group[data-current] .gufa-session-group-head{color:var(--gufa-text)}
1123
+ .gufa-session-group .gufa-session-row{margin-left:10px}
1124
+ .gufa-session-workspace{font-size:11px;color:var(--gufa-text-dim);max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1125
+ .gufa-session-row-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis}
1126
+ .gufa-session-row-when{flex:0 0 auto;font-size:10.5px;color:var(--gufa-solid-text-dim, var(--dsw-alias-label-secondary));font-variant-numeric:tabular-nums}
1127
+ .gufa-strength-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:11px;height:11px;border-radius:50%;
1128
+ background:var(--gufa-accent);border:2px solid var(--gufa-layer);box-shadow:0 1px 3px color-mix(in srgb, #000 30%, transparent);cursor:grab}
1129
+ /* IDE 模式下:官方 MenuSurface/Menu 弹层通过 portal 挂到 body 且自身没有 z-index,
1130
+ 会被全屏覆盖层(shell.overlay 的层叠上下文)盖住——表现为弹窗被聊天框/覆盖层遮挡。
1131
+ 给这些语义节点一个高于覆盖层的层级(覆盖层 z-index 20、浮动会话 2147483040)。 */
1132
+ html[data-gufa-usage] .gufa-sidepanel .gufa-panel-body{padding-bottom:calc(var(--gufa-usage-height, 0px) + 4px)}
1133
+ [data-gufa-usage-dock="1"]{pointer-events:auto!important}
1134
+ /* 停靠的侧栏 footer 里隐藏我们自己的「返回经典模式」按钮,避免与标题栏重复 */
1135
+ [data-gufa-foot-dock="1"] .gufa-footer-action{display:none!important}
1136
+ [data-gufa-foot-dock="1"]{padding:0 2px!important}
1137
+ html[data-gufa-ide] [data-menu-material],
1138
+ html[data-gufa-ide] [data-menu-backing],
1139
+ html[data-gufa-ide] [role="menu"],
1140
+ html[data-gufa-ide] [role="listbox"],
1141
+ html[data-gufa-ide] [role="tooltip"],
1142
+ html[data-gufa-ide] [role="dialog"] {
1143
+ z-index: 2147483060 !important;
1144
+ }
1145
+ .gufa-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:38px;height:22px;padding:0 7px;border-radius:6px;
1146
+ border:0.5px solid var(--gufa-border-strong);background:var(--gufa-solid-layer, rgba(255,255,255,0.06));
1147
+ color:var(--gufa-solid-text, var(--dsw-alias-label-primary));font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
1148
+ .gufa-shortcuts-dialog{width:min(540px,94vw)}
1149
+ .gufa-shortcuts-dialog .gufa-modal-body{max-height:min(60vh,520px);overflow:auto;padding:6px 14px}
1150
+ .gufa-session-backdrop{position:absolute;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;padding:20px;
1151
+ background:color-mix(in srgb, #000 42%, transparent)}
1152
+ .gufa-session-dialog{width:min(520px,92vw);color:var(--gufa-solid-text, var(--dsw-alias-label-primary));
1153
+ background:linear-gradient(160deg, var(--gufa-solid-layer2, var(--dsw-alias-bg-layer-2)) 0%, var(--gufa-solid-layer, var(--dsw-alias-bg-layer-1)) 100%)}
1154
+ .gufa-session-dialog .gufa-modal-body{max-height:min(52vh,460px);overflow:auto;gap:4px;padding:10px 12px}
1155
+ .gufa-session-row{display:flex;align-items:center;gap:8px;width:100%;min-width:0;height:34px;padding:0 10px;border:0;border-radius:8px;
1156
+ background:transparent;color:inherit;cursor:pointer;font-size:12.5px;text-align:left;white-space:nowrap;flex:0 0 auto}
1157
+ .gufa-session-row:hover{background:color-mix(in srgb, var(--gufa-solid-text, #888) 9%, transparent)}
1158
+ .gufa-session-row[data-current]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1159
+ .gufa-session-group{display:flex;flex-direction:column;padding:2px 4px}
1160
+ .gufa-session-group-head{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;border:0;border-radius:7px;
1161
+ background:transparent;color:var(--gufa-text-dim);cursor:pointer;font:inherit;font-size:11.5px;text-align:left}
1162
+ .gufa-session-group-head:hover{background:var(--gufa-hover)}
1163
+ .gufa-session-group-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--gufa-text);font-weight:600}
1164
+ .gufa-session-group-count{font-size:10.5px;opacity:.6;font-variant-numeric:tabular-nums}
1165
+ .gufa-session-group[data-current] .gufa-session-group-head{color:var(--gufa-text)}
1166
+ .gufa-session-group .gufa-session-row{margin-left:10px}
1167
+ .gufa-session-workspace{font-size:11px;color:var(--gufa-text-dim);max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1168
+ .gufa-session-row-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis}
1169
+ .gufa-session-row-when{flex:0 0 auto;font-size:10.5px;color:var(--gufa-solid-text-dim, var(--dsw-alias-label-secondary));font-variant-numeric:tabular-nums}
1170
+ .gufa-strength-range:active::-webkit-slider-thumb{cursor:grabbing}
1171
+ .gufa-strength-value{font-size:10.5px;min-width:30px;text-align:right;font-variant-numeric:tabular-nums}
1172
+ /* IDE 模式下:官方 MenuSurface/Menu 弹层通过 portal 挂到 body 且自身没有 z-index,
1173
+ 会被全屏覆盖层(shell.overlay 的层叠上下文)盖住——表现为弹窗被聊天框/覆盖层遮挡。
1174
+ 给这些语义节点一个高于覆盖层的层级(覆盖层 z-index 20、浮动会话 2147483040)。 */
1175
+ html[data-gufa-usage] .gufa-sidepanel .gufa-panel-body{padding-bottom:calc(var(--gufa-usage-height, 0px) + 4px)}
1176
+ [data-gufa-usage-dock="1"]{pointer-events:auto!important}
1177
+ /* 停靠的侧栏 footer 里隐藏我们自己的「返回经典模式」按钮,避免与标题栏重复 */
1178
+ [data-gufa-foot-dock="1"] .gufa-footer-action{display:none!important}
1179
+ [data-gufa-foot-dock="1"]{padding:0 2px!important}
1180
+ html[data-gufa-ide] [data-menu-material],
1181
+ html[data-gufa-ide] [data-menu-backing],
1182
+ html[data-gufa-ide] [role="menu"],
1183
+ html[data-gufa-ide] [role="listbox"],
1184
+ html[data-gufa-ide] [role="tooltip"],
1185
+ html[data-gufa-ide] [role="dialog"] {
1186
+ z-index: 2147483060 !important;
1187
+ }
1188
+ .gufa-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:38px;height:22px;padding:0 7px;border-radius:6px;
1189
+ border:0.5px solid var(--gufa-border-strong);background:var(--gufa-solid-layer, rgba(255,255,255,0.06));
1190
+ color:var(--gufa-solid-text, var(--dsw-alias-label-primary));font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
1191
+ .gufa-shortcuts-dialog{width:min(540px,94vw)}
1192
+ .gufa-shortcuts-dialog .gufa-modal-body{max-height:min(60vh,520px);overflow:auto;padding:6px 14px}
1193
+ .gufa-session-backdrop{position:absolute;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;padding:20px;
1194
+ background:color-mix(in srgb, #000 42%, transparent)}
1195
+ .gufa-session-dialog{width:min(520px,92vw);color:var(--gufa-solid-text, var(--dsw-alias-label-primary));
1196
+ background:linear-gradient(160deg, var(--gufa-solid-layer2, var(--dsw-alias-bg-layer-2)) 0%, var(--gufa-solid-layer, var(--dsw-alias-bg-layer-1)) 100%)}
1197
+ .gufa-session-dialog .gufa-modal-body{max-height:min(52vh,460px);overflow:auto;gap:4px;padding:10px 12px}
1198
+ .gufa-session-row{display:flex;align-items:center;gap:8px;width:100%;min-width:0;height:34px;padding:0 10px;border:0;border-radius:8px;
1199
+ background:transparent;color:inherit;cursor:pointer;font-size:12.5px;text-align:left;white-space:nowrap;flex:0 0 auto}
1200
+ .gufa-session-row:hover{background:color-mix(in srgb, var(--gufa-solid-text, #888) 9%, transparent)}
1201
+ .gufa-session-row[data-current]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1202
+ .gufa-session-group{display:flex;flex-direction:column;padding:2px 4px}
1203
+ .gufa-session-group-head{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;border:0;border-radius:7px;
1204
+ background:transparent;color:var(--gufa-text-dim);cursor:pointer;font:inherit;font-size:11.5px;text-align:left}
1205
+ .gufa-session-group-head:hover{background:var(--gufa-hover)}
1206
+ .gufa-session-group-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--gufa-text);font-weight:600}
1207
+ .gufa-session-group-count{font-size:10.5px;opacity:.6;font-variant-numeric:tabular-nums}
1208
+ .gufa-session-group[data-current] .gufa-session-group-head{color:var(--gufa-text)}
1209
+ .gufa-session-group .gufa-session-row{margin-left:10px}
1210
+ .gufa-session-workspace{font-size:11px;color:var(--gufa-text-dim);max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1211
+ .gufa-session-row-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis}
1212
+ .gufa-session-row-when{flex:0 0 auto;font-size:10.5px;color:var(--gufa-solid-text-dim, var(--dsw-alias-label-secondary));font-variant-numeric:tabular-nums}
1213
+ .gufa-strength-range
1214
+ -webkit-mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.12) 6%, rgba(0,0,0,.55) 16%, #000 34%, #000 100%);
1215
+ mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.12) 6%, rgba(0,0,0,.55) 16%, #000 34%, #000 100%)}
1216
+ .gufa-backdrop-scrim{position:absolute;inset:0;
1217
+ background:linear-gradient(90deg, var(--liang-mask-strong, transparent) 0%, var(--liang-mask-soft, transparent) 38%, transparent 66%),
1218
+ linear-gradient(var(--liang-mask-base, transparent), var(--liang-mask-base, transparent))}
1219
+ .gufa-shell{position:relative;z-index:1;flex:1 1 auto;display:flex;flex-direction:column;height:100%;min-height:0;width:100%;min-width:0}
1220
+ html[data-windows-titlebar] .gufa-overlay-root{padding-top:var(--dsh-windows-titlebar-height,0px)}
1221
+ .gufa-root{--gufa-accent:#c0392b;--gufa-accent-soft:color-mix(in srgb, var(--gufa-accent) 16%, transparent);
1222
+ --gufa-bg:var(--gufa-solid-bg, var(--dsw-alias-bg-base));--gufa-layer:var(--gufa-solid-layer, var(--dsw-alias-bg-layer-1));--gufa-layer2:var(--gufa-solid-layer2, var(--dsw-alias-bg-layer-2));
1223
+ --gufa-solid-overlay-bg:var(--gufa-solid-overlay, var(--dsw-alias-bg-overlay));
1224
+ --gufa-border:var(--dsw-alias-border-l1);--gufa-border-strong:var(--dsw-alias-border-l2);
1225
+ --gufa-text:var(--dsw-alias-label-primary);--gufa-text-dim:var(--dsw-alias-label-secondary);
1226
+ --gufa-hover:color-mix(in srgb, var(--gufa-text) 7%, transparent);
1227
+ --gufa-active:color-mix(in srgb, var(--gufa-text) 11%, transparent);
1228
+ position:relative;display:flex;flex-direction:column;height:100%;min-height:0;width:100%;overflow:hidden;
1229
+ background:var(--gufa-bg);color:var(--gufa-text);font-size:13px;line-height:1.45}
1230
+ .gufa-root *{box-sizing:border-box}
1231
+ .gufa-root button{font:inherit;color:inherit}
1232
+ .gufa-titlebar{display:flex;align-items:center;gap:10px;height:44px;padding:0 10px 0 14px;flex:0 0 auto;
1233
+ border-bottom:1px solid var(--gufa-border);background:var(--gufa-layer);-webkit-app-region:drag}
1234
+ html[data-platform=darwin] .gufa-titlebar{height:48px;padding-left:86px}
1235
+ html[data-platform=darwin][data-fullscreen] .gufa-titlebar{height:44px;padding-left:16px}
1236
+ .gufa-titlebar button,.gufa-titlebar input,.gufa-titlebar .gufa-project,.gufa-titlebar-action{-webkit-app-region:no-drag}
1237
+ .gufa-brand{display:flex;align-items:center;gap:8px;min-width:0}
1238
+ .gufa-brand-logo{width:24px;height:24px;flex:0 0 auto}
1239
+ .gufa-brand-name{font-weight:650;font-size:14px;letter-spacing:.02em;white-space:nowrap}
1240
+ .gufa-brand-sub{font-size:10px;letter-spacing:.18em;color:var(--gufa-text-dim);white-space:nowrap}
1241
+ .gufa-project{appearance:none;border:0;background:transparent;font:inherit;color:inherit;cursor:pointer}
1242
+ .gufa-project:hover{background:var(--gufa-hover)}
1243
+ .gufa-workspace-row{height:auto;min-height:38px;padding:5px 8px}
1244
+ .gufa-workspace-title{display:block;font-size:12px;line-height:1.3}
1245
+ .gufa-workspace-path{display:block;font-size:10.5px;color:var(--gufa-text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1246
+ .gufa-project{display:flex;align-items:center;gap:6px;min-width:0;margin-left:6px;padding:3px 9px;border-radius:7px;
1247
+ border:1px solid var(--gufa-border);background:var(--gufa-layer2);color:var(--gufa-text-dim);font-size:12px}
1248
+ .gufa-project-name{color:var(--gufa-text);font-weight:550;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}
1249
+ .gufa-titlebar-spacer{flex:1 1 auto}
1250
+ .gufa-titlebar-actions{display:flex;align-items:center;gap:6px;flex:0 0 auto}
1251
+ .gufa-body{flex:1 1 auto;display:flex;min-height:0;min-width:0;position:relative}
1252
+ .gufa-activity{flex:0 0 48px;display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 0;
1253
+ border-right:1px solid var(--gufa-border);background:var(--gufa-layer)}
1254
+ .gufa-activity-btn{width:38px;height:38px;border:0;border-radius:9px;background:transparent;color:var(--gufa-text-dim);
1255
+ display:flex;align-items:center;justify-content:center;cursor:pointer;position:relative}
1256
+ .gufa-activity-btn:hover{background:var(--gufa-hover);color:var(--gufa-text)}
1257
+ .gufa-activity-btn[data-active]{background:var(--gufa-active);color:var(--gufa-text)}
1258
+ .gufa-activity-btn[data-active]:before{content:"";position:absolute;left:-5px;top:9px;bottom:9px;width:3px;border-radius:2px;background:var(--gufa-accent)}
1259
+ .gufa-activity-fill{flex:1 1 auto}
1260
+ .gufa-sidepanel{flex:0 0 auto;display:flex;flex-direction:column;min-width:0;border-right:1px solid var(--gufa-border);background:var(--gufa-bg)}
1261
+ .gufa-panel-head{display:flex;align-items:center;gap:6px;height:34px;padding:0 6px 0 12px;flex:0 0 auto;
1262
+ font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--gufa-text-dim);border-bottom:1px solid var(--gufa-border)}
1263
+ .gufa-panel-head-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1264
+ .gufa-panel-body{flex:1 1 auto;overflow:auto;min-height:0}
1265
+ .gufa-iconbtn{width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--gufa-text-dim);
1266
+ display:inline-flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}
1267
+ .gufa-iconbtn:hover{background:var(--gufa-hover);color:var(--gufa-text)}
1268
+ .gufa-iconbtn[data-active]{background:var(--gufa-active);color:var(--gufa-text)}
1269
+ .gufa-iconbtn:disabled{opacity:.4;cursor:default}
1270
+ .gufa-btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 11px;border-radius:7px;cursor:pointer;
1271
+ border:1px solid var(--gufa-border-strong);background:var(--gufa-layer2);color:var(--gufa-text);font-size:12px;white-space:nowrap}
1272
+ .gufa-btn:hover{border-color:var(--gufa-accent);color:var(--gufa-text)}
1273
+ .gufa-btn[data-primary]{background:var(--gufa-accent);border-color:var(--gufa-accent);color:#fff}
1274
+ .gufa-btn[data-primary]:hover{background:color-mix(in srgb, var(--gufa-accent) 86%, #000)}
1275
+ .gufa-btn:disabled{opacity:.5;cursor:default}
1276
+ .gufa-main{flex:1 1 auto;display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--gufa-bg)}
1277
+ .gufa-main-row{flex:1 1 auto;display:flex;min-height:0;min-width:0}
1278
+ .gufa-editor-area{flex:1 1 auto;display:flex;flex-direction:column;min-width:0;min-height:0}
1279
+ .gufa-tabs{display:flex;align-items:stretch;height:36px;flex:0 0 auto;overflow-x:auto;overflow-y:hidden;
1280
+ border-bottom:1px solid var(--gufa-border);background:var(--gufa-layer);scrollbar-width:none}
1281
+ .gufa-tabs::-webkit-scrollbar{display:none}
1282
+ .gufa-tab{display:flex;align-items:center;gap:7px;padding:0 8px 0 12px;border:0;border-right:1px solid var(--gufa-border);
1283
+ background:transparent;color:var(--gufa-text-dim);cursor:pointer;font-size:12px;white-space:nowrap;max-width:220px;flex:0 0 auto}
1284
+ .gufa-tab:hover{background:var(--gufa-hover)}
1285
+ .gufa-tab[data-active]{background:var(--gufa-bg);color:var(--gufa-text);box-shadow:inset 0 2px 0 var(--gufa-accent)}
1286
+ .gufa-tab-name{overflow:hidden;text-overflow:ellipsis}
1287
+ .gufa-tab-dot{width:7px;height:7px;border-radius:50%;background:var(--gufa-accent);flex:0 0 auto}
1288
+ .gufa-tab-close{width:18px;height:18px;border:0;border-radius:4px;background:transparent;color:inherit;cursor:pointer;
1289
+ display:flex;align-items:center;justify-content:center;opacity:.65;flex:0 0 auto}
1290
+ .gufa-tab-close:hover{background:var(--gufa-active);opacity:1}
1291
+ .gufa-breadcrumb{display:flex;align-items:center;gap:4px;height:26px;padding:0 12px;flex:0 0 auto;font-size:11.5px;color:var(--gufa-text-dim);
1292
+ border-bottom:1px solid var(--gufa-border);overflow:hidden;white-space:nowrap}
1293
+ .gufa-breadcrumb b{font-weight:500;color:var(--gufa-text)}
1294
+ .gufa-editor-host{flex:1 1 auto;position:relative;min-height:0;min-width:0;overflow:hidden}
1295
+ .gufa-editor-fallback{position:absolute;inset:0;overflow:auto;margin:0;padding:14px 18px;font:12.5px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre;tab-size:2}
1296
+ .gufa-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;height:100%;padding:28px;
1297
+ color:var(--gufa-text-dim);text-align:center}
1298
+ .gufa-empty-title{font-size:14px;font-weight:600;color:var(--gufa-text)}
1299
+ .gufa-empty-hint{font-size:12px;max-width:340px;line-height:1.6}
1300
+ .gufa-seal{width:54px;height:54px;opacity:.85}
1301
+ .gufa-chatpane{flex:0 0 auto;display:flex;flex-direction:column;min-width:0;border-left:1px solid var(--gufa-border);background:var(--gufa-bg);position:relative}
1302
+ .gufa-root[data-backdrop="on"] .gufa-chatpane:before{content:"";position:absolute;inset:0;pointer-events:none;z-index:0;
1303
+ background:linear-gradient(90deg, color-mix(in srgb, var(--gufa-bg) 62%, transparent) 0%, color-mix(in srgb, var(--gufa-bg) 18%, transparent) 46%, transparent 100%)}
1304
+ .gufa-root[data-backdrop="on"] .gufa-chatdock{position:relative;z-index:1}
1305
+ .gufa-statusbar{flex:0 0 auto;display:flex;align-items:center;gap:14px;height:24px;padding:0 12px;
1306
+ border-top:1px solid var(--gufa-border);background:var(--gufa-layer);color:var(--gufa-text-dim);font-size:11px}
1307
+ .gufa-status-item{display:inline-flex;align-items:center;gap:5px;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1308
+ .gufa-status-fill{flex:1 1 auto}
1309
+ .gufa-splitter{flex:0 0 5px;cursor:col-resize;background:transparent;position:relative;z-index:2}
1310
+ .gufa-splitter:after{content:"";position:absolute;top:0;bottom:0;left:2px;width:1px;background:var(--gufa-border)}
1311
+ .gufa-splitter:hover:after,.gufa-splitter[data-dragging]:after{background:var(--gufa-accent);width:2px;left:1.5px}
1312
+ .gufa-hsplitter{flex:0 0 5px;cursor:row-resize;position:relative}
1313
+ .gufa-hsplitter:after{content:"";position:absolute;left:0;right:0;top:2px;height:1px;background:var(--gufa-border)}
1314
+ .gufa-hsplitter:hover:after{background:var(--gufa-accent);height:2px;top:1.5px}
1315
+ .gufa-tree{display:flex;flex-direction:column;padding:4px 0}
1316
+ .gufa-tree-row{display:flex;align-items:center;gap:5px;height:24px;padding:0 8px 0 8px;border:0;background:transparent;
1317
+ color:var(--gufa-text);cursor:pointer;text-align:left;font-size:12.5px;white-space:nowrap;min-width:0;width:100%}
1318
+ .gufa-tree-row:hover{background:var(--gufa-hover)}
1319
+ .gufa-tree-row[data-selected]{background:var(--gufa-active)}
1320
+ .gufa-tree-row[data-kind="dir"]{color:var(--gufa-text)}
1321
+ .gufa-tree-name{overflow:hidden;text-overflow:ellipsis;flex:1 1 auto;min-width:0}
1322
+ .gufa-tree-chevron{width:14px;flex:0 0 auto;opacity:.7;display:flex;align-items:center;justify-content:center}
1323
+ .gufa-tree-icon{width:15px;flex:0 0 auto;opacity:.85;display:flex;align-items:center;justify-content:center}
1324
+ .gufa-tree-badge{font-size:10px;color:var(--gufa-accent);flex:0 0 auto;font-weight:600}
1325
+ .gufa-panel-section{display:flex;flex-direction:column;padding:8px 10px;gap:8px}
1326
+ .gufa-row{display:flex;align-items:center;gap:8px;min-width:0}
1327
+ .gufa-input{width:100%;min-width:0;height:28px;padding:0 9px;border-radius:7px;border:1px solid var(--gufa-border-strong);
1328
+ background:var(--gufa-layer);color:var(--gufa-text);font:inherit;font-size:12px;outline:none}
1329
+ .gufa-input:focus{border-color:var(--gufa-accent)}
1330
+ .gufa-input::placeholder{color:var(--gufa-text-dim)}
1331
+ .gufa-textarea{width:100%;min-height:64px;resize:vertical;padding:7px 9px;border-radius:7px;border:1px solid var(--gufa-border-strong);
1332
+ background:var(--gufa-layer);color:var(--gufa-text);font:inherit;font-size:12px;outline:none}
1333
+ .gufa-textarea:focus{border-color:var(--gufa-accent)}
1334
+ .gufa-git-change{display:flex;align-items:center;gap:7px;height:26px;padding:0 8px;cursor:pointer;border-radius:6px;min-width:0}
1335
+ .gufa-git-change:hover{background:var(--gufa-hover)}
1336
+ .gufa-git-status{width:15px;flex:0 0 auto;text-align:center;font-weight:700;font-size:11px;color:var(--gufa-accent)}
1337
+ .gufa-git-status[data-kind="D"]{color:var(--dsw-alias-state-error-primary)}
1338
+ .gufa-git-status[data-kind="??"]{color:var(--dsw-alias-state-success-primary)}
1339
+ .gufa-git-path{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;direction:rtl;text-align:left}
1340
+ .gufa-badge{display:inline-flex;align-items:center;justify-content:center;min-width:17px;height:17px;padding:0 5px;border-radius:9px;
1341
+ background:var(--gufa-accent);color:#fff;font-size:10px;font-weight:700}
1342
+ .gufa-ssh-host{display:flex;align-items:center;gap:8px;height:30px;padding:0 10px;cursor:pointer;border-radius:6px;min-width:0}
1343
+ .gufa-ssh-host:hover{background:var(--gufa-hover)}
1344
+ .gufa-ssh-alias{font-weight:600;font-size:12px;white-space:nowrap}
1345
+ .gufa-ssh-hostname{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--gufa-text-dim);font-size:11px;direction:rtl;text-align:left}
1346
+ .gufa-hint{font-size:11px;color:var(--gufa-text-dim);line-height:1.6}
1347
+ .gufa-error-fallback{position:absolute;inset:0;z-index:130;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px;
1348
+ background:var(--gufa-solid-bg, #17191e);color:var(--gufa-solid-text, #e8eaed);text-align:center}
1349
+ .gufa-error-fallback-title{font-size:14px;font-weight:600}
1350
+ .gufa-error-fallback-detail{max-width:min(720px,90vw);max-height:40vh;overflow:auto;margin:0;padding:10px 12px;border-radius:8px;border:1px solid var(--gufa-border-strong);
1351
+ background:var(--gufa-solid-layer2, #23252a);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;text-align:left;white-space:pre-wrap}
1352
+ .gufa-error-fallback-actions{display:flex;gap:10px}
1353
+ /* Monaco diff 的红绿兜底:直接覆盖差异行/字符的 class,不依赖主题 token(皮肤可能覆盖变量)。 */
1354
+ .gufa-root .monaco-diff-editor .line-insert{background-color:rgba(46,160,67,0.42)!important}
1355
+ .gufa-root .monaco-diff-editor .line-delete{background-color:rgba(248,81,73,0.42)!important}
1356
+ .gufa-root .monaco-diff-editor .char-insert{background-color:rgba(63,185,80,0.62)!important;border-radius:2px}
1357
+ .gufa-root .monaco-diff-editor .char-delete{background-color:rgba(248,81,73,0.62)!important;border-radius:2px}
1358
+ .gufa-root .monaco-diff-editor .gutter-insert{background-color:rgba(46,160,67,0.85)!important}
1359
+ .gufa-root .monaco-diff-editor .gutter-delete{background-color:rgba(248,81,73,0.85)!important}
1360
+ .gufa-root .monaco-diff-editor .inline-deleted-margin-view-zone{background-color:rgba(248,81,73,0.28)!important}
1361
+ .gufa-root .monaco-diff-editor .inline-deleted-text{background-color:rgba(248,81,73,0.42)!important}
1362
+ .gufa-graph-view{position:absolute;inset:0;display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--gufa-bg,#111)}
1363
+ .gufa-graph-head{display:flex;align-items:center;gap:8px;height:32px;padding:0 10px;flex:0 0 auto;border-bottom:0.5px solid var(--gufa-border);font-size:12px}
1364
+ .gufa-graph-branch{display:inline-flex;align-items:center;gap:4px;color:var(--gufa-text-dim);font-size:11.5px}
1365
+ .gufa-graph-loading{display:flex;align-items:center;justify-content:center;gap:8px;height:100%;color:var(--gufa-text-dim);font-size:12px}
1366
+ .gufa-graph-detail{display:flex;align-items:center;gap:10px;flex:0 0 auto;padding:6px 12px;border-bottom:0.5px solid var(--gufa-border);font-size:11.5px;background:var(--gufa-solid-layer, rgba(255,255,255,0.04))}
1367
+ .gufa-graph-detail-hash{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--gufa-accent)}
1368
+ .gufa-graph-detail-subject{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1369
+ .gufa-graph-split{flex:1 1 auto;min-height:0;display:flex;align-items:stretch}
1370
+ .gufa-graph-body{flex:1 1 auto;min-width:0;min-height:0;overflow:auto}
1371
+ .gufa-graph-aside{flex:0 0 340px;min-width:0;border-left:0.5px solid var(--gufa-border);display:flex;flex-direction:column;min-height:0;overflow:auto}
1372
+ .gufa-graph-aside-empty{padding:16px 14px;color:var(--gufa-text-dim);font-size:12px}
1373
+ .gufa-graph-commit{padding:10px 12px 8px;border-bottom:0.5px solid var(--gufa-border);display:flex;flex-direction:column;gap:6px}
1374
+ .gufa-graph-commit-subject{font-size:13px;line-height:1.4;word-break:break-word}
1375
+ .gufa-graph-commit-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--gufa-text-dim)}
1376
+ .gufa-graph-commit-meta code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--gufa-accent)}
1377
+ .gufa-graph-commit-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
1378
+ .gufa-graph-files-head{display:flex;align-items:center;gap:6px;padding:8px 12px 4px;font-size:11.5px;color:var(--gufa-text-dim)}
1379
+ .gufa-graph-files-count{padding:0 6px;border-radius:8px;background:color-mix(in srgb, var(--gufa-accent) 18%, transparent);color:var(--gufa-accent);font-size:10.5px}
1380
+ .gufa-graph-files{display:flex;flex-direction:column;padding:2px 6px 10px}
1381
+ .gufa-graph-file{display:flex;align-items:center;gap:8px;appearance:none;border:0;background:transparent;color:inherit;cursor:pointer;
1382
+ font:inherit;font-size:12px;padding:4px 6px;border-radius:6px;text-align:left;min-width:0}
1383
+ .gufa-graph-file:hover{background:var(--gufa-hover, rgba(255,255,255,0.06))}
1384
+ .gufa-graph-file-status{flex:0 0 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--gufa-text-dim)}
1385
+ .gufa-graph-file-status[data-kind="A"]{color:#3fb950}
1386
+ .gufa-graph-file-status[data-kind="D"]{color:#f85149}
1387
+ .gufa-graph-file-status[data-kind="M"]{color:#e5c07b}
1388
+ .gufa-graph-file-status[data-kind="R"]{color:#61afef}
1389
+ .gufa-graph-file-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1390
+ .gufa-graph-row{display:flex;align-items:center;gap:8px;height:26px;padding:0 10px;cursor:pointer;font-size:12px;white-space:nowrap}
1391
+ .gufa-graph-row:hover{background:var(--gufa-hover, rgba(255,255,255,0.05))}
1392
+ .gufa-graph-row[data-selected]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1393
+ .gufa-graph-svg{flex:0 0 auto;display:inline-flex;height:26px}
1394
+ .gufa-graph-refs{display:inline-flex;gap:4px;flex:0 0 auto;max-width:40%;overflow:hidden}
1395
+ .gufa-graph-ref{display:inline-flex;align-items:center;height:16px;padding:0 6px;border-radius:8px;font-size:10.5px;
1396
+ border:0.5px solid color-mix(in srgb, var(--gufa-accent) 45%, transparent);color:var(--gufa-accent);white-space:nowrap}
1397
+ .gufa-graph-ref[data-current]{background:color-mix(in srgb, var(--gufa-accent) 18%, transparent)}
1398
+ .gufa-graph-subject{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}
1399
+ .gufa-graph-meta{flex:0 0 auto;font-size:11px;color:var(--gufa-text-dim)}
1400
+ .gufa-branch-dialog-backdrop{position:fixed;inset:0;z-index:2147483200;display:flex;align-items:center;justify-content:center;
1401
+ background:rgba(0,0,0,.42);backdrop-filter:blur(2px)}
1402
+ .gufa-branch-dialog{width:min(380px,92vw);max-height:min(72vh,560px);display:flex;flex-direction:column;
1403
+ border-radius:12px;border:1px solid var(--gufa-border-strong);background:var(--gufa-solid-overlay-bg);
1404
+ box-shadow:0 24px 60px rgba(0,0,0,.45);overflow:hidden}
1405
+ .gufa-branch-dialog-head{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:0.5px solid var(--gufa-border)}
1406
+ .gufa-branch-dialog-current{font-size:11px;color:var(--gufa-text-dim);margin-left:4px}
1407
+ .gufa-branch-dialog-list{flex:1 1 auto;min-height:0;overflow:auto;padding:6px}
1408
+ .gufa-branch-row{display:flex;align-items:center;gap:8px;width:100%;height:30px;padding:0 8px;border:0;border-radius:7px;
1409
+ background:transparent;color:var(--gufa-text);cursor:pointer;font-size:12px;text-align:left}
1410
+ .gufa-branch-row:hover{background:var(--gufa-hover)}
1411
+ .gufa-branch-row[data-current]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1412
+ .gufa-branch-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1413
+ .gufa-branch-tag{font-size:10px;padding:1px 6px;border-radius:8px;color:var(--gufa-accent);
1414
+ background:color-mix(in srgb, var(--gufa-accent) 18%, transparent)}
1415
+ .gufa-branch-dialog-new{display:flex;gap:6px;padding:10px 12px;border-top:0.5px solid var(--gufa-border)}
1416
+ .gufa-branch-menu .gufa-menu-item[data-current]{background:color-mix(in srgb, var(--gufa-accent) 16%, transparent)}
1417
+ /* IDE 模式下第三方皮肤(滑动变祖等)的背景层会持续跑 canvas 动画;IDE 自己已渲染背景,直接隐藏以省性能 */
1418
+ /* IDE 模式下第三方皮肤背景层拿不到稳定渲染(停靠/层级问题),统一隐藏,由我们的背景层+粒子复刻 */
1419
+ html[data-gufa-ide] .liang-skin-backdrop{display:none!important}
1420
+ /* 右侧 AI 面板会挡住皮肤原位置的人像:IDE 模式下把人像往中间挪,让编辑区也能看到 */
1421
+ .gufa-root[data-backdrop="on"] .gufa-backdrop-portrait{right:0;left:26%;width:74%;height:108%;opacity:.9;
1422
+ object-fit:cover;object-position:58% 40%;
1423
+ -webkit-mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.28) 10%, rgba(0,0,0,.8) 28%, #000 42%, #000 100%);
1424
+ mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.28) 10%, rgba(0,0,0,.8) 28%, #000 42%, #000 100%)}
1425
+ .gufa-diff-toolbar{position:absolute;top:0;left:0;right:0;height:30px;z-index:6;display:flex;align-items:center;gap:4px;
1426
+ padding:0 8px;border-bottom:0.5px solid var(--gufa-border);background:var(--gufa-solid-layer, var(--gufa-bg))}
1427
+ .gufa-sync{appearance:none;border:0;background:transparent;color:var(--gufa-accent);cursor:pointer;font:inherit;font-size:11.5px;
1428
+ padding:1px 5px;border-radius:5px;font-variant-numeric:tabular-nums}
1429
+ .gufa-sync:hover:not(:disabled){background:color-mix(in srgb, var(--gufa-accent) 14%, transparent)}
1430
+ .gufa-sync:disabled{opacity:.5;cursor:default}
1431
+ .gufa-git-sync{display:inline-flex;align-items:center;gap:2px;appearance:none;border:0;background:transparent;color:var(--gufa-text-dim);
1432
+ cursor:pointer;font:inherit;height:22px;padding:0 4px;border-radius:6px}
1433
+ .gufa-git-sync:hover:not(:disabled){background:var(--gufa-hover);color:var(--gufa-text)}
1434
+ .gufa-git-sync:disabled{opacity:.5;cursor:default}
1435
+ .gufa-git-sync[data-active]{color:var(--gufa-accent)}
1436
+ .gufa-git-sync-count{font-size:10.5px;font-variant-numeric:tabular-nums;min-width:8px;text-align:center}
1437
+ .gufa-git-sync[data-active] .gufa-git-sync-count{font-weight:700}
1438
+ .gufa-info-banner{display:flex;align-items:center;gap:6px;margin:8px 10px 0;padding:4px 8px;border-radius:6px;font-size:11.5px;
1439
+ color:var(--dsw-alias-state-success-primary, #3fa66f);border:1px solid color-mix(in srgb, currentColor 35%, transparent);
1440
+ background:color-mix(in srgb, currentColor 10%, transparent)}
1441
+ .gufa-toast-success{position:fixed;top:14px;left:50%;transform:translateX(-50%);z-index:2147483300;
1442
+ display:flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;font-size:12px;pointer-events:none;
1443
+ color:var(--dsw-alias-state-success-primary, #3fb950);
1444
+ background:color-mix(in srgb, #3fb950 16%, var(--gufa-solid-overlay-bg, #1c1c1c));
1445
+ border:1px solid color-mix(in srgb, #3fb950 50%, transparent);
1446
+ box-shadow:0 8px 24px rgba(0,0,0,.35);backdrop-filter:blur(8px)}
1447
+ /* 资源管理器:Git 改动状态颜色(M/A/D/R/??) */
1448
+ .gufa-tree-row[data-git-status="M"] .gufa-tree-name{color:#e5c07b}
1449
+ .gufa-tree-row[data-git-status="A"] .gufa-tree-name{color:#3fb950}
1450
+ .gufa-tree-row[data-git-status="D"] .gufa-tree-name{color:#f85149;text-decoration:line-through}
1451
+ .gufa-tree-row[data-git-status="R"] .gufa-tree-name{color:#61afef}
1452
+ .gufa-tree-row[data-git-status="?"] .gufa-tree-name,.gufa-tree-row[data-git-status="??"] .gufa-tree-name{color:#3fb950;font-style:italic}
1453
+ .gufa-tree-row[data-git-status] .gufa-tree-icon{opacity:.95}
1454
+ .gufa-tree-problem{display:inline-block;width:6px;height:6px;border-radius:50%;background:#f85149;margin-left:5px;flex:0 0 auto}
1455
+ .gufa-tree-problem[data-level="warning"]{background:#e5c07b}
1456
+ .gufa-tree-problem[data-level="dir"]{background:color-mix(in srgb, var(--gufa-accent) 80%, transparent)}
1457
+ .gufa-tab-problem{display:inline-block;width:6px;height:6px;border-radius:50%;background:#f85149;margin-left:4px}
1458
+ .gufa-tab-problem[data-level="warning"]{background:#e5c07b}
1459
+ .gufa-success-banner{display:flex;align-items:center;gap:6px;flex:0 0 auto;margin:8px 10px 0;padding:5px 9px;border-radius:6px;font-size:12px;
1460
+ color:var(--dsw-alias-state-success-primary, #3fa66f);border:1px solid color-mix(in srgb, currentColor 45%, transparent);
1461
+ background:color-mix(in srgb, currentColor 12%, transparent)}
1462
+ .gufa-error-banner{display:flex;align-items:center;gap:8px;margin:8px 10px;padding:8px 10px;border-radius:8px;font-size:12px;
1463
+ border:1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 42%, transparent);
1464
+ background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary)}
1465
+ .gufa-spinner{width:14px;height:14px;border-radius:50%;border:2px solid var(--gufa-border-strong);border-top-color:var(--gufa-accent);
1466
+ animation:gufa-spin .7s linear infinite;flex:0 0 auto}
1467
+ @keyframes gufa-spin{to{transform:rotate(360deg)}}
1468
+ .gufa-modal-backdrop{position:absolute;inset:0;background:color-mix(in srgb, #000 42%, transparent);display:flex;align-items:center;
1469
+ justify-content:center;z-index:120;padding:20px calc(var(--gufa-dock-width,0px) + 20px) 20px 20px}
1470
+ .gufa-modal{width:min(460px,92vw);max-height:80vh;display:flex;flex-direction:column;border-radius:12px;overflow:hidden;
1471
+ border:1px solid var(--gufa-border-strong);background:var(--gufa-solid-overlay-bg);color:var(--gufa-solid-text, var(--dsw-alias-label-primary));
1472
+ box-shadow:0 18px 48px color-mix(in srgb, #000 34%, transparent)}
1473
+ .gufa-modal-head{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid var(--dsw-alias-border-l1);font-weight:600;font-size:13px}
1474
+ .gufa-modal-body{padding:14px;display:flex;flex-direction:column;gap:10px;overflow:auto;font-size:12.5px}
1475
+ .gufa-modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:10px 14px;border-top:1px solid var(--dsw-alias-border-l1)}
1476
+ .gufa-modal-close{margin-left:auto}
1477
+ .gufa-more-entry{display:flex;align-items:center;gap:8px;width:100%;height:30px;padding:0 8px;border:0;border-radius:6px;
1478
+ background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;cursor:pointer;text-align:left}
1479
+ .gufa-more-entry:hover{background:var(--dsw-alias-interactive-bg-hover, var(--gufa-hover))}
1480
+ .gufa-more-entry-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1481
+ .gufa-menu{position:absolute;z-index:2147483000;min-width:150px;padding:5px;border-radius:9px;border:1px solid var(--gufa-border-strong);
1482
+ background:var(--gufa-solid-overlay-bg);box-shadow:0 12px 30px color-mix(in srgb, #000 28%, transparent)}
1483
+ .gufa-menu-item{display:flex;align-items:center;gap:8px;width:100%;height:28px;padding:0 9px;border:0;border-radius:6px;
1484
+ background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font-size:12px;text-align:left;white-space:nowrap}
1485
+ .gufa-menu-item:hover{background:var(--gufa-hover)}
1486
+ .gufa-menu-item[data-danger]{color:var(--dsw-alias-state-error-primary)}
1487
+ .gufa-scroll::-webkit-scrollbar{width:9px;height:9px}
1488
+ .gufa-scroll::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--gufa-text) 22%, transparent);border-radius:6px;border:2px solid transparent;background-clip:content-box}
1489
+ .gufa-scroll::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb, var(--gufa-text) 34%, transparent);background-clip:content-box;border:2px solid transparent}
1490
+ .gufa-scroll::-webkit-scrollbar-track{background:transparent}
1491
+ /* 皮肤背景模式:各面板半透明,让右侧人像与渐变透出来(背景层在覆盖层内部,不会露出 DSH 原界面) */
1492
+ .gufa-root[data-backdrop="on"] .gufa-titlebar{background:color-mix(in srgb, var(--gufa-layer) 76%, transparent)}
1493
+ .gufa-root[data-backdrop="on"] .gufa-activity{background:color-mix(in srgb, var(--gufa-layer) 58%, transparent)}
1494
+ .gufa-root[data-backdrop="on"] .gufa-sidepanel{background:color-mix(in srgb, var(--gufa-bg) 72%, transparent)}
1495
+ .gufa-root[data-backdrop="on"] .gufa-main{background:color-mix(in srgb, var(--gufa-bg) 58%, transparent)}
1496
+ .gufa-root[data-backdrop="on"] .gufa-editor-area{background:color-mix(in srgb, var(--gufa-bg) 46%, transparent)}
1497
+ .gufa-root[data-backdrop="on"] .gufa-tabs{background:color-mix(in srgb, var(--gufa-layer) 64%, transparent)}
1498
+ .gufa-root[data-backdrop="on"] .gufa-chatpane{background:color-mix(in srgb, var(--gufa-bg) 46%, transparent)}
1499
+ .gufa-root[data-backdrop="on"] .gufa-dock-host{background:transparent}
1500
+ .gufa-root[data-backdrop="on"] .gufa-chatdock{background:transparent}
1501
+ .gufa-root[data-backdrop="on"] .gufa-statusbar{background:color-mix(in srgb, var(--gufa-layer) 70%, transparent)}
1502
+ .gufa-root[data-backdrop="on"] .gufa-dock-toolbar{background:color-mix(in srgb, var(--gufa-layer) 62%, transparent)}
1503
+ .gufa-root[data-backdrop="on"] .gufa-empty{background:transparent}
1504
+ /* 皮肤背景模式下:编辑区保持半透明(Monaco 主题背景是实色,会盖住背景层,这里覆盖成透明) */
1505
+ .gufa-root[data-backdrop="on"] .gufa-editor-host{background:transparent}
1506
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .monaco-editor,
1507
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .monaco-editor .margin,
1508
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .monaco-editor-background,
1509
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .monaco-diff-editor .editor.original,
1510
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .monaco-diff-editor .editor.modified{background:transparent!important}
1511
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .gufa-empty,
1512
+ .gufa-root[data-backdrop="on"] .gufa-editor-host .gufa-editor-fallback{background:transparent!important}
1513
+ .gufa-chatdock{display:flex;flex-direction:column;height:100%;min-height:0;min-width:0;width:100%;position:relative}
1514
+ .gufa-dock-sessions{position:absolute;top:34px;right:calc(100% + 10px);z-index:60;min-width:250px;max-width:320px;max-height:min(380px,60vh);
1515
+ display:flex;flex-direction:column;padding:5px;border-radius:10px;border:1px solid var(--gufa-border-strong);
1516
+ background:var(--gufa-solid-overlay-bg);box-shadow:0 14px 36px color-mix(in srgb, #000 30%, transparent)}
1517
+ .gufa-dock-session{display:flex;align-items:center;gap:8px;width:100%;min-width:0;height:30px;padding:0 9px;border:0;border-radius:6px;
1518
+ background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font-size:12px;text-align:left;white-space:nowrap}
1519
+ .gufa-dock-session:hover{background:var(--gufa-hover)}
1520
+ .gufa-dock-session[data-current]{background:var(--gufa-active)}
1521
+ .gufa-dock-session-title{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis}
1522
+ .gufa-dock-session-when{flex:0 0 auto;font-size:10.5px;color:var(--gufa-text-dim)}
1523
+ .gufa-dock-sep{height:1px;margin:4px 6px;background:var(--gufa-border)}
1524
+ .gufa-dock-toolbar{display:flex;align-items:center;gap:6px;height:32px;padding:0 6px 0 10px;flex:0 0 auto;
1525
+ border-bottom:1px solid var(--gufa-border);background:var(--gufa-layer)}
1526
+ .gufa-dock-title{display:flex;align-items:center;gap:6px;min-width:0;flex:0 1 auto;font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1527
+ .gufa-dock-sub{font-size:10.5px;font-weight:400;color:var(--gufa-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1528
+ .gufa-dock-fill{flex:1 1 auto}
1529
+ .gufa-dock-live{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-success-primary);flex:0 0 auto;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary) 20%, transparent)}
1530
+ .gufa-dock-host{flex:1 1 auto;min-height:0;min-width:0;position:relative;background:var(--gufa-bg);display:flex;flex-direction:column}
1531
+ .gufa-dock-fallback{display:flex;flex-direction:column;height:100%;min-height:0}
1532
+ .gufa-dock-notice{display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;color:var(--gufa-text-dim);border-bottom:1px solid var(--gufa-border);background:var(--gufa-layer)}
1533
+ .gufa-dock-missing{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:10px}
1534
+ [data-gufa-dock="1"] [class*="headerCorner"]{display:none!important}
1535
+ [data-gufa-dock="1"] [class*="widthHandle"]{display:none!important}
1536
+ [data-gufa-dock="1"]{box-sizing:border-box!important;border-left:1px solid var(--dsw-alias-border-l1)!important;
1537
+ box-shadow:-10px 0 28px color-mix(in srgb, #000 14%, transparent)!important;border-radius:0!important}
1538
+ .gufa-footer-action{display:flex;align-items:center;gap:8px;width:100%;min-width:0;height:32px;padding:0 10px;border:0;border-radius:8px;
1539
+ background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font-size:12.5px;white-space:nowrap}
1540
+ .gufa-footer-action:hover{background:var(--dsw-alias-bg-layer-2)}
1541
+ .gufa-footer-action-label{overflow:hidden;text-overflow:ellipsis}
1542
+ .gufa-panel-icon{border-radius:6px}
1543
+ `
1544
+
1545
+ /** 注入插件样式(幂等;unmount 时移除)。 */
1546
+ G.injectStyles = function injectStyles() {
1547
+ const id = 'gufa-code/styles'
1548
+ if (document.querySelector(`style[data-gufa="${id}"]`) !== null) return () => {}
1549
+ const style = document.createElement('style')
1550
+ style.dataset.gufa = id
1551
+ style.textContent = G.CSS
1552
+ document.head.append(style)
1553
+ return () => style.remove()
1554
+ }
1555
+
1556
+ /* ── 基础控件(React 元素,无第三方依赖) ───────────────────────────────── */
1557
+ /** 小图标按钮。 */
1558
+ G.IconButton = function IconButton(props) {
1559
+ return h(
1560
+ 'button',
1561
+ {
1562
+ type: 'button',
1563
+ className: G.cls('gufa-iconbtn', props.className),
1564
+ title: props.title,
1565
+ 'aria-label': props.title,
1566
+ disabled: props.disabled,
1567
+ onClick: props.onClick,
1568
+ style: props.style,
1569
+ },
1570
+ props.children,
1571
+ )
1572
+ }
1573
+
1574
+ /** 常规按钮。 */
1575
+ G.Button = function Button(props) {
1576
+ return h(
1577
+ 'button',
1578
+ {
1579
+ type: 'button',
1580
+ className: G.cls('gufa-btn', props.className),
1581
+ 'data-primary': props.primary ? '' : undefined,
1582
+ disabled: props.disabled,
1583
+ title: props.title,
1584
+ onClick: props.onClick,
1585
+ style: props.style,
1586
+ },
1587
+ props.icon ? h(G.Icon, { name: props.icon, size: 14 }) : null,
1588
+ props.children,
1589
+ )
1590
+ }
1591
+
1592
+ G.Spinner = function Spinner() {
1593
+ return h('span', { className: 'gufa-spinner', role: 'status', 'aria-label': G.t('common.loading') })
1594
+ }
1595
+
1596
+ /** 空状态。 */
1597
+ G.Empty = function Empty(props) {
1598
+ return h(
1599
+ 'div',
1600
+ { className: 'gufa-empty' },
1601
+ props.icon === 'seal' ? h(G.Seal, { className: 'gufa-seal' }) : props.icon ? h(G.Icon, { name: props.icon, size: 30 }) : null,
1602
+ props.title ? h('div', { className: 'gufa-empty-title' }, props.title) : null,
1603
+ props.hint ? h('div', { className: 'gufa-empty-hint' }, props.hint) : null,
1604
+ props.children ?? null,
1605
+ )
1606
+ }
1607
+
1608
+ /** 面板小标题。 */
1609
+ G.SectionHeader = function SectionHeader(props) {
1610
+ return h(
1611
+ 'div',
1612
+ { className: 'gufa-panel-head' },
1613
+ h('span', { className: 'gufa-panel-head-title' }, props.title),
1614
+ props.children ?? null,
1615
+ )
1616
+ }
1617
+
1618
+ /** 弹窗(Escape 关闭、焦点管理尽量贴近 Harness 风格)。 */
1619
+ G.Modal = function Modal(props) {
1620
+ const [value, setValue] = useState(props.initialValue ?? '')
1621
+ const inputRef = useRef(null)
1622
+ useLayoutEffect(() => {
1623
+ if (inputRef.current !== null) {
1624
+ inputRef.current.focus()
1625
+ if (typeof inputRef.current.select === 'function') inputRef.current.select()
1626
+ }
1627
+ }, [])
1628
+ useEffect(() => {
1629
+ const onKey = (event) => {
1630
+ if (event.key === 'Escape') {
1631
+ event.stopPropagation()
1632
+ props.onCancel()
1633
+ }
1634
+ }
1635
+ window.addEventListener('keydown', onKey, true)
1636
+ return () => window.removeEventListener('keydown', onKey, true)
1637
+ }, [value, props])
1638
+ return h(
1639
+ 'div',
1640
+ { className: 'gufa-modal-backdrop', onMouseDown: (event) => { if (event.target === event.currentTarget) props.onCancel() } },
1641
+ h(
1642
+ 'div',
1643
+ { className: 'gufa-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': props.title },
1644
+ h(
1645
+ 'div',
1646
+ { className: 'gufa-modal-head' },
1647
+ props.title,
1648
+ h(G.IconButton, { className: 'gufa-modal-close', title: G.t('common.close'), onClick: props.onCancel }, h(G.Icon, { name: 'close', size: 14 })),
1649
+ ),
1650
+ h(
1651
+ 'div',
1652
+ { className: 'gufa-modal-body' },
1653
+ props.description ? h('div', { className: 'gufa-hint' }, props.description) : null,
1654
+ props.children?.({ value, setValue, inputRef }) ??
1655
+ (props.multiline
1656
+ ? h('textarea', {
1657
+ className: 'gufa-textarea',
1658
+ ref: inputRef,
1659
+ value,
1660
+ placeholder: props.placeholder,
1661
+ onChange: (event) => setValue(event.target.value),
1662
+ })
1663
+ : h('input', {
1664
+ className: 'gufa-input',
1665
+ ref: inputRef,
1666
+ value,
1667
+ placeholder: props.placeholder,
1668
+ onChange: (event) => setValue(event.target.value),
1669
+ onKeyDown: (event) => {
1670
+ if (event.key === 'Enter' && props.onSubmit !== undefined) {
1671
+ event.preventDefault()
1672
+ props.onSubmit(value)
1673
+ }
1674
+ },
1675
+ })),
1676
+ props.error ? h('div', { className: 'gufa-error-banner', style: { margin: 0 } }, props.error) : null,
1677
+ ),
1678
+ h(
1679
+ 'div',
1680
+ { className: 'gufa-modal-foot' },
1681
+ h(G.Button, { onClick: props.onCancel }, G.t('common.cancel')),
1682
+ h(G.Button, { primary: true, disabled: props.pending, onClick: () => props.onSubmit(value) }, props.pending ? h(G.Spinner) : null, props.submitLabel ?? G.t('common.confirm')),
1683
+ ),
1684
+ ),
1685
+ )
1686
+ }