skill-memo-ilife 0.3.2 → 0.3.13

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 (320) hide show
  1. package/SKILL.md +33 -34
  2. package/dist/checkin/index.d.ts +8 -0
  3. package/dist/checkin/index.d.ts.map +1 -0
  4. package/dist/checkin/index.js +8 -0
  5. package/dist/checkin/index.js.map +1 -0
  6. package/dist/checkin/receipt.d.ts +17 -0
  7. package/dist/checkin/receipt.d.ts.map +1 -0
  8. package/dist/checkin/receipt.js +63 -0
  9. package/dist/checkin/receipt.js.map +1 -0
  10. package/dist/checkin/routes.d.ts +4 -0
  11. package/dist/checkin/routes.d.ts.map +1 -1
  12. package/dist/checkin/routes.js +2 -2
  13. package/dist/checkin/routes.js.map +1 -1
  14. package/dist/cli/cmd_read.js +19 -53
  15. package/dist/cli/cmd_read.js.map +1 -1
  16. package/dist/cli/config.d.ts +4 -4
  17. package/dist/cli/config.d.ts.map +1 -1
  18. package/dist/cli/config.js +25 -4
  19. package/dist/cli/config.js.map +1 -1
  20. package/dist/cli/health/items.js +2 -2
  21. package/dist/cli/health/items.js.map +1 -1
  22. package/dist/cli/health.d.ts.map +1 -1
  23. package/dist/cli/health.js +3 -2
  24. package/dist/cli/health.js.map +1 -1
  25. package/dist/cli/keys.d.ts +6 -3
  26. package/dist/cli/keys.d.ts.map +1 -1
  27. package/dist/cli/keys.js +2 -5
  28. package/dist/cli/keys.js.map +1 -1
  29. package/dist/cli/registry.d.ts +1 -1
  30. package/dist/{fetch/db.d.ts → db/readonly.d.ts} +1 -1
  31. package/dist/db/readonly.d.ts.map +1 -0
  32. package/dist/{fetch/db.js → db/readonly.js} +5 -5
  33. package/dist/db/readonly.js.map +1 -0
  34. package/dist/help/lookup.d.ts +18 -1
  35. package/dist/help/lookup.d.ts.map +1 -1
  36. package/dist/help/lookup.js +53 -37
  37. package/dist/help/lookup.js.map +1 -1
  38. package/dist/help/sceneData.js +1 -1
  39. package/dist/help/scenes/checkin.d.ts +1 -1
  40. package/dist/help/scenes/checkin.js +1 -1
  41. package/dist/help/scenes/init.d.ts +1 -1
  42. package/dist/help/scenes/init.js +1 -1
  43. package/dist/help/scenes/memo.d.ts +1 -1
  44. package/dist/help/scenes/memo.js +1 -1
  45. package/dist/help/scenes/mood.d.ts +1 -1
  46. package/dist/help/scenes/mood.js +1 -1
  47. package/dist/help/scenes/remind.d.ts +1 -1
  48. package/dist/help/scenes/remind.js +2 -2
  49. package/dist/help/scenes/remind.js.map +1 -1
  50. package/dist/help/scenes/search.d.ts +1 -1
  51. package/dist/help/scenes/search.js +1 -1
  52. package/dist/help/scenes/sync.d.ts +1 -1
  53. package/dist/help/scenes/sync.js +1 -1
  54. package/dist/help/scenes/wish.d.ts +1 -1
  55. package/dist/help/scenes/wish.js +1 -1
  56. package/dist/index.d.ts +11 -4
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +11 -5
  59. package/dist/index.js.map +1 -1
  60. package/dist/init/diagnosis.d.ts +2 -1
  61. package/dist/init/diagnosis.d.ts.map +1 -1
  62. package/dist/init/diagnosis.js.map +1 -1
  63. package/dist/init/page.d.ts +3 -2
  64. package/dist/init/page.d.ts.map +1 -1
  65. package/dist/init/page.js +20 -10
  66. package/dist/init/page.js.map +1 -1
  67. package/dist/init/routes.d.ts +4 -1
  68. package/dist/init/routes.d.ts.map +1 -1
  69. package/dist/init/routes.js +14 -0
  70. package/dist/init/routes.js.map +1 -1
  71. package/dist/init/run.d.ts.map +1 -1
  72. package/dist/init/run.js +7 -1
  73. package/dist/init/run.js.map +1 -1
  74. package/dist/{fetch → memo}/batch.d.ts +6 -2
  75. package/dist/memo/batch.d.ts.map +1 -0
  76. package/dist/{fetch → memo}/batch.js +8 -4
  77. package/dist/memo/batch.js.map +1 -0
  78. package/dist/memo/category.d.ts.map +1 -0
  79. package/dist/{policy → memo}/category.js +1 -1
  80. package/dist/memo/category.js.map +1 -0
  81. package/dist/memo/commands.d.ts +3 -12
  82. package/dist/memo/commands.d.ts.map +1 -1
  83. package/dist/memo/commands.js +1 -9
  84. package/dist/memo/commands.js.map +1 -1
  85. package/dist/{policy → memo}/crud.d.ts +1 -1
  86. package/dist/memo/crud.d.ts.map +1 -0
  87. package/dist/{policy → memo}/crud.js +22 -7
  88. package/dist/memo/crud.js.map +1 -0
  89. package/dist/memo/index.d.ts +4 -2
  90. package/dist/memo/index.d.ts.map +1 -1
  91. package/dist/memo/index.js +4 -2
  92. package/dist/memo/index.js.map +1 -1
  93. package/dist/memo/media.d.ts.map +1 -0
  94. package/dist/{policy → memo}/media.js +2 -2
  95. package/dist/memo/media.js.map +1 -0
  96. package/dist/memo/receipt.d.ts +35 -0
  97. package/dist/memo/receipt.d.ts.map +1 -0
  98. package/dist/memo/receipt.js +62 -0
  99. package/dist/memo/receipt.js.map +1 -0
  100. package/dist/memo/receiptPage.d.ts +44 -0
  101. package/dist/memo/receiptPage.d.ts.map +1 -0
  102. package/dist/memo/receiptPage.js +50 -0
  103. package/dist/memo/receiptPage.js.map +1 -0
  104. package/dist/memo/routes.d.ts.map +1 -1
  105. package/dist/memo/routes.js +32 -0
  106. package/dist/memo/routes.js.map +1 -1
  107. package/dist/memo/run.d.ts +9 -8
  108. package/dist/memo/run.d.ts.map +1 -1
  109. package/dist/memo/run.js +140 -97
  110. package/dist/memo/run.js.map +1 -1
  111. package/dist/mood/routes.d.ts.map +1 -1
  112. package/dist/mood/routes.js +36 -0
  113. package/dist/mood/routes.js.map +1 -1
  114. package/dist/remind/index.d.ts +5 -2
  115. package/dist/remind/index.d.ts.map +1 -1
  116. package/dist/remind/index.js +5 -2
  117. package/dist/remind/index.js.map +1 -1
  118. package/dist/{policy/reminder.d.ts → remind/policy.d.ts} +1 -1
  119. package/dist/remind/policy.d.ts.map +1 -0
  120. package/dist/{policy/reminder.js → remind/policy.js} +2 -2
  121. package/dist/remind/policy.js.map +1 -0
  122. package/dist/remind/routes.d.ts +1 -2
  123. package/dist/remind/routes.d.ts.map +1 -1
  124. package/dist/remind/routes.js +32 -2
  125. package/dist/remind/routes.js.map +1 -1
  126. package/dist/remind/run.d.ts +1 -1
  127. package/dist/remind/run.d.ts.map +1 -1
  128. package/dist/remind/run.js +112 -7
  129. package/dist/remind/run.js.map +1 -1
  130. package/dist/{fetch/reminders.d.ts → remind/store.d.ts} +2 -2
  131. package/dist/remind/store.d.ts.map +1 -0
  132. package/dist/{fetch/reminders.js → remind/store.js} +3 -3
  133. package/dist/remind/store.js.map +1 -0
  134. package/dist/render/envelope.d.ts.map +1 -1
  135. package/dist/render/envelope.js +3 -2
  136. package/dist/render/envelope.js.map +1 -1
  137. package/dist/render/html.js +1 -1
  138. package/dist/render/html.js.map +1 -1
  139. package/dist/render/index.d.ts +4 -1
  140. package/dist/render/index.d.ts.map +1 -1
  141. package/dist/render/index.js +5 -3
  142. package/dist/render/index.js.map +1 -1
  143. package/dist/render/listPage.d.ts +34 -0
  144. package/dist/render/listPage.d.ts.map +1 -0
  145. package/dist/render/listPage.js +39 -0
  146. package/dist/render/listPage.js.map +1 -0
  147. package/dist/render/memoPageAssets.d.ts +32 -0
  148. package/dist/render/memoPageAssets.d.ts.map +1 -0
  149. package/dist/render/memoPageAssets.js +320 -0
  150. package/dist/render/memoPageAssets.js.map +1 -0
  151. package/dist/render/pageAssets.js +3 -3
  152. package/dist/render/pageAssets.js.map +1 -1
  153. package/dist/render/pages.d.ts +9 -2
  154. package/dist/render/pages.d.ts.map +1 -1
  155. package/dist/render/pages.js +8 -16
  156. package/dist/render/pages.js.map +1 -1
  157. package/dist/render/receipt.d.ts +12 -3
  158. package/dist/render/receipt.d.ts.map +1 -1
  159. package/dist/render/receipt.js +55 -10
  160. package/dist/render/receipt.js.map +1 -1
  161. package/dist/search/routes.d.ts.map +1 -1
  162. package/dist/search/routes.js +1 -0
  163. package/dist/search/routes.js.map +1 -1
  164. package/dist/search/run.d.ts +11 -3
  165. package/dist/search/run.d.ts.map +1 -1
  166. package/dist/search/run.js +130 -11
  167. package/dist/search/run.js.map +1 -1
  168. package/dist/shared/commandSpec.d.ts +4 -5
  169. package/dist/shared/commandSpec.d.ts.map +1 -1
  170. package/dist/shared/errors.d.ts.map +1 -0
  171. package/dist/shared/errors.js.map +1 -0
  172. package/dist/{fetch → shared}/paths.d.ts +1 -1
  173. package/dist/shared/paths.d.ts.map +1 -0
  174. package/dist/{fetch → shared}/paths.js +3 -3
  175. package/dist/shared/paths.js.map +1 -0
  176. package/dist/shared/validators.d.ts +3 -0
  177. package/dist/shared/validators.d.ts.map +1 -0
  178. package/dist/shared/validators.js +15 -0
  179. package/dist/shared/validators.js.map +1 -0
  180. package/dist/sync/auth.d.ts.map +1 -0
  181. package/dist/{fetch → sync}/auth.js +3 -3
  182. package/dist/sync/auth.js.map +1 -0
  183. package/dist/sync/commands.d.ts.map +1 -1
  184. package/dist/sync/commands.js.map +1 -1
  185. package/dist/sync/feishu.d.ts.map +1 -0
  186. package/dist/{fetch → sync}/feishu.js +9 -9
  187. package/dist/sync/feishu.js.map +1 -0
  188. package/dist/sync/index.d.ts +5 -2
  189. package/dist/sync/index.d.ts.map +1 -1
  190. package/dist/sync/index.js +4 -2
  191. package/dist/sync/index.js.map +1 -1
  192. package/dist/sync/run.d.ts +1 -1
  193. package/dist/sync/run.d.ts.map +1 -1
  194. package/dist/sync/run.js +2 -1
  195. package/dist/sync/run.js.map +1 -1
  196. package/dist/{fetch → sync}/sentinel.d.ts +1 -1
  197. package/dist/sync/sentinel.d.ts.map +1 -0
  198. package/dist/{fetch → sync}/sentinel.js +1 -2
  199. package/dist/sync/sentinel.js.map +1 -0
  200. package/dist/triggers/routes.generated.d.ts +1 -1
  201. package/dist/triggers/routes.generated.d.ts.map +1 -1
  202. package/dist/triggers/routes.generated.js +19 -6
  203. package/dist/triggers/routes.generated.js.map +1 -1
  204. package/dist/triggers/routing.d.ts +7 -0
  205. package/dist/triggers/routing.d.ts.map +1 -0
  206. package/dist/triggers/routing.js +35 -0
  207. package/dist/triggers/routing.js.map +1 -0
  208. package/dist/wish/complete.d.ts +1 -1
  209. package/dist/wish/complete.d.ts.map +1 -1
  210. package/dist/wish/complete.js +1 -1
  211. package/dist/wish/complete.js.map +1 -1
  212. package/dist/wish/due.d.ts +1 -1
  213. package/dist/wish/due.d.ts.map +1 -1
  214. package/dist/wish/due.js +1 -1
  215. package/dist/wish/due.js.map +1 -1
  216. package/dist/wish/ensure.d.ts +2 -2
  217. package/dist/wish/ensure.d.ts.map +1 -1
  218. package/dist/wish/ensure.js +2 -2
  219. package/dist/wish/ensure.js.map +1 -1
  220. package/dist/wish/gate.d.ts +1 -1
  221. package/dist/wish/gate.d.ts.map +1 -1
  222. package/dist/wish/gate.js +1 -1
  223. package/dist/wish/gate.js.map +1 -1
  224. package/dist/wish/index.d.ts +4 -0
  225. package/dist/wish/index.d.ts.map +1 -1
  226. package/dist/wish/index.js +7 -0
  227. package/dist/wish/index.js.map +1 -1
  228. package/dist/{policy/wish.d.ts → wish/policy.d.ts} +1 -1
  229. package/dist/wish/policy.d.ts.map +1 -0
  230. package/dist/{policy/wish.js → wish/policy.js} +3 -3
  231. package/dist/wish/policy.js.map +1 -0
  232. package/dist/wish/receipt.d.ts +45 -0
  233. package/dist/wish/receipt.d.ts.map +1 -0
  234. package/dist/wish/receipt.js +68 -0
  235. package/dist/wish/receipt.js.map +1 -0
  236. package/dist/wish/reconcile.d.ts +1 -1
  237. package/dist/wish/reconcile.d.ts.map +1 -1
  238. package/dist/wish/reconcile.js +2 -2
  239. package/dist/wish/reconcile.js.map +1 -1
  240. package/dist/wish/routes.d.ts.map +1 -1
  241. package/dist/wish/routes.js +11 -1
  242. package/dist/wish/routes.js.map +1 -1
  243. package/dist/wish/run.d.ts +11 -3
  244. package/dist/wish/run.d.ts.map +1 -1
  245. package/dist/wish/run.js +75 -7
  246. package/dist/wish/run.js.map +1 -1
  247. package/dist/wish/taskRemove.js +2 -2
  248. package/dist/wish/taskRemove.js.map +1 -1
  249. package/dist/wish/taskSync.d.ts +1 -1
  250. package/dist/wish/taskSync.d.ts.map +1 -1
  251. package/dist/wish/taskSync.js +3 -3
  252. package/dist/wish/taskSync.js.map +1 -1
  253. package/dist/wish/taskWrite.d.ts.map +1 -0
  254. package/dist/{fetch → wish}/taskWrite.js +2 -2
  255. package/dist/wish/taskWrite.js.map +1 -0
  256. package/dist/wish/tasks.d.ts.map +1 -0
  257. package/dist/{fetch → wish}/tasks.js +2 -2
  258. package/dist/wish/tasks.js.map +1 -0
  259. package/dist/wish/wizards.d.ts +1 -1
  260. package/dist/wish/wizards.d.ts.map +1 -1
  261. package/dist/wish/wizards.js +1 -1
  262. package/dist/wish/wizards.js.map +1 -1
  263. package/package.json +4 -7
  264. package/templates/change_category.html +52 -28
  265. package/templates/init_report.html +15 -14
  266. package/templates/memo_query.html +59 -24
  267. package/templates/receipt.html +97 -27
  268. package/templates/sync_report.html +106 -98
  269. package/templates/wish_complete.html +48 -30
  270. package/templates/wish_plan.html +35 -20
  271. package/dist/fetch/auth.d.ts.map +0 -1
  272. package/dist/fetch/auth.js.map +0 -1
  273. package/dist/fetch/batch.d.ts.map +0 -1
  274. package/dist/fetch/batch.js.map +0 -1
  275. package/dist/fetch/db.d.ts.map +0 -1
  276. package/dist/fetch/db.js.map +0 -1
  277. package/dist/fetch/errors.d.ts.map +0 -1
  278. package/dist/fetch/errors.js.map +0 -1
  279. package/dist/fetch/feishu.d.ts.map +0 -1
  280. package/dist/fetch/feishu.js.map +0 -1
  281. package/dist/fetch/index.d.ts +0 -19
  282. package/dist/fetch/index.d.ts.map +0 -1
  283. package/dist/fetch/index.js +0 -13
  284. package/dist/fetch/index.js.map +0 -1
  285. package/dist/fetch/paths.d.ts.map +0 -1
  286. package/dist/fetch/paths.js.map +0 -1
  287. package/dist/fetch/reminders.d.ts.map +0 -1
  288. package/dist/fetch/reminders.js.map +0 -1
  289. package/dist/fetch/sentinel.d.ts.map +0 -1
  290. package/dist/fetch/sentinel.js.map +0 -1
  291. package/dist/fetch/taskWrite.d.ts.map +0 -1
  292. package/dist/fetch/taskWrite.js.map +0 -1
  293. package/dist/fetch/tasks.d.ts.map +0 -1
  294. package/dist/fetch/tasks.js.map +0 -1
  295. package/dist/policy/category.d.ts.map +0 -1
  296. package/dist/policy/category.js.map +0 -1
  297. package/dist/policy/crud.d.ts.map +0 -1
  298. package/dist/policy/crud.js.map +0 -1
  299. package/dist/policy/index.d.ts +0 -11
  300. package/dist/policy/index.d.ts.map +0 -1
  301. package/dist/policy/index.js +0 -8
  302. package/dist/policy/index.js.map +0 -1
  303. package/dist/policy/media.d.ts.map +0 -1
  304. package/dist/policy/media.js.map +0 -1
  305. package/dist/policy/reminder.d.ts.map +0 -1
  306. package/dist/policy/reminder.js.map +0 -1
  307. package/dist/policy/wakewords.d.ts +0 -14
  308. package/dist/policy/wakewords.d.ts.map +0 -1
  309. package/dist/policy/wakewords.js +0 -68
  310. package/dist/policy/wakewords.js.map +0 -1
  311. package/dist/policy/wish.d.ts.map +0 -1
  312. package/dist/policy/wish.js.map +0 -1
  313. /package/dist/{policy → memo}/category.d.ts +0 -0
  314. /package/dist/{policy → memo}/media.d.ts +0 -0
  315. /package/dist/{fetch → shared}/errors.d.ts +0 -0
  316. /package/dist/{fetch → shared}/errors.js +0 -0
  317. /package/dist/{fetch → sync}/auth.d.ts +0 -0
  318. /package/dist/{fetch → sync}/feishu.d.ts +0 -0
  319. /package/dist/{fetch → wish}/taskWrite.d.ts +0 -0
  320. /package/dist/{fetch → wish}/tasks.d.ts +0 -0
