aimeat 1.16.2 → 1.18.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 (281) hide show
  1. package/dist/.env.example +6 -1
  2. package/dist/locales/en.json +166 -4
  3. package/dist/locales/fi.json +166 -4
  4. package/dist/public/components/Card.js +17 -3
  5. package/dist/public/components/Collapsible.js +39 -0
  6. package/dist/public/components/CopyButton.js +55 -24
  7. package/dist/public/components/DataTable.js +57 -0
  8. package/dist/public/components/EmptyState.js +28 -0
  9. package/dist/public/components/KeyValueRow.js +37 -0
  10. package/dist/public/components/Modal.js +22 -5
  11. package/dist/public/components/Pagination.js +70 -0
  12. package/dist/public/components/SearchBar.js +44 -0
  13. package/dist/public/components/StatusDot.js +29 -0
  14. package/dist/public/components/TagList.js +53 -0
  15. package/dist/public/components/ToggleSwitch.js +28 -0
  16. package/dist/public/components/index.js +41 -1
  17. package/dist/public/css/components/tags.css +24 -0
  18. package/dist/public/css/style-testbed.css +135 -0
  19. package/dist/public/css/theme.css +556 -34
  20. package/dist/public/css/views/admin-agent-integration.css +2 -33
  21. package/dist/public/css/views/admin.css +730 -686
  22. package/dist/public/css/views/agents-detail.css +402 -14
  23. package/dist/public/css/views/help.css +2 -1
  24. package/dist/public/css/views/hobbies.css +53 -33
  25. package/dist/public/css/views/marketplace.css +73 -60
  26. package/dist/public/css/views/openclaw.css +12 -6
  27. package/dist/public/css/views/portal-classic.css +34 -26
  28. package/dist/public/css/views/portal-dev.css +41 -32
  29. package/dist/public/css/views/portal.css +14 -18
  30. package/dist/public/css/views/profile.css +252 -164
  31. package/dist/public/css/views/public-knowledge-viewer.css +18 -6
  32. package/dist/public/css/views/scheduler.css +100 -0
  33. package/dist/public/js/format.js +41 -0
  34. package/dist/public/js/services/agent-tasks.js +14 -1
  35. package/dist/public/js/services/generator-spec-validate.js +5 -1
  36. package/dist/public/js/services/schedules.js +53 -0
  37. package/dist/public/js/style-testbed.js +170 -0
  38. package/dist/public/js/theme.js +114 -0
  39. package/dist/public/spa.html +74 -16
  40. package/dist/public/style-testbed.html +420 -0
  41. package/dist/public/views/admin/agent-integration-tab.js +23 -38
  42. package/dist/public/views/admin/agent-tasks-tab.js +14 -21
  43. package/dist/public/views/admin/chat-instances-tab.js +1 -1
  44. package/dist/public/views/admin/config-tab.js +7 -2
  45. package/dist/public/views/admin/cortex-tab.js +20 -14
  46. package/dist/public/views/admin/csm-tab.js +22 -18
  47. package/dist/public/views/admin/directory-tab.js +3 -3
  48. package/dist/public/views/admin/email-tab.js +23 -28
  49. package/dist/public/views/admin/federation-tab.js +15 -5
  50. package/dist/public/views/admin/generator-debug-tab.js +10 -8
  51. package/dist/public/views/admin/genesis-tab.js +4 -4
  52. package/dist/public/views/admin/ghii-tab.js +3 -3
  53. package/dist/public/views/admin/knowledge-tab.js +15 -5
  54. package/dist/public/views/admin/matching-tab.js +3 -3
  55. package/dist/public/views/admin/memory-tab.js +16 -20
  56. package/dist/public/views/admin/msm-tab.js +30 -17
  57. package/dist/public/views/admin/overview-tab.js +7 -7
  58. package/dist/public/views/admin/packages-tab.js +10 -9
  59. package/dist/public/views/admin/portal-tab.js +14 -4
  60. package/dist/public/views/admin/prompts-tab.js +5 -5
  61. package/dist/public/views/admin/push-tab.js +15 -4
  62. package/dist/public/views/admin/realtime-tab.js +3 -3
  63. package/dist/public/views/admin/services-tab.js +18 -27
  64. package/dist/public/views/admin/shared.js +48 -58
  65. package/dist/public/views/admin/sharing-groups-tab.js +5 -10
  66. package/dist/public/views/admin/stats-tab.js +3 -3
  67. package/dist/public/views/guides.js +9 -13
  68. package/dist/public/views/help.js +3 -24
  69. package/dist/public/views/hobbies.js +47 -41
  70. package/dist/public/views/marketplace.js +42 -34
  71. package/dist/public/views/openclaw.js +0 -1
  72. package/dist/public/views/portal-classic.js +4 -15
  73. package/dist/public/views/portal-dev.js +28 -30
  74. package/dist/public/views/portal.js +16 -21
  75. package/dist/public/views/portfolio.js +11 -12
  76. package/dist/public/views/profile/access-tab.js +250 -1
  77. package/dist/public/views/profile/agents/agent-card.js +75 -4
  78. package/dist/public/views/profile/agents/tab-activity.js +15 -13
  79. package/dist/public/views/profile/agents/tab-agent-config.js +60 -6
  80. package/dist/public/views/profile/agents/tab-integration.js +37 -60
  81. package/dist/public/views/profile/agents/tab-messages.js +10 -1
  82. package/dist/public/views/profile/agents/tab-quality.js +25 -19
  83. package/dist/public/views/profile/agents/tab-schedules.js +110 -0
  84. package/dist/public/views/profile/agents-activity-subtab.js +4 -4
  85. package/dist/public/views/profile/agents-services-subtab.js +5 -4
  86. package/dist/public/views/profile/agents-tab.js +141 -37
  87. package/dist/public/views/profile/agents-tasks-subtab.js +221 -26
  88. package/dist/public/views/profile/calibrator-batch.js +5 -11
  89. package/dist/public/views/profile/calibrator-tab.js +4 -9
  90. package/dist/public/views/profile/chat-sessions-tab.js +9 -5
  91. package/dist/public/views/profile/email-tab.js +2 -2
  92. package/dist/public/views/profile/extensions-tab.js +27 -22
  93. package/dist/public/views/profile/federation-tab.js +4 -1
  94. package/dist/public/views/profile/foundry-detail.js +22 -23
  95. package/dist/public/views/profile/foundry-tab.js +12 -13
  96. package/dist/public/views/profile/generator-dashboard/DebugPanel.js +4 -7
  97. package/dist/public/views/profile/generator-dashboard/use-dashboard-core.js +4 -1
  98. package/dist/public/views/profile/generator-dashboard/use-edit-mode.js +3 -2
  99. package/dist/public/views/profile/generator-detail.js +26 -30
  100. package/dist/public/views/profile/generator-tab.js +5 -5
  101. package/dist/public/views/profile/knowledge-tab.js +12 -12
  102. package/dist/public/views/profile/landing-page.js +100 -149
  103. package/dist/public/views/profile/memory-tab.js +15 -11
  104. package/dist/public/views/profile/node-stats-tab.js +3 -3
  105. package/dist/public/views/profile/nodes-tab.js +10 -3
  106. package/dist/public/views/profile/notifications-tab.js +3 -3
  107. package/dist/public/views/profile/packages-tab.js +14 -14
  108. package/dist/public/views/profile/scheduler-tab.js +340 -0
  109. package/dist/public/views/profile/services-tab.js +1 -1
  110. package/dist/public/views/profile/shared.js +16 -12
  111. package/dist/public/views/profile/wallet-tab.js +27 -50
  112. package/dist/public/views/profile/work-tab.js +29 -31
  113. package/dist/public/views/profile.js +24 -0
  114. package/dist/public/views/public-knowledge-viewer.js +9 -18
  115. package/dist/src/auth/middleware.d.ts +3 -2
  116. package/dist/src/auth/middleware.d.ts.map +1 -1
  117. package/dist/src/auth/middleware.js +78 -3
  118. package/dist/src/auth/middleware.js.map +1 -1
  119. package/dist/src/cli/init-wizard.d.ts.map +1 -1
  120. package/dist/src/cli/init-wizard.js +13 -1
  121. package/dist/src/cli/init-wizard.js.map +1 -1
  122. package/dist/src/config.d.ts +6 -0
  123. package/dist/src/config.d.ts.map +1 -1
  124. package/dist/src/config.js +12 -1
  125. package/dist/src/config.js.map +1 -1
  126. package/dist/src/generated/api-types.d.ts +708 -8
  127. package/dist/src/generated/api-types.d.ts.map +1 -1
  128. package/dist/src/mcp/agent-schedules.d.ts +23 -0
  129. package/dist/src/mcp/agent-schedules.d.ts.map +1 -0
  130. package/dist/src/mcp/agent-schedules.js +175 -0
  131. package/dist/src/mcp/agent-schedules.js.map +1 -0
  132. package/dist/src/mcp/annotations.d.ts.map +1 -1
  133. package/dist/src/mcp/annotations.js +6 -0
  134. package/dist/src/mcp/annotations.js.map +1 -1
  135. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  136. package/dist/src/mcp/catalog/definitions.js +55 -0
  137. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  138. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  139. package/dist/src/mcp/catalog/scopes.js +12 -0
  140. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  141. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  142. package/dist/src/mcp/catalog/surfaces.js +2 -0
  143. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  144. package/dist/src/mcp/index.d.ts.map +1 -1
  145. package/dist/src/mcp/index.js +2 -0
  146. package/dist/src/mcp/index.js.map +1 -1
  147. package/dist/src/models/agent-task-schemas.d.ts +6 -0
  148. package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
  149. package/dist/src/models/agent-task-schemas.js +6 -0
  150. package/dist/src/models/agent-task-schemas.js.map +1 -1
  151. package/dist/src/models/schemas.d.ts +1 -0
  152. package/dist/src/models/schemas.d.ts.map +1 -1
  153. package/dist/src/models/schemas.js +4 -0
  154. package/dist/src/models/schemas.js.map +1 -1
  155. package/dist/src/routes/access-tokens.d.ts +17 -0
  156. package/dist/src/routes/access-tokens.d.ts.map +1 -0
  157. package/dist/src/routes/access-tokens.js +164 -0
  158. package/dist/src/routes/access-tokens.js.map +1 -0
  159. package/dist/src/routes/agent-tasks.d.ts +2 -0
  160. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  161. package/dist/src/routes/agent-tasks.js +118 -23
  162. package/dist/src/routes/agent-tasks.js.map +1 -1
  163. package/dist/src/routes/agents.d.ts.map +1 -1
  164. package/dist/src/routes/agents.js +46 -0
  165. package/dist/src/routes/agents.js.map +1 -1
  166. package/dist/src/routes/ai.d.ts +6 -9
  167. package/dist/src/routes/ai.d.ts.map +1 -1
  168. package/dist/src/routes/ai.js +26 -175
  169. package/dist/src/routes/ai.js.map +1 -1
  170. package/dist/src/routes/apps.d.ts.map +1 -1
  171. package/dist/src/routes/apps.js +104 -34
  172. package/dist/src/routes/apps.js.map +1 -1
  173. package/dist/src/routes/auth.d.ts.map +1 -1
  174. package/dist/src/routes/auth.js +77 -4
  175. package/dist/src/routes/auth.js.map +1 -1
  176. package/dist/src/routes/cortex.d.ts +2 -1
  177. package/dist/src/routes/cortex.d.ts.map +1 -1
  178. package/dist/src/routes/cortex.js +13 -8
  179. package/dist/src/routes/cortex.js.map +1 -1
  180. package/dist/src/routes/extensions.d.ts.map +1 -1
  181. package/dist/src/routes/extensions.js +85 -51
  182. package/dist/src/routes/extensions.js.map +1 -1
  183. package/dist/src/routes/generator.d.ts.map +1 -1
  184. package/dist/src/routes/generator.js +109 -43
  185. package/dist/src/routes/generator.js.map +1 -1
  186. package/dist/src/routes/ghii.d.ts.map +1 -1
  187. package/dist/src/routes/ghii.js +34 -13
  188. package/dist/src/routes/ghii.js.map +1 -1
  189. package/dist/src/routes/libs.d.ts +15 -0
  190. package/dist/src/routes/libs.d.ts.map +1 -1
  191. package/dist/src/routes/libs.js +178 -55
  192. package/dist/src/routes/libs.js.map +1 -1
  193. package/dist/src/routes/portal.d.ts.map +1 -1
  194. package/dist/src/routes/portal.js +1 -0
  195. package/dist/src/routes/portal.js.map +1 -1
  196. package/dist/src/routes/schedules.d.ts +33 -0
  197. package/dist/src/routes/schedules.d.ts.map +1 -0
  198. package/dist/src/routes/schedules.js +380 -0
  199. package/dist/src/routes/schedules.js.map +1 -0
  200. package/dist/src/server-bootstrap/config-init.js +2 -2
  201. package/dist/src/server-bootstrap/config-init.js.map +1 -1
  202. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  203. package/dist/src/server-bootstrap/routes-loader.js +7 -0
  204. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  205. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  206. package/dist/src/server-bootstrap/service-init.js +2 -1
  207. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  208. package/dist/src/services/access-token.d.ts +28 -0
  209. package/dist/src/services/access-token.d.ts.map +1 -0
  210. package/dist/src/services/access-token.js +37 -0
  211. package/dist/src/services/access-token.js.map +1 -0
  212. package/dist/src/services/ai-completion.d.ts +83 -0
  213. package/dist/src/services/ai-completion.d.ts.map +1 -0
  214. package/dist/src/services/ai-completion.js +187 -0
  215. package/dist/src/services/ai-completion.js.map +1 -0
  216. package/dist/src/services/config-schema.d.ts.map +1 -1
  217. package/dist/src/services/config-schema.js +3 -0
  218. package/dist/src/services/config-schema.js.map +1 -1
  219. package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
  220. package/dist/src/services/generator-prompt-seeds.js +6 -3
  221. package/dist/src/services/generator-prompt-seeds.js.map +1 -1
  222. package/dist/src/services/generator-prompts/spec-validate.d.ts.map +1 -1
  223. package/dist/src/services/generator-prompts/spec-validate.js +6 -2
  224. package/dist/src/services/generator-prompts/spec-validate.js.map +1 -1
  225. package/dist/src/services/generator-registration.d.ts +12 -4
  226. package/dist/src/services/generator-registration.d.ts.map +1 -1
  227. package/dist/src/services/generator-registration.js +71 -0
  228. package/dist/src/services/generator-registration.js.map +1 -1
  229. package/dist/src/services/generator-validate.d.ts +10 -0
  230. package/dist/src/services/generator-validate.d.ts.map +1 -1
  231. package/dist/src/services/generator-validate.js +180 -36
  232. package/dist/src/services/generator-validate.js.map +1 -1
  233. package/dist/src/services/owner-session.d.ts +43 -0
  234. package/dist/src/services/owner-session.d.ts.map +1 -0
  235. package/dist/src/services/owner-session.js +175 -0
  236. package/dist/src/services/owner-session.js.map +1 -0
  237. package/dist/src/services/schedule-constraints.d.ts +65 -0
  238. package/dist/src/services/schedule-constraints.d.ts.map +1 -0
  239. package/dist/src/services/schedule-constraints.js +106 -0
  240. package/dist/src/services/schedule-constraints.js.map +1 -0
  241. package/dist/src/services/scheduler.d.ts +34 -0
  242. package/dist/src/services/scheduler.d.ts.map +1 -1
  243. package/dist/src/services/scheduler.js +266 -22
  244. package/dist/src/services/scheduler.js.map +1 -1
  245. package/dist/src/services/skill-bundle/generator.js +19 -0
  246. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  247. package/dist/src/storage/interface.d.ts +48 -4
  248. package/dist/src/storage/interface.d.ts.map +1 -1
  249. package/dist/src/storage/interface.js.map +1 -1
  250. package/dist/src/storage/providers/mongodb/index.d.ts +31 -0
  251. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  252. package/dist/src/storage/providers/mongodb/index.js +187 -9
  253. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  254. package/dist/src/storage/providers/sqlite/index.d.ts +31 -0
  255. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  256. package/dist/src/storage/providers/sqlite/index.js +131 -8
  257. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  258. package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
  259. package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
  260. package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
  261. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  262. package/dist/src/storage/providers/sqlite/schema.js +79 -8
  263. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  264. package/dist/src/storage/repositories/pat.repository.d.ts +40 -0
  265. package/dist/src/storage/repositories/pat.repository.d.ts.map +1 -0
  266. package/dist/src/storage/repositories/pat.repository.js +14 -0
  267. package/dist/src/storage/repositories/pat.repository.js.map +1 -0
  268. package/dist/src/storage/repositories/scheduler.repository.d.ts +2 -0
  269. package/dist/src/storage/repositories/scheduler.repository.d.ts.map +1 -1
  270. package/dist/src/storage/repositories/session.repository.d.ts +51 -0
  271. package/dist/src/storage/repositories/session.repository.d.ts.map +1 -1
  272. package/dist/src/storage/repositories/session.repository.js +12 -0
  273. package/dist/src/storage/repositories/session.repository.js.map +1 -1
  274. package/dist/src/utils/env-config.d.ts.map +1 -1
  275. package/dist/src/utils/env-config.js +25 -1
  276. package/dist/src/utils/env-config.js.map +1 -1
  277. package/dist/src/utils/env-validator.d.ts.map +1 -1
  278. package/dist/src/utils/env-validator.js +4 -0
  279. package/dist/src/utils/env-validator.js.map +1 -1
  280. package/package.json +1 -1
  281. package/prisma/schema.prisma +48 -0
