@my-life-buddies/cli 0.13.3 → 0.15.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 (193) hide show
  1. package/README.md +74 -22
  2. package/dist/bin/buddy.js +29 -10
  3. package/dist/bin/buddy.js.map +2 -2
  4. package/dist/bin/core.js +1480 -611
  5. package/dist/bin/core.js.map +4 -4
  6. package/dist/bin/preview.js +324 -27
  7. package/dist/bin/preview.js.map +4 -4
  8. package/dist/web/app.css +65 -6
  9. package/dist/web/app.js +103 -39
  10. package/dist/web/diagnostics.js +184 -0
  11. package/dist/web/index.html +34 -34
  12. package/dist/web/widgets.css +59 -28
  13. package/dist/web/widgets.js +230 -76
  14. package/package.json +3 -3
  15. package/resources/README.md +6 -50
  16. package/resources/agent-template/package-lock.json +4 -4
  17. package/resources/agent-template/package.json +2 -2
  18. package/resources/buddy-creator/INSTALL.md +0 -25
  19. package/resources/buddy-creator/PATCHES.md +0 -53
  20. package/resources/buddy-creator/SKILL.md +0 -76
  21. package/resources/buddy-creator/THIRD_PARTY_NOTICES.md +0 -154
  22. package/resources/buddy-creator/agents/openai.yaml +0 -4
  23. package/resources/buddy-creator/assets/licenses/bail.txt +0 -26
  24. package/resources/buddy-creator/assets/licenses/ccount.txt +0 -26
  25. package/resources/buddy-creator/assets/licenses/character-entities-html4.txt +0 -26
  26. package/resources/buddy-creator/assets/licenses/character-entities-legacy.txt +0 -26
  27. package/resources/buddy-creator/assets/licenses/character-entities.txt +0 -26
  28. package/resources/buddy-creator/assets/licenses/character-reference-invalid.txt +0 -26
  29. package/resources/buddy-creator/assets/licenses/comma-separated-tokens.txt +0 -26
  30. package/resources/buddy-creator/assets/licenses/debug.txt +0 -24
  31. package/resources/buddy-creator/assets/licenses/decode-named-character-reference.txt +0 -26
  32. package/resources/buddy-creator/assets/licenses/dequal.txt +0 -25
  33. package/resources/buddy-creator/assets/licenses/devlop.txt +0 -26
  34. package/resources/buddy-creator/assets/licenses/escape-string-regexp.txt +0 -13
  35. package/resources/buddy-creator/assets/licenses/estree-util-is-identifier-name.txt +0 -26
  36. package/resources/buddy-creator/assets/licenses/extend.txt +0 -27
  37. package/resources/buddy-creator/assets/licenses/hast-util-to-jsx-runtime.txt +0 -26
  38. package/resources/buddy-creator/assets/licenses/hast-util-whitespace.txt +0 -26
  39. package/resources/buddy-creator/assets/licenses/html-url-attributes.txt +0 -25
  40. package/resources/buddy-creator/assets/licenses/inline-style-parser.txt +0 -13
  41. package/resources/buddy-creator/assets/licenses/is-alphabetical.txt +0 -26
  42. package/resources/buddy-creator/assets/licenses/is-alphanumerical.txt +0 -26
  43. package/resources/buddy-creator/assets/licenses/is-decimal.txt +0 -26
  44. package/resources/buddy-creator/assets/licenses/is-hexadecimal.txt +0 -26
  45. package/resources/buddy-creator/assets/licenses/is-plain-obj.txt +0 -13
  46. package/resources/buddy-creator/assets/licenses/longest-streak.txt +0 -26
  47. package/resources/buddy-creator/assets/licenses/markdown-table.txt +0 -26
  48. package/resources/buddy-creator/assets/licenses/mdast-util-find-and-replace.txt +0 -26
  49. package/resources/buddy-creator/assets/licenses/mdast-util-from-markdown.txt +0 -26
  50. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-autolink-literal.txt +0 -26
  51. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-footnote.txt +0 -26
  52. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-strikethrough.txt +0 -26
  53. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-table.txt +0 -26
  54. package/resources/buddy-creator/assets/licenses/mdast-util-gfm-task-list-item.txt +0 -26
  55. package/resources/buddy-creator/assets/licenses/mdast-util-gfm.txt +0 -26
  56. package/resources/buddy-creator/assets/licenses/mdast-util-mdx-expression.txt +0 -26
  57. package/resources/buddy-creator/assets/licenses/mdast-util-mdx-jsx.txt +0 -26
  58. package/resources/buddy-creator/assets/licenses/mdast-util-mdxjs-esm.txt +0 -26
  59. package/resources/buddy-creator/assets/licenses/mdast-util-phrasing.txt +0 -27
  60. package/resources/buddy-creator/assets/licenses/mdast-util-to-hast.txt +0 -26
  61. package/resources/buddy-creator/assets/licenses/mdast-util-to-markdown.txt +0 -26
  62. package/resources/buddy-creator/assets/licenses/mdast-util-to-string.txt +0 -26
  63. package/resources/buddy-creator/assets/licenses/micromark-core-commonmark.txt +0 -26
  64. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-autolink-literal.txt +0 -26
  65. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-footnote.txt +0 -26
  66. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-strikethrough.txt +0 -26
  67. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-table.txt +0 -26
  68. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-tagfilter.txt +0 -26
  69. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-task-list-item.txt +0 -26
  70. package/resources/buddy-creator/assets/licenses/micromark-extension-gfm.txt +0 -26
  71. package/resources/buddy-creator/assets/licenses/micromark-factory-destination.txt +0 -26
  72. package/resources/buddy-creator/assets/licenses/micromark-factory-label.txt +0 -26
  73. package/resources/buddy-creator/assets/licenses/micromark-factory-space.txt +0 -26
  74. package/resources/buddy-creator/assets/licenses/micromark-factory-title.txt +0 -26
  75. package/resources/buddy-creator/assets/licenses/micromark-factory-whitespace.txt +0 -26
  76. package/resources/buddy-creator/assets/licenses/micromark-util-character.txt +0 -26
  77. package/resources/buddy-creator/assets/licenses/micromark-util-chunked.txt +0 -26
  78. package/resources/buddy-creator/assets/licenses/micromark-util-classify-character.txt +0 -26
  79. package/resources/buddy-creator/assets/licenses/micromark-util-combine-extensions.txt +0 -26
  80. package/resources/buddy-creator/assets/licenses/micromark-util-decode-numeric-character-reference.txt +0 -26
  81. package/resources/buddy-creator/assets/licenses/micromark-util-decode-string.txt +0 -26
  82. package/resources/buddy-creator/assets/licenses/micromark-util-encode.txt +0 -26
  83. package/resources/buddy-creator/assets/licenses/micromark-util-html-tag-name.txt +0 -26
  84. package/resources/buddy-creator/assets/licenses/micromark-util-normalize-identifier.txt +0 -26
  85. package/resources/buddy-creator/assets/licenses/micromark-util-resolve-all.txt +0 -26
  86. package/resources/buddy-creator/assets/licenses/micromark-util-sanitize-uri.txt +0 -26
  87. package/resources/buddy-creator/assets/licenses/micromark-util-subtokenize.txt +0 -26
  88. package/resources/buddy-creator/assets/licenses/micromark-util-symbol.txt +0 -26
  89. package/resources/buddy-creator/assets/licenses/micromark-util-types.txt +0 -26
  90. package/resources/buddy-creator/assets/licenses/micromark.txt +0 -26
  91. package/resources/buddy-creator/assets/licenses/ms.txt +0 -25
  92. package/resources/buddy-creator/assets/licenses/parse-entities.txt +0 -26
  93. package/resources/buddy-creator/assets/licenses/phosphor-icons--react.txt +0 -25
  94. package/resources/buddy-creator/assets/licenses/property-information.txt +0 -26
  95. package/resources/buddy-creator/assets/licenses/react-dom.txt +0 -25
  96. package/resources/buddy-creator/assets/licenses/react-markdown.txt +0 -25
  97. package/resources/buddy-creator/assets/licenses/react.txt +0 -25
  98. package/resources/buddy-creator/assets/licenses/remark-gfm.txt +0 -26
  99. package/resources/buddy-creator/assets/licenses/remark-parse.txt +0 -25
  100. package/resources/buddy-creator/assets/licenses/remark-rehype.txt +0 -26
  101. package/resources/buddy-creator/assets/licenses/remark-stringify.txt +0 -25
  102. package/resources/buddy-creator/assets/licenses/scheduler.txt +0 -25
  103. package/resources/buddy-creator/assets/licenses/space-separated-tokens.txt +0 -26
  104. package/resources/buddy-creator/assets/licenses/stringify-entities.txt +0 -26
  105. package/resources/buddy-creator/assets/licenses/style-to-js.txt +0 -26
  106. package/resources/buddy-creator/assets/licenses/style-to-object.txt +0 -26
  107. package/resources/buddy-creator/assets/licenses/trim-lines.txt +0 -26
  108. package/resources/buddy-creator/assets/licenses/trough.txt +0 -25
  109. package/resources/buddy-creator/assets/licenses/types--debug.txt +0 -25
  110. package/resources/buddy-creator/assets/licenses/types--estree-jsx.txt +0 -25
  111. package/resources/buddy-creator/assets/licenses/types--estree.txt +0 -25
  112. package/resources/buddy-creator/assets/licenses/types--hast.txt +0 -25
  113. package/resources/buddy-creator/assets/licenses/types--mdast.txt +0 -25
  114. package/resources/buddy-creator/assets/licenses/types--ms.txt +0 -25
  115. package/resources/buddy-creator/assets/licenses/types--unist.txt +0 -25
  116. package/resources/buddy-creator/assets/licenses/ungap--structured-clone.txt +0 -19
  117. package/resources/buddy-creator/assets/licenses/unified.txt +0 -25
  118. package/resources/buddy-creator/assets/licenses/unist-util-is.txt +0 -26
  119. package/resources/buddy-creator/assets/licenses/unist-util-position.txt +0 -26
  120. package/resources/buddy-creator/assets/licenses/unist-util-stringify-position.txt +0 -26
  121. package/resources/buddy-creator/assets/licenses/unist-util-visit-parents.txt +0 -26
  122. package/resources/buddy-creator/assets/licenses/unist-util-visit.txt +0 -26
  123. package/resources/buddy-creator/assets/licenses/vfile-message.txt +0 -26
  124. package/resources/buddy-creator/assets/licenses/vfile.txt +0 -25
  125. package/resources/buddy-creator/assets/licenses/zwitch.txt +0 -26
  126. package/resources/buddy-creator/assets/preview/assets/index-B5VmkoSx.js +0 -39
  127. package/resources/buddy-creator/assets/preview/assets/index-DWKP6b-3.css +0 -4
  128. package/resources/buddy-creator/assets/preview/brand-logo.png +0 -0
  129. package/resources/buddy-creator/assets/preview/brand.css +0 -100
  130. package/resources/buddy-creator/assets/preview/index.html +0 -7
  131. package/resources/buddy-creator/assets/preview/reader.css +0 -165
  132. package/resources/buddy-creator/assets/preview/reader.js +0 -135
  133. package/resources/buddy-creator/assets/preview/widgets.css +0 -1
  134. package/resources/buddy-creator/assets/preview/widgets.js +0 -168
  135. package/resources/buddy-creator/assets/widget-reference/conversation.png +0 -0
  136. package/resources/buddy-creator/assets/widget-reference/expanded.png +0 -0
  137. package/resources/buddy-creator/assets/widget-v2/app.js +0 -62
  138. package/resources/buddy-creator/assets/widget-v2/style.css +0 -1
  139. package/resources/buddy-creator/assets/widget-v3/app.js +0 -65
  140. package/resources/buddy-creator/assets/widget-v3/style.css +0 -103
  141. package/resources/buddy-creator/assets/widget-v4/app.js +0 -66
  142. package/resources/buddy-creator/assets/widget-v4/style.css +0 -187
  143. package/resources/buddy-creator/references/artifact-schema.md +0 -70
  144. package/resources/buddy-creator/references/catalog.json +0 -401
  145. package/resources/buddy-creator/references/definition.md +0 -24
  146. package/resources/buddy-creator/references/dialogue-examples.md +0 -133
  147. package/resources/buddy-creator/references/host-guide.md +0 -219
  148. package/resources/buddy-creator/references/interview.md +0 -235
  149. package/resources/buddy-creator/references/knowledge.md +0 -40
  150. package/resources/buddy-creator/references/methods.md +0 -77
  151. package/resources/buddy-creator/references/opening.md +0 -14
  152. package/resources/buddy-creator/references/preview-panel.md +0 -66
  153. package/resources/buddy-creator/references/recovery.md +0 -46
  154. package/resources/buddy-creator/references/service.md +0 -76
  155. package/resources/buddy-creator/references/widgets-protocol.md +0 -83
  156. package/resources/buddy-creator/references/widgets.md +0 -99
  157. package/resources/buddy-creator/scripts/buddy.py +0 -108
  158. package/resources/buddy-creator/scripts/buddy_client.py +0 -67
  159. package/resources/buddy-creator/scripts/buddy_core.py +0 -865
  160. package/resources/buddy-creator/scripts/completion.py +0 -296
  161. package/resources/buddy-creator/scripts/interview.py +0 -215
  162. package/resources/buddy-creator/scripts/preview.py +0 -617
  163. package/resources/buddy-creator/scripts/preview_panel.py +0 -80
  164. package/resources/buddy-creator/scripts/sources.py +0 -239
  165. package/resources/buddy-creator/scripts/widget_render.py +0 -66
  166. package/resources/buddy-creator/scripts/widget_render_v1.py +0 -176
  167. package/resources/buddy-creator/scripts/widget_render_v2.py +0 -98
  168. package/resources/buddy-creator/scripts/widget_render_v3.py +0 -86
  169. package/resources/buddy-creator/scripts/widget_render_v4.py +0 -121
  170. package/resources/buddy-creator/scripts/widgets.py +0 -227
  171. package/resources/buddy-creator/version.json +0 -8
  172. package/resources/buddy-creator.manifest.json +0 -791
  173. package/resources/dada-object-avatar/SKILL.md +0 -37
  174. package/resources/dada-object-avatar/agents/openai.yaml +0 -4
  175. package/resources/dada-object-avatar/assets/examples/cat.png +0 -0
  176. package/resources/dada-object-avatar/assets/examples/cooking.png +0 -0
  177. package/resources/dada-object-avatar/assets/examples/divination.png +0 -0
  178. package/resources/dada-object-avatar/assets/examples/doctor.png +0 -0
  179. package/resources/dada-object-avatar/assets/examples/relationship.png +0 -0
  180. package/resources/dada-object-avatar/assets/examples/work.png +0 -0
  181. package/resources/dada-object-avatar/assets/preview.png +0 -0
  182. package/resources/dada-object-avatar/references/evaluation-rubric.md +0 -22
  183. package/resources/dada-object-avatar/references/examples.json +0 -8
  184. package/resources/dada-object-avatar/references/prompt-template.md +0 -27
  185. package/resources/dada-object-avatar/references/style-system.md +0 -54
  186. package/resources/dada-object-avatar/references/subject-design.md +0 -23
  187. package/resources/dada-object-avatar/references/training-guide.md +0 -14
  188. package/resources/dada-object-avatar/references/training-log.jsonl +0 -0
  189. package/resources/dada-object-avatar/references/training-record.schema.json +0 -20
  190. package/resources/dada-object-avatar/scripts/choose_gaze.py +0 -36
  191. package/resources/dada-object-avatar/scripts/record_example.py +0 -22
  192. package/resources/dada-object-avatar/scripts/validate_png.py +0 -52
  193. package/resources/dada-object-avatar.manifest.json +0 -85
