aimeat 1.25.0 → 1.26.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 (252) hide show
  1. package/README.md +8 -1
  2. package/dist/locales/en.json +144 -0
  3. package/dist/locales/fi.json +144 -0
  4. package/dist/public/components/ImageDeliverable.js +2 -1
  5. package/dist/public/components/Modal.js +3 -2
  6. package/dist/public/components/NotificationBell.js +2 -1
  7. package/dist/public/components/PresenceDot.js +53 -0
  8. package/dist/public/css/components/markdown.css +67 -0
  9. package/dist/public/css/theme.css +30 -0
  10. package/dist/public/css/views/notebook.css +195 -0
  11. package/dist/public/css/views/profile.css +15 -0
  12. package/dist/public/js/api.js +5 -3
  13. package/dist/public/js/components/auth-image.js +3 -2
  14. package/dist/public/js/components/tag-cloud.js +1 -1
  15. package/dist/public/js/components/tag-editor.js +1 -1
  16. package/dist/public/js/hooks.js +1 -2
  17. package/dist/public/js/presence-store.js +97 -0
  18. package/dist/public/js/services/admin.js +14 -5
  19. package/dist/public/js/services/boards.js +1 -1
  20. package/dist/public/js/services/ecosystem.js +1 -1
  21. package/dist/public/js/services/foundry-prompts-base.js +2 -2
  22. package/dist/public/js/services/foundry-smoke.js +2 -1
  23. package/dist/public/js/services/foundry-validate.js +6 -8
  24. package/dist/public/js/services/foundry.js +4 -3
  25. package/dist/public/js/services/generator-prompts-base.js +2 -2
  26. package/dist/public/js/services/generator-smoke.js +2 -1
  27. package/dist/public/js/services/generator-validate.js +5 -7
  28. package/dist/public/js/services/generator.js +2 -1
  29. package/dist/public/js/services/memory.js +13 -0
  30. package/dist/public/js/services/messages.js +2 -1
  31. package/dist/public/js/services/notebook.js +124 -0
  32. package/dist/public/js/services/packages.js +3 -2
  33. package/dist/public/js/services/presence.js +35 -0
  34. package/dist/public/js/style-testbed.js +6 -5
  35. package/dist/public/lib/minidenticons.min.js +1 -0
  36. package/dist/public/lib/yaml.mjs +1 -0
  37. package/dist/public/spa.html +90 -8
  38. package/dist/public/types/aimeat-globals.d.ts +52 -0
  39. package/dist/public/types/vendored-libs.d.ts +56 -0
  40. package/dist/public/views/_template.js +3 -1
  41. package/dist/public/views/admin/agent-integration-tab.js +4 -3
  42. package/dist/public/views/admin/agents-tab.js +3 -2
  43. package/dist/public/views/admin/config-tab.js +4 -3
  44. package/dist/public/views/admin/economy-tab.js +4 -3
  45. package/dist/public/views/admin/federation-tab.js +138 -1
  46. package/dist/public/views/admin/maintenance-tab.js +1 -1
  47. package/dist/public/views/admin/messages-tab.js +115 -0
  48. package/dist/public/views/admin/overview-tab.js +4 -3
  49. package/dist/public/views/admin/portal-tab.js +90 -2
  50. package/dist/public/views/admin/push-tab.js +8 -6
  51. package/dist/public/views/admin/security-tab.js +7 -7
  52. package/dist/public/views/admin.js +29 -9
  53. package/dist/public/views/doc-solo.js +3 -1
  54. package/dist/public/views/guides.js +8 -4
  55. package/dist/public/views/hobbies.js +4 -2
  56. package/dist/public/views/marketplace.js +10 -9
  57. package/dist/public/views/openclaw.js +1 -1
  58. package/dist/public/views/portal-dev.js +5 -2
  59. package/dist/public/views/portal.js +3 -2
  60. package/dist/public/views/profile/agents/shared-board.js +4 -2
  61. package/dist/public/views/profile/agents/tab-messages.js +13 -11
  62. package/dist/public/views/profile/agents-messages-subtab.js +2 -1
  63. package/dist/public/views/profile/apps-tab.js +2 -1
  64. package/dist/public/views/profile/boards-tab.js +3 -2
  65. package/dist/public/views/profile/calibrator-batch.js +3 -2
  66. package/dist/public/views/profile/data-wallet-tab.js +2 -1
  67. package/dist/public/views/profile/extensions-tab.js +4 -3
  68. package/dist/public/views/profile/federation-tab.js +4 -0
  69. package/dist/public/views/profile/foundry-dashboard/use-autopilot.js +3 -2
  70. package/dist/public/views/profile/foundry-detail.js +1 -1
  71. package/dist/public/views/profile/generator-detail.js +1 -1
  72. package/dist/public/views/profile/inbox-tab.js +7 -3
  73. package/dist/public/views/profile/inline-panels.js +3 -1
  74. package/dist/public/views/profile/knowledge-tab.js +2 -1
  75. package/dist/public/views/profile/landing-page.js +88 -0
  76. package/dist/public/views/profile/memory-tab.js +6 -5
  77. package/dist/public/views/profile/notebook-tab.js +500 -0
  78. package/dist/public/views/profile/organisms/activity-panel.js +135 -0
  79. package/dist/public/views/profile/organisms/agents.js +141 -0
  80. package/dist/public/views/profile/organisms/document.js +274 -0
  81. package/dist/public/views/profile/organisms/helpers.js +61 -0
  82. package/dist/public/views/profile/organisms/home.js +315 -0
  83. package/dist/public/views/profile/organisms/members.js +220 -0
  84. package/dist/public/views/profile/organisms/panels.js +173 -0
  85. package/dist/public/views/profile/organisms/participants-panel.js +160 -0
  86. package/dist/public/views/profile/organisms/schema-form.js +127 -0
  87. package/dist/public/views/profile/organisms/sources-panel.js +164 -0
  88. package/dist/public/views/profile/organisms/widgets.js +58 -0
  89. package/dist/public/views/profile/organisms/workspace-comments.js +99 -0
  90. package/dist/public/views/profile/organisms/workspace-list.js +268 -0
  91. package/dist/public/views/profile/organisms/workspace.js +1272 -0
  92. package/dist/public/views/profile/organisms-tab.js +456 -3819
  93. package/dist/public/views/profile/shared.js +64 -2
  94. package/dist/public/views/profile/workflows-form.js +1 -1
  95. package/dist/public/views/profile.js +16 -14
  96. package/dist/public/views/public-knowledge-viewer.js +3 -2
  97. package/dist/public/views/public-workspace-viewer.js +2 -1
  98. package/dist/scripts/check-importmap.d.ts +2 -0
  99. package/dist/scripts/check-importmap.d.ts.map +1 -0
  100. package/dist/scripts/check-importmap.js +102 -0
  101. package/dist/scripts/check-importmap.js.map +1 -0
  102. package/dist/src/cli/scaffold.d.ts +26 -3
  103. package/dist/src/cli/scaffold.d.ts.map +1 -1
  104. package/dist/src/cli/scaffold.js +33 -3
  105. package/dist/src/cli/scaffold.js.map +1 -1
  106. package/dist/src/config.d.ts +10 -0
  107. package/dist/src/config.d.ts.map +1 -1
  108. package/dist/src/config.js +5 -0
  109. package/dist/src/config.js.map +1 -1
  110. package/dist/src/generated/api-types.d.ts +927 -11
  111. package/dist/src/generated/api-types.d.ts.map +1 -1
  112. package/dist/src/generated/prisma-postgres/edge.js +32 -6
  113. package/dist/src/generated/prisma-postgres/index-browser.js +29 -3
  114. package/dist/src/generated/prisma-postgres/index.d.ts +1970 -160
  115. package/dist/src/generated/prisma-postgres/index.js +32 -6
  116. package/dist/src/generated/prisma-postgres/package.json +1 -1
  117. package/dist/src/generated/prisma-postgres/schema.prisma +45 -12
  118. package/dist/src/generated/prisma-postgres/wasm.js +32 -6
  119. package/dist/src/index.js +36 -4
  120. package/dist/src/index.js.map +1 -1
  121. package/dist/src/routes/admin-monitoring.d.ts.map +1 -1
  122. package/dist/src/routes/admin-monitoring.js +8 -0
  123. package/dist/src/routes/admin-monitoring.js.map +1 -1
  124. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  125. package/dist/src/routes/federation-peer.js +327 -43
  126. package/dist/src/routes/federation-peer.js.map +1 -1
  127. package/dist/src/routes/federation-sync.d.ts +2 -0
  128. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  129. package/dist/src/routes/federation-sync.js +20 -1
  130. package/dist/src/routes/federation-sync.js.map +1 -1
  131. package/dist/src/routes/librarian.d.ts +18 -0
  132. package/dist/src/routes/librarian.d.ts.map +1 -0
  133. package/dist/src/routes/librarian.js +88 -0
  134. package/dist/src/routes/librarian.js.map +1 -0
  135. package/dist/src/routes/messages.d.ts.map +1 -1
  136. package/dist/src/routes/messages.js +2 -1
  137. package/dist/src/routes/messages.js.map +1 -1
  138. package/dist/src/routes/portal.d.ts.map +1 -1
  139. package/dist/src/routes/portal.js +9 -1
  140. package/dist/src/routes/portal.js.map +1 -1
  141. package/dist/src/routes/presence.d.ts +23 -0
  142. package/dist/src/routes/presence.d.ts.map +1 -0
  143. package/dist/src/routes/presence.js +88 -0
  144. package/dist/src/routes/presence.js.map +1 -0
  145. package/dist/src/routes/site.d.ts +12 -0
  146. package/dist/src/routes/site.d.ts.map +1 -1
  147. package/dist/src/routes/site.js +45 -1
  148. package/dist/src/routes/site.js.map +1 -1
  149. package/dist/src/routes/sse.d.ts +2 -0
  150. package/dist/src/routes/sse.d.ts.map +1 -1
  151. package/dist/src/routes/sse.js +9 -0
  152. package/dist/src/routes/sse.js.map +1 -1
  153. package/dist/src/routes/wellknown.d.ts.map +1 -1
  154. package/dist/src/routes/wellknown.js +5 -0
  155. package/dist/src/routes/wellknown.js.map +1 -1
  156. package/dist/src/routes/work.d.ts.map +1 -1
  157. package/dist/src/routes/work.js +6 -0
  158. package/dist/src/routes/work.js.map +1 -1
  159. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  160. package/dist/src/server-bootstrap/routes-loader.js +13 -0
  161. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  162. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  163. package/dist/src/server-bootstrap/service-init.js +34 -0
  164. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  165. package/dist/src/services/config-schema.d.ts.map +1 -1
  166. package/dist/src/services/config-schema.js +2 -0
  167. package/dist/src/services/config-schema.js.map +1 -1
  168. package/dist/src/services/federation-availability.d.ts +28 -0
  169. package/dist/src/services/federation-availability.d.ts.map +1 -0
  170. package/dist/src/services/federation-availability.js +58 -0
  171. package/dist/src/services/federation-availability.js.map +1 -0
  172. package/dist/src/services/federation-book.d.ts +70 -0
  173. package/dist/src/services/federation-book.d.ts.map +1 -0
  174. package/dist/src/services/federation-book.js +198 -0
  175. package/dist/src/services/federation-book.js.map +1 -0
  176. package/dist/src/services/federation-tiers.d.ts +45 -0
  177. package/dist/src/services/federation-tiers.d.ts.map +1 -0
  178. package/dist/src/services/federation-tiers.js +58 -0
  179. package/dist/src/services/federation-tiers.js.map +1 -0
  180. package/dist/src/services/federation.d.ts +15 -0
  181. package/dist/src/services/federation.d.ts.map +1 -1
  182. package/dist/src/services/federation.js +12 -0
  183. package/dist/src/services/federation.js.map +1 -1
  184. package/dist/src/services/librarian.d.ts +62 -0
  185. package/dist/src/services/librarian.d.ts.map +1 -0
  186. package/dist/src/services/librarian.js +135 -0
  187. package/dist/src/services/librarian.js.map +1 -0
  188. package/dist/src/services/message-delivery.d.ts +3 -1
  189. package/dist/src/services/message-delivery.d.ts.map +1 -1
  190. package/dist/src/services/message-delivery.js +23 -2
  191. package/dist/src/services/message-delivery.js.map +1 -1
  192. package/dist/src/services/network-policy.d.ts +88 -0
  193. package/dist/src/services/network-policy.d.ts.map +1 -0
  194. package/dist/src/services/network-policy.js +154 -0
  195. package/dist/src/services/network-policy.js.map +1 -0
  196. package/dist/src/services/notebook-classify-prompt.d.ts +15 -0
  197. package/dist/src/services/notebook-classify-prompt.d.ts.map +1 -0
  198. package/dist/src/services/notebook-classify-prompt.js +39 -0
  199. package/dist/src/services/notebook-classify-prompt.js.map +1 -0
  200. package/dist/src/services/notebook-classify.d.ts +80 -0
  201. package/dist/src/services/notebook-classify.d.ts.map +1 -0
  202. package/dist/src/services/notebook-classify.js +150 -0
  203. package/dist/src/services/notebook-classify.js.map +1 -0
  204. package/dist/src/services/presence.d.ts +108 -0
  205. package/dist/src/services/presence.d.ts.map +1 -0
  206. package/dist/src/services/presence.js +304 -0
  207. package/dist/src/services/presence.js.map +1 -0
  208. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  209. package/dist/src/services/prompt-defaults.js +13 -0
  210. package/dist/src/services/prompt-defaults.js.map +1 -1
  211. package/dist/src/services/site.d.ts +20 -0
  212. package/dist/src/services/site.d.ts.map +1 -1
  213. package/dist/src/services/site.js +70 -0
  214. package/dist/src/services/site.js.map +1 -1
  215. package/dist/src/storage/interface.d.ts +48 -0
  216. package/dist/src/storage/interface.d.ts.map +1 -1
  217. package/dist/src/storage/interface.js.map +1 -1
  218. package/dist/src/storage/providers/mongodb/index.d.ts +11 -1
  219. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  220. package/dist/src/storage/providers/mongodb/index.js +128 -2
  221. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  222. package/dist/src/storage/providers/sqlite/index.d.ts +7 -1
  223. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  224. package/dist/src/storage/providers/sqlite/index.js +27 -2
  225. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  226. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts +5 -1
  227. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts.map +1 -1
  228. package/dist/src/storage/providers/sqlite/repos/direct-message.js +48 -0
  229. package/dist/src/storage/providers/sqlite/repos/direct-message.js.map +1 -1
  230. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +2 -0
  231. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  232. package/dist/src/storage/providers/sqlite/repos/memory.js +55 -0
  233. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  234. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  235. package/dist/src/storage/providers/sqlite/schema.js +61 -0
  236. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  237. package/dist/src/storage/repositories/direct-message.repository.d.ts +5 -1
  238. package/dist/src/storage/repositories/direct-message.repository.d.ts.map +1 -1
  239. package/dist/src/storage/repositories/memory.repository.d.ts +25 -0
  240. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  241. package/dist/src/utils/node-descriptor.d.ts +34 -0
  242. package/dist/src/utils/node-descriptor.d.ts.map +1 -0
  243. package/dist/src/utils/node-descriptor.js +42 -0
  244. package/dist/src/utils/node-descriptor.js.map +1 -0
  245. package/dist/src/utils/version.d.ts +3 -0
  246. package/dist/src/utils/version.d.ts.map +1 -0
  247. package/dist/src/utils/version.js +40 -0
  248. package/dist/src/utils/version.js.map +1 -0
  249. package/dist/static/app-catalog.html +643 -4
  250. package/package.json +5 -2
  251. package/prisma/schema.postgres.prisma +51 -18
  252. package/prisma/schema.prisma +29 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @file notebook.css