@@ -5,6 +5,7 @@
5
5
  * content with syntax highlighting.
6
6
  * @version-history
7
7
  * v1.0.0 — 2026-03-23 — Initial implementation
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: main btn-* classes → adm-btn* (btn-primary→adm-btn, btn-ghost→adm-btn-action, btn-outline→adm-btn-action).
8
9
  */
9
10
  import { h } from 'preact';
10
11
  import { useState, useEffect } from 'preact/hooks';
@@ -12,6 +13,8 @@ import htm from 'htm';
12
13
  const html = htm.bind(h);
13
14
  import { t } from '/js/i18n.js';
14
15
  import { apiGet, apiDelete } from '/js/api.js';
16
+ import { copyToClipboard } from '/js/utils.js';
17
+ import { CopyButton } from '/components/CopyButton.js';
15
18
 
16
19
  export default function GeneratorDebugTab() {
17
20
  const [projects, setProjects] = useState([]);
@@ -127,7 +130,7 @@ export default function GeneratorDebugTab() {
127
130
  for (const { path, content } of loaded) {
128
131
  parts.push('='.repeat(60), `FILE: ${path}`, '='.repeat(60), content, '');
129
132
  }
130
- await navigator.clipboard.writeText(parts.join('\n'));
133
+ await copyToClipboard(parts.join('\n'));
131
134
  setCopying('done');
132
135
  setTimeout(() => setCopying(false), 2000);
133
136
  } catch (e) {
@@ -171,10 +174,10 @@ export default function GeneratorDebugTab() {
171
174
  <div class="adm-card adm-card-clickable" onClick=${() => selectProject(p.projectId)}>
172
175
  <div class="adm-card-header">
173
176
  <strong>${p.source === 'foundry' ? '🏭 ' : '🔴 '}${p.projectId}</strong>
174
- <button class="btn-primary btn-sm" onClick=${e => { e.stopPropagation(); handleCopyAll(p.projectId); }}>
177
+ <button class="adm-btn" onClick=${e => { e.stopPropagation(); handleCopyAll(p.projectId); }}>
175
178
  ${copying === 'done' ? '\u2705 Copied!' : copying ? '...' : 'Copy All'}
176
179
  </button>
177
- <button class="btn-ghost btn-sm" onClick=${e => { e.stopPropagation(); handleDeleteProject(p.projectId); }}>
180
+ <button class="adm-btn-action" onClick=${e => { e.stopPropagation(); handleDeleteProject(p.projectId); }}>
178
181
  Delete
179
182
  </button>
180
183
  </div>
@@ -191,11 +194,11 @@ export default function GeneratorDebugTab() {
191
194
  ${selectedProject && html`
192
195
  <div>
193
196
  <div class="adm-breadcrumb">
194
- <button class="btn-ghost btn-sm" onClick=${() => { setSelectedProject(null); setFiles([]); setSelectedFile(null); }}>
197
+ <button class="adm-btn-action" onClick=${() => { setSelectedProject(null); setFiles([]); setSelectedFile(null); }}>
195
198
  ← Back to projects
196
199
  </button>
197
200
  <strong>${selectedProject}</strong>
198
- <button class="btn-primary btn-sm" onClick=${() => handleCopyAll(selectedProject)}>
201
+ <button class="adm-btn" onClick=${() => handleCopyAll(selectedProject)}>
199
202
  ${copying === 'done' ? '\u2705 Copied!' : copying ? '...' : 'Copy All Debug'}
200
203
  </button>
201
204
  <span class="adm-count">${files.length} files</span>
@@ -220,9 +223,8 @@ export default function GeneratorDebugTab() {
220
223
  ${selectedFile ? html`
221
224
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;padding-bottom:4px;border-bottom:1px solid var(--border)">
222
225
  <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-size:0.85rem">${selectedFile}</span>
223
- <button class="btn-outline btn-sm" style="flex-shrink:0" onClick=${() => {
224
- navigator.clipboard.writeText(fileContent).catch(() => {});
225
- }}>Copy File</button>
226
+ <span style="flex-shrink:0"><${CopyButton} text=${fileContent}
227
+ className="adm-btn-action" label="Copy File" copiedLabel="Copy File" /></span>
226
228
  </div>
227
229
  ${loadingFile
228
230
  ? html`<p>${t('profile.loading')}</p>`
@@ -30,10 +30,10 @@ export default function GenesisTab({ data, reload }) {
30
30
  <//>
31
31
 
32
32
  <${StatsGrid} items=${[
33
- { label: t('dashboard.totalPeers'), value: peers.length, color: '#06b6d4' },
34
- { label: t('dashboard.approved'), value: peers.filter(p => p.status === 'approved' || p.status === 'active').length, color: '#22c55e' },
35
- { label: t('dashboard.suspended'), value: peers.filter(p => p.status === 'suspended').length, color: '#ef4444' },
36
- { label: t('dashboard.pending'), value: peers.filter(p => p.status === 'pending').length, color: '#f59e0b' },
33
+ { label: t('dashboard.totalPeers'), value: peers.length, tone: 'cyan' },
34
+ { label: t('dashboard.approved'), value: peers.filter(p => p.status === 'approved' || p.status === 'active').length, tone: 'green' },
35
+ { label: t('dashboard.suspended'), value: peers.filter(p => p.status === 'suspended').length, tone: 'red' },
36
+ { label: t('dashboard.pending'), value: peers.filter(p => p.status === 'pending').length, tone: 'amber' },
37
37
  ]} />
38
38
 
39
39
  ${!peers.length
@@ -51,9 +51,9 @@ export default function GhiiTab({ data, reload }) {
51
51
  </div>
52
52
 
53
53
  <${StatsGrid} items=${[
54
- { label: t('dashboard.totalGhiiUsers'), value: users.length, color: '#06b6d4' },
55
- { label: t('dashboard.totpEnabled'), value: users.filter(u => u.totp_enabled).length, color: '#22c55e' },
56
- { label: t('dashboard.verifiedL2'), value: users.filter(u => u.verification_level === 2).length, color: '#a855f7' },
54
+ { label: t('dashboard.totalGhiiUsers'), value: users.length, tone: 'cyan' },
55
+ { label: t('dashboard.totpEnabled'), value: users.filter(u => u.totp_enabled).length, tone: 'green' },
56
+ { label: t('dashboard.verifiedL2'), value: users.filter(u => u.verification_level === 2).length, tone: 'purple' },
57
57
  ]} />
58
58
 
59
59
  ${!users.length
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @file knowledge-tab.js
3
+ * @description Admin dashboard Knowledge tab — operator view to create system
4
+ * knowledge packages, review/moderate packages, and manage flags.
5
+ * @structure KnowledgeAdminTab (default)
6
+ * @usage Mounted by the admin dashboard tab router.
7
+ * @version-history
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: entry-content textarea
9
+ * adm-input → adm-textarea (drop redundant resize/font-family inline style);
10
+ * delete-package button inline color → adm-btn-danger (campsite fix).
11
+ */
1
12
  import { h } from 'preact';
2
13
  import { useState, useEffect, useCallback } from 'preact/hooks';
3
14
  import htm from 'htm';
@@ -93,7 +104,7 @@ export default function KnowledgeAdminTab({ data, reload }) {
93
104
  ...f, entries: f.entries.map((e, idx) => idx === i ? { ...e, [field]: val } : e),
94
105
  }));
95
106
 
96
- if (loading) return html`<${Spinner} text="Loading..." />`;
107
+ if (loading) return html`<${Spinner} text=${t('common.loading')} />`;
97
108
 
98
109
  return html`
99
110
  <div class="adm-section">
@@ -172,11 +183,10 @@ export default function KnowledgeAdminTab({ data, reload }) {
172
183
  </button>
173
184
  `}
174
185
  </div>
175
- <textarea class="adm-input adm-input-full" value=${entry.content}
186
+ <textarea class="adm-textarea adm-input-full" value=${entry.content}
176
187
  onInput=${e => updateEntry(i, 'content', e.target.value)}
177
188
  placeholder=${t('knowledge.operator.createForm.entryContent')}
178
- rows="3"
179
- style="resize:vertical;font-family:inherit" />
189
+ rows="3" />
180
190
  </div>
181
191
  `)}
182
192
  <button class="adm-btn-sm adm-mt-sm" onClick=${addEntry}>
@@ -244,7 +254,7 @@ export default function KnowledgeAdminTab({ data, reload }) {
244
254
  <button class="adm-btn-sm" onClick=${() => setReviewingId(reviewingId === pkg.package_id ? null : pkg.package_id)}>
245
255
  ${t('knowledge.operator.review')}
246
256
  </button>
247
- <button class="adm-btn-sm" style="color:#e74c3c" onClick=${() => deletePackage(pkg.package_id)}>
257
+ <button class="adm-btn-sm adm-btn-danger" onClick=${() => deletePackage(pkg.package_id)}>
248
258
  ${t('knowledge.operator.deletePackage')}
249
259
  </button>
250
260
  </div>
@@ -28,9 +28,9 @@ export default function MatchingTab({ data }) {
28
28
  <${ExpandableHelp} title=${t('dashboard.matchingHelpTitle')}>${t('dashboard.matchingHelpDetail')}</${ExpandableHelp}>
29
29
 
30
30
  <${StatsGrid} items=${[
31
- { label: t('dashboard.totalProfiles'), value: m?.total_profiles || 0, color: '#06b6d4' },
32
- { label: t('dashboard.activeMatches'), value: m?.active_matches || 0, color: '#22c55e' },
33
- { label: t('dashboard.lastRun'), value: m?.last_run || '\u2014', color: '#a855f7' },
31
+ { label: t('dashboard.totalProfiles'), value: m?.total_profiles || 0, tone: 'cyan' },
32
+ { label: t('dashboard.activeMatches'), value: m?.active_matches || 0, tone: 'green' },
33
+ { label: t('dashboard.lastRun'), value: m?.last_run || '\u2014', tone: 'purple' },
34
34
  ]} />
35
35
 
36
36
  <div class="adm-card" style="margin-top:12px">
@@ -5,6 +5,10 @@
5
5
  * and delete functionality.
6
6
  * @version-history
7
7
  * v1.0.0 — 2026-03-16 — Initial implementation
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: fix the broken useToast wiring
9
+ * (array hook was used as an object via .show()/.current/.dismiss — toasts never
10
+ * rendered and delete threw); now canonical [msg,showErr,showOk,clear] + correct
11
+ * <Toast>. The bespoke adm-mem-stat summary → canonical <StatsGrid>.
8
12
  */
9
13
  import { h } from 'preact';
10
14
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -12,7 +16,7 @@ import htm from 'htm';
12
16
  const html = htm.bind(h);
13
17
  import { t } from '/js/i18n.js';
14
18
  import { escHtml } from '/js/utils.js';
15
- import { num, dt, Empty, useToast, Toast } from './shared.js';
19
+ import { dt, Empty, StatsGrid, useToast, Toast } from './shared.js';
16
20
  import { getAdminMemory, deleteAdminMemory } from '/js/services/admin.js';
17
21
 
18
22
  const PAGE_SIZE = 50;
@@ -31,10 +35,10 @@ export default function MemoryTab({ data, reload, session }) {
31
35
  // Expanded rows
32
36
  const [expanded, setExpanded] = useState({});
33
37
 
34
- const toast = useToast();
38
+ const [toast, showErr, showOk, clearToast] = useToast();
35
39
 
36
- const loadMemory = useCallback(async (off = 0) => {
37
- setLoading(true);
40
+ const loadMemory = useCallback(async (off = 0, { showSpinner = true } = {}) => {
41
+ if (showSpinner) setLoading(true);
38
42
  try {
39
43
  const params = { limit: PAGE_SIZE, offset: off };
40
44
  if (ownerFilter.trim()) params.owner = ownerFilter.trim();
@@ -56,7 +60,7 @@ export default function MemoryTab({ data, reload, session }) {
56
60
 
57
61
  // Listen for live updates
58
62
  useEffect(() => {
59
- const handler = () => { loadMemory(offset); };
63
+ const handler = () => { loadMemory(offset, { showSpinner: false }); }; // silent: no flash
60
64
  window.addEventListener('aimeat-live-update', handler);
61
65
  return () => window.removeEventListener('aimeat-live-update', handler);
62
66
  }, [loadMemory, offset]);
@@ -73,10 +77,10 @@ export default function MemoryTab({ data, reload, session }) {
73
77
  if (!confirm(t('dashboard.memoryDeleteConfirm'))) return;
74
78
  try {
75
79
  await deleteAdminMemory(ownerGaii, key);
76
- toast.show('success', t('dashboard.memoryDeleted'));
80
+ showOk(t('dashboard.memoryDeleted'));
77
81
  loadMemory(offset);
78
82
  } catch (e) {
79
- toast.show('error', e.message);
83
+ showErr(e.message);
80
84
  }
81
85
  }
82
86
 
@@ -105,21 +109,13 @@ export default function MemoryTab({ data, reload, session }) {
105
109
 
106
110
  return html`
107
111
  <div>
108
- ${toast.current && html`<${Toast} ...${toast.current} onDismiss=${toast.dismiss} />`}
112
+ ${toast && html`<${Toast} type=${toast.type} text=${toast.text} onDismiss=${clearToast} />`}
109
113
 
110
114
  <!-- Stats summary -->
111
- <div class="adm-mem-stats">
112
- <div class="adm-mem-stat">
113
- <span class="adm-mem-stat-value">${num(total)}</span>
114
- <span class="adm-mem-stat-label">${t('dashboard.memoryTotal')}</span>
115
- </div>
116
- ${Object.entries(visCounts).map(([vis, count]) => html`
117
- <div class="adm-mem-stat">
118
- <span class="adm-mem-stat-value">${num(count)}</span>
119
- <span class="adm-mem-stat-label">${vis}</span>
120
- </div>
121
- `)}
122
- </div>
115
+ <${StatsGrid} items=${[
116
+ { label: t('dashboard.memoryTotal'), value: total },
117
+ ...Object.entries(visCounts).map(([vis, count]) => ({ label: vis, value: count })),
118
+ ]} />
123
119
 
124
120
  <!-- Filter bar -->
125
121
  <form class="adm-mem-filters" onSubmit=${handleSearch}>
@@ -1,11 +1,29 @@
1
+ /**
2
+ * @file msm-tab.js
3
+ * @description Admin dashboard MSM (Marketplace Service Manifest) tab — list,
4
+ * detail, create, edit and typed-name delete of registered service manifests.
5
+ * @structure default export MsmTab({ data, reload }); internal renderDeleteModal,
6
+ * list/detail/create/edit views.
7
+ * @usage Mounted by the admin dashboard tab router.
8
+ * @version-history
9
+ * v1.1.0 — 2026-06-02 — Component unification (#3): delete-confirm overlay uses
10
+ * canonical .modal-overlay (theme.css) instead of removed .adm-modal-overlay.
11
+ * v1.2.0 — 2026-06-02 — Component unification (#2): delete-confirm modal uses the
12
+ * canonical <Modal> component (header ✕ + Escape/backdrop close); the typed-name
13
+ * guard stays in the body, with the danger cue moved to the message text.
14
+ * v1.3.0 — 2026-06-02 — Admin design unification: inline danger styles → adm-btn-danger
15
+ * (detail + list delete buttons) / adm-btn-danger-solid (modal delete), raw textarea →
16
+ * adm-textarea adm-input-full, error divs → <ErrorBox> (edit + create views).
17
+ */
1
18
  import { h } from 'preact';
2
19
  import { useState } from 'preact/hooks';
3
20
  import htm from 'htm';
4
21
  const html = htm.bind(h);
5
22
  import { t } from '/js/i18n.js';
6
23
  import { escHtml } from '/js/utils.js';
7
- import { dt, Badge, Empty, ExpandableHelp, useToast, Toast } from './shared.js';
24
+ import { dt, Badge, Empty, ExpandableHelp, ErrorBox, useToast, Toast } from './shared.js';
8
25
  import { getMsmDetail, createMsm, updateMsm, deleteMsm, getMsmTemplates, getMsmTemplate } from '/js/services/admin.js';
26
+ import { Modal } from '/components/Modal.js';
9
27
 
10
28
  export default function MsmTab({ data, reload }) {
11
29
  const integrations = data.msm?.integrations || [];
@@ -127,12 +145,10 @@ export default function MsmTab({ data, reload }) {
127
145
  // ── Delete confirmation modal ──
128
146
  function renderDeleteModal() {
129
147
  if (!deleteTarget) return null;
148
+ const closeDelete = () => { setDeleteTarget(null); setDeleteInput(''); };
130
149
  return html`
131
- <div class="adm-modal-overlay"
132
- onClick=${e => { if (e.target === e.currentTarget) { setDeleteTarget(null); setDeleteInput(''); } }}>
133
- <div class="adm-card" style="max-width:460px;width:90%;margin:0">
134
- <h4 class="adm-mb-sm adm-text-error" style="margin:0">${t('dashboard.msmDeleteTitle')}</h4>
135
- <p class="adm-text-base adm-mb-md" style="margin:0">
150
+ <${Modal} open=${true} onClose=${closeDelete} title=${t('dashboard.msmDeleteTitle')}>
151
+ <p class="adm-text-base adm-mb-md adm-text-error" style="margin:0 0 1rem">
136
152
  ${t('dashboard.msmDeleteConfirm').replace('{name}', deleteTarget)}
137
153
  </p>
138
154
  <input
@@ -143,20 +159,18 @@ export default function MsmTab({ data, reload }) {
143
159
  onInput=${e => setDeleteInput(e.target.value)}
144
160
  />
145
161
  <div class="adm-flex" style="justify-content:flex-end">
146
- <button class="adm-btn-sm" onClick=${() => { setDeleteTarget(null); setDeleteInput(''); }}>
162
+ <button class="adm-btn-sm" onClick=${closeDelete}>
147
163
  ${t('dashboard.cancel')}
148
164
  </button>
149
165
  <button
150
- class="adm-btn"
151
- style="background:#ef4444;border-color:#ef4444"
166
+ class="adm-btn adm-btn-danger-solid"
152
167
  disabled=${deleteInput !== deleteTarget || loading}
153
168
  onClick=${doDelete}
154
169
  >
155
170
  ${loading ? '...' : t('dashboard.delete')}
156
171
  </button>
157
172
  </div>
158
- </div>
159
- </div>
173
+ <//>
160
174
  `;
161
175
  }
162
176
 
@@ -192,7 +206,7 @@ export default function MsmTab({ data, reload }) {
192
206
  ${t('dashboard.msmEditFederate')}
193
207
  </label>
194
208
 
195
- ${err && html`<div class="error-box adm-mb-sm">${err}</div>`}
209
+ ${err && html`<div class="adm-mb-sm"><${ErrorBox} message=${err} /></div>`}
196
210
 
197
211
  <div class="adm-flex">
198
212
  <button class="adm-btn" onClick=${doEdit} disabled=${loading}>
@@ -219,7 +233,7 @@ export default function MsmTab({ data, reload }) {
219
233
  <h4 style="margin:0">${escHtml(detail.name)}</h4>
220
234
  <div class="adm-flex">
221
235
  <button class="adm-btn-sm" onClick=${openEdit}>${t('dashboard.edit')}</button>
222
- <button class="adm-btn-sm adm-text-error" style="border-color:#ef4444" onClick=${() => openDelete(detail.name)}>
236
+ <button class="adm-btn-sm adm-btn-danger" onClick=${() => openDelete(detail.name)}>
223
237
  ${t('dashboard.delete')}
224
238
  </button>
225
239
  </div>
@@ -264,8 +278,7 @@ export default function MsmTab({ data, reload }) {
264
278
  <label class="adm-text-base adm-text-dim adm-mb-xs" style="display:block">${t('dashboard.msmDefinition')}</label>
265
279
  <textarea
266
280
  rows="20"
267
- class="adm-input adm-input-full adm-text-sm"
268
- style="font-family:monospace;resize:vertical"
281
+ class="adm-textarea adm-input-full"
269
282
  placeholder=${t('dashboard.msmYamlPlaceholder')}
270
283
  value=${yaml}
271
284
  onInput=${e => setYaml(e.target.value)}
@@ -276,7 +289,7 @@ export default function MsmTab({ data, reload }) {
276
289
  ${t('dashboard.msmEditFederate')}
277
290
  </label>
278
291
 
279
- ${err && html`<div class="error-box adm-mt-sm">${err}</div>`}
292
+ ${err && html`<div class="adm-mt-sm"><${ErrorBox} message=${err} /></div>`}
280
293
 
281
294
  <div class="adm-flex adm-mt-md">
282
295
  <button class="adm-btn" onClick=${doCreate} disabled=${loading || !yaml.trim()}>
@@ -328,7 +341,7 @@ export default function MsmTab({ data, reload }) {
328
341
  <td style="white-space:nowrap">
329
342
  <button class="adm-btn-sm" onClick=${() => showDetail(m.name)}>${t('dashboard.edit')}</button>
330
343
  ${' '}
331
- <button class="adm-btn-sm adm-text-error" style="border-color:#ef4444" onClick=${() => openDelete(m.name)}>
344
+ <button class="adm-btn-sm adm-btn-danger" onClick=${() => openDelete(m.name)}>
332
345
  ${t('dashboard.delete')}
333
346
  </button>
334
347
  </td>
@@ -59,13 +59,13 @@ export default function OverviewTab({ data }) {
59
59
 
60
60
  <!-- Stats -->
61
61
  <${StatsGrid} items=${[
62
- { label: t('dashboard.registeredOwners'), value: c.owners, color: '#3b82f6' },
63
- { label: t('dashboard.registeredAgents'), value: c.agents, sub: '(' + c.active_agents_24h + ' ' + t('dashboard.active24h') + ')', color: '#a855f7' },
64
- { label: t('dashboard.publishedActions'), value: c.actions, color: '#06b6d4' },
65
- { label: t('dashboard.activeBoards'), value: c.boards, color: '#22c55e' },
66
- { label: t('dashboard.activeChatSessions'), value: c.chat_instances || 0, color: '#06b6d4' },
67
- { label: t('dashboard.agentTasksActiveTasks'), value: activeTaskCount != null ? activeTaskCount : '--', color: '#f97316' },
68
- { label: t('dashboard.sharingGroupsTotalCount'), value: sharingGroupCount != null ? sharingGroupCount : '--', color: '#8b5cf6' },
62
+ { label: t('dashboard.registeredOwners'), value: c.owners, tone: 'blue' },
63
+ { label: t('dashboard.registeredAgents'), value: c.agents, sub: '(' + c.active_agents_24h + ' ' + t('dashboard.active24h') + ')', tone: 'purple' },
64
+ { label: t('dashboard.publishedActions'), value: c.actions, tone: 'cyan' },
65
+ { label: t('dashboard.activeBoards'), value: c.boards, tone: 'green' },
66
+ { label: t('dashboard.activeChatSessions'), value: c.chat_instances || 0, tone: 'cyan' },
67
+ { label: t('dashboard.agentTasksActiveTasks'), value: activeTaskCount != null ? activeTaskCount : '--', tone: 'orange' },
68
+ { label: t('dashboard.sharingGroupsTotalCount'), value: sharingGroupCount != null ? sharingGroupCount : '--', tone: 'purple' },
69
69
  ]} />
70
70
 
71
71
  <!-- Economy & Config -->
@@ -13,6 +13,7 @@
13
13
  * @version-history
14
14
  * v1.0.0 — 2026-03-15 — initial implementation (Phase 6)
15
15
  * v1.1.0 — 2026-03-20 — add template moderation queue subtab
16
+ * v1.2.0 — 2026-06-02 — Admin design unification: main btn-* classes → adm-btn* (btn-success→adm-btn, btn-danger→adm-btn-action adm-btn-danger), error divs → <ErrorBox>.
16
17
  */
17
18
  import { h } from 'preact';
18
19
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -20,7 +21,7 @@ import htm from 'htm';
20
21
  const html = htm.bind(h);
21
22
  import { t } from '/js/i18n.js';
22
23
  import { escHtml } from '/js/utils.js';
23
- import { StatsGrid, Empty, Spinner, Badge, ExpandableHelp, dt } from './shared.js';
24
+ import { StatsGrid, Empty, Spinner, Badge, ExpandableHelp, ErrorBox, dt } from './shared.js';
24
25
  import * as pkgService from '/js/services/packages.js';
25
26
  import {
26
27
  seedExamples,
@@ -114,7 +115,7 @@ function ReviewPanel({ item, onDone }) {
114
115
  <//>
115
116
  ` : null}
116
117
 
117
- ${error ? html`<div class="error-box">${escHtml(error)}</div>` : null}
118
+ ${error ? html`<${ErrorBox} message=${error} />` : null}
118
119
 
119
120
  <div class="adm-mod-actions">
120
121
  <div class="adm-mod-action-group">
@@ -126,7 +127,7 @@ function ReviewPanel({ item, onDone }) {
126
127
  onInput=${e => setComment(e.target.value)}
127
128
  placeholder=${t('dashboard.modCommentPlaceholder') || 'Optional approval comment...'}
128
129
  />
129
- <button class="btn-success" onClick=${handleApprove} disabled=${acting}>
130
+ <button class="adm-btn" onClick=${handleApprove} disabled=${acting}>
130
131
  ${t('dashboard.modApprove') || 'Approve'}
131
132
  </button>
132
133
  </div>
@@ -139,7 +140,7 @@ function ReviewPanel({ item, onDone }) {
139
140
  onInput=${e => setReason(e.target.value)}
140
141
  placeholder=${t('dashboard.modReasonPlaceholder') || 'Reason for rejection...'}
141
142
  />
142
- <button class="btn-danger" onClick=${handleReject} disabled=${acting}>
143
+ <button class="adm-btn-action adm-btn-danger" onClick=${handleReject} disabled=${acting}>
143
144
  ${t('dashboard.modReject') || 'Reject'}
144
145
  </button>
145
146
  </div>
@@ -241,7 +242,7 @@ function PublishedTemplates({ templates, onReload }) {
241
242
  <tr key=${tpl.id + '-suspend'}>
242
243
  <td colspan="4">
243
244
  <div class="adm-sub-panel adm-mod-suspend-panel">
244
- ${error ? html`<div class="error-box">${escHtml(error)}</div>` : null}
245
+ ${error ? html`<${ErrorBox} message=${error} />` : null}
245
246
  <label class="adm-mod-action-label">${t('dashboard.modSuspendReason') || 'Suspension reason (required)'}</label>
246
247
  <div class="adm-mod-suspend-row">
247
248
  <input
@@ -251,7 +252,7 @@ function PublishedTemplates({ templates, onReload }) {
251
252
  onInput=${e => setSuspendReason(e.target.value)}
252
253
  placeholder=${t('dashboard.modSuspendReasonPlaceholder') || 'Reason for suspension...'}
253
254
  />
254
- <button class="btn-danger" onClick=${() => handleSuspend(tpl.id)} disabled=${acting}>
255
+ <button class="adm-btn-action adm-btn-danger" onClick=${() => handleSuspend(tpl.id)} disabled=${acting}>
255
256
  ${t('dashboard.modConfirmSuspend') || 'Confirm Suspend'}
256
257
  </button>
257
258
  </div>
@@ -305,8 +306,8 @@ export default function PackagesAdminTab({ data, reload, session }) {
305
306
  const [seedMsg, setSeedMsg] = useState('');
306
307
  const [subtab, setSubtab] = useState('packages');
307
308
 
308
- const loadData = useCallback(async () => {
309
- setLoading(true);
309
+ const loadData = useCallback(async ({ showSpinner = true } = {}) => {
310
+ if (showSpinner) setLoading(true);
310
311
  try {
311
312
  const [pkgRes, instRes, tplRes, pendRes] = await Promise.all([
312
313
  pkgService.listPackages({ limit: 50 }),
@@ -329,7 +330,7 @@ export default function PackagesAdminTab({ data, reload, session }) {
329
330
 
330
331
  // Live updates
331
332
  useEffect(() => {
332
- const handler = () => { loadData(); };
333
+ const handler = () => { loadData({ showSpinner: false }); }; // silent: no flash
333
334
  window.addEventListener('aimeat-live-update', handler);
334
335
  return () => window.removeEventListener('aimeat-live-update', handler);
335
336
  }, [loadData]);
@@ -1,9 +1,19 @@
1
+ /**
2
+ * @file portal-tab.js
3
+ * @description Admin dashboard Portal tab — landing-page template editor,
4
+ * site memory keys, KV pairs, AI prompt, and changelog (with LB-mode banner).
5
+ * @structure PortalTab (default)
6
+ * @usage Mounted by the admin dashboard tab router.
7
+ * @version-history
8
+ * v1.1.0 — 2026-06-02 — Admin design unification: raw template textarea →
9
+ * adm-textarea adm-input-full (drop inline mono/border/background styles).
10
+ */
1
11
  import { h } from 'preact';
2
12
  import { useState, useEffect } from 'preact/hooks';
3
13
  import htm from 'htm';
4
14
  const html = htm.bind(h);
5
15
  import { t } from '/js/i18n.js';
6
- import { escHtml } from '/js/utils.js';
16
+ import { escHtml, copyToClipboard } from '/js/utils.js';
7
17
  import { dt, Badge, ExpandableHelp, useToast, Toast } from './shared.js';
8
18
  import {
9
19
  saveSiteTemplate, deleteSiteTemplate, clearSiteCache,
@@ -74,7 +84,7 @@ export default function PortalTab({ data, reload }) {
74
84
  try {
75
85
  const res = await getSitePrompt();
76
86
  const prompt = res.data?.prompt || 'No prompt available';
77
- await navigator.clipboard.writeText(prompt);
87
+ await copyToClipboard(prompt);
78
88
  showOk(t('dashboard.portalAiCopied'));
79
89
  } catch (e) { showErr(e.message); }
80
90
  }
@@ -132,8 +142,8 @@ export default function PortalTab({ data, reload }) {
132
142
  </tbody>
133
143
  </table>
134
144
  </${ExpandableHelp}>
135
- <textarea rows="20" value=${template} onInput=${e => setTemplate(e.target.value)}
136
- style="width:100%;font-family:monospace;font-size:13px;padding:12px;border:1px solid var(--glass-border);border-radius:8px;background:var(--glass-bg);color:var(--text-bright);resize:vertical"></textarea>
145
+ <textarea class="adm-textarea adm-input-full" rows="20" value=${template} onInput=${e => setTemplate(e.target.value)}
146
+ style="font-size:13px"></textarea>
137
147
  <div class="adm-flex adm-mt-sm">
138
148
  <button class="adm-btn-action" onClick=${saveTemplate}>\u{1F4BE} ${t('dashboard.portalSaveTemplate')}</button>
139
149
  <button class="adm-btn-action" onClick=${downloadTemplate}>\u{1F4E5} ${t('dashboard.portalDownload')}</button>
@@ -37,8 +37,8 @@ export default function PromptsTab({ data, reload }) {
37
37
 
38
38
  const stats = [
39
39
  { label: t('dashboard.promptsTotal'), value: prompts.length },
40
- { label: t('dashboard.promptsStatusActive'), value: prompts.filter(p => p.active).length, color: '#22c55e' },
41
- { label: t('dashboard.promptsStatusInactive'), value: prompts.filter(p => !p.active).length, color: '#ef4444' },
40
+ { label: t('dashboard.promptsStatusActive'), value: prompts.filter(p => p.active).length, tone: 'green' },
41
+ { label: t('dashboard.promptsStatusInactive'), value: prompts.filter(p => !p.active).length, tone: 'red' },
42
42
  { label: t('dashboard.promptsGroups'), value: Object.keys(groups).length },
43
43
  ];
44
44
 
@@ -188,7 +188,7 @@ export default function PromptsTab({ data, reload }) {
188
188
  <button class="adm-btn-action" onClick=${handleSave} disabled=${saving}>
189
189
  ${saving ? '...' : t('dashboard.promptsSave')}
190
190
  </button>
191
- <button class="adm-btn-action" style="background:#dc262633;color:#ef4444" onClick=${handleReset} disabled=${saving}>
191
+ <button class="adm-btn-action adm-btn-danger" onClick=${handleReset} disabled=${saving}>
192
192
  ${t('dashboard.promptsReset')}
193
193
  </button>
194
194
  </div>
@@ -225,7 +225,7 @@ export default function PromptsTab({ data, reload }) {
225
225
  </${ExpandableHelp}>
226
226
 
227
227
  <div style="margin:12px 0">
228
- <button class="adm-btn-action" style="background:#dc262633;color:#ef4444" onClick=${handleResetAll} disabled=${saving}>
228
+ <button class="adm-btn-action adm-btn-danger" onClick=${handleResetAll} disabled=${saving}>
229
229
  ${t('dashboard.promptsResetAll')}
230
230
  </button>
231
231
  </div>
@@ -238,7 +238,7 @@ export default function PromptsTab({ data, reload }) {
238
238
  ${t('dashboard.' + (GROUP_NAMES[g] || g))} (${items.length})
239
239
  </summary>
240
240
  <div style="text-align:right;padding:4px 8px">
241
- <button class="btn-danger" style="font-size:12px;padding:4px 12px" onClick=${() => {
241
+ <button class="adm-btn-action adm-btn-danger" style="font-size:12px;padding:4px 12px" onClick=${() => {
242
242
  confirm(
243
243
  t('dashboard.promptsResetGroupConfirm') || 'Reset all prompts in this group to factory defaults?',
244
244
  async () => {
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @file push-tab.js
3
+ * @description Admin dashboard Push Notifications tab — subscription stats, browser
4
+ * subscribe/test/unsubscribe, per-locale web-push/email template editing, reset,
5
+ * notification-trigger reference, and the subscriptions table.
6
+ * @structure default export PushTab({ data, reload }); template editor + subscriptions list.
7
+ * @usage Mounted by the admin dashboard tab router.
8
+ * @version-history
9
+ * v1.1.0 — 2026-06-02 — Admin design unification: reset button inline danger style →
10
+ * adm-btn-danger, template body textarea → adm-textarea (dynamic min-height kept).
11
+ */
1
12
  import { h } from 'preact';
2
13
  import { useState } from 'preact/hooks';
3
14
  import htm from 'htm';
@@ -224,8 +235,8 @@ export default function PushTab({ data, reload }) {
224
235
 
225
236
  <label class="adm-text-dim" style="font-size:.75rem">${t('dashboard.pushFieldBody')}</label>
226
237
  <textarea
227
- class="adm-input"
228
- style="font-size:.85rem;font-family:monospace;min-height:${isWebPush ? '40px' : '100px'};resize:vertical"
238
+ class="adm-textarea"
239
+ style="min-height:${isWebPush ? '40px' : '100px'}"
229
240
  onInput=${(e) => updateField(tpl.id, 'body', e.target.value)}
230
241
  >${tpl.fields.body || ''}</textarea>
231
242
  </div>
@@ -244,8 +255,8 @@ export default function PushTab({ data, reload }) {
244
255
 
245
256
  <!-- Reset button -->
246
257
  <div style="display:flex;justify-content:flex-end;margin-top:4px">
247
- <button class="adm-btn-action adm-text-sm" onClick=${handleReset} disabled=${resetStatus === 'resetting'}
248
- style="color:#ef4444;border-color:rgba(239,68,68,0.3)">${resetStatus === 'resetting' ? t('dashboard.pushResetting') : t('dashboard.pushResetBtn')}</button>
258
+ <button class="adm-btn-action adm-text-sm adm-btn-danger" onClick=${handleReset} disabled=${resetStatus === 'resetting'}
259
+ >${resetStatus === 'resetting' ? t('dashboard.pushResetting') : t('dashboard.pushResetBtn')}</button>
249
260
  </div>
250
261
  </div>
251
262
 
@@ -26,9 +26,9 @@ export default function RealtimeTab({ data, reload }) {
26
26
  return html`
27
27
  ${toast && html`<${Toast} ...${toast} onDismiss=${clearToast} />`}
28
28
  <${StatsGrid} items=${[
29
- { label: t('dashboard.activeRooms'), value: rooms.length, color: '#06b6d4' },
30
- { label: t('dashboard.totalPeers'), value: rooms.reduce((a, r) => a + (r.peer_count || r.peers?.length || 0), 0), color: '#22c55e' },
31
- { label: t('dashboard.yjsDocs'), value: docs.length, color: '#a855f7' },
29
+ { label: t('dashboard.activeRooms'), value: rooms.length, tone: 'cyan' },
30
+ { label: t('dashboard.totalPeers'), value: rooms.reduce((a, r) => a + (r.peer_count || r.peers?.length || 0), 0), tone: 'green' },
31
+ { label: t('dashboard.yjsDocs'), value: docs.length, tone: 'purple' },
32
32
  ]} />
33
33
 
34
34
  ${rooms.length > 0 && html`