@@ -29,12 +29,20 @@ select{border:1px solid var(--line);border-radius:12px;padding:9px 12px;font-siz
29
29
  .note{background:var(--card);border-radius:18px;padding:18px;margin:12px 0;box-shadow:0 1px 3px rgba(0,0,0,.04);transition:box-shadow .15s,opacity .15s}
30
30
  #noteList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;align-items:stretch}
31
31
  #noteList .note{margin:0;padding:14px;display:flex;flex-direction:column;height:100%}
32
- #noteList .hm-empty{grid-column:1/-1}
32
+ /* 空态件由公共层给(`ilife-empty-*`)。本页空分支的形状是
33
+ `list.innerHTML = '<div class="panel">' + window.emptyState({…}) + '</div>'`
34
+ ⇒ **落进 `#noteList` 栅格的分栏项是那层 `.panel`,不是空卡本身**,故跨列要挂在 `.panel` 上;
35
+ 只给 `.ilife-empty` 挂 `grid-column` 是空转(空卡是 `.panel` 的子元素,不参与外层栅格定位)。
36
+ **实测**(#877 复核):撤掉这条 `.panel` 规则,1280 档 `#noteList` 宽 936、空卡只占 419px(半个卡);
37
+ 挂上后分栏项 936=整行、空卡 892px。两条都留着:`.panel` 那条管本页当前形状,
38
+ `.ilife-empty` 那条管将来空卡直接落进栅格的情形。 */
39
+ #noteList > .panel,
40
+ #noteList .ilife-empty{grid-column:1/-1}
33
41
  #noteList .note-head{flex-wrap:nowrap;gap:6px;margin-bottom:8px;align-items:flex-start}
34
42
  #noteList .note-id{font-size:13px;min-width:0;white-space:nowrap}
35
43
  #noteList .note-content{font-size:14px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-all;flex:1 1 auto}
36
44
  #noteList .note-meta{margin:auto 0 0;gap:8px;padding-top:8px}
37
- #noteList .note-meta .badge{font-size:11px;padding:2px 8px;margin:2px 4px 2px 0}
45
+ #noteList .note-meta .badge{font-size:12px;padding:2px 8px;margin:2px 4px 2px 0}
38
46
  .note.off{opacity:.5}
39
47
  .note.off .content{text-decoration:line-through;color:var(--fg3)}
40
48
  .note-head{display:flex;align-items:center;gap:12px;margin-bottom:10px}
@@ -53,11 +61,15 @@ pre{white-space:pre-wrap;background:#f5f5f7;color:var(--fg);border:1px solid var
53
61
  .notice{padding:12px 16px;border-radius:14px;margin:10px 0;font-size:14px}
54
62
  .notice.warn{background:#fff7e8;color:#b25b00}
55
63
  .notice.err{background:#fff5f5;color:#ff3b30}
56
- @media(max-width:800px){.wrap{padding:28px 14px 70px}.hero{padding:26px 20px}h1{font-size:30px}.flow{flex-direction:column}}
64
+ /* #820 收尾:断点取仓内既有集合(help 模板同档 820);原写 800 是新造的档位。 */
65
+ @media(max-width:820px){.wrap{padding:28px 14px 70px}.hero{padding:26px 20px}h1{font-size:30px}.flow{flex-direction:column}}
66
+
67
+ /* #870 · 表单控件命中区 ≥44×44px(t849 §2 硬线;实测基线 33 处不足全在这一族) */
68
+ input[type=text],input[type=date],input[type=search],input:not([type]),select{min-height:44px;box-sizing:border-box}
57
69
  </style>
58
70
  </head>
59
71
  <body>
60
- <div class="wrap">
72
+ <div class="wrap ilife-page-ui">
61
73
  <header class="hero">
62
74
  <h1 id="title">批量改分类向导</h1>
63
75
  <p class="lead" id="subtitle">数据加载中</p>
@@ -68,9 +80,9 @@ pre{white-space:pre-wrap;background:#f5f5f7;color:var(--fg);border:1px solid var
68
80
  <section class="kpi" id="kpis"></section>
69
81
 
70
82
  <section class="panel">
71
- <div style="font-weight:700;margin-bottom:10px">🎯 改到哪个分类</div>
83
+ <div style="font-weight:700;margin-bottom:10px">改到哪个分类</div>
72
84
  <div class="toolbar">
73
- <label style="color:var(--fg2);font-size:14px">目标分类:</label>
85
+ <label style="color:var(--fg2);font-size:14px">目标分类:</label>
74
86
  <select id="toCatSelect">
75
87
  <option value="备忘">备忘</option>
76
88
  <option value="心愿">心愿</option>
@@ -82,27 +94,35 @@ pre{white-space:pre-wrap;background:#f5f5f7;color:var(--fg);border:1px solid var
82
94
  <button class="ghost" onclick="selectAll(false)">清空选择</button>
83
95
  </div>
84
96
  </div>
85
- <p class="hint">已选择 <b id="selCount">0</b> 条,共 <span id="selTotal">0</span> 条</p>
97
+ <p class="hint">已选择 <b id="selCount">0</b> 条,共 <span id="selTotal">0</span> 条</p>
86
98
  </section>
87
99
 
88
100
  <section id="noteList"></section>
89
101
 
90
102
  <section class="panel">
91
- <div style="font-weight:700;margin-bottom:8px">📋 发给 AI 的指令</div>
92
- <p style="color:var(--fg2);font-size:13px;margin-bottom:12px">点击后复制指令,粘贴给 AI,AI 会把勾选笔记的分类改成目标分类(子分类保持不变),改完给你回执。</p>
103
+ <div style="font-weight:700;margin-bottom:8px">发给助手的指令</div>
104
+ <p style="color:var(--fg2);font-size:13px;margin-bottom:12px">点击后复制指令,粘贴给助手,助手会把勾选笔记的分类改成目标分类(子分类保持不变),改完给你回执。</p>
93
105
  <div class="toolbar">
94
- <button class="primary" onclick="adopt()">📋 复制修改指令</button>
106
+ <button class="primary" onclick="adopt()">复制修改指令</button>
95
107
  </div>
96
108
  <div id="warnArea"></div>
109
+ <p class="small" id="previewNote" style="display:none">下面是复制出去的原文,里面的符号留给 AI 解析。</p>
97
110
  <pre id="promptOut" style="display:none"></pre>
98
111
  </section>
99
112
 
100
- <section class="panel">
101
- <strong>数据与日志</strong>
102
- <div class="hint">点复制数据保存当前笔记列表 · 点复制日志用于反馈问题(日志不含隐私内容)</div>
103
- <div class="hm-actions">
104
- <button class="copy ghost" onclick="copyData()">复制数据</button>
105
- <button class="copy ghost" onclick="copyLog()">复制日志</button>
113
+ <section class="ilife-block ilife-block-copy-block">
114
+ <div class="ilife-action-bar">
115
+ <div class="ilife-action-row ilife-action-row-ghost">
116
+ <div class="ilife-copy-menu-wrap">
117
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-fmt-open="1" aria-haspopup="menu" aria-expanded="false" aria-label="复制数据(点开选格式)">复制数据</button>
118
+ <div class="ilife-copy-menu" role="menu">
119
+ <button type="button" class="ilife-copy-menu-item" data-fmt="text" data-t=""><span class="ilife-copy-menu-label">纯文本</span></button>
120
+ <button type="button" class="ilife-copy-menu-item" data-fmt="json" data-t=""><span class="ilife-copy-menu-label">JSON</span></button>
121
+ <button type="button" class="ilife-copy-menu-item" data-fmt="csv" data-t=""><span class="ilife-copy-menu-label">CSV</span></button>
122
+ </div>
123
+ </div>
124
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-action-id="memo-copy-log">复制日志</button>
125
+ </div>
106
126
  </div>
107
127
  </section>
108
128
  </div>
@@ -116,7 +136,7 @@ var raw=document.getElementById("payload");
116
136
  var payload=null,perr=null;
117
137
  try{payload=JSON.parse(raw.textContent||"{}");}catch(e){perr=e.message;}
118
138
  if(perr){document.getElementById("injectError").style.display="block";document.getElementById("injectError").innerHTML=window.errorReceipt({message:"数据解析失败",log:perr,payload:null});return;}
119
- if(!payload||payload.status!=="ok"||!payload.data){document.getElementById("injectError").style.display="block";document.getElementById("injectError").innerHTML=window.errorReceipt({message:(payload&&payload.message)||"数据未注入",retryPrompt:"请重新运行批量改分类(唤醒词:备忘改分类)",payload:null});return;}
139
+ if(!payload||payload.status!=="ok"||!payload.data){document.getElementById("injectError").style.display="block";document.getElementById("injectError").innerHTML=window.errorReceipt({message:(payload&&payload.message)||"数据未注入",retryPrompt:"请重新运行批量改分类(唤醒词:备忘改分类)",payload:null});return;}
120
140
  window.__hmPayload=payload;
121
141
  function data(){return payload.data}
122
142
  function items(){return data().items||[]}
@@ -124,9 +144,9 @@ function items(){return data().items||[]}
124
144
  function init(){
125
145
  var d=data();
126
146
  document.getElementById('title').textContent=d.title||'批量改分类向导';
127
- document.getElementById('subtitle').textContent='勾选下方笔记 → 复制指令 → 粘贴给 AI 帮你完成改分类';
147
+ document.getElementById('subtitle').textContent='先勾选下方笔记,再复制指令,粘贴给助手帮你完成改分类';
128
148
  var meta=d.meta||{};
129
- document.getElementById('meta').textContent=`从「${d.from_category||'当前分类'}」改到「${d.to_category||'目标分类'}」 · 生成时间:${d.generated_at||''}`;
149
+ document.getElementById('meta').textContent=`从「${d.from_category||'当前分类'}」改到「${d.to_category||'目标分类'}」(生成时间 ${d.generated_at||''})`;
130
150
  if(d.to_category){
131
151
  var sel=document.getElementById('toCatSelect');
132
152
  sel.value=d.to_category;
@@ -187,6 +207,7 @@ function selectAll(on){
187
207
  var cb=document.getElementById('cb_'+w.id);
188
208
  if(cb){cb.checked=on;document.getElementById('note_'+w.id).classList.toggle('off',!on)}
189
209
  });
210
+ updateSelCount();
190
211
  }
191
212
 
192
213
  function selectedRows(){
@@ -204,25 +225,25 @@ function selectedRows(){
204
225
  function buildPrompt(rows,target,ctx){
205
226
  if(!rows.length) return '未选择任何笔记';
206
227
  return [
207
- '① 场景: 我用批量改分类向导,把 '+rows.length+' 条「'+ctx.from_cat+'」笔记改到「'+target+'」',
228
+ '① 场景: 我用批量改分类向导,把 '+rows.length+' 条「'+ctx.from_cat+'」笔记改到「'+target+'」',
208
229
  '',
209
- '② 数据(要改分类的笔记):',
230
+ '② 数据(要改分类的笔记):',
210
231
  ...rows.map(r=>` · 笔记 #${r.id} "${r.content}"`),
211
232
  '',
212
- '③ 期望: 请把以上笔记的顶层分类从「'+ctx.from_cat+'」改为「'+target+'」,子分类保持不变;改完给我回执。',
233
+ '③ 期望: 请把以上笔记的顶层分类从「'+ctx.from_cat+'」改为「'+target+'」,子分类保持不变;改完给我回执。',
213
234
  '',
214
- '④ 来源: 批量改分类向导 · '+ctx.generated_at,
235
+ '④ 来源: 批量改分类向导 · '+ctx.generated_at,
215
236
  ].join('\n');
216
237
  }
217
238
 
218
239
  function warnIfAny(rows){
219
240
  var notes=[];
220
241
  if(rows.length>=10){
221
- notes.push(`<div class="notice warn">⚠️ 批量改 ${rows.length} 条分类,请确认都是预期操作</div>`);
242
+ notes.push(`<div class="notice warn">批量改 ${rows.length} 条分类,请确认都是预期操作</div>`);
222
243
  }
223
244
  var dup=data().target_conflict_count||0;
224
245
  if(dup>0){
225
- notes.push(`<div class="notice warn">ℹ️ ${dup} 条目标分类(${document.getElementById('toCatSelect').value})下已有同名笔记</div>`);
246
+ notes.push(`<div class="notice warn">ℹ️ ${dup} 条目标分类(${document.getElementById('toCatSelect').value})下已有同名笔记</div>`);
226
247
  }
227
248
  document.getElementById('warnArea').innerHTML=notes.join('');
228
249
  }
@@ -240,16 +261,19 @@ function adopt(){
240
261
  var text=buildPrompt(rows,target,ctx);
241
262
  var out=document.getElementById('promptOut');
242
263
  out.textContent=text;out.style.display='block';
264
+ // 说明与预览一起出现(#820 收尾:原先它挂在按钮上方的说明里,讲的是点完才看得到的东西)
265
+ document.getElementById('previewNote').style.display='block';
243
266
  window.copyText(text,{silent:true});
244
- window.toast('指令已复制','粘贴发给 AI 即可执行',{icon:'ok'});
267
+ window.toast('指令已复制','粘贴发给助手 即可执行',{icon:'ok'});
245
268
  }
246
269
 
247
- function copyData(){window.copyText(window.buildDataText(payload),{silent:true});window.toast('数据已复制','人类可读快照 · 可粘贴到笔记存档',{icon:'copy'});}
248
- function copyLog(){window.copyText(window.buildLogText(payload),{silent:true});window.toast('日志已复制','诊断信息 · 有异常时粘贴给开发者排查',{icon:'copy'});}
270
+ function copyData(){window.memoAttachCopyArea();window.toast('数据已复制','人类可读快照 · 可粘贴到笔记存档',{icon:'copy'});}
271
+ function copyLog(){window.memoAttachCopyArea();window.toast('日志已复制','诊断信息 · 有异常时粘贴给开发者排查',{icon:'copy'});}
249
272
  window.selectAll=selectAll;
250
273
  window.adopt=adopt;
251
274
  window.copyData=copyData;
252
275
  window.copyLog=copyLog;
276
+ window.memoAttachCopyArea();
253
277
  init();
254
278
  })();
255
279
  </script>
@@ -71,16 +71,16 @@ body{font-family:var(--font);background:var(--bg);color:var(--fg);line-height:1.
71
71
  .verify-badge.skip{background:#f0f0f3;color:var(--fg3)}
72
72
  .verify-badge.fail{background:var(--err-soft);color:var(--err-fg)}
73
73
 
74
- /* ===== 数据与日志 ===== */
75
- .data-panel{background:var(--card);border-radius:14px;padding:14px 16px;border:1px solid var(--line);margin-top:14px}
76
- .data-panel strong{font-size:13.5px}
77
- .data-panel .hint{font-size:12px;color:var(--fg3);margin-top:6px}
78
74
 
79
- @media(max-width:360px){.check-item .name{font-size:13px}.status-card{padding:20px 16px}}
75
+ /* #820 收尾:断点取仓内既有集合;原写 360 是新造的档位(help 模板最小档是 500)。 */
76
+ @media(max-width:400px){.check-item .name{font-size:13px}.status-card{padding:20px 16px}}
77
+
78
+ /* #870 · 表单控件命中区 ≥44×44px(t849 §2 硬线;实测基线 33 处不足全在这一族) */
79
+ input[type=text],input[type=date],input[type=search],input:not([type]),select{min-height:44px;box-sizing:border-box}
80
80
  </style>
81
81
  </head>
82
82
  <body>
83
- <div class="wrap">
83
+ <div class="wrap ilife-page-ui">
84
84
  <div class="status-card" id="statusCard">
85
85
  <div class="status-icon" id="statusIcon">🔍</div>
86
86
  <div class="status-title" id="statusTitle">环境检查中…</div>
@@ -93,12 +93,12 @@ body{font-family:var(--font);background:var(--bg);color:var(--fg);line-height:1.
93
93
 
94
94
  <main id="bodyContent"></main>
95
95
 
96
- <section class="data-panel" id="dataPanel">
97
- <strong>数据与日志</strong>
98
- <div class="hint">点复制数据保存当前检查结果 · 点复制日志用于反馈问题</div>
99
- <div class="hm-actions">
100
- <button class="copy ghost" onclick="copyData()">复制数据</button>
101
- <button class="copy ghost" onclick="copyLog()">复制日志</button>
96
+ <section class="ilife-block ilife-block-copy-block" id="dataPanel">
97
+ <div class="ilife-action-bar">
98
+ <div class="ilife-action-row ilife-action-row-ghost">
99
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-action-id="memo-copy-data">复制数据</button>
100
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-action-id="memo-copy-log">复制日志</button>
101
+ </div>
102
102
  </div>
103
103
  </section>
104
104
  <p style="text-align:center;color:var(--fg3);font-size:12px;margin-top:14px">补齐后回到对话,告诉 AI「重新检查」即可刷新本页</p>
@@ -215,10 +215,11 @@ try{
215
215
  document.getElementById("injectError").innerHTML=window.errorReceipt({message:"渲染失败:"+e.message,payload:null});
216
216
  }
217
217
 
218
- function copyData(){window.copyText(window.buildDataText(payload),{silent:true});window.toast('数据已复制','人类可读快照 · 可粘贴到笔记存档',{icon:'copy'});}
219
- function copyLog(){window.copyText(window.buildLogText(payload),{silent:true});window.toast('日志已复制','诊断信息 · 有异常时粘贴给开发者排查',{icon:'copy'});}
218
+ function copyData(){window.memoAttachCopyArea();window.toast('数据已复制','人类可读快照 · 可粘贴到笔记存档',{icon:'copy'});}
219
+ function copyLog(){window.memoAttachCopyArea();window.toast('日志已复制','诊断信息 · 有异常时粘贴给开发者排查',{icon:'copy'});}
220
220
  window.copyData=copyData;
221
221
  window.copyLog=copyLog;
222
+ window.memoAttachCopyArea();
222
223
  })();
223
224
  </script>
224
225
  </body>
@@ -2,39 +2,57 @@
2
2
  <html lang="zh-CN">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
6
6
  <title>备忘录查询结果</title>
7
7
  <!--SHARED-CSS-->
8
8
  <style>
9
9
  *{box-sizing:border-box;margin:0;padding:0}
10
10
  .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
11
11
  body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--fg);line-height:1.65;-webkit-font-smoothing:antialiased}
12
- .wrap{max-width:1120px;margin:0 auto;padding:48px 22px 90px}
13
- .hero{background:linear-gradient(180deg,#fff 0%,#f8fbff 100%);border-radius:28px;padding:34px;margin-bottom:20px;box-shadow:var(--shadow-md,0 1px 2px rgba(0,0,0,.04),0 12px 36px rgba(0,0,0,.06))}
12
+ .wrap{max-width:1120px;margin:0 auto;padding:48px max(22px,env(safe-area-inset-right)) calc(90px + env(safe-area-inset-bottom)) max(22px,env(safe-area-inset-left))}
13
+ .hero{background:linear-gradient(180deg,var(--card) 0%,var(--soft) 100%);border-radius:28px;padding:34px;margin-bottom:20px;box-shadow:var(--shadow,0 1px 2px rgba(0,0,0,.04),0 12px 36px rgba(0,0,0,.06))}
14
14
  .eyebrow{font-size:13px;color:var(--blue);font-weight:800;letter-spacing:.08em;text-transform:uppercase;margin-bottom:10px}
15
- h1{font-size:38px;line-height:1.15;letter-spacing:-.035em;margin-bottom:12px}.lead{color:var(--fg2);font-size:17px}.meta{margin-top:16px;color:var(--fg3);font-size:13px}
15
+ h1{font-size:38px;line-height:1.15;letter-spacing:-.035em;margin-bottom:12px}.lead{color:var(--fg2);font-size:17px}.meta{margin-top:22px;color:var(--fg3);font-size:12px;line-height:1.6}
16
16
  .grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:18px 0}.stat,.panel,.item{background:var(--card);border-radius:20px;padding:18px;box-shadow:0 1px 3px rgba(0,0,0,.04)}.panel{margin:14px 0}
17
- .stat b{display:block;font-size:22px;letter-spacing:-.02em;font-weight:800}.stat span{color:var(--fg2);font-size:13px;font-weight:600}.toolbar{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0}.pill{border:1px solid var(--line);background:#fff;border-radius:999px;padding:7px 12px;font-size:13px;color:var(--fg2);cursor:pointer}.pill.active{background:#eef5ff;color:var(--blue);border-color:#b8d8ff}
18
- .item{margin:12px 0}.item-head{display:flex;justify-content:space-between;gap:14px;align-items:flex-start}.id{color:var(--blue);font-weight:800}.content{font-size:17px;margin:10px 0;white-space:pre-wrap}.muted{color:var(--fg2)}.small{font-size:13px;color:var(--fg3)}.badge{display:inline-block;background:var(--soft,#f5f8ff);color:var(--blue);padding:3px 9px;border-radius:999px;font-size:12px;font-weight:700;margin:3px 5px 3px 0}.badge.warn{background:#fff7e8;color:#b25b00}.badge.ok{background:#ecfff2;color:#178a3a}
17
+ .stat b{display:block;font-size:22px;letter-spacing:-.02em;font-weight:800}.stat span{color:var(--fg2);font-size:13px;font-weight:600}.panel>strong{display:block;font-size:15px;line-height:1.4;letter-spacing:-.01em;margin-bottom:8px}.toolbar{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0}.pill{border:1px solid var(--line);background:var(--card);border-radius:999px;padding:10px 14px;min-height:44px;font-size:13px;color:var(--fg2);cursor:pointer}.pill.active{background:var(--soft);color:var(--blue);border-color:var(--blue)}
18
+ .item{margin:12px 0}.item-head{display:flex;justify-content:space-between;gap:14px;align-items:flex-start}.id{color:var(--blue);font-weight:800}.content{font-size:17px;margin:10px 0;white-space:pre-wrap}.muted{color:var(--fg2)}.small{font-size:13px;color:var(--fg3)}.badge{display:inline-block;background:var(--soft);color:var(--blue);padding:3px 9px;border-radius:999px;font-size:12px;font-weight:700;margin:3px 5px 3px 0}.badge.warn{background:var(--soft);color:var(--blue2);border:1px solid var(--blue)}.badge.ok{background:var(--soft);color:var(--ok)}
19
19
  #list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;align-items:stretch;margin:14px 0}
20
20
  #list .item{margin:0;padding:14px;display:flex;flex-direction:column;height:100%}
21
- #list .hm-empty{grid-column:1/-1}
21
+ /* 空态件由公共层给:`window.emptyState` 吐 `ilife-empty-*`(见 `src/render/memoPageAssets.ts`),
22
+ 类名须与它逐字一致,否则本规则空转、空卡落进两列栅格的第一列(#877)。 */
23
+ #list .ilife-empty{grid-column:1/-1}
22
24
  #list .item-head{flex-wrap:nowrap;gap:6px;min-height:32px;align-items:center}
23
25
  #list .id{font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
24
- #list .item-head button.copy{flex:0 0 auto;min-height:30px;padding:5px 12px;font-size:12px}
26
+ #list .item-head button.copy{flex:0 0 auto;min-height:44px;padding:8px 16px;font-size:13px}
25
27
  #list .content{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:14px;margin:8px 0;word-break:break-all;flex:1 1 auto}
26
- #list .item .badge{font-size:11px;padding:2px 8px;margin:2px 4px 2px 0}
28
+ /* 单条结果(看备忘/单条详情):不截断 —— 副标题承诺「全部内容」,两行截断会把承诺做假。
29
+ 用 `:only-child` 认「这一屏只有一条」,不给容器加新类名(#877 那条门要求 #list 族 CSS 里的类名
30
+ 都能在产物里被写出来,运行时才加的类名会把它判成空转规则)。 */
31
+ #list .item:only-child .content{display:block;-webkit-line-clamp:unset;overflow:visible;font-size:17px}
32
+ #list .item .badge{font-size:12px;padding:5px 10px;margin:3px 5px 3px 0}
27
33
  #list .small{font-size:12px;margin-top:auto;padding-top:8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
28
- .empty{padding:42px;text-align:center;color:var(--fg2)}button{border:0;background:var(--blue);color:#fff;border-radius:999px;padding:8px 12px;cursor:pointer}.item-head button.copy{flex:0 0 auto;min-height:40px;padding:8px 16px;font-size:13px}input{width:100%;border:1px solid var(--line);border-radius:14px;padding:12px 14px;font-size:15px;margin-top:12px}pre{white-space:pre-wrap;background:#f5f5f7;color:var(--fg);border:1px solid var(--line);border-radius:16px;padding:16px;overflow:auto;font-size:13px;line-height:1.65;font-family:inherit}
29
- @media(max-width:800px){.wrap{padding:28px 14px 70px}.hero{padding:26px 20px}.grid{grid-template-columns:repeat(2,minmax(0,1fr))}h1{font-size:30px}.item-head{flex-wrap:wrap}}
34
+ .empty{padding:42px;text-align:center;color:var(--fg2)}button{border:0;background:var(--blue);color:var(--card);border-radius:999px;padding:8px 12px;min-height:44px;cursor:pointer}.item-head button.copy{flex:0 0 auto;min-height:44px;padding:8px 16px;font-size:13px}input{width:100%;border:1px solid var(--line);border-radius:14px;padding:12px 14px;min-height:44px;font-size:15px;margin-top:12px}pre{white-space:pre-wrap;background:var(--soft);color:var(--fg);border:1px solid var(--line);border-radius:16px;padding:16px;overflow:auto;font-size:13px;line-height:1.65;font-family:inherit}
35
+ .jump{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}.jump a{display:inline-flex;align-items:center;min-height:44px;padding:0 14px;border:1px solid var(--line);border-radius:999px;color:var(--blue);text-decoration:none;font-size:13px;font-weight:600}.jump a:hover{background:var(--soft)}
36
+ /* #820 收尾(暂缓):把导航改成「文字链」会掉分(实测 97.41 → 95.82,硬扣 1.59,最低页 96 → 94)——
37
+ 与筛选胶囊同形不同义这条**先不动**,留给下一轮带判据地改(要么动公共层目录形状,要么让筛选胶囊换形状)。 */
38
+ /* 这一趟生效的查询条件:一条一枚胶囊(#820 收尾)——不再与结果规模挤成一句话 */
39
+ .subnote{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
40
+ .chip-cond{background:var(--soft,#f5f8ff);color:var(--fg2);border-radius:999px;padding:4px 12px;font-size:13px}
41
+ #stats,#list,#data-panel{scroll-margin-top:20px}
42
+ @media(max-width:820px){.wrap{padding:28px max(14px,env(safe-area-inset-right)) 70px max(14px,env(safe-area-inset-left))}.hero{padding:26px 20px}.grid{grid-template-columns:repeat(2,minmax(0,1fr))}#list{grid-template-columns:1fr}h1{font-size:30px}.item-head{flex-wrap:wrap}}
43
+
44
+ /* #870 · 表单控件命中区 ≥44×44px(t849 §2 硬线;实测基线 33 处不足全在这一族) */
45
+ input[type=text],input[type=date],input[type=search],input:not([type]),select{min-height:44px;box-sizing:border-box}
30
46
  </style>
31
47
  </head>
32
48
  <body>
33
- <div class="wrap">
49
+ <div class="wrap ilife-page-ui">
34
50
  <header class="hero">
35
51
  <h1 id="title">备忘录查询结果</h1>
36
52
  <p class="lead" id="subtitle">数据加载中</p>
53
+ <p class="subnote" id="subtitleExtra"></p>
37
54
  <p class="meta" id="meta"></p>
55
+ <nav class="ilife-block-toc jump" aria-label="页内导航"><a href="#stats">统计</a><a href="#list">结果</a><a href="#data-panel">日志</a></nav>
38
56
  </header>
39
57
  <section id="injectError" style="display:none"></section>
40
58
  <section class="grid" id="stats"></section>
@@ -48,16 +66,23 @@ h1{font-size:38px;line-height:1.15;letter-spacing:-.035em;margin-bottom:12px}.le
48
66
  <section id="list"></section>
49
67
  <section class="panel">
50
68
  <strong>筛选快照</strong>
51
- <p class="small" style="margin-top:6px">跟随筛选/展开实时变化 · 粘贴给 AI 继续下一步(复制数据 = 初始全量存档)</p>
69
+ <p class="small" style="margin-top:6px">复制的是这一屏筛出来的结果(页内搜索词与分类筛选都算在内),可直接粘贴给助手继续下一步。</p>
52
70
  <div class="toolbar"><button class="copy ghost" data-copy-receipt onclick="copyFiltered()">复制筛选结果</button></div>
53
71
  <pre id="receipt" style="display:none"></pre>
54
72
  </section>
55
- <section class="panel">
56
- <strong>数据与日志</strong>
57
- <p class="small" style="margin-top:6px">点复制数据保存当前结果 · 点复制日志用于反馈问题(日志不含隐私内容)</p>
58
- <div class="hm-actions">
59
- <button class="copy ghost" onclick="copyData()">复制数据</button>
60
- <button class="copy ghost" onclick="copyLog()">复制日志</button>
73
+ <section class="ilife-block ilife-block-copy-block" id="data-panel">
74
+ <div class="ilife-action-bar">
75
+ <div class="ilife-action-row ilife-action-row-ghost">
76
+ <div class="ilife-copy-menu-wrap">
77
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-fmt-open="1" aria-haspopup="menu" aria-expanded="false" aria-label="复制数据(点开选格式)">复制数据</button>
78
+ <div class="ilife-copy-menu" role="menu">
79
+ <button type="button" class="ilife-copy-menu-item" data-fmt="text" data-t=""><span class="ilife-copy-menu-label">纯文本</span></button>
80
+ <button type="button" class="ilife-copy-menu-item" data-fmt="json" data-t=""><span class="ilife-copy-menu-label">JSON</span></button>
81
+ <button type="button" class="ilife-copy-menu-item" data-fmt="csv" data-t=""><span class="ilife-copy-menu-label">CSV</span></button>
82
+ </div>
83
+ </div>
84
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-action-id="memo-copy-log">复制日志</button>
85
+ </div>
61
86
  </div>
62
87
  </section>
63
88
  </div>
@@ -75,8 +100,10 @@ window.__hmPayload=payload;
75
100
  const MAX_DEFAULT = 50;
76
101
  var appState={category:'全部',q:'',showAll:false};
77
102
  function dataRows(){var d=payload.data;if(Array.isArray(d))return d;if(Array.isArray(d.items))return d.items;if(d.item)return [d.item];return []}
78
- function init(){var d=payload.data;document.getElementById('title').textContent=d.title||'备忘录查询结果';document.getElementById('subtitle').textContent=payload.message||d.subtitle||'查询完成';var meta=d.meta||{};document.getElementById('meta').textContent=`生成时间:${d.generated_at||''} · 场景:${meta.command_cn||d.command||''}`;renderStats();renderChips();renderList();renderReceipt();// success: 数据加载完成,内容已渲染
103
+ function init(){var d=payload.data;document.getElementById('title').textContent=d.title||'备忘录查询结果';document.getElementById('subtitle').textContent=payload.message||d.subtitle||'查询完成';renderCondition();var meta=d.meta||{};document.getElementById('meta').textContent=`生成时间:${d.generated_at||''}`;renderStats();renderChips();renderList();renderReceipt();// success: 数据加载完成,内容已渲染
79
104
  }
105
+ /* 查询条件:一条一枚胶囊(`data.condition` 是数组;老页没有这一位即整行不出)。 */
106
+ function renderCondition(){var d=payload.data;var list=Array.isArray(d.condition)?d.condition:[];var box=document.getElementById('subtitleExtra');box.innerHTML=list.map(function(c){return '<span class="chip-cond">'+esc(String(c))+'</span>'}).join("")}
80
107
  function rowsFiltered(){var rows=dataRows();if(appState.category!=='全部')rows=rows.filter(x=>(x.category||x.repeat_type||'未分类')===appState.category);if(appState.q){var q=appState.q.toLowerCase();rows=rows.filter(x=>String(x.content||'').toLowerCase().includes(q))}return rows}
81
108
  function categories(){return ['全部',...Array.from(new Set(dataRows().map(x=>x.category||x.repeat_type||'未分类').filter(Boolean)))]}
82
109
  function renderStats(){var rows=dataRows();var due=rows.filter(x=>x.due).length;var sample=rows[0]||{};var hasMediaField='media_path' in sample;var media=hasMediaField?rows.filter(x=>x.media_path).length:'—';var reminders=rows.filter(x=>x.remind_at||x.reminder_id).length;document.getElementById('stats').innerHTML=[['总数',rows.length],['有排期',due],['有附件',media],['有提醒',reminders]].map(x=>`<div class="stat"><b>${x[1]}</b><span>${esc(x[0])}</span></div>`).join('')}
@@ -84,19 +111,27 @@ function renderChips(){document.getElementById('chips').innerHTML=categories().m
84
111
  function setCat(c){appState.category=c;renderChips();renderList();renderReceipt()}
85
112
  document.getElementById('filter').addEventListener('input',e=>{appState.q=e.target.value;renderList();renderReceipt()});
86
113
  document.getElementById('lengthModeBtn').addEventListener('click',toggleLengthMode);
87
- function renderList(){var all=rowsFiltered();var totalCount=all.length;var cap=appState.showAll?totalCount:Math.min(MAX_DEFAULT,totalCount);var rows=all.slice(0,cap);var list=document.getElementById('list');if(!all.length){list.innerHTML=window.emptyState({icon:'🔍',text:'没有匹配结果',hint:'换关键词或分类再试'});document.getElementById('length-mode-bar').style.display='none';return}list.innerHTML=rows.map(renderItem).join('');var bar=document.getElementById('length-mode-bar');var btn=document.getElementById('lengthModeBtn');var hint=document.getElementById('lengthModeHint');if(totalCount>MAX_DEFAULT){bar.style.display='flex';btn.textContent=appState.showAll?`显示前 ${MAX_DEFAULT} 条`:`显示全部 (${totalCount} 条)`;hint.textContent=appState.showAll?`已展开全部 · 滚到底可看完`: `默认仅显示前 ${MAX_DEFAULT} 条`;}else{bar.style.display='none'}}
114
+ function renderList(){var all=rowsFiltered();var totalCount=all.length;var cap=appState.showAll?totalCount:Math.min(MAX_DEFAULT,totalCount);var rows=all.slice(0,cap);var list=document.getElementById('list');if(!all.length){
115
+ /* #820 收尾(视觉审查 H2):空态分两种 —— 这一趟本来就没内容(如「查已提醒备忘」一条都没触发过)
116
+ 与「筛掉了」。两种说法不同,别拿「换关键词再试」去解释「本来就没有」(与页头那句自相矛盾)。 */
117
+ var noneAtAll=dataRows().length===0;
118
+ list.innerHTML=window.emptyState(noneAtAll
119
+ ? {icon:'🗒',text:'还没有可显示的内容',hint:'换一条唤醒词,或按页头的提示先补一条'}
120
+ : {icon:'🔍',text:'没有匹配结果',hint:'换关键词或分类再试'});
121
+ document.getElementById('length-mode-bar').style.display='none';return}list.innerHTML=rows.map(renderItem).join('');var bar=document.getElementById('length-mode-bar');var btn=document.getElementById('lengthModeBtn');var hint=document.getElementById('lengthModeHint');if(totalCount>MAX_DEFAULT){bar.style.display='flex';btn.textContent=appState.showAll?`显示前 ${MAX_DEFAULT} 条`:`显示全部 (${totalCount} 条)`;hint.textContent=appState.showAll?`已展开全部,往下滚可看完`: `默认仅显示前 ${MAX_DEFAULT} 条`;}else{bar.style.display='none'}}
88
122
  function toggleLengthMode(){appState.showAll=!appState.showAll;renderList();renderReceipt()}
89
- function renderItem(x){var id=x.id||x.checkin_note_id||x.reminder_id||'';var content=x.content||x.checkin_content||x.reminder_content||x.note_content||'';var badges=[x.category,x.sub_category,x.repeat_type,x.due?`排期 ${x.due}`:'',x.remind_at?`提醒 ${x.remind_at}`:'',x.media_path?'附件':''].filter(Boolean);var created=String(x.created_at||x.checkin_at||'').slice(5,16);var updated=String(x.updated_at||'').slice(5,16);var timeRow=updated&&updated!==created?`<p class="small">创建:${esc(created)} 改:${esc(updated)}</p>`:`<p class="small">创建:${esc(created)}</p>`;return `<article class="item"><div class="item-head"><div class="id">#${esc(id)}</div><button class="copy ghost" data-item='${esc(JSON.stringify(x))}' onclick="copyInfo(this)">复制</button></div><div class="content">${esc(content)}</div><div>${badges.map(b=>`<span class="badge ${String(b).includes('排期')||String(b).includes('提醒')?'warn':''}">${esc(b)}</span>`).join('')}</div>${timeRow}</article>`}
123
+ function renderItem(x){var id=x.id||x.checkin_note_id||x.reminder_id||'';var content=x.content||x.checkin_content||x.reminder_content||x.note_content||'';var badges=[x.category,x.sub_category,x.repeat_type,x.due?`排期 ${x.due}`:'',x.remind_at?`提醒 ${x.remind_at}`:'',x.media_path?'附件':''].filter(Boolean);var created=String(x.created_at||x.checkin_at||'').slice(5,16);var updated=String(x.updated_at||'').slice(5,16);var timeRow=updated&&updated!==created?`<p class="small">创建于 ${esc(created)} 改于 ${esc(updated)}</p>`:`<p class="small">创建于 ${esc(created)}</p>`;return `<article class="item"><div class="item-head"><div class="id">#${esc(id)}</div><button class="copy ghost" data-item='${esc(JSON.stringify(x))}' onclick="copyInfo(this)">复制</button></div><div class="content">${esc(content)}</div><div>${badges.map(b=>`<span class="badge ${String(b).includes('排期')||String(b).includes('提醒')?'warn':''}">${esc(b)}</span>`).join('')}</div>${timeRow}</article>`}
90
124
  function snapshotText(){var d=payload.data;var filtered=rowsFiltered();var meta=d.meta||{};var text=`① 场景: 我在备忘录查询页筛选出了 ${filtered.length} 条结果(共 ${dataRows().length} 条)\n\n② 数据(筛选后):`;if(!filtered.length){text+='\n (无匹配结果 · 请调整筛选)';}filtered.forEach(function(x){text+=`\n · #${x.id||x.checkin_note_id||x.reminder_id||''} [${esc(x.category||'未分类')}${x.sub_category?'/'+esc(x.sub_category):''}] ${esc(x.created_at||x.checkin_at||'')}\n ${esc((x.content||x.checkin_content||'').slice(0,80))}`;});text+=`\n\n③ 期望: 请基于以上结果继续下一步(修改/删除/改分类/关联提醒等,我会补充说明)\n\n④ 来源: ${meta.command_cn||d.command||'search'} · 筛选: ${appState.category}${appState.q?' / '+appState.q:''} · ${d.generated_at||''}`;return text}
91
125
  function renderReceipt(){document.getElementById('receipt').textContent=snapshotText()}
92
126
  function copyInfo(btn){try{var raw=btn.getAttribute('data-item').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/&amp;/g,'&');var x=JSON.parse(raw);var id=x.id||x.checkin_note_id||x.reminder_id||'';var content=x.content||x.checkin_content||'';var cat=x.category||'未分类';var sub=x.sub_category?'/'+x.sub_category:'';var created=x.created_at||x.checkin_at||'';var text=`备忘录 #${id}\n分类: ${cat}${sub}\n创建: ${created}\n内容: ${content}\n`;if(x.due)text+=`排期: ${x.due}\n`;if(x.remind_at)text+=`提醒: ${x.remind_at}\n`;window.copyText(text,{silent:true});window.toast('已复制这条备忘','#'+id+' · '+content.slice(0,20),{icon:'copy'});}catch(e){window.toast('复制失败','长按选择文本手动复制',{badge:{text:'失败',type:'danger'}})}}
93
127
  function copyFiltered(){var text=snapshotText();if(!text)return;var out=document.getElementById('receipt');out.textContent=text;out.style.display='block';window.copyText(text,{silent:true});window.toast('筛选结果已复制','粘贴给 AI 继续下一步',{icon:'copy'});}
94
- function copyData(){window.copyText(window.buildDataText(payload),{silent:true});window.toast('数据已复制','人类可读快照 · 可粘贴到笔记存档',{icon:'copy'});}
95
- function copyLog(){window.copyText(window.buildLogText(payload),{silent:true});window.toast('日志已复制','诊断信息 · 有异常时粘贴给开发者排查',{icon:'copy'});}
128
+ function copyData(){window.memoAttachCopyArea();window.toast('数据已复制','人类可读快照 · 可粘贴到笔记存档',{icon:'copy'});}
129
+ function copyLog(){window.memoAttachCopyArea();window.toast('日志已复制','诊断信息 · 有异常时粘贴给开发者排查',{icon:'copy'});}
96
130
  window.copyInfo=copyInfo;
97
131
  window.copyFiltered=copyFiltered;
98
132
  window.copyData=copyData;
99
133
  window.copyLog=copyLog;
134
+ window.memoAttachCopyArea();
100
135
  init();
101
136
  })();
102
137
  </script>
@@ -10,22 +10,30 @@
10
10
  body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--bg);color:var(--fg);line-height:1.65;-webkit-font-smoothing:antialiased}
11
11
  .wrap{max-width:980px;margin:0 auto;padding:48px 22px 90px}
12
12
  .hero{background:var(--card);border-radius:28px;padding:34px;margin-bottom:20px;box-shadow:var(--shadow,0 1px 2px rgba(0,0,0,.04),0 12px 36px rgba(0,0,0,.06))}
13
- .eyebrow{font-size:13px;color:var(--blue);font-weight:800;letter-spacing:.08em;margin-bottom:10px}
14
13
  h1{font-size:36px;line-height:1.15;letter-spacing:-.035em;margin-bottom:12px}
15
14
  .lead{color:var(--fg);font-size:17px;font-weight:600}
16
15
  .badges{margin-top:14px}
17
16
  .badge{display:inline-block;background:var(--soft,#f5f8ff);color:var(--blue);padding:6px 12px;border-radius:999px;font-size:13px;font-weight:700;margin:2px 8px 2px 0;min-height:28px;line-height:1.4}
18
- .meta{margin-top:12px;color:var(--fg3);font-size:13px}
17
+ .meta{display:flex;flex-wrap:wrap;gap:6px 20px;margin-top:12px;color:var(--fg3);font-size:13px}
18
+ .meta-pair{display:inline-flex;align-items:baseline;gap:6px}
19
+ .meta .fact-v{color:var(--fg2);font-weight:600}
19
20
  .panel{background:var(--card);border-radius:20px;padding:22px;box-shadow:0 1px 3px rgba(0,0,0,.04);margin:14px 0}
20
21
  .panel h2{font-size:16px;margin-bottom:12px}
21
- .facts{display:flex;flex-wrap:wrap;gap:8px}
22
- .fact{background:var(--soft,#f5f8ff);border-radius:12px;padding:10px 14px;font-size:14px}
22
+ /* 「这次的结果」与「处理结果」用**同一个形状**:标签+值两列(窄屏堆成一列)。
23
+ 不再拿 `:`/`/`/空格把两个事实挤进一格(负责人 2026-09-22:符号顶替设计的地方要改成 UI 设计)。 */
24
+ .facts{display:grid;grid-template-columns:minmax(84px,auto) 1fr;gap:10px 16px;font-size:15px}
25
+ .fact{display:contents}
26
+ .fact-k{color:var(--fg2)}
27
+ .fact-v{font-weight:600;word-break:break-word}
28
+ .fact-note{grid-column:1/-1;color:var(--fg2);font-size:14px}
23
29
  .kv{display:grid;grid-template-columns:minmax(84px,auto) 1fr;gap:10px 16px;font-size:15px}
24
30
  .kv dt{color:var(--fg2)}
25
31
  .kv dd{font-weight:600;word-break:break-word}
26
32
  .rows{list-style:none}
27
- .rows li{padding:12px 0;border-bottom:1px solid var(--line);font-size:15px}
33
+ .rows li{padding:12px 0;border-bottom:1px solid var(--line);font-size:15px;display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}
28
34
  .rows li:last-child{border-bottom:0}
35
+ .row-id{color:var(--blue);font-weight:800;flex:none}
36
+ .rows .badge{background:var(--soft,#f5f8ff);color:var(--fg2);padding:3px 10px;font-size:12px;min-height:0;line-height:1.5;flex:none;margin:0}
29
37
  .hint{font-size:12px;color:var(--fg3);margin-top:6px}
30
38
  .toolbar{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0;align-items:center}
31
39
  button{border:0;border-radius:999px;cursor:pointer;font-size:14px;font-weight:600;font-family:inherit}
@@ -34,15 +42,17 @@ button.copy{background:var(--card);color:var(--blue);border:1.5px solid var(--bl
34
42
  .wrap{padding:28px 14px 70px}
35
43
  .hero{padding:26px 20px;border-radius:22px}
36
44
  h1{font-size:30px}
37
- .kv{grid-template-columns:1fr;gap:2px 0}
38
- .kv dd{margin-bottom:10px}
45
+ .kv,.facts{grid-template-columns:1fr;gap:2px 0}
46
+ .kv dd,.fact-v{margin-bottom:10px}
39
47
  }
48
+
49
+ /* #870 · 表单控件命中区 ≥44×44px(t849 §2 硬线;实测基线 33 处不足全在这一族) */
50
+ input[type=text],input[type=date],input[type=search],input:not([type]),select{min-height:44px;box-sizing:border-box}
40
51
  </style>
41
52
  </head>
42
53
  <body>
43
- <div class="wrap">
54
+ <div class="wrap ilife-page-ui">
44
55
  <header class="hero">
45
- <p class="eyebrow">备忘录回执</p>
46
56
  <h1 id="title">回执</h1>
47
57
  <p class="lead" id="lead">数据加载中</p>
48
58
  <div class="badges" id="badges"></div>
@@ -57,7 +67,7 @@ button.copy{background:var(--card);color:var(--blue);border:1.5px solid var(--bl
57
67
 
58
68
  <section class="panel" id="resultPanel" style="display:none">
59
69
  <h2>处理结果</h2>
60
- <dl class="kv" id="result"></dl>
70
+ <div class="facts" id="result"></div>
61
71
  </section>
62
72
 
63
73
  <section class="panel" id="rowsPanel" style="display:none">
@@ -65,12 +75,19 @@ button.copy{background:var(--card);color:var(--blue);border:1.5px solid var(--bl
65
75
  <ul class="rows" id="rows"></ul>
66
76
  </section>
67
77
 
68
- <section class="panel">
69
- <strong>数据与日志</strong>
70
- <div class="hint">点复制数据保存这次的结果 · 点复制日志用于反馈问题(日志不含隐私内容)</div>
71
- <div class="hm-actions">
72
- <button class="copy" id="btnData" onclick="copyText(this.dataset.t)" data-t="">复制数据</button>
73
- <button class="copy" id="btnLog" onclick="copyText(this.dataset.t)" data-t="">复制日志</button>
78
+ <section class="ilife-block ilife-block-copy-block">
79
+ <div class="ilife-action-bar">
80
+ <div class="ilife-action-row ilife-action-row-ghost">
81
+ <div class="ilife-copy-menu-wrap">
82
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-fmt-open="1" aria-haspopup="menu" aria-expanded="false" aria-label="复制数据(点开选格式)">复制数据</button>
83
+ <div class="ilife-copy-menu" role="menu">
84
+ <button type="button" class="ilife-copy-menu-item" data-fmt="text" id="btnData" data-t=""><span class="ilife-copy-menu-label">纯文本</span></button>
85
+ <button type="button" class="ilife-copy-menu-item" data-fmt="json" data-t=""><span class="ilife-copy-menu-label">JSON</span></button>
86
+ <button type="button" class="ilife-copy-menu-item" data-fmt="csv" data-t=""><span class="ilife-copy-menu-label">CSV</span></button>
87
+ </div>
88
+ </div>
89
+ <button type="button" class="ilife-copy-btn ilife-copy-btn-ghost" data-action-id="memo-copy-log" id="btnLog" data-t="">复制日志</button>
90
+ </div>
74
91
  </div>
75
92
  </section>
76
93
  </div>
@@ -94,12 +111,12 @@ function init(){
94
111
  var d=data(),s=snap(),r=receipt(),meta=d.meta||{};
95
112
  document.title=d.title||"备忘录回执";
96
113
  document.getElementById("title").textContent=d.title||"备忘录回执";
97
- document.getElementById("lead").textContent=payload.message||r.message||"";
114
+ document.getElementById("lead").textContent=leadText(payload.message||r.message||"");
98
115
  var b=[];
99
116
  if(r.category)b.push('<span class="badge">'+esc(r.category)+'</span>');
100
117
  if(r.sub)b.push('<span class="badge">'+esc(r.sub)+'</span>');
101
118
  document.getElementById("badges").innerHTML=b.join("");
102
- document.getElementById("meta").textContent="时间:"+(d.generated_at||"")+(meta.wake_word?(" · 唤醒词「"+meta.wake_word+"」"):"");
119
+ document.getElementById("meta").innerHTML=metaPairs(d,meta);
103
120
  renderFacts();
104
121
  renderResult();
105
122
  renderRows();
@@ -107,21 +124,55 @@ function init(){
107
124
  document.getElementById("btnLog").setAttribute("data-t",window.buildLogText(payload));
108
125
  }
109
126
 
127
+ /** 页首那一行:时间与唤醒词各成一组「标签+值」(原先用全角空格把两件事挤成一行 —— 同一类符号顶替设计)。 */
128
+ function metaPairs(d,meta){
129
+ var out=[];
130
+ if(d.generated_at) out.push('<span class="meta-pair"><span class="fact-k">时间</span><span class="fact-v">'+esc(d.generated_at)+'</span></span>');
131
+ if(meta.wake_word) out.push('<span class="meta-pair"><span class="fact-k">唤醒词</span><span class="fact-v">「'+esc(meta.wake_word)+'」</span></span>');
132
+ return out.join("");
133
+ }
134
+
135
+ /* #820 收尾(终审 D3·H3):写侧 message 末尾那个数字是**记录号**(`已记一条:18`=这条的编号是 18),
136
+ 不是条数 —— 读起来像「记了 18 条」;而同一个号在卡里还印两遍(`笔记编号 18`/`备忘 #18`)。
137
+ 上屏时把尾号剁掉(号在卡里读得到),**原始 message 一个字不改**:复制日志的「结论」与载荷
138
+ 仍是机器面原样(`buildLogText` 用的是 `payload.message`,不走本函数)。 */
139
+ function leadText(m){
140
+ var t=String(m||"");var x=/^(.*?)[::]\s*\d+\s*$/.exec(t);
141
+ return x===null?t:x[1];
142
+ }
143
+
144
+ /** 一格「标签+值」;值若写成「结论(说明)」,把说明拆到**下一行**(灰、整宽)。
145
+ *
146
+ * #820 收尾(终审 D3):原先说明塞在值格里,`远端侧 不适用(这条没有飞书任务)` 的值行长度
147
+ * 是同类行长的两三倍,窄屏更挤。拆开后值格只留结论,说明整宽单独一行。 */
148
+ function factHtml(k,v){
149
+ var m=/^(.*?)(([^()]+))$/.exec(v);
150
+ var note=(m===null)?"":'<div class="fact-note">'+esc(m[2])+'</div>';
151
+ return '<div class="fact"><span class="fact-k">'+esc(k)+'</span><span class="fact-v">'
152
+ + esc(m===null?v:m[1])+'</span></div>'+note;
153
+ }
154
+
155
+ /** 事实条:一条一格「标签+值」(冒号只作**数据分隔**,屏上是两列,不是一句话);没冒号的当整行说明。 */
110
156
  function renderFacts(){
111
157
  var s=snap();var sum=Array.isArray(s.summary)?s.summary:[];
112
158
  if(!sum.length)return;
113
159
  document.getElementById("factsPanel").style.display="block";
114
- document.getElementById("facts").innerHTML=sum.map(function(x){return '<span class="fact">'+esc(x)+'</span>'}).join("");
160
+ document.getElementById("facts").innerHTML=sum.map(function(x){
161
+ var t=String(x);var i=t.indexOf(":");
162
+ if(i<0)return '<div class="fact-note">'+esc(t)+'</div>';
163
+ return factHtml(t.slice(0,i),t.slice(i+1));
164
+ }).join("");
115
165
  }
116
166
 
167
+ /** 「处理结果」与「这次的结果」**同一形状**(#820 收尾:原先这一块是 `<dl><dt><dd>` 另一套行型)。 */
117
168
  function renderResult(){
118
169
  var rows=receipt().rows;
119
170
  if(!Array.isArray(rows)||!rows.length)return;
120
171
  document.getElementById("resultPanel").style.display="block";
121
172
  document.getElementById("result").innerHTML=rows.map(function(line){
122
- var i=String(line).indexOf(":");
123
- if(i<0)return "<dt></dt><dd>"+esc(line)+"</dd>";
124
- return "<dt>"+esc(String(line).slice(0,i))+"</dt><dd>"+esc(String(line).slice(i+1))+"</dd>";
173
+ var t=String(line);var i=t.indexOf(":");
174
+ if(i<0)return '<div class="fact-note">'+esc(t)+'</div>';
175
+ return factHtml(t.slice(0,i),t.slice(i+1));
125
176
  }).join("");
126
177
  }
127
178
 
@@ -131,13 +182,32 @@ function renderRows(){
131
182
  if(!rest.length)return;
132
183
  document.getElementById("rowsPanel").style.display="block";
133
184
  document.getElementById("rowsHead").textContent=rest.length===1?rest[0].heading:"明细";
134
- var li=[];
185
+ /* #820 收尾(视觉审查 H4):一页只有一种键值形状 —— 区块里若整组都是「标签:值」,
186
+ 就跟「这次的结果」用同一种排法(标签+值两列),不再另起一套「标签:值」同行+细线。 */
187
+ var html=[];
135
188
  rest.forEach(function(sec){
136
- sec.rows.forEach(function(row){
137
- li.push('<li>'+esc(typeof row==="object"&&row!==null?row.text:row)+'</li>');
138
- });
189
+ var rows=sec.rows,kv=rows.length>0&&rows.every(function(r){return typeof r==="string"&&r.indexOf(":")>0});
190
+ if(kv){
191
+ html.push('<div class="facts">'+rows.map(function(r){
192
+ var t=String(r),i=t.indexOf(":");
193
+ return '<div class="fact"><span class="fact-k">'+esc(t.slice(0,i))+'</span><span class="fact-v">'+esc(t.slice(i+1))+'</span></div>';
194
+ }).join("")+'</div>');
195
+ return;
196
+ }
197
+ html.push('<ul class="rows">'+rows.map(function(row){return '<li>'+rowHtml(row)+'</li>'}).join("")+'</ul>');
139
198
  });
140
- document.getElementById("rows").innerHTML=li.join("");
199
+ document.getElementById("rows").innerHTML=html.join("");
200
+ }
201
+
202
+ /** 一行:带结构的(`{id,text,status}`)落成「编号徽章 + 正文 + 状态徽章」;
203
+ * 纯字符串原样上屏(老形状)。编号与状态都是**另一个维度**,不拼进正文那一串里。 */
204
+ function rowHtml(row){
205
+ if(typeof row!=="object"||row===null)return esc(String(row));
206
+ var h='';
207
+ if(row.id!==undefined&&row.id!==null&&row.id!=="")h+='<span class="row-id">#'+esc(String(row.id))+'</span>';
208
+ h+=esc(String(row.text||""));
209
+ if(row.status)h+='<span class="badge">'+esc(String(row.status))+'</span>';
210
+ return h;
141
211
  }
142
212
 
143
213
  function dataText(){