3
+ * @description Styles for the profile Notebook tab (pf-nb-* prefix): capture box, librarian hits,
4
+ * and inbox notes. Dark-theme only — all colors/spacing via theme.css variables.
5
+ * @version-history
6
+ * v1.0.0 — 2026-06-19 — Initial: capture + librarian + inbox (slice A).
7
+ */
8
+
9
+ .pf-nb-capture {
10
+ margin-bottom: var(--space-lg, 1.5rem);
11
+ }
12
+ .pf-nb-textarea {
13
+ width: 100%;
14
+ resize: vertical;
15
+ min-height: 5rem;
16
+ font-family: inherit;
17
+ }
18
+ .pf-nb-capture-actions {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: var(--space-sm, 0.75rem);
22
+ margin-top: var(--space-sm, 0.75rem);
23
+ flex-wrap: wrap;
24
+ }
25
+
26
+ .pf-nb-section {
27
+ margin-top: var(--space-lg, 1.5rem);
28
+ }
29
+
30
+ .pf-nb-search {
31
+ flex: 1;
32
+ }
33
+
34
+ .pf-nb-scope { margin-bottom: var(--space-sm, 0.75rem); }
35
+ .pf-nb-hit-producer {
36
+ margin-top: var(--space-xs, 0.4rem);
37
+ color: var(--text-dim, var(--text));
38
+ }
39
+
40
+ .pf-nb-hits {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--space-sm, 0.75rem);
44
+ }
45
+ .pf-nb-hit {
46
+ background: var(--card);
47
+ border: 1px solid var(--border);
48
+ border-radius: var(--radius, 8px);
49
+ padding: var(--space-sm, 0.75rem) var(--space-md, 1rem);
50
+ }
51
+ .pf-nb-hit-head {
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: space-between;
55
+ gap: var(--space-sm, 0.75rem);
56
+ }
57
+ .pf-nb-hit-title {
58
+ font-weight: 600;
59
+ color: var(--text);
60
+ }
61
+ .pf-nb-hit-snippet {
62
+ margin-top: var(--space-xs, 0.4rem);
63
+ color: var(--text-dim, var(--text));
64
+ font-size: 0.9em;
65
+ line-height: 1.4;
66
+ }
67
+ .pf-nb-hit-foot {
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: space-between;
71
+ gap: var(--space-sm, 0.75rem);
72
+ margin-top: var(--space-xs, 0.4rem);
73
+ }
74
+ .pf-nb-hit-key {
75
+ overflow: hidden;
76
+ text-overflow: ellipsis;
77
+ white-space: nowrap;
78
+ font-family: var(--font-mono, monospace);
79
+ }
80
+
81
+ .pf-nb-inbox {
82
+ display: flex;
83
+ flex-direction: column;
84
+ gap: var(--space-sm, 0.75rem);
85
+ }
86
+ .pf-nb-note {
87
+ background: var(--bg-dim, var(--card));
88
+ border: 1px solid var(--border);
89
+ border-radius: var(--radius, 8px);
90
+ padding: var(--space-sm, 0.75rem) var(--space-md, 1rem);
91
+ }
92
+ .pf-nb-note-text {
93
+ color: var(--text);
94
+ word-break: break-word;
95
+ }
96
+ .pf-nb-note-line {
97
+ display: block;
98
+ white-space: nowrap;
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ color: var(--text-dim, var(--text));
102
+ }
103
+ .pf-nb-note-text--peek {
104
+ max-height: 9rem;
105
+ overflow: hidden;
106
+ position: relative;
107
+ }
108
+ .pf-nb-note-text--peek::after {
109
+ content: '';
110
+ position: absolute;
111
+ left: 0;
112
+ right: 0;
113
+ bottom: 0;
114
+ height: 2.5rem;
115
+ background: linear-gradient(transparent, var(--bg-dim, var(--card)));
116
+ pointer-events: none;
117
+ }
118
+
119
+ .pf-nb-inbox-bar { margin-bottom: var(--space-sm, 0.75rem); }
120
+ .pf-nb-sort { max-width: 12rem; }
121
+ .pf-nb-note-meta {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: var(--space-xs, 0.4rem);
125
+ }
126
+ .pf-nb-view-btn {
127
+ padding: 0.05rem 0.45rem;
128
+ line-height: 1.2;
129
+ }
130
+ .pf-nb-note-foot {
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: space-between;
134
+ gap: var(--space-sm, 0.75rem);
135
+ margin-top: var(--space-xs, 0.4rem);
136
+ }
137
+ .pf-nb-note-btns {
138
+ display: flex;
139
+ gap: var(--space-xs, 0.4rem);
140
+ }
141
+
142
+ .pf-nb-suggest {
143
+ margin-top: var(--space-sm, 0.75rem);
144
+ padding: var(--space-md, 1rem);
145
+ background: var(--surface, var(--card));
146
+ border: 1px solid var(--border);
147
+ border-radius: var(--radius, 8px);
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: var(--space-xs, 0.4rem);
151
+ }
152
+ .pf-nb-suggest-reason {
153
+ font-style: italic;
154
+ margin-bottom: var(--space-xs, 0.4rem);
155
+ }
156
+ .pf-nb-suggest-label {
157
+ font-size: 0.8em;
158
+ color: var(--text-dim, var(--text));
159
+ margin-top: var(--space-xs, 0.4rem);
160
+ }
161
+ .pf-nb-suggest-body {
162
+ width: 100%;
163
+ resize: vertical;
164
+ font-family: inherit;
165
+ }
166
+ .pf-nb-suggest-alts {
167
+ display: flex;
168
+ flex-wrap: wrap;
169
+ align-items: center;
170
+ gap: var(--space-xs, 0.4rem);
171
+ margin-top: var(--space-xs, 0.4rem);
172
+ }
173
+ .pf-nb-suggest-actions {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: var(--space-sm, 0.75rem);
177
+ margin-top: var(--space-sm, 0.75rem);
178
+ }
179
+
180
+ .pf-nb-progress { gap: var(--space-sm, 0.75rem); }
181
+ .pf-nb-steps {
182
+ list-style: none;
183
+ margin: 0;
184
+ padding: 0;
185
+ display: flex;
186
+ flex-direction: column;
187
+ gap: 0.2rem;
188
+ }
189
+ .pf-nb-step {
190
+ font-size: 0.85em;
191
+ color: var(--text-dim, var(--text));
192
+ opacity: 0.6;
193
+ }
194
+ .pf-nb-step.active { opacity: 1; color: var(--text); font-weight: 600; }
195
+ .pf-nb-step.done { opacity: 0.85; color: var(--success, var(--text)); }
@@ -2427,6 +2427,8 @@
2427
2427
  .pj-doc-imgbar { margin: 0.3rem 0; display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }
2428
2428
  .pj-file-btn { cursor: pointer; }
2429
2429
  .pj-file-btn input { display: none; }
2430
+ /* Visually-hidden file input (organism / workspace ZIP import) — replaces inline style="display:none". */
2431
+ .pj-hidden-input { display: none; }
2430
2432
  .pj-file-btn-icon { margin-right: 0.15rem; }
2431
2433
  .pj-imgbar-hint { font-size: 0.78rem; color: var(--muted); }
2432
2434
  .pj-img-vis { margin: 0.2rem 0 0.5rem; padding: 0.55rem 0.65rem; border: 1px solid var(--border); border-radius: 8px; background: var(--card); }
@@ -2942,3 +2944,16 @@
2942
2944
  .pf-eco-tech-section-title { font-weight: 700; font-size: .82rem; margin-bottom: .4rem; color: var(--text-dim); }
2943
2945
  .pf-eco-tech-principal { display: inline-block; background: var(--card); border: 1px solid var(--border); border-radius: 6px; padding: .3rem .5rem; word-break: break-all; }
2944
2946
  .pf-eco-tech-access { margin: -.15rem 0 .4rem; }
2947
+
2948
+ /* ── Presence control (Koti home) — backed by PresenceControl in landing-page.js.
2949
+ Self-contained card; previews the same <PresenceDot> shown next to people. ── */
2950
+ .pf-presence-card { border: 1px solid var(--border); border-radius: 12px; background: var(--card); padding: 1rem 1.1rem; margin-bottom: 1rem; }
2951
+ .pf-presence-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
2952
+ .pf-presence-head .section-desc { margin-bottom: .75rem; }
2953
+ .pf-presence-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin: .5rem 0; }
2954
+ .pf-presence-label { font-size: .85rem; font-weight: 600; color: var(--text-dim); }
2955
+ .pf-presence-modes { display: inline-flex; gap: .4rem; }
2956
+ .pf-presence-pill { border: 1px solid var(--border); background: var(--bg-dim); color: var(--text); border-radius: 999px; padding: .3rem .85rem; font-size: .82rem; cursor: pointer; }
2957
+ .pf-presence-pill--on { background: var(--accent); border-color: var(--accent); color: #fff; }
2958
+ .pf-presence-pill:disabled { opacity: .6; cursor: default; }
2959
+ .pf-presence-hint { font-size: .78rem; color: var(--text-dim); margin: .15rem 0 .35rem; }
@@ -123,7 +123,9 @@ export function apiPatch(path, body) {
123
123
  return api(path, { method: 'PATCH', body: JSON.stringify(body) });
124
124
  }
125
125
 
126
- /** DELETE shorthand. */
127
- export function apiDelete(path) {
128
- return api(path, { method: 'DELETE' });
126
+ /** DELETE shorthand. Forwards an optional request body (e.g. `{ target }`). */
127
+ export function apiDelete(path, body) {
128
+ return api(path, body === undefined
129
+ ? { method: 'DELETE' }
130
+ : { method: 'DELETE', body: JSON.stringify(body) });
129
131
  }
@@ -7,6 +7,7 @@
7
7
  * html`<${AuthImage} src="/v1/memory/files/foo.png" class="file-thumb" alt="foo" />`
8
8
  * @version-history
9
9
  * v1.0.0 — 2026-03-17 — Initial implementation
10
+ * v1.0.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
10
11
  */
11
12
  import { h } from 'preact';
12
13
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -21,7 +22,7 @@ export default function AuthImage({ src, alt, ...props }) {
21
22
  if (!src) return;
22
23
 
23
24
  let cancelled = false;
24
- const headers = {};
25
+ const headers = /** @type {Record<string,string>} */ ({});
25
26
 
26
27
  if (window.AIMEAT?.auth?.hasSession) {
27
28
  const session = window.AIMEAT.auth.getSession();
@@ -32,7 +33,7 @@ export default function AuthImage({ src, alt, ...props }) {
32
33
 
33
34
  fetch(src, { headers })
34
35
  .then(r => {
35
- if (!r.ok) throw new Error(r.status);
36
+ if (!r.ok) throw new Error(String(r.status));
36
37
  return r.blob();
37
38
  })
38
39
  .then(blob => {
@@ -12,7 +12,7 @@ import { t } from '/js/i18n.js';
12
12
  * @param {Set<string>} props.selected — currently active filter tags
13
13
  * @param {(tag: string) => void} props.onToggle — called when a tag is clicked
14
14
  * @param {() => void} props.onClear — called when clear button is clicked
15
- * @param {number} [props.limit] — show only the first N tags behind a "show all" toggle.
15
+ * @param {number} [props.limit] - show only the first N tags behind a "show all" toggle.
16
16
  * Selected tags always stay visible so an active filter can't hide its own pill.
17
17
  */
18
18
  export default function TagCloud({ tags, selected, onToggle, onClear, limit }) {
@@ -9,7 +9,7 @@ import { t } from '/js/i18n.js';
9
9
  * @param {Object} props
10
10
  * @param {string[]} props.tags — current tags on the item
11
11
  * @param {(tags: string[]) => void} props.onSave — called with updated tag array on add/remove
12
- * @param {number} [props.maxTags=20] — maximum tags allowed
12
+ * @param {number} [props.maxTags=20] - maximum tags allowed
13
13
  */
14
14
  export default function TagEditor({ tags, onSave, maxTags = 20 }) {
15
15
  const [input, setInput] = useState('');
@@ -52,8 +52,7 @@ export function useApiCall(endpoint, options = {}) {
52
52
  if (!cancelled) setLoading(false);
53
53
  });
54
54
  return () => { cancelled = true; };
55
- // deps are intentionally dynamic — eslint-disable-next-line is correct here
56
- // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ // deps are intentionally dynamic (endpoint/method/reloadKey + caller-supplied deps)
57
56
  }, [endpoint, method, reloadKey, ...deps]);
58
57
 
59
58
  /** Call reload() to re-trigger the fetch (e.g. after a mutation). */
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @file presence-store.js
3
+ * @description Shared client-side presence store powering the `usePresence(ghii)`
4
+ * hook. Every <PresenceDot> in a list subscribes here; the store COALESCES all
5
+ * ghiis requested within one tick into a single batched GET /v1/presence call —
6
+ * so a 50-row member list makes one request, not fifty. Results are cached with
7
+ * a short TTL and re-fetched on the global `aimeat-live-update` SSE event, so all
8
+ * dots flip live without per-component polling.
9
+ * @structure
10
+ * - usePresence(ghii) — preact hook → { status, since } | undefined
11
+ * - refreshAll() — invalidate + re-batch all currently-subscribed ghiis
12
+ * - internal: cache, subscriber registry, pending-batch flusher
13
+ * @usage import { usePresence } from '/js/presence-store.js';
14
+ * @version-history
15
+ * v1.0.0 — 2026-06-19 — Initial shared presence store (cache + batch + live refresh).
16
+ */
17
+ import { useState, useEffect } from 'preact/hooks';
18
+ import { getPresenceBatch } from '/js/services/presence.js';
19
+
20
+ const cache = new Map(); // ghii → { status, since }
21
+ const subs = new Map(); // ghii → Set<fn>
22
+ const lastFetched = new Map(); // ghii → ms
23
+ let pending = new Set();
24
+ let timer = null;
25
+ let liveBound = false;
26
+
27
+ const TTL_MS = 60_000;
28
+ const BATCH_DELAY_MS = 40;
29
+
30
+ function notify(ghii) {
31
+ const set = subs.get(ghii);
32
+ if (set) for (const fn of set) fn(cache.get(ghii));
33
+ }
34
+
35
+ async function flush() {
36
+ timer = null;
37
+ const ids = [...pending];
38
+ pending = new Set();
39
+ if (!ids.length) return;
40
+ try {
41
+ const map = await getPresenceBatch(ids);
42
+ const now = Date.now();
43
+ for (const ghii of ids) {
44
+ cache.set(ghii, map[ghii] || { status: 'unknown', since: null });
45
+ lastFetched.set(ghii, now);
46
+ notify(ghii);
47
+ }
48
+ } catch {
49
+ /* leave the cache as-is; a later live-update or remount retries */
50
+ }
51
+ }
52
+
53
+ function schedule() {
54
+ if (!timer) timer = setTimeout(flush, BATCH_DELAY_MS);
55
+ }
56
+
57
+ function request(ghii, force) {
58
+ if (!force && lastFetched.has(ghii) && Date.now() - lastFetched.get(ghii) < TTL_MS) return;
59
+ pending.add(ghii);
60
+ schedule();
61
+ }
62
+
63
+ /** Invalidate and re-fetch every currently-subscribed identity (called on SSE updates). */
64
+ export function refreshAll() {
65
+ lastFetched.clear();
66
+ for (const ghii of subs.keys()) pending.add(ghii);
67
+ if (pending.size) schedule();
68
+ }
69
+
70
+ function bindLive() {
71
+ if (liveBound) return;
72
+ liveBound = true;
73
+ window.addEventListener('aimeat-live-update', refreshAll);
74
+ }
75
+
76
+ /**
77
+ * Subscribe to a single identity's presence. Returns { status, since } (or
78
+ * undefined until the first fetch resolves). Pass a falsy ghii to opt out.
79
+ */
80
+ export function usePresence(ghii) {
81
+ const [val, setVal] = useState(() => (ghii ? cache.get(ghii) : undefined));
82
+ useEffect(() => {
83
+ if (!ghii) { setVal(undefined); return; }
84
+ bindLive();
85
+ let set = subs.get(ghii);
86
+ if (!set) { set = new Set(); subs.set(ghii, set); }
87
+ const fn = (v) => setVal(v);
88
+ set.add(fn);
89
+ setVal(cache.get(ghii));
90
+ request(ghii);
91
+ return () => {
92
+ set.delete(fn);
93
+ if (!set.size) subs.delete(ghii);
94
+ };
95
+ }, [ghii]);
96
+ return val;
97
+ }
@@ -38,6 +38,9 @@ export const patchBoardMembers = (id, body) => apiPatch(`/v1/boards/${encodeURIC
38
38
  // ── Work ──
39
39
  export const getAdminWork = () => apiGet('/v1/admin/work');
40
40
 
41
+ // ── Direct-message delivery telemetry ──
42
+ export const getMessageStats = () => apiGet('/v1/admin/messages/stats');
43
+
41
44
  // ── Maintenance ──
42
45
  export const getMaintenance = () => apiGet('/v1/admin/maintenance');
43
46
  export const setMaintenance = (enabled, message) => apiPost('/v1/admin/maintenance', { enabled, message });
@@ -62,6 +65,10 @@ export const removePeer = (nodeId) => apiDelete(`/v1/federation/peers/${encodeUR
62
65
  export const removePeerEmergency = (nodeId) => apiDelete(`/v1/federation/peers/${encodeURIComponent(nodeId)}?emergency=true`);
63
66
  export const testFederationNode = (targetUrl) => apiPost('/v1/federation/test', { target_url: targetUrl });
64
67
  export const updatePeerPolicy = (nodeId, policy) => apiPut(`/v1/federation/peers/${encodeURIComponent(nodeId)}`, policy);
68
+ export const promotePeer = (nodeId, opts = {}) => apiPost(`/v1/federation/peers/${encodeURIComponent(nodeId)}/promote`, opts);
69
+ export const getFederationBook = () => apiGet('/v1/federation/book');
70
+ export const rebuildFederationBook = () => apiPost('/v1/federation/book/rebuild', {});
71
+ export const pullFederationBook = () => apiPost('/v1/federation/book/pull', {});
65
72
  export const joinGenesisNetwork = (genesisUrl, role) => apiPost('/v1/admin/federation/join', { genesis_url: genesisUrl, role: role || 'contributor' });
66
73
 
67
74
  // ── Hooks ──
@@ -162,6 +169,8 @@ export const importSiteBundle = (bundle) => apiPost('/v1/site/import', bundle);
162
169
  export const clearSiteCache = () => apiPost('/v1/site/cache-invalidate');
163
170
  export const getSitePrompt = () => apiGet('/v1/site/prompt');
164
171
  export const triggerLbSync = () => apiPost('/v1/admin/site/sync');
172
+ export const getHeaderNav = () => apiGet('/v1/site/header-nav');
173
+ export const saveHeaderNav = (order, hidden) => apiPut('/v1/site/header-nav', { order, hidden });
165
174
 
166
175
  // ── Memory (for portal memory keys) ──
167
176
  export const addMemory = (key, value) => apiPost('/v1/memory', { key, value, visibility: 'private' });
@@ -269,9 +278,9 @@ export const getAdminAgentTasks = (params = {}) => {
269
278
 
270
279
  // ── Sharing Groups (admin) ──
271
280
  export const getAdminSharingGroups = () => apiGet('/v1/admin/sharing-groups');
272
-
273
- // ── Subdomain mappings (operator) ──
274
- export const getSubdomainSites = () => apiGet('/v1/admin/subdomains');
275
- export const createSubdomainSite = (body) => apiPost('/v1/admin/subdomains', body);
276
- export const updateSubdomainSite = (sub, updates) => apiPatch(`/v1/admin/subdomains/${encodeURIComponent(sub)}`, updates);
281
+
282
+ // ── Subdomain mappings (operator) ──
283
+ export const getSubdomainSites = () => apiGet('/v1/admin/subdomains');
284
+ export const createSubdomainSite = (body) => apiPost('/v1/admin/subdomains', body);
285
+ export const updateSubdomainSite = (sub, updates) => apiPatch(`/v1/admin/subdomains/${encodeURIComponent(sub)}`, updates);
277
286
  export const deleteSubdomainSite = (sub) => apiDelete(`/v1/admin/subdomains/${encodeURIComponent(sub)}`);
@@ -66,7 +66,7 @@ export async function deletePost(boardId, postId) {
66
66
  }
67
67
 
68
68
  /** Update board members (add/remove GAIIs). */
69
- export async function updateBoardMembers(boardId, { add, remove }) {
69
+ export async function updateBoardMembers(boardId, { add, remove } = /** @type {{ add?: any, remove?: any }} */ ({})) {
70
70
  return api(`/v1/boards/${encodeURIComponent(boardId)}/members`, {
71
71
  method: 'PATCH',
72
72
  body: JSON.stringify({ add, remove }),
@@ -41,7 +41,7 @@ export async function listPending() {
41
41
  }
42
42
 
43
43
  /** Approve or deny a pending request by its user code, selecting scopes (and optional data areas). */
44
- export async function approve(userCode, { action, scopes, data_areas } = {}) {
44
+ export async function approve(userCode, { action, scopes, data_areas } = /** @type {{ action?: string, scopes?: any, data_areas?: any }} */ ({})) {
45
45
  return apiPost(`/v1/ecosystem-apps/${encodeURIComponent(userCode)}/approve`, { action, scopes, data_areas });
46
46
  }
47
47
 
@@ -27,7 +27,7 @@ Available building blocks:
27
27
  - Cortex: Client-side JS domain library (IIFE on AIMEAT namespace). Wraps extension APIs and memory reads into clean domain methods for apps.
28
28
 
29
29
  Extensions run in an ISOLATED V8 sandbox with ONLY this API (no Node.js, no global fetch, no setTimeout, no require, no import):
30
- ctx.memory.get(key) → value or null (ALWAYS null-check before using: `|| []` or `|| {}`)
30
+ ctx.memory.get(key) → value or null (ALWAYS null-check before using: \`|| []\` or \`|| {}\`)
31
31
  ctx.memory.set(key, value) → void
32
32
  ctx.memory.search(prefix) → Array<{ key, value }> (NOT plain strings!)
33
33
  ctx.memory.delete(key) → boolean
@@ -1481,7 +1481,7 @@ export function summarizeExtensionApi(result) {
1481
1481
  : '';
1482
1482
 
1483
1483
  if (actionsSection) {
1484
- const actionBlocks = actionsSection.split(/\n - id:/);
1484
+ const actionBlocks = actionsSection.split(/\n {2}- id:/);
1485
1485
  for (const block of actionBlocks.slice(1)) {
1486
1486
  const id = block.split('\n')[0].trim();
1487
1487
  const desc = block.match(/description:\s*"([^"]+)"/)?.[1] || '';
@@ -7,6 +7,7 @@
7
7
  * const result = await smokeTest('extension', 'prh-tiedonhaku', session);
8
8
  * @version-history
9
9
  * v1.0.0 — 2026-03-26 — Initial smoke test module
10
+ * v1.0.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
10
11
  */
11
12
 
12
13
  import { apiPost, apiGet } from '/js/api.js';
@@ -16,7 +17,7 @@ import { apiPost, apiGet } from '/js/api.js';
16
17
  * @param {string} type - Component type: 'extension', 'cortex', 'app'
17
18
  * @param {string} registeredAs - The registered name of the component
18
19
  * @param {object} session - Auth session for API calls
19
- * @returns {{ passed: boolean, error: string|null }}
20
+ * @returns {Promise<{ passed: boolean, error: string|null }>}
20
21
  */
21
22
  export async function smokeTest(type, registeredAs, session) {
22
23
  try {
@@ -47,6 +47,7 @@
47
47
  * v5.0.0 — 2026-03-26 — Add validateSkeleton with cross-check against interview
48
48
  * sampleResponse; add validateUnit with anti-pattern scan; add crossCheckSampleData,
49
49
  * extractOutputFieldsNearUrl, deepFieldExists helpers
50
+ * v5.0.1 — 2026-06-19 — lint fixes (misleading-char-class/unused-expression/empty-block)
50
51
  */
51
52
  import { parse as parseYaml, stringify as stringifyYaml } from '/lib/yaml.mjs';
52
53
 
@@ -66,7 +67,7 @@ function sanitizeJson(text) {
66
67
  // Strip markdown backslash escaping: \[ \] \{ \} \( \) \* \_ \` \| \~ \#
67
68
  s = s.replace(/\\([[\]{}()*_`|~#])/g, '$1');
68
69
  s = s.replace(/,\s*([}\]])/g, '$1');
69
- s = s.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
70
+ s = s.replace(/\u200B|\u200C|\u200D|\uFEFF/g, '');
70
71
  return s;
71
72
  }
72
73
 
@@ -84,7 +85,7 @@ function preCleanYaml(text) {
84
85
  s = s.replace(/\\\[/g, '[').replace(/\\\]/g, ']');
85
86
  s = s.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
86
87
  // Remove zero-width unicode
87
- s = s.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
88
+ s = s.replace(/\u200B|\u200C|\u200D|\uFEFF/g, '');
88
89
  // Fix common AI mistake: action list items missing "id:" key
89
90
  // e.g. " - refreshQuotes\n description:" → " - id: refreshQuotes\n description:"
90
91
  // Only match lines under "actions:" where the list item is a bare word followed by description on next line
@@ -889,11 +890,8 @@ export function validateBlueprint(result) {
889
890
  }
890
891
  }
891
892
 
892
- // Preserve new top-level blueprint fields
893
- if (parsed.settings) parsed.settings = parsed.settings;
894
- if (parsed.testScenarios) parsed.testScenarios = parsed.testScenarios;
895
- if (parsed.architecture) parsed.architecture = parsed.architecture;
896
-
893
+ // New top-level blueprint fields (settings/testScenarios/architecture) are preserved
894
+ // automatically — `parsed` is returned in full below.
897
895
  return { valid: errors.length === 0, errors, warnings, parsed, extracted: json };
898
896
  } catch (e) {
899
897
  errors.push(`Invalid JSON: ${e.message}`);
@@ -1022,7 +1020,7 @@ function extractOutputFieldsNearUrl(yaml, url) {
1022
1020
  }
1023
1021
  }
1024
1022
  // Stop at the next action/method
1025
- if (nearUrl && inOutput && /^ - id:|^ - name:|^\w/.test(line) && !line.includes(url)) {
1023
+ if (nearUrl && inOutput && /^ {2}- id:|^ {2}- name:|^\w/.test(line) && !line.includes(url)) {
1026
1024
  break;
1027
1025
  }
1028
1026
  }
@@ -40,6 +40,7 @@
40
40
  * v6.0.0 — 2026-03-26 — Add multi-pass data model: PASS_TYPES, MULTI_PASS_TYPES,
41
41
  * createInitialPasses, createUnitPasses, extractUnitsFromSkeleton, getNextPass,
42
42
  * isDuplicatePrescription; skeleton/unit/test/reflection storage functions
43
+ * v6.0.1 — 2026-06-19 — lint fixes (misleading-char-class/unused-expression/empty-block)
43
44
  */
44
45
  import { apiGet, apiPost, apiPut, apiDelete } from '/js/api.js';
45
46
  import { parse as parseYaml, stringify as stringifyYaml } from '/lib/yaml.mjs';
@@ -716,7 +717,7 @@ function cleanYaml(text) {
716
717
  s = s.replace(/\\_/g, '_');
717
718
  s = s.replace(/\\\[/g, '[').replace(/\\\]/g, ']');
718
719
  s = s.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
719
- s = s.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
720
+ s = s.replace(/\u200B|\u200C|\u200D|\uFEFF/g, '');
720
721
  try {
721
722
  const parsed = parseYaml(s);
722
723
  return stringifyYaml(parsed, { lineWidth: 0 });
@@ -1050,8 +1051,8 @@ export async function removeComponents(projectId, componentIds, includeMemory, s
1050
1051
  const locale = name.replace(/^.*i18n-/, '');
1051
1052
  if (locale) {
1052
1053
  // Find service slug from sibling extension/csm
1053
- const extSibling = comps.find(c => c.type === 'extension' && c.registeredAs);
1054
- const csmSibling = comps.find(c => c.type === 'csm' && c.registeredAs);
1054
+ const extSibling = components.find(c => c.type === 'extension' && c.registeredAs);
1055
+ const csmSibling = components.find(c => c.type === 'csm' && c.registeredAs);
1055
1056
  const svcSlug = extSibling?.registeredAs || csmSibling?.registeredAs?.split('/')?.pop() || '';
1056
1057
  const memKey = svcSlug ? `${svcSlug}.i18n.${locale}` : `i18n.${locale}`;
1057
1058
  try { await apiDelete(`/v1/memory/${encodeURIComponent(memKey)}`); } catch { /* best effort */ }
@@ -28,7 +28,7 @@ Available building blocks:
28
28
  - Cortex: Client-side JS domain library (IIFE on AIMEAT namespace). Wraps extension APIs and memory reads into clean domain methods for apps.
29
29
 
30
30
  Extensions run in an ISOLATED V8 sandbox with ONLY this API (no Node.js, no global fetch, no setTimeout, no require, no import):
31
- ctx.memory.get(key) → value or null (ALWAYS null-check before using: `|| []` or `|| {}`)
31
+ ctx.memory.get(key) → value or null (ALWAYS null-check before using: \`|| []\` or \`|| {}\`)
32
32
  ctx.memory.set(key, value) → void
33
33
  ctx.memory.search(prefix) → Array<{ key, value }> (NOT plain strings!)
34
34
  ctx.memory.delete(key) → boolean
@@ -1483,7 +1483,7 @@ export function summarizeExtensionApi(result) {
1483
1483
  : '';
1484
1484
 
1485
1485
  if (actionsSection) {
1486
- const actionBlocks = actionsSection.split(/\n - id:/);
1486
+ const actionBlocks = actionsSection.split(/\n {2}- id:/);
1487
1487
  for (const block of actionBlocks.slice(1)) {
1488
1488
  const id = block.split('\n')[0].trim();
1489
1489
  const desc = block.match(/description:\s*"([^"]+)"/)?.[1] || '';
@@ -7,6 +7,7 @@
7
7
  * const result = await smokeTest('extension', 'prh-tiedonhaku', session);
8
8
  * @version-history
9
9
  * v1.0.0 — 2026-03-26 — Initial smoke test module
10
+ * v1.0.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
10
11
  */
11
12
 
12
13
  import { apiPost, apiGet } from '/js/api.js';
@@ -16,7 +17,7 @@ import { apiPost, apiGet } from '/js/api.js';
16
17
  * @param {string} type - Component type: 'extension', 'cortex', 'app'
17
18
  * @param {string} registeredAs - The registered name of the component
18
19
  * @param {object} session - Auth session for API calls
19
- * @returns {{ passed: boolean, error: string|null }}
20
+ * @returns {Promise<{ passed: boolean, error: string|null }>}
20
21
  */
21
22
  export async function smokeTest(type, registeredAs, session) {
22
23
  try {
@@ -44,6 +44,7 @@
44
44
  * v4.5.0 — 2026-03-16 — Cortex validator supports single-block format (YAML + JS
45
45
  * separated by // lib/ comment) in addition to legacy separate blocks. Accept
46
46
  * var/let/const for LIB_NAME declaration.
47
+ * v4.5.1 — 2026-06-19 — lint fixes (misleading-char-class/unused-expression/empty-block)
47
48
  */
48
49
  import { parse as parseYaml, stringify as stringifyYaml } from '../../lib/yaml.mjs';
49
50
 
@@ -63,7 +64,7 @@ function sanitizeJson(text) {
63
64
  // Strip markdown backslash escaping: \[ \] \{ \} \( \) \* \_ \` \| \~ \#
64
65
  s = s.replace(/\\([[\]{}()*_`|~#])/g, '$1');
65
66
  s = s.replace(/,\s*([}\]])/g, '$1');
66
- s = s.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
67
+ s = s.replace(/\u200B|\u200C|\u200D|\uFEFF/g, '');
67
68
  return s;
68
69
  }
69
70
 
@@ -81,7 +82,7 @@ function preCleanYaml(text) {
81
82
  s = s.replace(/\\\[/g, '[').replace(/\\\]/g, ']');
82
83
  s = s.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
83
84
  // Remove zero-width unicode
84
- s = s.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
85
+ s = s.replace(/\u200B|\u200C|\u200D|\uFEFF/g, '');
85
86
  // Fix common AI mistake: action list items missing "id:" key
86
87
  // e.g. " - refreshQuotes\n description:" → " - id: refreshQuotes\n description:"
87
88
  // Only match lines under "actions:" where the list item is a bare word followed by description on next line
@@ -895,11 +896,8 @@ export function validateBlueprint(result) {
895
896
  }
896
897
  }
897
898
 
898
- // Preserve new top-level blueprint fields
899
- if (parsed.settings) parsed.settings = parsed.settings;
900
- if (parsed.testScenarios) parsed.testScenarios = parsed.testScenarios;
901
- if (parsed.architecture) parsed.architecture = parsed.architecture;
902
-
899
+ // New top-level blueprint fields (settings/testScenarios/architecture) are preserved
900
+ // automatically — `parsed` is returned in full below.
903
901
  return { valid: errors.length === 0, errors, warnings, parsed, extracted: json };
904
902
  } catch (e) {
905
903
  errors.push(`Invalid JSON: ${e.message}`);
@@ -37,6 +37,7 @@
37
37
  * v4.5.0 — 2026-03-19 — Add writeProjectLog for user-action activity logging; fix apiPatch import
38
38
  * v5.0.0 — 2026-03-20 — Remove agent-related functions (replaced by OpenRouter autopilot)
39
39
  * v5.1.0 — 2026-03-21 — Add saveProjectSettings/getProjectSettings for settings collection step
40
+ * v5.1.1 — 2026-06-19 — lint fixes (misleading-char-class/unused-expression/empty-block)
40
41
  */
41
42
  import { apiGet, apiPost, apiPut, apiDelete } from '/js/api.js';
42
43
  import { parse as parseYaml, stringify as stringifyYaml } from '/lib/yaml.mjs';
@@ -487,7 +488,7 @@ function cleanYaml(text) {
487
488
  s = s.replace(/\\_/g, '_');
488
489
  s = s.replace(/\\\[/g, '[').replace(/\\\]/g, ']');
489
490
  s = s.replace(/\\\{/g, '{').replace(/\\\}/g, '}');
490
- s = s.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
491
+ s = s.replace(/\u200B|\u200C|\u200D|\uFEFF/g, '');
491
492
  try {
492
493
  const parsed = parseYaml(s);
493
494
  return stringifyYaml(parsed, { lineWidth: 0 });