@@ -0,0 +1,184 @@
1
+ (() => {
2
+ "use strict";
3
+ window.createPreviewDiagnostics = ({ node, formatTime, formatDuration, toast, showError }) => {
4
+ const $ = id => document.getElementById(id);
5
+ const models = $("model-records"), logs = $("protocol-log");
6
+ const modelConversation = $("models-conversation"), logConversation = $("logs-conversation");
7
+ const kind = $("models-kind"), level = $("logs-level");
8
+ const modelSearch = $("models-search"), logSearch = $("logs-search");
9
+ let snapshot = {}, conversation = "", visibleLogs = [];
10
+ const cached = new Map(), expanded = new Map();
11
+ const object = value => value && typeof value === "object" && !Array.isArray(value) ? value : {};
12
+ const json = value => typeof value === "string" ? value : JSON.stringify(value, null, 2);
13
+ const number = value => typeof value === "number" && Number.isFinite(value) && value >= 0;
14
+ const metricHelp = {
15
+ "模型响应耗时": "本次模型请求发出至响应结束。",
16
+ "模型首字耗时": "本次模型请求发出至首段输出。",
17
+ "结束原因": "模型停止原因;toolUse 表示请求调用工具。",
18
+ "输入 Token": "本次调用的输入 Token 数。",
19
+ "输出 Token": "本次调用的输出 Token 数。",
20
+ "缓存读取": "本次调用读取缓存的 Token 数。",
21
+ "缓存写入": "本次调用写入缓存的 Token 数。",
22
+ "总 Token": "本次调用上报的 Token 总数。",
23
+ };
24
+ function section(label, value, key) {
25
+ const details = node("details", { class: "diagnostic-section", "data-detail": key });
26
+ details.append(node("summary", {}, label));
27
+ details.append(node("pre", {}, json(value)));
28
+ return details;
29
+ }
30
+ function metrics(pairs) {
31
+ const list = node("dl", { class: "diagnostic-metrics" });
32
+ for (const [label, value] of pairs) {
33
+ const item = node("div");
34
+ const detail = node("dd", {}, value);
35
+ if (metricHelp[label]) {
36
+ detail.append(node("p", { class: "metric-help" }, metricHelp[label]));
37
+ }
38
+ item.append(node("dt", {}, label), detail);
39
+ list.append(item);
40
+ }
41
+ return list;
42
+ }
43
+ async function copy(text) {
44
+ try { await navigator.clipboard.writeText(text); toast("日志已复制"); }
45
+ catch { showError(new Error("浏览器未允许复制,请展开原始日志后手动选择")); }
46
+ }
47
+ function describe(record) {
48
+ const data = object(record.data), payload = object(data.payload);
49
+ if (record.kind === "request") {
50
+ const messages = payload.messages || payload.input;
51
+ const count = Array.isArray(messages) ? `${messages.length} 条上下文` : "请求上下文";
52
+ return `${typeof payload.model === "string" ? payload.model : "模型未上报"} · ${count}${Array.isArray(payload.tools) ? ` · ${payload.tools.length} 个工具` : ""}`;
53
+ }
54
+ const content = Array.isArray(data.content) ? data.content : [];
55
+ const texts = content.filter(item => item?.type === "text").map(item => item.text).filter(text => typeof text === "string");
56
+ const tools = content.filter(item => item?.type === "toolCall").map(item => item.name).filter(name => typeof name === "string");
57
+ return typeof data.error === "string" ? data.error : tools.length ? `请求工具:${tools.join("、")}` : texts.join(" ") || record.title;
58
+ }
59
+ function row(record, model) {
60
+ const data = object(record.data);
61
+ const details = node("details", { class: "diagnostic-entry", "data-record": record.id, "data-detail": "entry" });
62
+ details.dataset.level = data.error || data.stopReason === "error" ? "error" : record.level;
63
+ const summary = node("summary");
64
+ summary.append(node("span", { class: "diagnostic-badge", "data-kind": record.kind }, model ? record.kind === "request" ? "请求" : "响应" : record.level.toUpperCase()));
65
+ const heading = node("span", { class: "diagnostic-heading" });
66
+ heading.append(node("strong", {}, model ? record.kind === "request" ? "模型请求" : "模型响应" : record.title));
67
+ if (model) heading.append(node("span", { class: "diagnostic-preview" }, describe(record)));
68
+ summary.append(heading, node("time", { dateTime: record.timestamp, title: record.timestamp }, formatTime(record.timestamp)));
69
+ details.append(summary);
70
+ const body = node("div", { class: "diagnostic-body" });
71
+ const meta = node("div", { class: "diagnostic-meta" });
72
+ meta.append(node("span", {}, `${record.convoKey || "未标注会话"} · ${record.source}`));
73
+ const button = node("button", { type: "button", class: "text-button" }, "复制原始日志");
74
+ button.addEventListener("click", () => void copy(record.raw));
75
+ meta.append(button); body.append(meta);
76
+ if (record.truncated || (model && !record.data)) {
77
+ body.append(node("p", { class: "diagnostic-warning" }, record.truncated
78
+ ? "日志已截断,无法还原完整数据。请查看原始日志。"
79
+ : "尚未收到完整数据,或日志格式无法解析。已保留原始日志。"));
80
+ }
81
+ if (model && record.data) {
82
+ if (record.kind === "request") {
83
+ const payload = object(data.payload);
84
+ const messages = payload.messages ?? payload.input;
85
+ const system = payload.system ?? (Array.isArray(messages) ? messages.filter(item => ["system", "developer"].includes(item?.role)) : undefined);
86
+ if (system != null && (!Array.isArray(system) || system.length)) body.append(section("系统提示词", system, "system"));
87
+ if (messages != null) body.append(section(`请求上下文${Array.isArray(messages) ? ` · ${messages.length} 条` : ""}`, messages, "messages"));
88
+ if (payload.tools != null) body.append(section(`可用工具${Array.isArray(payload.tools) ? ` · ${payload.tools.length} 个` : ""}`, payload.tools, "tools"));
89
+ const { messages: _messages, input: _input, system: _system, tools: _tools, ...parameters } = payload;
90
+ body.append(section("模型与请求参数", parameters, "parameters"));
91
+ body.append(section("完整请求体", data.payload ?? data, "payload"));
92
+ } else {
93
+ const usage = object(data.usage);
94
+ body.append(metrics([
95
+ ["模型响应耗时", number(data.ms) ? formatDuration(data.ms) : "未上报"],
96
+ ["模型首字耗时", number(data.firstTokenMs) ? formatDuration(data.firstTokenMs) : "未上报"],
97
+ ["结束原因", typeof data.stopReason === "string" ? data.stopReason : "未上报"],
98
+ ]));
99
+ const tokenFields = [["输入 Token", "input"], ["输出 Token", "output"], ["缓存读取", "cacheRead"], ["缓存写入", "cacheWrite"], ["总 Token", "totalTokens"]];
100
+ body.append(metrics(tokenFields.map(([label, key]) => [label, number(usage[key]) ? String(usage[key]) : "未上报"])));
101
+ if (typeof data.error === "string" && data.error) body.append(node("p", { class: "diagnostic-warning" }, data.error));
102
+ const content = Array.isArray(data.content) ? data.content : [];
103
+ content.forEach((block, index) => {
104
+ if (!block || typeof block !== "object") return;
105
+ const label = block.type === "text" ? "响应正文" : block.type === "thinking" ? "思考内容(日志已提供)" : block.type === "toolCall" ? `工具调用请求 · ${block.name || "未命名"}` : `响应片段 · ${block.type || index + 1}`;
106
+ body.append(section(label, block.type === "text" ? block.text : block, `content-${index}`));
107
+ });
108
+ if (data.content != null && !content.length) body.append(section("响应内容", data.content, "content"));
109
+ if (data.usage != null) body.append(section("完整用量", data.usage, "usage"));
110
+ }
111
+ }
112
+ body.append(section("原始日志", record.raw, "raw"));
113
+ details.append(body);
114
+ return details;
115
+ }
116
+ function reconcile(container, records, model, emptyText) {
117
+ const prefix = model ? "model:" : "log:";
118
+ const wanted = new Set();
119
+ records.forEach((record, index) => {
120
+ const key = prefix + record.id;
121
+ wanted.add(key);
122
+ const signature = JSON.stringify(record);
123
+ let entry = cached.get(key);
124
+ if (!entry || entry.signature !== signature) {
125
+ if (entry) expanded.set(key, [...entry.node.querySelectorAll("details"), entry.node].filter(item => item.open).map(item => item.dataset.detail));
126
+ const element = row(record, model);
127
+ for (const item of [...element.querySelectorAll("details"), element]) item.open = (expanded.get(key) || []).includes(item.dataset.detail);
128
+ entry?.node.remove();
129
+ entry = { node: element, signature };
130
+ cached.set(key, entry);
131
+ }
132
+ if (container.children[index] !== entry.node) container.insertBefore(entry.node, container.children[index] || null);
133
+ });
134
+ for (const child of [...container.children]) if (!wanted.has(prefix + child.dataset.record)) child.remove();
135
+ if (!records.length) {
136
+ const empty = node("div", { class: "diagnostic-empty" });
137
+ empty.append(node("strong", {}, emptyText[0]), node("p", {}, emptyText[1]));
138
+ container.append(empty);
139
+ }
140
+ }
141
+ function updateConversations(records) {
142
+ const keys = [...new Set(records.map(record => record.convoKey).filter(Boolean))].sort();
143
+ if (conversation && !keys.includes(conversation)) keys.push(conversation);
144
+ for (const select of [modelConversation, logConversation]) {
145
+ if (document.activeElement === select) continue;
146
+ const signature = JSON.stringify(keys);
147
+ if (select.dataset.options !== signature) {
148
+ select.replaceChildren(node("option", { value: "" }, "全部会话"), ...keys.map(key => node("option", { value: key }, key)));
149
+ select.dataset.options = signature;
150
+ }
151
+ select.value = conversation;
152
+ }
153
+ }
154
+ function render() {
155
+ const runtime = snapshot.diagnostics?.records || [];
156
+ updateConversations(runtime);
157
+ const matches = (record, search) => (!conversation || record.convoKey === conversation) && (!search || record.raw.toLocaleLowerCase().includes(search.toLocaleLowerCase()));
158
+ const modelRecords = runtime.filter(record => record.kind === "request" || record.kind === "response");
159
+ const filteredModels = modelRecords.filter(record => (!kind.value || record.kind === kind.value) && matches(record, modelSearch.value.trim()));
160
+ reconcile(models, filteredModels, true, modelRecords.length
161
+ ? ["没有匹配的模型记录", "试试其他会话、类型或关键词。"]
162
+ : ["等待模型调用", "启动 DEV 并发送消息后,Runtime 提供的模型请求与响应会出现在这里。旧版本或未输出这些日志的 Runtime 可先查看运行日志。"]);
163
+ $("models-count").textContent = `${filteredModels.length} 条记录${snapshot.diagnostics?.dropped ? ` · 已淘汰 ${snapshot.diagnostics.dropped} 条较早日志` : ""}`;
164
+ const events = snapshot.events || [];
165
+ const deltas = events.filter(event => event.type === "run.output.delta").length;
166
+ const other = events.filter(event => event.type !== "run.output.delta" && (!snapshot.diagnostics || !/^agent\.(stdout|stderr)$/.test(event.type))).map(event => ({
167
+ id: event.eventId, timestamp: event.timestamp, source: event.channel, level: /failed|error|stderr/.test(event.type) ? "error" : "info", title: event.summary || event.type,
168
+ kind: "log", raw: `${event.timestamp} ${event.channel} ${event.type}${event.runId ? ` ${event.runId}` : ""}\n${event.summary || ""}`,
169
+ }));
170
+ const allLogs = [...runtime, ...other].sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
171
+ visibleLogs = allLogs.filter(record => (!level.value || record.level === level.value || level.value === "error" && (record.data?.error || record.data?.stopReason === "error")) && matches(record, logSearch.value.trim()));
172
+ reconcile(logs, visibleLogs, false, allLogs.length ? ["没有匹配的日志", "调整会话、级别或搜索条件后再试。"] : ["等待运行日志", "启动 DEV 后,这里会保留进程输出与连接事件。"]);
173
+ $("logs-count").textContent = `${visibleLogs.length} 条可见日志${deltas ? ` · 已折叠 ${deltas} 条文字分片事件` : ""}${snapshot.diagnostics?.dropped ? ` · 已淘汰 ${snapshot.diagnostics.dropped} 条较早日志` : ""}`;
174
+ const retained = new Set([...runtime.map(record => record.id), ...other.map(record => record.id)]);
175
+ for (const key of cached.keys()) if (!retained.has(key.slice(key.indexOf(":") + 1))) { cached.delete(key); expanded.delete(key); }
176
+ }
177
+ for (const select of [modelConversation, logConversation]) {
178
+ select.addEventListener("change", () => { conversation = select.value; render(); });
179
+ select.addEventListener("blur", render);
180
+ }
181
+ for (const input of [kind, level, modelSearch, logSearch]) input.addEventListener(input.type === "search" ? "input" : "change", render);
182
+ return { update(next) { snapshot = next; render(); }, copyLogs() { return copy(visibleLogs.map(record => record.raw).join("\n\n")); } };
183
+ };
184
+ })();
@@ -13,6 +13,7 @@
13
13
  <link rel="stylesheet" href="/assets/app.css">
14
14
  <script src="/assets/iphone-16-max.js" defer></script>
15
15
  <script src="/assets/widgets.js" defer></script>
16
+ <script src="/assets/diagnostics.js" defer></script>
16
17
  <script src="/assets/app.js" defer></script>
17
18
  <link rel="stylesheet" href="/assets/widgets.css">
18
19
  </head>
@@ -30,16 +31,13 @@
30
31
  <p class="sidebar-label">开发调试</p>
31
32
  <nav class="debug-tabs" role="tablist" aria-label="本地回合调试视图">
32
33
  <button id="tab-overview" role="tab" aria-selected="true" aria-controls="panel-overview" tabindex="0">
33
- <svg aria-hidden="true" viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg><span>概览</span>
34
+ <svg aria-hidden="true" viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg><span>运行概况</span>
34
35
  </button>
35
- <button id="tab-timeline" role="tab" aria-selected="false" aria-controls="panel-timeline" tabindex="-1">
36
- <svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 7v5l3.5 2"/></svg><span>执行时间线</span>
37
- </button>
38
- <button id="tab-context" role="tab" aria-selected="false" aria-controls="panel-context" tabindex="-1">
39
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 5h13v10H9l-4 4v-4H4Z"/><path d="M8 9h5M8 12h3M18 8h2v10h-7"/></svg><span>对话记录</span>
36
+ <button id="tab-models" role="tab" aria-selected="false" aria-controls="panel-models" tabindex="-1">
37
+ <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M4 5h16v14H4ZM8 9h8M8 13h5"/></svg><span>模型调用</span>
40
38
  </button>
41
39
  <button id="tab-protocol" role="tab" aria-selected="false" aria-controls="panel-protocol" tabindex="-1">
42
- <svg aria-hidden="true" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="m7.5 9 3 3-3 3M13 15h3.5"/></svg><span>协议日志</span>
40
+ <svg aria-hidden="true" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="14" rx="2"/><path d="m7.5 9 3 3-3 3M13 15h3.5"/></svg><span>运行日志</span>
43
41
  </button>
44
42
  </nav>
45
43
  </div>
@@ -85,6 +83,18 @@
85
83
 
86
84
  <main class="workspace" id="debug-content">
87
85
  <section class="debugger" aria-label="本地回合调试器">
86
+ <div class="debug-panels">
87
+ <section id="panel-models" class="debug-panel" role="tabpanel" aria-labelledby="tab-models" hidden>
88
+ <div class="section-heading"><div><h3>模型调用</h3></div></div>
89
+ <div class="diagnostic-filters">
90
+ <label>会话<select id="models-conversation" aria-label="模型会话"><option value="">全部会话</option></select></label>
91
+ <label>类型<select id="models-kind"><option value="">请求与响应</option><option value="request">模型请求</option><option value="response">模型响应</option></select></label>
92
+ <label class="diagnostic-search">搜索<input id="models-search" type="search" placeholder="搜索模型、工具或内容" autocomplete="off"></label>
93
+ </div>
94
+ <p class="diagnostic-note" id="models-count"></p>
95
+ <div id="model-records" class="diagnostic-records"></div>
96
+ </section>
97
+ <section id="panel-overview" class="debug-panel" role="tabpanel" aria-labelledby="tab-overview">
88
98
  <div class="run-strip">
89
99
  <div class="run-heading">
90
100
  <div class="run-identity">
@@ -93,7 +103,7 @@
93
103
  <h2 id="run-title">尚无本地回合</h2>
94
104
  <span class="status-pill" id="run-status" data-status="idle">等待消息</span>
95
105
  </div>
96
- <details class="run-reference" id="run-reference" hidden><summary>回合标识</summary>
106
+ <details class="run-reference" id="run-reference" hidden><summary>本地发送回执 ID</summary>
97
107
  <span id="run-id"></span>
98
108
  <button type="button" class="text-button" id="copy-run-id" disabled>复制 ID</button>
99
109
  </details>
@@ -112,28 +122,25 @@
112
122
  </div>
113
123
 
114
124
  <dl class="run-metrics" aria-label="本地回合指标">
115
- <div><dt>接收</dt><dd id="metric-accept">未记录</dd></div>
116
- <div><dt>首段文字</dt><dd id="metric-first">未记录</dd></div>
117
- <div><dt>完整耗时</dt><dd id="metric-response">未记录</dd></div>
118
- <div><dt>会话消息</dt><dd id="metric-messages">0</dd></div>
125
+ <div><dt>消息被接收</dt><dd><span id="metric-accept">未记录</span><p class="metric-help">Preview 发起发送至平台确认接收。</p></dd></div>
126
+ <div><dt>首字耗时</dt><dd><span id="metric-first">未记录</span><p class="metric-help">Preview 发起发送至收到首段文字。</p></dd></div>
127
+ <div><dt>回复结束</dt><dd><span id="metric-response">未记录</span><p class="metric-help">Preview 发起发送至观察到回复结束。</p></dd></div>
128
+ <div><dt>会话消息</dt><dd><span id="metric-messages">0</span><p class="metric-help">当前会话的消息总数,包含历史消息。</p></dd></div>
119
129
  </dl>
120
130
 
121
131
  </div>
122
-
123
- <div class="debug-panels">
124
- <section id="panel-overview" class="debug-panel" role="tabpanel" aria-labelledby="tab-overview">
125
132
  <div class="overview-grid">
126
133
  <article class="run-summary">
127
134
  <div class="section-heading">
128
- <div><h3>执行过程</h3></div>
135
+ <div><h3>回复观察</h3></div>
129
136
  </div>
130
137
  <div class="empty-state" id="overview-empty">
131
138
  <h4>发一条消息,开始记录</h4>
132
- <p>右侧发送消息后,这里显示本次执行过程。</p>
139
+ <p>右侧发送消息后,这里显示消息发送与回复的观察结果。</p>
133
140
  </div>
134
141
  <div id="overview-content" hidden>
135
142
  <ol class="run-progress" id="run-progress" aria-label="回合执行进度"></ol>
136
- <div class="waterfall" id="waterfall" aria-label="本地回合耗时分布"></div>
143
+ <div class="waterfall" id="waterfall" aria-label="消息时间节点,均从发起发送计时"></div>
137
144
  <p class="run-summary-copy" id="run-summary-copy"></p>
138
145
  </div>
139
146
  </article>
@@ -148,28 +155,21 @@
148
155
  </dl>
149
156
  </article>
150
157
  </div>
151
- </section>
152
-
153
- <section id="panel-timeline" class="debug-panel" role="tabpanel" aria-labelledby="tab-timeline" hidden>
154
- <div class="section-heading">
155
- <div><h3>执行时间线</h3><p>查看当前回合的事件。</p></div>
156
- </div>
157
- <ol class="timeline" id="timeline-list"></ol>
158
- </section>
159
-
160
- <section id="panel-context" class="debug-panel" role="tabpanel" aria-labelledby="tab-context" hidden>
161
- <div class="section-heading">
162
- <div><h3>当前对话记录</h3><p>包含当前会话的历史消息,不限于所选回合。</p></div>
163
- <span class="source-label" id="context-source">平台聊天记录</span>
164
- </div>
165
- <div class="context-list" id="context-list"></div>
158
+ <details class="diagnostic-history"><summary>消息与连接事件</summary><ol class="timeline" id="timeline-list"></ol></details>
159
+ <details class="diagnostic-history"><summary>平台对话记录 <span id="context-source">平台聊天记录</span></summary><div class="context-list" id="context-list"></div></details>
166
160
  </section>
167
161
 
168
162
  <section id="panel-protocol" class="debug-panel" role="tabpanel" aria-labelledby="tab-protocol" hidden>
169
163
  <div class="section-heading">
170
- <div><h3>协议日志</h3><p>查看当前本地连接的完整日志。</p></div>
164
+ <div><h3>运行日志</h3></div>
171
165
  <button class="text-button" id="copy-protocol" type="button">复制可见日志</button>
172
166
  </div>
167
+ <div class="diagnostic-filters">
168
+ <label>会话<select id="logs-conversation" aria-label="日志会话"><option value="">全部会话</option></select></label>
169
+ <label>级别<select id="logs-level"><option value="">全部级别</option><option value="error">ERROR</option><option value="warn">WARN</option><option value="info">INFO</option></select></label>
170
+ <label class="diagnostic-search">搜索<input id="logs-search" type="search" placeholder="搜索日志内容" autocomplete="off"></label>
171
+ </div>
172
+ <p class="diagnostic-note" id="logs-count"></p>
173
173
  <div class="protocol-log" id="protocol-log" role="log" aria-live="polite"></div>
174
174
  </section>
175
175
  </div>
@@ -1,31 +1,62 @@
1
- /* Native points expressed against the simulator's 415 pt canvas. */
2
- .widget-shortcut { position:absolute; z-index:3; top:24.1cqw; left:2.89cqw; max-width:calc(100% - 5.78cqw); }
3
- .widget-shortcut[hidden], .widget-overlay[hidden] { display:none; }
4
- .widget-pill { display:flex; align-items:center; gap:1.93cqw; min-height:12.53cqw; max-width:62.65cqw; border-radius:99px; padding:1.93cqw 4.82cqw 1.93cqw 3.855cqw; color:#20252b; background:linear-gradient(135deg,#fff,#bbf1a7); border:.5px solid #20252b1f; font-size:3.855cqw; font-weight:400; box-shadow:none; }
5
- .widget-pill .widget-pill-avatar { width:6.747cqw; height:6.747cqw; flex:none; border-radius:50%; }
1
+ /* iOS ff1845b: dimensions in native points, scaled once with the device canvas. */
2
+ .widget-shortcut { position:absolute; z-index:3; top:calc(var(--widget-unit, 1px) * 100); left:calc(var(--widget-unit, 1px) * 12); max-width:calc(100% - calc(var(--widget-unit, 1px) * 24)); }
3
+ .widget-shortcut[hidden], .widget-overlay[hidden], .widget-more[hidden] { display:none; }
4
+ .widget-pill { display:flex; align-items:center; gap:calc(var(--widget-unit, 1px) * 8); height:calc(var(--widget-unit, 1px) * 52); max-width:calc(var(--widget-unit, 1px) * 260); border-radius:99px; padding:0 calc(var(--widget-unit, 1px) * 20) 0 calc(var(--widget-unit, 1px) * 16); color:#000; background:linear-gradient(110deg,#fff,#bbf1a7); border:calc(var(--widget-unit, 1px) * .5) solid #0000001f; font-size:calc(var(--widget-unit, 1px) * 16); font-weight:400; box-shadow:none; transition:padding .22s ease-in-out; }
5
+ .widget-pill .widget-pill-avatar { width:calc(var(--widget-unit, 1px) * 28); height:calc(var(--widget-unit, 1px) * 28); flex:none; border-radius:50%; }
6
6
  .widget-pill-title { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
7
- .widget-chevron { margin-left:1.9cqw; font-size:2.65cqw; }
8
- .widget-choices,.widget-options { position:absolute; z-index:5; top:calc(100% + 2cqw); left:0; width:55.9cqw; padding:1cqw 0; border-radius:3cqw; background:#f7f7f9; box-shadow:0 3cqw 9cqw #0003; overflow:hidden; }
9
- .widget-choices button,.widget-options button { display:block; width:100%; min-height:10.6cqw; padding:2.9cqw 3.855cqw; text-align:left; background:transparent; color:#20252b; font-size:3.37cqw; font-weight:400; border-bottom:.3px solid #0002; }
10
- .widget-overlay { position:absolute; z-index:10; inset:0; display:flex; align-items:center; justify-content:center; background:#0003; animation:widget-fade .2s ease-in-out; }
11
- .widget-modal { position:relative; width:calc(100% - 7.71cqw); height:min(134.94cqw,calc(100% - 11.57cqw)); max-width:560px; border-radius:5.783cqw; background:#fff; color:#20252b; overflow:hidden; display:flex; flex-direction:column; padding-bottom:2.89cqw; }
12
- .widget-modal-header { display:flex; gap:1.93cqw; align-items:center; padding:1.93cqw 1.93cqw 0; flex:none; }
13
- .widget-modal-header button { width:10.6cqw; height:10.6cqw; flex:none; padding:0; background:transparent; color:inherit; font-size:5.3cqw; font-weight:400; }
14
- .widget-modal-header h3 { flex:1; margin:0; text-align:center; font-size:3.37cqw; font-weight:500; line-height:1.4; }
15
- .widget-modal-body { flex:1; min-height:0; overflow:auto; text-align:center; font-size:3.37cqw; }
16
- .widget-modal-body>p,.widget-modal-body>h4 { padding:0 5.78cqw; }
17
- .widget-modal-body>h4,.widget-loading { margin-top:25%; }
18
- .widget-modal-body>button { background:#f3f3f5; color:inherit; padding:2.9cqw; border-radius:2cqw; }
19
- .widget-document { width:100%; height:100%; border:0; display:block; background:white; }
20
- .widget-options { top:12.5cqw; right:2cqw; left:auto; }
21
- .widget-options p { padding:3cqw; font-size:3.37cqw; }
22
- .widget-pin-note { position:absolute; top:14.45cqw; left:50%; transform:translateX(-50%); white-space:nowrap; border-radius:99px; background:#f5f5f5ed; padding:2.9cqw; font-size:2.89cqw; }
23
- .widget-entry { width:71.57cqw; max-width:100%; padding:2.89cqw; border:.3px solid #dadbdd; border-radius:3.855cqw; background:#fff; color:#20252b; font-size:3.3735cqw; }
24
- .widget-entry h4 { margin:0 0 2.89cqw; font:500 3.3735cqw/1.5 sans-serif; }
25
- .widget-entry p { margin:0; color:#717782; line-height:1.5; overflow-wrap:anywhere; }
26
- .widget-entry-details { display:flex; align-items:center; gap:2.89cqw; }
27
- .widget-entry-photo { width:15.42cqw; height:15.42cqw; border-radius:50%; object-fit:cover; flex:none; }
28
- .widget-entry-open { width:100%; min-height:10.6cqw; margin-top:2.89cqw; background:#f3f3f5; color:inherit; border-radius:1.93cqw; font-size:3.3735cqw; font-weight:500; }
7
+ .widget-chevron { margin-left:calc(var(--widget-unit, 1px) * 8); font-size:calc(var(--widget-unit, 1px) * 18); }
8
+ .widget-shortcut[data-phase="collapsed"] .widget-pill { width:calc(var(--widget-unit, 1px) * 52); padding:0 calc(var(--widget-unit, 1px) * 12); }
9
+ .widget-shortcut[data-phase="collapsed"] :is(.widget-pill-title,.widget-chevron) { display:none; }
10
+ .widget-choices,.widget-options { position:absolute; z-index:5; background:#fff; border:calc(var(--widget-unit, 1px) * .3) solid #0000001f; border-radius:calc(var(--widget-unit, 1px) * 16); box-shadow:0 calc(var(--widget-unit, 1px) * 4) calc(var(--widget-unit, 1px) * 16) #0000000d; overflow:auto; }
11
+ .widget-choices { top:calc(100% + calc(var(--widget-unit, 1px) * 8)); left:0; width:calc(var(--widget-unit, 1px) * 240); max-height:calc(var(--widget-unit, 1px) * 320); padding:calc(var(--widget-unit, 1px) * 4) 0; }
12
+ .widget-choices button { display:flex; align-items:center; justify-content:space-between; gap:calc(var(--widget-unit, 1px) * 8); width:100%; min-height:calc(var(--widget-unit, 1px) * 48); padding:calc(var(--widget-unit, 1px) * 12) calc(var(--widget-unit, 1px) * 16); text-align:left; background:transparent; color:#000; font-size:calc(var(--widget-unit, 1px) * 16); font-weight:400; }
13
+ .widget-choices button>span:first-child { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
14
+ .widget-arrow { color:#0009; }
15
+ .widget-choices button:hover,.widget-options button:hover { background:#f1f3f4; }
16
+ .widget-overlay { position:absolute; z-index:10; inset:0; display:flex; align-items:center; justify-content:center; background:#0003; opacity:1; animation:widget-fade .22s ease-in-out; transition:opacity .22s ease-in-out; }
17
+ .widget-overlay[data-state="closing"] { opacity:0; pointer-events:none; }
18
+ .widget-modal { position:relative; width:calc(100% - calc(var(--widget-unit, 1px) * 32)); height:65%; border-radius:calc(var(--widget-unit, 1px) * 24); background:#fff; color:#000; overflow:hidden; display:flex; flex-direction:column; padding-bottom:calc(var(--widget-unit, 1px) * 12); }
19
+ .widget-modal-header { display:flex; gap:0; align-items:center; min-height:calc(var(--widget-unit, 1px) * 52); padding:calc(var(--widget-unit, 1px) * 4) calc(var(--widget-unit, 1px) * 4) 0; flex:none; }
20
+ .widget-modal-header button,.widget-more-slot { width:calc(var(--widget-unit, 1px) * 44); height:calc(var(--widget-unit, 1px) * 44); flex:none; padding:0; background:transparent; color:#000; font-size:calc(var(--widget-unit, 1px) * 22); font-weight:400; }
21
+ .widget-modal-header h3 { flex:1; min-width:0; margin:0; text-align:center; font-size:calc(var(--widget-unit, 1px) * 14); font-weight:500; line-height:1.4; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
22
+ .widget-modal-body { flex:1; position:relative; min-height:0; overflow:hidden; font-size:calc(var(--widget-unit, 1px) * 14); }
23
+ .widget-document { position:absolute; top:0; left:0; border:0; display:block; background:#fff; }
24
+ .widget-loading,.widget-failure { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:calc(var(--widget-unit, 1px) * 12); padding:calc(var(--widget-unit, 1px) * 24); text-align:center; color:#0009; }
25
+ .widget-loading p,.widget-failure p { margin:0; font-size:calc(var(--widget-unit, 1px) * 14); line-height:1.5; }
26
+ .widget-failure h4 { margin:0; font-size:calc(var(--widget-unit, 1px) * 17); font-weight:600; color:#000; }
27
+ .widget-spinner { width:calc(var(--widget-unit, 1px) * 20); height:calc(var(--widget-unit, 1px) * 20); border:calc(var(--widget-unit, 1px) * 2) solid #0002; border-top-color:#0009; border-radius:50%; animation:widget-spin .85s linear infinite; }
28
+ .widget-failure-mark { display:grid; place-items:center; width:calc(var(--widget-unit, 1px) * 32); height:calc(var(--widget-unit, 1px) * 32); border:calc(var(--widget-unit, 1px) * 1.5) solid #0006; border-radius:50%; font-size:calc(var(--widget-unit, 1px) * 22); }
29
+ .widget-failure button { margin-top:calc(var(--widget-unit, 1px) * 4); padding:calc(var(--widget-unit, 1px) * 10) calc(var(--widget-unit, 1px) * 16); border-radius:calc(var(--widget-unit, 1px) * 8); background:#f1f3f4; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); }
30
+ .widget-options { top:calc(var(--widget-unit, 1px) * 52); right:calc(var(--widget-unit, 1px) * 8); width:calc(var(--widget-unit, 1px) * 232); padding:calc(var(--widget-unit, 1px) * 8) 0; }
31
+ /* Menu rhythm and monochrome icons match the native action menu. */
32
+ .widget-options button { position:relative; display:flex; align-items:center; gap:calc(var(--widget-unit, 1px) * 6); width:100%; min-height:calc(var(--widget-unit, 1px) * 36); padding:calc(var(--widget-unit, 1px) * 6) calc(var(--widget-unit, 1px) * 16); text-align:left; background:transparent; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); font-weight:400; }
33
+ .widget-options button+button { margin-top:calc(var(--widget-unit, 1px) * 8); }
34
+ .widget-options button+button::before { content:""; position:absolute; top:calc(var(--widget-unit, 1px) * -4); left:calc(var(--widget-unit, 1px) * 16); right:calc(var(--widget-unit, 1px) * 16); height:calc(var(--widget-unit, 1px) * .3); background:#00000026; }
35
+ .widget-options svg { width:calc(var(--widget-unit, 1px) * 20); height:calc(var(--widget-unit, 1px) * 20); flex:none; fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
36
+ .widget-options .widget-destructive,.widget-confirm .widget-destructive { color:#d73340; }
37
+ .widget-options button:disabled,.widget-confirm button:disabled { opacity:.45; cursor:wait; }
38
+ .widget-confirm-backdrop { position:absolute; inset:0; z-index:7; display:grid; place-items:center; background:#0003; padding:calc(var(--widget-unit, 1px) * 20); }
39
+ .widget-confirm { width:calc(var(--widget-unit, 1px) * 270); max-width:100%; padding:calc(var(--widget-unit, 1px) * 20) calc(var(--widget-unit, 1px) * 16) calc(var(--widget-unit, 1px) * 10); border-radius:calc(var(--widget-unit, 1px) * 16); background:#fff; text-align:center; }
40
+ .widget-confirm h4 { margin:0 0 calc(var(--widget-unit, 1px) * 8); font-size:calc(var(--widget-unit, 1px) * 17); font-weight:600; }
41
+ .widget-confirm p { margin:0; font-size:calc(var(--widget-unit, 1px) * 13); line-height:1.5; }
42
+ .widget-confirm-actions { display:flex; margin-top:calc(var(--widget-unit, 1px) * 16); border-top:calc(var(--widget-unit, 1px) * .5) solid #0002; }
43
+ .widget-confirm-actions button { flex:1; padding:calc(var(--widget-unit, 1px) * 12); font-size:calc(var(--widget-unit, 1px) * 16); background:transparent; color:#000; }
44
+ .widget-native-share { position:absolute; z-index:6; inset:calc(var(--widget-unit, 1px) * 52) 0 0; padding:calc(var(--widget-unit, 1px) * 28) calc(var(--widget-unit, 1px) * 24); background:#fff; font-size:calc(var(--widget-unit, 1px) * 14); }
45
+ .widget-native-share h4 { margin:0 0 calc(var(--widget-unit, 1px) * 12); font-size:calc(var(--widget-unit, 1px) * 17); }
46
+ .widget-native-share p { line-height:1.6; color:#0009; margin:0 0 calc(var(--widget-unit, 1px) * 24); }
47
+ .widget-native-share button { display:block; width:100%; margin-top:calc(var(--widget-unit, 1px) * 12); padding:calc(var(--widget-unit, 1px) * 12); border-radius:calc(var(--widget-unit, 1px) * 8); color:#000; background:#f1f3f4; font-size:calc(var(--widget-unit, 1px) * 14); }
48
+ .widget-pin-note { position:absolute; z-index:8; top:calc(17.5% + calc(var(--widget-unit, 1px) * 58)); left:50%; transform:translateX(-50%); max-width:90%; border-radius:calc(var(--widget-unit, 1px) * 16); background:#f1f3f4f5; box-shadow:0 calc(var(--widget-unit, 1px) * 4) calc(var(--widget-unit, 1px) * 16) #0001; padding:calc(var(--widget-unit, 1px) * 12) calc(var(--widget-unit, 1px) * 16); font-size:calc(var(--widget-unit, 1px) * 12); text-align:center; }
49
+ .widget-entry { width:calc(var(--widget-unit, 1px) * 297); max-width:100%; padding:calc(var(--widget-unit, 1px) * 12); border:calc(var(--widget-unit, 1px) * .3) solid #00000026; border-radius:calc(var(--widget-unit, 1px) * 16); background:#fff; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); }
50
+ .widget-entry h4 { margin:0 0 calc(var(--widget-unit, 1px) * 12); font:500 calc(var(--widget-unit, 1px) * 14)/1.4 -apple-system,BlinkMacSystemFont,sans-serif; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
51
+ .widget-entry p { margin:0; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); line-height:1.4; overflow-wrap:anywhere; display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }
52
+ .widget-entry p.widget-secondary { color:#0009; }
53
+ .widget-entry-details { display:flex; align-items:center; gap:calc(var(--widget-unit, 1px) * 12); }
54
+ .widget-entry-photo { width:calc(var(--widget-unit, 1px) * 64); height:calc(var(--widget-unit, 1px) * 64); border-radius:50%; object-fit:cover; flex:none; }
55
+ .widget-entry-open { width:100%; height:calc(var(--widget-unit, 1px) * 40); min-height:0; padding:0; margin-top:calc(var(--widget-unit, 1px) * 12); background:#f1f3f4; color:#000; border-radius:calc(var(--widget-unit, 1px) * 8); font-size:calc(var(--widget-unit, 1px) * 14); font-weight:500; }
56
+ .widget-entry-metadata { display:flex; min-width:0; margin-top:calc(var(--widget-unit, 1px) * 12); font-size:calc(var(--widget-unit, 1px) * 11); line-height:1.4; color:#0009; }
57
+ .widget-creator { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
58
+ .widget-permission { flex:none; }
29
59
  .widget-entry button:focus-visible,.widget-shortcut button:focus-visible,.widget-overlay button:focus-visible { outline:2px solid #007a9b; outline-offset:2px; }
30
60
  @keyframes widget-fade { from { opacity:0; } to { opacity:1; } }
31
- @media(prefers-reduced-motion:reduce) { .widget-overlay { animation-duration:.1s; } }
61
+ @keyframes widget-spin { to { transform:rotate(360deg); } }
62
+ @media(prefers-reduced-motion:reduce) { .widget-overlay { animation-duration:.1s; transition-duration:.1s; } .widget-pill { transition:none; } }