aimeat 1.33.2 → 1.34.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 (291) hide show
  1. package/dist/locales/en.json +388 -14
  2. package/dist/locales/fi.json +388 -14
  3. package/dist/public/cortex-bundled/aimeat-charts.js +3 -3
  4. package/dist/public/cortex-bundled/aimeat-charts.yaml +3 -3
  5. package/dist/public/cortex-bundled/aimeat-i18n.js +123 -0
  6. package/dist/public/cortex-bundled/aimeat-i18n.yaml +64 -0
  7. package/dist/public/css/views/landing.css +2 -0
  8. package/dist/public/css/views/secretary.css +412 -2
  9. package/dist/public/js/services/apps.js +4 -2
  10. package/dist/public/js/services/memory.js +9 -3
  11. package/dist/public/js/services/offers.js +1 -1
  12. package/dist/public/js/services/organisms.js +49 -17
  13. package/dist/public/js/services/secretary-helpers.js +262 -17
  14. package/dist/public/js/services/secretary-policy.js +1 -0
  15. package/dist/public/js/services/secretary-rules.js +70 -0
  16. package/dist/public/js/theme.js +20 -0
  17. package/dist/public/lib/VENDORED-STYLING.md +31 -0
  18. package/dist/public/lib/aimeat-daisyui-bridge.css +48 -0
  19. package/dist/public/lib/chartjs@4.js +14 -0
  20. package/dist/public/lib/daisyui@5.css +7 -0
  21. package/dist/public/lib/tailwindcss@4.js +8 -0
  22. package/dist/public/llms-template.txt +9 -6
  23. package/dist/public/showroom.html +104 -0
  24. package/dist/public/showroom.js +247 -0
  25. package/dist/public/spa.html +18 -16
  26. package/dist/public/views/aimeat-os.js +3 -2
  27. package/dist/public/views/landing.js +50 -6
  28. package/dist/public/views/profile/agents/agent-card.js +7 -0
  29. package/dist/public/views/profile/agents/state-detector.js +6 -0
  30. package/dist/public/views/profile/agents/tab-integration.js +6 -0
  31. package/dist/public/views/profile/apps-tab.js +46 -0
  32. package/dist/public/views/profile/extensions-tab.js +4 -0
  33. package/dist/public/views/profile/inbox-tab.js +1 -1
  34. package/dist/public/views/profile/landing-page.js +8 -3
  35. package/dist/public/views/profile/memory-tab.js +37 -6
  36. package/dist/public/views/profile/organisms/home.js +32 -0
  37. package/dist/public/views/profile/organisms/workspace-list.js +36 -5
  38. package/dist/public/views/profile/organisms/workspace.js +29 -4
  39. package/dist/public/views/profile/organisms-tab.js +61 -8
  40. package/dist/public/views/secretary/calendar.js +327 -0
  41. package/dist/public/views/secretary/cards-reach.js +16 -17
  42. package/dist/public/views/secretary/cards.js +173 -66
  43. package/dist/public/views/secretary/dashboard.js +551 -0
  44. package/dist/public/views/secretary/quality.js +156 -0
  45. package/dist/public/views/secretary/use-autonomy.js +17 -2
  46. package/dist/public/views/secretary/use-freshness.js +50 -0
  47. package/dist/public/views/secretary/use-intake.js +23 -4
  48. package/dist/public/views/secretary/use-layout.js +122 -0
  49. package/dist/public/views/secretary/use-quick-actions.js +98 -0
  50. package/dist/public/views/secretary/use-specialists.js +103 -0
  51. package/dist/public/views/secretary/use-triggers.js +159 -0
  52. package/dist/public/views/secretary/use-whats-next.js +340 -0
  53. package/dist/public/views/secretary/workflow-design.js +112 -0
  54. package/dist/public/views/secretary.js +663 -39
  55. package/dist/scripts/check-no-max-tokens.d.ts +2 -0
  56. package/dist/scripts/check-no-max-tokens.d.ts.map +1 -0
  57. package/dist/scripts/check-no-max-tokens.js +71 -0
  58. package/dist/scripts/check-no-max-tokens.js.map +1 -0
  59. package/dist/src/data/aimeat-iam-package.d.ts +25 -0
  60. package/dist/src/data/aimeat-iam-package.d.ts.map +1 -0
  61. package/dist/src/data/aimeat-iam-package.js +312 -0
  62. package/dist/src/data/aimeat-iam-package.js.map +1 -0
  63. package/dist/src/data/aimeat-marketplace-package.d.ts +25 -0
  64. package/dist/src/data/aimeat-marketplace-package.d.ts.map +1 -0
  65. package/dist/src/data/aimeat-marketplace-package.js +231 -0
  66. package/dist/src/data/aimeat-marketplace-package.js.map +1 -0
  67. package/dist/src/data/app-templates.d.ts +48 -0
  68. package/dist/src/data/app-templates.d.ts.map +1 -0
  69. package/dist/src/data/app-templates.js +856 -0
  70. package/dist/src/data/app-templates.js.map +1 -0
  71. package/dist/src/data/example-packages.d.ts.map +1 -1
  72. package/dist/src/data/example-packages.js +3 -1
  73. package/dist/src/data/example-packages.js.map +1 -1
  74. package/dist/src/generated/api-types.d.ts +380 -5
  75. package/dist/src/generated/api-types.d.ts.map +1 -1
  76. package/dist/src/generated/prisma-postgres/edge.js +10 -3
  77. package/dist/src/generated/prisma-postgres/index-browser.js +7 -0
  78. package/dist/src/generated/prisma-postgres/index.d.ts +205 -2
  79. package/dist/src/generated/prisma-postgres/index.js +10 -3
  80. package/dist/src/generated/prisma-postgres/package.json +1 -1
  81. package/dist/src/generated/prisma-postgres/schema.prisma +23 -13
  82. package/dist/src/generated/prisma-postgres/wasm.js +10 -3
  83. package/dist/src/mcp/annotations.d.ts.map +1 -1
  84. package/dist/src/mcp/annotations.js +1 -0
  85. package/dist/src/mcp/annotations.js.map +1 -1
  86. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  87. package/dist/src/mcp/catalog/definitions.js +16 -1
  88. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  89. package/dist/src/mcp/catalog/surfaces.js +3 -3
  90. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  91. package/dist/src/mcp/index.d.ts +3 -0
  92. package/dist/src/mcp/index.d.ts.map +1 -1
  93. package/dist/src/mcp/index.js +11 -0
  94. package/dist/src/mcp/index.js.map +1 -1
  95. package/dist/src/mcp/organisms.d.ts.map +1 -1
  96. package/dist/src/mcp/organisms.js +48 -8
  97. package/dist/src/mcp/organisms.js.map +1 -1
  98. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  99. package/dist/src/mcp/workspaces.js +6 -4
  100. package/dist/src/mcp/workspaces.js.map +1 -1
  101. package/dist/src/models/offer-schemas.d.ts +8 -8
  102. package/dist/src/routes/ai.d.ts +8 -4
  103. package/dist/src/routes/ai.d.ts.map +1 -1
  104. package/dist/src/routes/ai.js +38 -14
  105. package/dist/src/routes/ai.js.map +1 -1
  106. package/dist/src/routes/app-grants.d.ts +2 -0
  107. package/dist/src/routes/app-grants.d.ts.map +1 -1
  108. package/dist/src/routes/app-grants.js +7 -1
  109. package/dist/src/routes/app-grants.js.map +1 -1
  110. package/dist/src/routes/app-templates.d.ts +16 -0
  111. package/dist/src/routes/app-templates.d.ts.map +1 -0
  112. package/dist/src/routes/app-templates.js +40 -0
  113. package/dist/src/routes/app-templates.js.map +1 -0
  114. package/dist/src/routes/apps.d.ts.map +1 -1
  115. package/dist/src/routes/apps.js +55 -5
  116. package/dist/src/routes/apps.js.map +1 -1
  117. package/dist/src/routes/auth.d.ts +3 -0
  118. package/dist/src/routes/auth.d.ts.map +1 -1
  119. package/dist/src/routes/auth.js +9 -4
  120. package/dist/src/routes/auth.js.map +1 -1
  121. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  122. package/dist/src/routes/bootstrap.js +1 -0
  123. package/dist/src/routes/bootstrap.js.map +1 -1
  124. package/dist/src/routes/generator.d.ts.map +1 -1
  125. package/dist/src/routes/generator.js +3 -2
  126. package/dist/src/routes/generator.js.map +1 -1
  127. package/dist/src/routes/lib-ai.d.ts +0 -1
  128. package/dist/src/routes/lib-ai.d.ts.map +1 -1
  129. package/dist/src/routes/lib-ai.js +13 -6
  130. package/dist/src/routes/lib-ai.js.map +1 -1
  131. package/dist/src/routes/lib-header.d.ts +2 -0
  132. package/dist/src/routes/lib-header.d.ts.map +1 -1
  133. package/dist/src/routes/lib-header.js +7 -16
  134. package/dist/src/routes/lib-header.js.map +1 -1
  135. package/dist/src/routes/lib-markdown.d.ts +18 -0
  136. package/dist/src/routes/lib-markdown.d.ts.map +1 -0
  137. package/dist/src/routes/lib-markdown.js +236 -0
  138. package/dist/src/routes/lib-markdown.js.map +1 -0
  139. package/dist/src/routes/libs.d.ts +22 -0
  140. package/dist/src/routes/libs.d.ts.map +1 -1
  141. package/dist/src/routes/libs.js +133 -10
  142. package/dist/src/routes/libs.js.map +1 -1
  143. package/dist/src/routes/memory.d.ts.map +1 -1
  144. package/dist/src/routes/memory.js +21 -2
  145. package/dist/src/routes/memory.js.map +1 -1
  146. package/dist/src/routes/openrouter.d.ts +4 -0
  147. package/dist/src/routes/openrouter.d.ts.map +1 -1
  148. package/dist/src/routes/openrouter.js +17 -0
  149. package/dist/src/routes/openrouter.js.map +1 -1
  150. package/dist/src/routes/organisms.d.ts.map +1 -1
  151. package/dist/src/routes/organisms.js +107 -7
  152. package/dist/src/routes/organisms.js.map +1 -1
  153. package/dist/src/routes/portal.d.ts +9 -0
  154. package/dist/src/routes/portal.d.ts.map +1 -1
  155. package/dist/src/routes/portal.js +1 -1
  156. package/dist/src/routes/portal.js.map +1 -1
  157. package/dist/src/routes/public-stats.d.ts +3 -0
  158. package/dist/src/routes/public-stats.d.ts.map +1 -1
  159. package/dist/src/routes/public-stats.js +9 -2
  160. package/dist/src/routes/public-stats.js.map +1 -1
  161. package/dist/src/routes/secretary.d.ts +29 -0
  162. package/dist/src/routes/secretary.d.ts.map +1 -0
  163. package/dist/src/routes/secretary.js +220 -0
  164. package/dist/src/routes/secretary.js.map +1 -0
  165. package/dist/src/routes/specialists.d.ts.map +1 -1
  166. package/dist/src/routes/specialists.js +21 -0
  167. package/dist/src/routes/specialists.js.map +1 -1
  168. package/dist/src/routes/subdomains.js +1 -1
  169. package/dist/src/routes/subdomains.js.map +1 -1
  170. package/dist/src/routes/workflows.d.ts +9 -3
  171. package/dist/src/routes/workflows.d.ts.map +1 -1
  172. package/dist/src/routes/workflows.js +9 -3
  173. package/dist/src/routes/workflows.js.map +1 -1
  174. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  175. package/dist/src/server-bootstrap/routes-loader.js +4 -0
  176. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  177. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  178. package/dist/src/server-bootstrap/service-init.js +7 -0
  179. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  180. package/dist/src/server-bootstrap/static-files.d.ts +4 -0
  181. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  182. package/dist/src/server-bootstrap/static-files.js +18 -0
  183. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  184. package/dist/src/services/archive.d.ts +74 -0
  185. package/dist/src/services/archive.d.ts.map +1 -0
  186. package/dist/src/services/archive.js +132 -0
  187. package/dist/src/services/archive.js.map +1 -0
  188. package/dist/src/services/cortex-seeder.d.ts +4 -1
  189. package/dist/src/services/cortex-seeder.d.ts.map +1 -1
  190. package/dist/src/services/cortex-seeder.js +25 -6
  191. package/dist/src/services/cortex-seeder.js.map +1 -1
  192. package/dist/src/services/directory.d.ts.map +1 -1
  193. package/dist/src/services/directory.js +3 -2
  194. package/dist/src/services/directory.js.map +1 -1
  195. package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
  196. package/dist/src/services/generator-prompt-seeds.js +5 -4
  197. package/dist/src/services/generator-prompt-seeds.js.map +1 -1
  198. package/dist/src/services/message-send.d.ts.map +1 -1
  199. package/dist/src/services/message-send.js +14 -0
  200. package/dist/src/services/message-send.js.map +1 -1
  201. package/dist/src/services/organism-search.d.ts +6 -2
  202. package/dist/src/services/organism-search.d.ts.map +1 -1
  203. package/dist/src/services/organism-search.js +2 -2
  204. package/dist/src/services/organism-search.js.map +1 -1
  205. package/dist/src/services/owner-memory.d.ts +1 -0
  206. package/dist/src/services/owner-memory.d.ts.map +1 -1
  207. package/dist/src/services/owner-memory.js.map +1 -1
  208. package/dist/src/services/owner-session.d.ts +1 -0
  209. package/dist/src/services/owner-session.d.ts.map +1 -1
  210. package/dist/src/services/owner-session.js +10 -2
  211. package/dist/src/services/owner-session.js.map +1 -1
  212. package/dist/src/services/package-seeder.d.ts +22 -0
  213. package/dist/src/services/package-seeder.d.ts.map +1 -0
  214. package/dist/src/services/package-seeder.js +30 -0
  215. package/dist/src/services/package-seeder.js.map +1 -0
  216. package/dist/src/services/presence.d.ts +3 -0
  217. package/dist/src/services/presence.d.ts.map +1 -1
  218. package/dist/src/services/presence.js +10 -0
  219. package/dist/src/services/presence.js.map +1 -1
  220. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  221. package/dist/src/services/prompt-defaults.js +8 -3
  222. package/dist/src/services/prompt-defaults.js.map +1 -1
  223. package/dist/src/services/scheduler.d.ts +41 -2
  224. package/dist/src/services/scheduler.d.ts.map +1 -1
  225. package/dist/src/services/scheduler.js +429 -26
  226. package/dist/src/services/scheduler.js.map +1 -1
  227. package/dist/src/services/secretary-authoring.d.ts +71 -0
  228. package/dist/src/services/secretary-authoring.d.ts.map +1 -0
  229. package/dist/src/services/secretary-authoring.js +336 -0
  230. package/dist/src/services/secretary-authoring.js.map +1 -0
  231. package/dist/src/services/secretary-clarify.d.ts +57 -0
  232. package/dist/src/services/secretary-clarify.d.ts.map +1 -0
  233. package/dist/src/services/secretary-clarify.js +104 -0
  234. package/dist/src/services/secretary-clarify.js.map +1 -0
  235. package/dist/src/services/secretary-feed.d.ts +11 -0
  236. package/dist/src/services/secretary-feed.d.ts.map +1 -0
  237. package/dist/src/services/secretary-feed.js +28 -0
  238. package/dist/src/services/secretary-feed.js.map +1 -0
  239. package/dist/src/services/secretary-tick.d.ts +243 -3
  240. package/dist/src/services/secretary-tick.d.ts.map +1 -1
  241. package/dist/src/services/secretary-tick.js +273 -3
  242. package/dist/src/services/secretary-tick.js.map +1 -1
  243. package/dist/src/services/specialist-runner.d.ts +36 -0
  244. package/dist/src/services/specialist-runner.d.ts.map +1 -0
  245. package/dist/src/services/specialist-runner.js +91 -0
  246. package/dist/src/services/specialist-runner.js.map +1 -0
  247. package/dist/src/services/specialist.d.ts +9 -0
  248. package/dist/src/services/specialist.d.ts.map +1 -1
  249. package/dist/src/services/specialist.js +42 -0
  250. package/dist/src/services/specialist.js.map +1 -1
  251. package/dist/src/services/structure-overview.d.ts +13 -0
  252. package/dist/src/services/structure-overview.d.ts.map +1 -1
  253. package/dist/src/services/structure-overview.js +34 -11
  254. package/dist/src/services/structure-overview.js.map +1 -1
  255. package/dist/src/services/system-agent-responder.d.ts +36 -0
  256. package/dist/src/services/system-agent-responder.d.ts.map +1 -0
  257. package/dist/src/services/system-agent-responder.js +107 -0
  258. package/dist/src/services/system-agent-responder.js.map +1 -0
  259. package/dist/src/services/system-agent.d.ts +34 -1
  260. package/dist/src/services/system-agent.d.ts.map +1 -1
  261. package/dist/src/services/system-agent.js +73 -0
  262. package/dist/src/services/system-agent.js.map +1 -1
  263. package/dist/src/storage/interface.d.ts +33 -0
  264. package/dist/src/storage/interface.d.ts.map +1 -1
  265. package/dist/src/storage/interface.js.map +1 -1
  266. package/dist/src/storage/providers/mongodb/index.d.ts +37 -0
  267. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  268. package/dist/src/storage/providers/mongodb/index.js +132 -12
  269. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  270. package/dist/src/storage/providers/sqlite/index.d.ts +23 -1
  271. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  272. package/dist/src/storage/providers/sqlite/index.js +60 -8
  273. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  274. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +29 -1
  275. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  276. package/dist/src/storage/providers/sqlite/repos/memory.js +109 -10
  277. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  278. package/dist/src/storage/providers/sqlite/schema.d.ts +3 -0
  279. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  280. package/dist/src/storage/providers/sqlite/schema.js +64 -9
  281. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  282. package/dist/src/storage/repositories/app.repository.d.ts +15 -0
  283. package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
  284. package/dist/src/storage/repositories/memory.repository.d.ts +41 -1
  285. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  286. package/dist/src/storage/repositories/organism.repository.d.ts +2 -1
  287. package/dist/src/storage/repositories/organism.repository.d.ts.map +1 -1
  288. package/dist/static/app-catalog.html +469 -122
  289. package/package.json +2 -1
  290. package/prisma/schema.postgres.prisma +10 -0
  291. package/prisma/schema.prisma +12 -0
@@ -33,6 +33,17 @@
33
33
  header (aimeat-home-link, label set from location.host) and a "Publish your own app" CTA in the
34
34
  iframe app-viewer toolbar (.aimeat-cta). Pairs with the inline-app badge injected server-side
35
35
  (apps.ts injectAimeatBadge) so visitors who open a shared app always have a way home + a publish hint.
36
+ v2.5.0 — 2026-06-26 — Add inline name + description editing in the Detail view's About section
37
+ (detailAboutEdit/Cancel/Save): edit in place where the details are shown, save PATCHes
38
+ /v1/apps/:filename { name, description } when published (and persists the local record) — the
39
+ app link (owner/filename) is unchanged, no re-publish and no new version.
40
+ v2.5.1 — 2026-06-26 — Fix: app names containing an apostrophe (e.g. "Fatalii's …") or other JS/HTML
41
+ delimiters broke the View/Fork buttons ("missing ) after argument list") because escapeHtml does
42
+ not escape quotes for an onclick JS-string context. Add jsArg() and use it for the name/filename
43
+ arguments interpolated into on* attributes.
44
+ v2.5.2 — 2026-06-26 — Add a pencil (✏️) next to the app name — on the detail-view title and on the
45
+ owner's own published + parked cards — that opens the inline name/description editor right where
46
+ the name is shown (editAppDetails opens the detail view with the About editor pre-opened).
36
47
  -->
37
48
  <html lang="en">
38
49
  <head>
@@ -699,6 +710,7 @@
699
710
  background: var(--surface);
700
711
  color: var(--text);
701
712
  }
713
+ .pb-template-select { margin-bottom: 8px; }
702
714
 
703
715
  .modal-fields {
704
716
  display: flex;
@@ -1467,6 +1479,21 @@
1467
1479
  white-space: nowrap;
1468
1480
  }
1469
1481
 
1482
+ /* Small inline pencil shown next to an editable app name (own cards + the detail
1483
+ title). Pressing it opens the name/description editor where the name shows. */
1484
+ .rename-pencil {
1485
+ background: none;
1486
+ border: none;
1487
+ cursor: pointer;
1488
+ font-size: .8rem;
1489
+ opacity: .5;
1490
+ padding: 0 2px;
1491
+ line-height: 1;
1492
+ vertical-align: middle;
1493
+ transition: opacity var(--transition);
1494
+ }
1495
+ .rename-pencil:hover { opacity: 1; }
1496
+
1470
1497
  .published-card-meta {
1471
1498
  font-size: .72rem;
1472
1499
  color: var(--text-muted);
@@ -1725,8 +1752,8 @@
1725
1752
  .prompt-builder-modal {
1726
1753
  background: var(--surface);
1727
1754
  border: 1px solid var(--border-subtle); border-radius: 16px;
1728
- padding: 28px; max-width: 900px; width: 96vw; max-height: 90vh;
1729
- display: flex; flex-direction: column; overflow: hidden;
1755
+ padding: 28px; max-width: 720px; width: 96vw; max-height: 90vh;
1756
+ display: flex; flex-direction: column; overflow-y: auto;
1730
1757
  box-shadow: 0 8px 40px rgba(0,0,0,.12);
1731
1758
  }
1732
1759
  .prompt-builder-modal h2 { margin: 0 0 16px; font-size: 1.1rem; }
@@ -1757,13 +1784,26 @@
1757
1784
  .pb-ext-chip.selected { background: rgba(232,86,74,.08); border-color: rgba(232,86,74,.35); }
1758
1785
  .pb-ext-chip input { accent-color: #E8564A; pointer-events: none; }
1759
1786
  .pb-preview {
1760
- flex: 1; min-height: 120px; overflow-y: auto; margin-bottom: 14px; padding: 12px;
1787
+ min-height: 90px; max-height: 150px; overflow-y: auto; margin-bottom: 12px; padding: 12px;
1761
1788
  background: var(--bg-subtle); border: 1px solid var(--border-subtle);
1762
1789
  border-radius: 10px; font-family: 'Consolas','Monaco','Courier New',monospace;
1763
1790
  font-size: .75rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
1764
1791
  color: var(--text-muted);
1765
1792
  }
1766
1793
  .pb-preview-label { font-size: .75rem; text-transform: uppercase; letter-spacing: .1em; color: var(--text-muted); margin-bottom: 6px; }
1794
+ /* Guided-steps layout for "Generate App with AI" */
1795
+ .pb-intro { font-size: .82rem; color: var(--text-muted); line-height: 1.55; margin: 0 0 18px; }
1796
+ .pb-step { border-top: 1px solid var(--border-subtle); padding-top: 16px; margin-bottom: 16px; }
1797
+ .pb-step:first-of-type { border-top: none; padding-top: 0; }
1798
+ .pb-step-head { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: .98rem; margin-bottom: 12px; }
1799
+ .pb-step-num {
1800
+ display: inline-flex; align-items: center; justify-content: center;
1801
+ width: 24px; height: 24px; border-radius: 50%; flex: 0 0 auto;
1802
+ background: #E8564A; color: #fff; font-size: .82rem; font-weight: 700;
1803
+ }
1804
+ .pb-step-hint { font-size: .8rem; color: var(--text-muted); line-height: 1.55; margin: 0 0 12px; }
1805
+ .pb-improving { font-size: .85rem; font-weight: 600; color: #E8564A; margin: 0 0 12px; }
1806
+ .pb-step-btn { width: 100%; justify-content: center; }
1767
1807
  </style>
1768
1808
  </head>
1769
1809
  <body>
@@ -2200,22 +2240,41 @@
2200
2240
  </div>
2201
2241
  </div>
2202
2242
 
2203
- <!-- Prompt Builder Panel -->
2243
+ <!-- Generate App with AI — guided steps -->
2204
2244
  <div id="prompt-builder-overlay">
2205
2245
  <div class="prompt-builder-modal">
2206
- <h2 id="pb-title" data-i18n="pb.title">&#x2728; Generate App Prompt</h2>
2207
- <div class="pb-mode-row">
2208
- <label class="pb-mode-radio"><input type="radio" name="pb-mode" value="new" checked> <span data-i18n="pb.new">New App</span></label>
2209
- <label class="pb-mode-radio"><input type="radio" name="pb-mode" value="improve"> <span data-i18n="pb.improve">Improve This App</span></label>
2246
+ <h2 id="pb-title" data-i18n="pb.title">&#x2728; Generate App with AI</h2>
2247
+ <p class="pb-intro" data-i18n="pb.intro">No coding. Describe your idea, copy the prompt, and paste it into any AI chat (Claude, ChatGPT…). The AI asks a few questions, builds a ready-to-use app, and gives you one HTML file — then add it here to put it live.</p>
2248
+
2249
+ <div class="pb-step">
2250
+ <div class="pb-step-head"><span class="pb-step-num">1</span><span data-i18n="pb.step1">Describe your app</span></div>
2251
+ <!-- Mode is implicit (new vs improve) — radios kept hidden for state; an "Improving:" note shows instead. -->
2252
+ <div class="pb-mode-row" id="pb-mode-row" style="display:none">
2253
+ <label class="pb-mode-radio"><input type="radio" name="pb-mode" value="new" checked> <span data-i18n="pb.new">New App</span></label>
2254
+ <label class="pb-mode-radio"><input type="radio" name="pb-mode" value="improve"> <span data-i18n="pb.improve">Improve This App</span></label>
2255
+ </div>
2256
+ <div id="pb-improving" class="pb-improving" style="display:none"></div>
2257
+ <textarea class="pb-desc-area" id="pb-description" placeholder="Describe what the app should do..." data-i18n-ph="pb.descPh"></textarea>
2258
+ <label class="pb-ext-toggle" for="pb-template"><span>&#x2728; <span data-i18n="pb.template">Start from a template (optional)</span></span></label>
2259
+ <select id="pb-template" class="modal-input pb-template-select"><option value="">(none &#x2014; build from scratch)</option></select>
2260
+ </div>
2261
+
2262
+ <div class="pb-step">
2263
+ <div class="pb-step-head"><span class="pb-step-num">2</span><span data-i18n="pb.step2">Copy the prompt &amp; paste it into your AI</span></div>
2264
+ <p class="pb-step-hint" data-i18n="pb.step2hint">Open Claude, ChatGPT or any AI chat, paste this in, and answer its questions. It will build your app and hand you a single HTML file.</p>
2265
+ <div class="pb-preview-label" data-i18n="pb.promptPreview">Prompt preview</div>
2266
+ <div class="pb-preview" id="pb-preview"></div>
2267
+ <button class="modal-btn primary pb-step-btn" id="pb-copy-btn" data-i18n="pb.copy">&#x1F4CB; Copy Prompt</button>
2268
+ </div>
2269
+
2270
+ <div class="pb-step">
2271
+ <div class="pb-step-head"><span class="pb-step-num">3</span><span data-i18n="pb.step3">Add &amp; publish your app</span></div>
2272
+ <p class="pb-step-hint" data-i18n="pb.step3hint">Got the HTML file from the AI? Add it here — it goes live on your node and you get a link to share.</p>
2273
+ <button class="modal-btn pb-step-btn" id="pb-add-btn" data-i18n="pb.addBtn">&#x2795; I have my app file — add it</button>
2210
2274
  </div>
2211
- <textarea class="pb-desc-area" id="pb-description" placeholder="Describe what the app should do, or what changes to make..." data-i18n-ph="pb.descPh"></textarea>
2212
- <label class="pb-ext-toggle"><input type="checkbox" id="pb-ext-checkbox"> <span data-i18n="pb.includeCortex">Include Cortex Extensions</span></label>
2213
- <div class="pb-ext-list" id="pb-ext-list"></div>
2214
- <div class="pb-preview-label" data-i18n="pb.livePreview">Live Preview</div>
2215
- <div class="pb-preview" id="pb-preview"></div>
2275
+
2216
2276
  <div class="modal-actions">
2217
- <button class="modal-btn secondary" id="pb-cancel-btn" data-i18n="common.cancel">Cancel</button>
2218
- <button class="modal-btn primary" id="pb-copy-btn" data-i18n="pb.copy">Copy Prompt</button>
2277
+ <button class="modal-btn secondary" id="pb-cancel-btn" data-i18n="common.close">Close</button>
2219
2278
  </div>
2220
2279
  </div>
2221
2280
  </div>
@@ -2322,6 +2381,9 @@
2322
2381
  'publish.title': '🚀 Publish App',
2323
2382
  'publish.filename': 'Filename',
2324
2383
  'publish.filenameHint': 'Letters, numbers, dots, hyphens, underscores. Must end in .html',
2384
+ 'publish.description': 'Description',
2385
+ 'publish.descriptionPh': 'What does this app do? 1-2 sentences (your AI can write it)',
2386
+ 'publish.descriptionHint': 'Shown in the catalogue and on the landing wall. Required for a new app.',
2325
2387
  'publish.accessCode': 'Access Code (optional)',
2326
2388
  'publish.accessCodePh': 'Leave empty for public',
2327
2389
  'publish.accessCodeHint': '4-64 chars. If set, only people with the code can download.',
@@ -2334,13 +2396,25 @@
2334
2396
  'versions.help': 'Every publish is kept. Restore re-publishes an older version as the new latest; Fork copies it into a new app. Older versions are never lost.',
2335
2397
  'versions.current': 'current',
2336
2398
  'versions.stored': 'versions stored',
2337
- 'pb.title': '✨ Generate App Prompt',
2399
+ 'pb.title': '✨ Generate App with AI',
2400
+ 'pb.intro': 'No coding. Describe your idea, copy the prompt, and paste it into any AI chat (Claude, ChatGPT…). The AI asks a few questions, builds a ready-to-use app, and gives you one HTML file — then add it here to put it live.',
2401
+ 'pb.step1': 'Describe your app',
2402
+ 'pb.step2': 'Copy the prompt & paste it into your AI',
2403
+ 'pb.step2hint': 'Open Claude, ChatGPT or any AI chat, paste this in, and answer its questions. It will build your app and hand you a single HTML file.',
2404
+ 'pb.step3': 'Add & publish your app',
2405
+ 'pb.step3hint': 'Got the code or HTML file from the AI? Paste it (or upload the file) here — it goes live on your node and you get a shareable link.',
2406
+ 'pb.template': 'Start from a template (optional)',
2407
+ 'pb.noneTemplate': '(none — build from scratch)',
2408
+ 'pb.promptPreview': 'Prompt preview',
2409
+ 'pb.addBtn': '➕ Add your app — paste it or upload the file',
2410
+ 'pb.improving': 'Improving',
2411
+ 'pb.copied': 'Copied!',
2338
2412
  'pb.new': 'New App',
2339
2413
  'pb.improve': 'Improve This App',
2340
2414
  'pb.descPh': 'Describe what the app should do, or what changes to make...',
2341
2415
  'pb.includeCortex': 'Include Cortex Extensions',
2342
2416
  'pb.livePreview': 'Live Preview',
2343
- 'pb.copy': 'Copy Prompt',
2417
+ 'pb.copy': '📋 Copy Prompt',
2344
2418
  'card.view': '👁 View',
2345
2419
  'card.remove': '🗑 Remove',
2346
2420
  'card.versions': '🕑 Versions',
@@ -2349,6 +2423,15 @@
2349
2423
  'card.subdomain': '🌐 Subdomain',
2350
2424
  'card.park': '🅿 Park',
2351
2425
  'card.unpark': '🚀 Publish',
2426
+ 'detail.editDetails': '✏️ Edit',
2427
+ 'detail.nameLabel': 'App name',
2428
+ 'detail.descLabel': 'Description',
2429
+ 'detail.renameHint': 'The app link stays the same — only the displayed name and description change.',
2430
+ 'detail.nameRequired': 'Name cannot be empty.',
2431
+ 'detail.descRequired': 'Description cannot be empty.',
2432
+ 'detail.saveDetails': '💾 Save',
2433
+ 'detail.cancelEdit': 'Cancel',
2434
+ 'detail.detailsUpdated': 'App details updated.',
2352
2435
  'card.parkHint': 'Hide from the public catalogue — moves to Parked Apps (still usable by you).',
2353
2436
  'card.unparkHint': 'Publish again — moves back to Published Apps, visible to everyone.',
2354
2437
  'card.parkedBadge': 'PARKED',
@@ -2559,6 +2642,9 @@
2559
2642
  'publish.title': '🚀 Julkaise sovellus',
2560
2643
  'publish.filename': 'Tiedostonimi',
2561
2644
  'publish.filenameHint': 'Kirjaimia, numeroita, pisteitä, viivoja, alaviivoja. Päätteen oltava .html',
2645
+ 'publish.description': 'Kuvaus',
2646
+ 'publish.descriptionPh': 'Mitä tämä sovellus tekee? 1–2 lausetta (tekoälysi voi kirjoittaa sen)',
2647
+ 'publish.descriptionHint': 'Näytetään katalogissa ja aloitusseinällä. Pakollinen uudelle sovellukselle.',
2562
2648
  'publish.accessCode': 'Pääsykoodi (valinnainen)',
2563
2649
  'publish.accessCodePh': 'Jätä tyhjäksi julkiseksi',
2564
2650
  'publish.accessCodeHint': '4–64 merkkiä. Jos asetettu, vain koodin tietävät voivat ladata.',
@@ -2571,13 +2657,25 @@
2571
2657
  'versions.help': 'Jokainen julkaisu säilytetään. Palauta julkaisee vanhan version uudelleen uusimmaksi; Haaroita kopioi sen uudeksi sovellukseksi. Vanhoja versioita ei koskaan menetetä.',
2572
2658
  'versions.current': 'nykyinen',
2573
2659
  'versions.stored': 'versiota tallennettu',
2574
- 'pb.title': '✨ Luo sovellusprompti',
2660
+ 'pb.title': '✨ Luo sovellus tekoälyllä',
2661
+ 'pb.intro': 'Ei koodausta. Kuvaile ideasi, kopioi prompti ja liitä se mihin tahansa tekoälykeskusteluun (Claude, ChatGPT…). Tekoäly kysyy muutaman kysymyksen, rakentaa valmiin sovelluksen ja antaa sinulle yhden HTML-tiedoston — lisää se sitten tästä julkaistaksesi.',
2662
+ 'pb.step1': 'Kuvaile sovelluksesi',
2663
+ 'pb.step2': 'Kopioi prompti & liitä se tekoälyysi',
2664
+ 'pb.step2hint': 'Avaa Claude, ChatGPT tai mikä tahansa tekoälykeskustelu, liitä tämä ja vastaa kysymyksiin. Se rakentaa sovelluksesi ja antaa sinulle yhden HTML-tiedoston.',
2665
+ 'pb.step3': 'Lisää & julkaise sovelluksesi',
2666
+ 'pb.step3hint': 'Saitko koodin tai HTML-tiedoston tekoälyltä? Liitä se (tai lataa tiedosto) tästä — se tulee live-tilaan solmuusi ja saat jaettavan linkin.',
2667
+ 'pb.template': 'Aloita mallipohjasta (valinnainen)',
2668
+ 'pb.noneTemplate': '(ei mitään — rakenna tyhjästä)',
2669
+ 'pb.promptPreview': 'Promptin esikatselu',
2670
+ 'pb.addBtn': '➕ Lisää sovelluksesi — liitä se tai lataa tiedosto',
2671
+ 'pb.improving': 'Parannetaan',
2672
+ 'pb.copied': 'Kopioitu!',
2575
2673
  'pb.new': 'Uusi sovellus',
2576
2674
  'pb.improve': 'Paranna tätä sovellusta',
2577
2675
  'pb.descPh': 'Kuvaile mitä sovelluksen tulisi tehdä, tai mitä muutoksia haluat...',
2578
2676
  'pb.includeCortex': 'Sisällytä Cortex-laajennukset',
2579
2677
  'pb.livePreview': 'Esikatselu',
2580
- 'pb.copy': 'Kopioi prompti',
2678
+ 'pb.copy': '📋 Kopioi prompti',
2581
2679
  'card.view': '👁 Näytä',
2582
2680
  'card.remove': '🗑 Poista',
2583
2681
  'card.versions': '🕑 Versiot',
@@ -2586,6 +2684,15 @@
2586
2684
  'card.subdomain': '🌐 Alidomain',
2587
2685
  'card.park': '🅿 Parkkiin',
2588
2686
  'card.unpark': '🚀 Julkaise',
2687
+ 'detail.editDetails': '✏️ Muokkaa',
2688
+ 'detail.nameLabel': 'Sovelluksen nimi',
2689
+ 'detail.descLabel': 'Kuvaus',
2690
+ 'detail.renameHint': 'Sovelluksen linkki pysyy samana — vain näytettävä nimi ja kuvaus muuttuvat.',
2691
+ 'detail.nameRequired': 'Nimi ei voi olla tyhjä.',
2692
+ 'detail.descRequired': 'Kuvaus ei voi olla tyhjä.',
2693
+ 'detail.saveDetails': '💾 Tallenna',
2694
+ 'detail.cancelEdit': 'Peruuta',
2695
+ 'detail.detailsUpdated': 'Sovelluksen tiedot päivitetty.',
2589
2696
  'card.parkHint': 'Piilota julkisesta katalogista — siirtyy Parkissa-osioon (sinä voit yhä käyttää).',
2590
2697
  'card.unparkHint': 'Julkaise uudelleen — siirtyy takaisin Julkaistuihin, näkyy kaikille.',
2591
2698
  'card.parkedBadge': 'PARKISSA',
@@ -3213,7 +3320,7 @@
3213
3320
  var app = {
3214
3321
  id: generateId(),
3215
3322
  name: name,
3216
- description: '',
3323
+ description: parseAppMeta(sourceCode).description || '',
3217
3324
  source: 'local',
3218
3325
  url: null,
3219
3326
  blob: encoded,
@@ -3237,11 +3344,50 @@
3237
3344
 
3238
3345
  function showModal() {
3239
3346
  document.getElementById('modal-overlay').hidden = false;
3240
- document.getElementById('modal-title').textContent = 'Add App';
3347
+ document.getElementById('modal-title').textContent = t('addModal.title');
3241
3348
  // Default to URL tab
3242
3349
  switchTab('url');
3243
3350
  }
3244
3351
 
3352
+ // Step 0 for not-yet-signed-in users: adding/publishing an app needs an account, so
3353
+ // open the shared sign-in/register dialog (the SAME one the golden pill opens —
3354
+ // Google one-click or email+password, full registration) and continue once logged in.
3355
+ function requireSignInThen(next) {
3356
+ if (getCortexOwnerToken()) { next(); return; }
3357
+ var done = false;
3358
+ function onLogin() {
3359
+ if (done) return; done = true;
3360
+ try { window.AIMEAT.auth.off('login', onLogin); } catch (e) {}
3361
+ next();
3362
+ }
3363
+ try {
3364
+ window.AIMEAT.auth.on('login', onLogin);
3365
+ window.AIMEAT.auth.showLoginModal({});
3366
+ } catch (e) {
3367
+ // Auth lib not ready yet — fall back to the pill's own Sign In button.
3368
+ var b = document.querySelector('#headerAuth .aimeat-sign-btn');
3369
+ if (b) b.click(); else next();
3370
+ }
3371
+ }
3372
+
3373
+ // Read the app name + description the templates embed (AIMEAT App Manifest comment),
3374
+ // falling back to <title>, so the Add dialog can pre-fill them from pasted code.
3375
+ function parseAppMeta(html) {
3376
+ var meta = { name: '', description: '' };
3377
+ try {
3378
+ var m = (html || '').match(/AIMEAT App Manifest([\s\S]*?)-->/i);
3379
+ if (m) {
3380
+ var nm = m[1].match(/\bname:\s*(.+)/i); if (nm) meta.name = nm[1].trim();
3381
+ var dm = m[1].match(/\bdescription:\s*(.+)/i); if (dm) meta.description = dm[1].trim();
3382
+ }
3383
+ if (!meta.name) { var tt = (html || '').match(/<title>([^<]+)<\/title>/i); if (tt) meta.name = tt[1].trim(); }
3384
+ } catch (e) {}
3385
+ // Drop unfilled {{template}} placeholders.
3386
+ if (/\{\{.*\}\}/.test(meta.name)) meta.name = '';
3387
+ if (/\{\{.*\}\}/.test(meta.description)) meta.description = '';
3388
+ return meta;
3389
+ }
3390
+
3245
3391
  function closeModal() {
3246
3392
  document.getElementById('modal-overlay').hidden = true;
3247
3393
  // Reset form
@@ -3417,6 +3563,23 @@
3417
3563
  return div.innerHTML;
3418
3564
  }
3419
3565
 
3566
+ // Escape a user-controlled string for use as a single-quoted JS argument INSIDE a
3567
+ // double-quoted HTML on* attribute (e.g. onclick="window._launcher.foo('<here>')").
3568
+ // escapeHtml only handles < > & — an apostrophe like in "Fatalii's" would otherwise
3569
+ // terminate the JS string and throw "missing ) after argument list", breaking the
3570
+ // button. JS-escape the backslash + single quote; HTML-escape the attribute/markup
3571
+ // delimiters (they decode back to plain chars before the JS engine sees them).
3572
+ function jsArg(str) {
3573
+ return String(str == null ? '' : str)
3574
+ .replace(/&/g, '&amp;')
3575
+ .replace(/\\/g, '\\\\')
3576
+ .replace(/'/g, "\\'")
3577
+ .replace(/"/g, '&quot;')
3578
+ .replace(/</g, '&lt;')
3579
+ .replace(/>/g, '&gt;')
3580
+ .replace(/[\r\n]+/g, ' ');
3581
+ }
3582
+
3420
3583
  function sourceLabel(source) {
3421
3584
  switch (source) {
3422
3585
  case 'local': return '\u{1F4C4} local';
@@ -5318,8 +5481,9 @@
5318
5481
  return;
5319
5482
  }
5320
5483
 
5321
- // Send the owner token when we have one: the server only returns the caller's OWN
5322
- // parked apps to an authenticated request, so the Parked Apps section needs it.
5484
+ // Send the owner token when we have one: the server decides visibility from who
5485
+ // is authenticated, so an authenticated owner gets back their OWN parked +
5486
+ // operator-hidden apps (badged) while everyone else never sees them.
5323
5487
  var listHeaders = {};
5324
5488
  var listTok = getCortexOwnerToken();
5325
5489
  if (listTok) listHeaders['Authorization'] = 'Bearer ' + listTok;
@@ -5414,7 +5578,7 @@
5414
5578
  (date ? '<span class="pcm-date">' + date + '</span>' : '') +
5415
5579
  '</div>' +
5416
5580
  '<div class="published-card-actions">' +
5417
- '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(viewUrl) + '?mode=inline\', \'' + escapeHtml(name) + '\')">' + t('card.view') + '</button>' +
5581
+ '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(viewUrl) + '?mode=inline\', \'' + jsArg(name) + '\')">' + t('card.view') + '</button>' +
5418
5582
  '<button onclick="window._launcher.forkVersion(\'' + escapeHtml(sa.owner || '') + '\', \'' + escapeHtml(sa.filename || '') + '\', ' + (sa.version_number || 0) + ')" title="Copy into your own apps">' + t('card.fork') + '</button>' +
5419
5583
  '</div>' +
5420
5584
  (version ? '<div class="published-card-badgerow"><span class="pcb-version">' + escapeHtml(version) + '</span></div>' : '') +
@@ -5446,6 +5610,7 @@
5446
5610
  html +=
5447
5611
  '<div class="published-card parked">' +
5448
5612
  '<div class="published-card-name">' + escapeHtml(name) +
5613
+ ' <button class="rename-pencil" title="' + escapeHtml(t('detail.editDetails')) + '" onclick="window._launcher.editAppDetails(\'' + jsArg(sa.owner || '') + '\', \'' + jsArg(sa.filename || '') + '\', \'\', ' + (sa.version_number || 0) + ')">✏️</button>' +
5449
5614
  (version ? ' <span style="opacity:.5;font-size:.85em">' + escapeHtml(version) + '</span>' : '') +
5450
5615
  ' <span class="published-source-badge parked">' + t('card.parkedBadge') + '</span>' +
5451
5616
  '</div>' +
@@ -5456,7 +5621,7 @@
5456
5621
  '<div class="published-card-actions">' +
5457
5622
  '<button class="primary" onclick="window._launcher.toggleParkApp(\'' + escapeHtml(sa.filename || '') + '\', false)" title="' + escapeHtml(t('card.unparkHint')) + '">' + t('card.unpark') + '</button>' +
5458
5623
  '<button onclick="window._launcher.openPublishedDetail(\'' + escapeHtml(sa.owner || '') + '\', \'' + escapeHtml(sa.filename || '') + '\', \'\', ' + (sa.version_number || 0) + ')">' + t('ctx.details') + '</button>' +
5459
- '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(viewUrl) + '?mode=inline\', \'' + escapeHtml(name) + '\')">' + t('card.view') + '</button>' +
5624
+ '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(viewUrl) + '?mode=inline\', \'' + jsArg(name) + '\')">' + t('card.view') + '</button>' +
5460
5625
  '<button onclick="window._launcher.showVersionsModal(\'' + escapeHtml(sa.owner || '') + '\', \'' + escapeHtml(sa.filename || '') + '\')">' + t('card.versions') + '</button>' +
5461
5626
  '<button onclick="window._launcher.deleteServerApp(\'' + escapeHtml(sa.filename || '') + '\')" class="danger">' + t('card.remove') + '</button>' +
5462
5627
  '</div>' +
@@ -5558,7 +5723,9 @@
5558
5723
 
5559
5724
  html +=
5560
5725
  '<div class="published-card">' +
5561
- '<div class="published-card-name">' + escapeHtml(app.name) + '</div>' +
5726
+ '<div class="published-card-name">' + escapeHtml(app.name) +
5727
+ ((app.isOwn && app.filename) ? ' <button class="rename-pencil" title="' + escapeHtml(t('detail.editDetails')) + '" onclick="window._launcher.editAppDetails(\'' + jsArg(app.owner || '') + '\', \'' + jsArg(app.filename) + '\', \'' + jsArg(app.localId || '') + '\', ' + (app.versionNumber || 0) + ')">✏️</button>' : '') +
5728
+ '</div>' +
5562
5729
  (app.operatorHidden ? '<div class="published-card-modnote"><span class="published-source-badge operator-hidden" title="' + escapeHtml(app.operatorHideReason || '') + '">' + t('card.operatorHidden') + '</span></div>' : '') +
5563
5730
  (app.description ? '<div class="published-card-desc">' + escapeHtml(app.description) + '</div>' : '') +
5564
5731
  '<div class="published-card-footer">' +
@@ -5569,7 +5736,7 @@
5569
5736
  '</div>' +
5570
5737
  '<div class="published-card-actions">' +
5571
5738
  (app.isOwn && app.filename ? '<button class="primary" onclick="window._launcher.openPublishedDetail(\'' + escapeHtml(app.owner || '') + '\', \'' + escapeHtml(app.filename) + '\', \'' + escapeHtml(app.localId || '') + '\', ' + (app.versionNumber || 0) + ')">' + t('ctx.details') + '</button>' : '') +
5572
- (app.viewUrl ? '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(app.viewUrl) + '?mode=inline\', \'' + escapeHtml(app.name) + '\')">' + t('card.view') + '</button>' : '') +
5739
+ (app.viewUrl ? '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(app.viewUrl) + '?mode=inline\', \'' + jsArg(app.name) + '\')">' + t('card.view') + '</button>' : '') +
5573
5740
  (app.isOwn && app.filename ? '<button onclick="window._launcher.showVersionsModal(\'' + escapeHtml(app.owner || '') + '\', \'' + escapeHtml(app.filename) + '\')">' + t('card.versions') + '</button>' : '') +
5574
5741
  parkBtnHtml +
5575
5742
  subBtnHtml +
@@ -5682,6 +5849,8 @@
5682
5849
  var detailDraftBlob = null; // base64 of the pending AI-generated draft (never overwrites app.blob until Keep)
5683
5850
  var detailAiAvailable = false;
5684
5851
  var detailVersionsHtml = null; // cached rendered versions list, survives re-renders
5852
+ var detailEditingAbout = false; // true while the About name/description inline editor is open
5853
+ var detailEditAboutOnOpen = false; // when set, open the About editor as soon as the detail view renders
5685
5854
 
5686
5855
  function detailGetApp() {
5687
5856
  for (var i = 0; i < allApps.length; i++) {
@@ -5718,6 +5887,7 @@
5718
5887
  detailAppId = appId;
5719
5888
  detailDraftBlob = null;
5720
5889
  detailVersionsHtml = null;
5890
+ detailEditingAbout = false;
5721
5891
  var app = detailGetApp();
5722
5892
  if (!app) return;
5723
5893
  document.getElementById('detail-view').hidden = false;
@@ -5728,6 +5898,18 @@
5728
5898
  if (app.published && app.publishedFilename && owner) {
5729
5899
  detailLoadVersions(owner, app.publishedFilename);
5730
5900
  }
5901
+ // Opened via a pencil ("edit the name") → jump straight into the About editor.
5902
+ if (detailEditAboutOnOpen) {
5903
+ detailEditAboutOnOpen = false;
5904
+ detailAboutEdit();
5905
+ }
5906
+ }
5907
+
5908
+ // Open an app's detail view with the name/description editor already open. Used by
5909
+ // the pencil icons on the cards so editing starts right where the name is shown.
5910
+ function editAppDetails(owner, filename, localId, versionNumber) {
5911
+ detailEditAboutOnOpen = true;
5912
+ openPublishedDetail(owner, filename, localId, versionNumber);
5731
5913
  }
5732
5914
 
5733
5915
  function closeDetailView() {
@@ -5816,9 +5998,13 @@
5816
5998
  var hasServer = !!config.aimeatUrl;
5817
5999
  var icon = app.icon || '\u{1F4DD}';
5818
6000
  var isUrlApp = (app.source === 'url' && !app.blob);
6001
+ // The name + description are editable in place (see the About section). A pencil
6002
+ // next to the title opens that editor right where the name is shown.
6003
+ var canEditAbout = !!app.id && !isUrlApp;
5819
6004
 
5820
6005
  document.getElementById('detail-title').innerHTML =
5821
- '<span style="font-size:1.3rem">' + escapeHtml(icon) + '</span> ' + escapeHtml(app.name || 'App');
6006
+ '<span style="font-size:1.3rem">' + escapeHtml(icon) + '</span> ' + escapeHtml(app.name || 'App') +
6007
+ ((canEditAbout && !detailEditingAbout) ? ' <button class="rename-pencil" style="font-size:1rem" title="' + escapeHtml(t('detail.editDetails')) + '" onclick="window._launcher.detailAboutEdit()">✏️</button>' : '');
5822
6008
 
5823
6009
  // ── STATUS ──
5824
6010
  var localBytes = app.blob ? Math.round(app.blob.length * 0.75) : 0; // base64 → bytes approx
@@ -5856,12 +6042,32 @@
5856
6042
  '</div>';
5857
6043
 
5858
6044
  // ── ABOUT ──
6045
+ // The display name + description are editable in place here (inline editor) —
6046
+ // the app's URL is keyed off owner/filename, so a rename never moves the link.
6047
+ // Editing is offered for the owner's own apps (a local record always exists once
6048
+ // the detail view is open); Save PATCHes the server when the app is published.
5859
6049
  var tags = (app.tags && app.tags.length) ? app.tags.join(', ') : '—';
5860
6050
  var cortex = (app.usesCortex && app.usesCortex.length) ? app.usesCortex.join(', ') : '—';
5861
6051
  var created = app.addedAt ? new Date(app.addedAt).toLocaleString() : '—';
5862
- var aboutHtml =
5863
- '<div class="dtl-section">' +
5864
- '<h3>' + t('detail.about') + '</h3>' +
6052
+ var aboutHeader =
6053
+ '<h3 style="display:flex;align-items:center;justify-content:space-between;gap:8px">' +
6054
+ '<span>' + t('detail.about') + '</span>' +
6055
+ ((canEditAbout && !detailEditingAbout) ? '<button class="dtl-btn" onclick="window._launcher.detailAboutEdit()">' + t('detail.editDetails') + '</button>' : '') +
6056
+ '</h3>';
6057
+ var aboutBody;
6058
+ if (detailEditingAbout) {
6059
+ aboutBody =
6060
+ '<label class="dtl-stat-label" for="detail-name-input">' + t('detail.nameLabel') + '</label>' +
6061
+ '<input id="detail-name-input" class="modal-input" maxlength="120" value="' + escapeHtml(app.name || '') + '" style="margin:4px 0 10px" />' +
6062
+ '<label class="dtl-stat-label" for="detail-desc-input">' + t('detail.descLabel') + '</label>' +
6063
+ '<textarea id="detail-desc-input" class="modal-input" rows="3" maxlength="2000" style="margin:4px 0 8px;resize:vertical">' + escapeHtml(app.description || '') + '</textarea>' +
6064
+ '<div class="dtl-sync none" style="margin:0 0 10px">' + t('detail.renameHint') + '</div>' +
6065
+ '<div class="dtl-btn-row">' +
6066
+ '<button class="dtl-btn primary" onclick="window._launcher.detailAboutSave()">' + t('detail.saveDetails') + '</button>' +
6067
+ '<button class="dtl-btn" onclick="window._launcher.detailAboutCancel()">' + t('detail.cancelEdit') + '</button>' +
6068
+ '</div>';
6069
+ } else {
6070
+ aboutBody =
5865
6071
  (app.description ? '<p class="dtl-desc">' + escapeHtml(app.description) + '</p>' : '') +
5866
6072
  '<div class="dtl-meta-grid">' +
5867
6073
  metaItem(t('detail.category'), app.category || 'utility') +
@@ -5871,8 +6077,9 @@
5871
6077
  metaItem(t('detail.size'), app.blob ? fmtSize(localBytes) : '—') +
5872
6078
  metaItem(t('detail.created'), created) +
5873
6079
  metaItem(t('detail.usesCortex'), cortex) +
5874
- '</div>' +
5875
- '</div>';
6080
+ '</div>';
6081
+ }
6082
+ var aboutHtml = '<div class="dtl-section">' + aboutHeader + aboutBody + '</div>';
5876
6083
 
5877
6084
  // ── EDIT WITH AI ──
5878
6085
  var aiHtml =
@@ -5933,6 +6140,74 @@
5933
6140
  statusHtml + aboutHtml + aiHtml + versionsHtml + actionsHtml;
5934
6141
  }
5935
6142
 
6143
+ // ── About: inline name + description editing ──────
6144
+ // Edit the display name/description right where they are shown. The URL is keyed
6145
+ // off owner/filename, so this never changes the app link. Save updates the local
6146
+ // record and, when the app is published, PATCHes /v1/apps/:filename in place.
6147
+ function detailAboutEdit() {
6148
+ if (!detailGetApp()) return;
6149
+ detailEditingAbout = true;
6150
+ renderDetailView();
6151
+ var nameEl = document.getElementById('detail-name-input');
6152
+ if (nameEl) { nameEl.focus(); nameEl.select(); }
6153
+ }
6154
+
6155
+ function detailAboutCancel() {
6156
+ detailEditingAbout = false;
6157
+ renderDetailView();
6158
+ }
6159
+
6160
+ function detailAboutSave() {
6161
+ var app = detailGetApp();
6162
+ if (!app) return;
6163
+ var nameEl = document.getElementById('detail-name-input');
6164
+ var descEl = document.getElementById('detail-desc-input');
6165
+ var newName = nameEl ? nameEl.value.trim() : '';
6166
+ var newDesc = descEl ? descEl.value.trim() : '';
6167
+ if (!newName) { alert(t('detail.nameRequired') || 'Name cannot be empty.'); if (nameEl) nameEl.focus(); return; }
6168
+ if (app.published && !newDesc) { alert(t('detail.descRequired') || 'Description cannot be empty.'); if (descEl) descEl.focus(); return; }
6169
+
6170
+ function finishLocal() {
6171
+ app.name = newName;
6172
+ app.description = newDesc;
6173
+ saveApp(app).then(function() {
6174
+ detailEditingAbout = false;
6175
+ renderApps();
6176
+ loadPublishedApps();
6177
+ renderDetailView();
6178
+ });
6179
+ }
6180
+
6181
+ // Not published on the server → it's a local-only record; just persist locally.
6182
+ if (!app.published || !app.publishedFilename) { finishLocal(); return; }
6183
+
6184
+ var token = getCortexOwnerToken();
6185
+ if (!token) { alert(t('common.loginRequired') || 'You must be logged in. Sign in first.'); return; }
6186
+ var config = loadConfig();
6187
+ var aimeatUrl = (config.aimeatUrl || '').replace(/\/+$/, '');
6188
+ var owner = detailServerOwner(app);
6189
+ var filename = app.publishedFilename;
6190
+ fetch(aimeatUrl + '/v1/apps/' + encodeURIComponent(filename), {
6191
+ method: 'PATCH',
6192
+ headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
6193
+ body: JSON.stringify({ name: newName, description: newDesc })
6194
+ })
6195
+ .then(function(resp) { return resp.json().then(function(j) { return { ok: resp.ok, j: j }; }); })
6196
+ .then(function(res) {
6197
+ if (res.ok && res.j && res.j.ok !== false) {
6198
+ // Keep the cached server manifest in sync so a re-render shows the new name.
6199
+ var key = owner + '\n' + filename;
6200
+ serverAppManifests[key] = serverAppManifests[key] || {};
6201
+ serverAppManifests[key].name = newName;
6202
+ serverAppManifests[key].description = newDesc;
6203
+ finishLocal();
6204
+ } else {
6205
+ alert('Failed: ' + ((res.j && res.j.error && res.j.error.message) || 'Unknown error'));
6206
+ }
6207
+ })
6208
+ .catch(function(err) { alert('Error: ' + (err.message || err)); });
6209
+ }
6210
+
5936
6211
  // Manual override: upload a custom image as this published app's thumbnail. (Bulk auto-capture is
5937
6212
  // the server-side `aimeat screenshot-worker`; the browser can't grab the cross-origin sandboxed app
5938
6213
  // itself, so this is a file picker, not an in-page capture.)
@@ -6066,7 +6341,7 @@
6066
6341
  '<div class="version-meta"><span class="version-num">v' + v.version_number + (isLatest ? ' <span class="version-current">' + t('versions.current') + '</span>' : '') + '</span> ' +
6067
6342
  '<span class="version-sub" style="color:var(--text-muted);font-size:.8rem">' + (kb ? kb : '') + (when ? ' · ' + when : '') + '</span></div>' +
6068
6343
  '<div class="dtl-btn-row">' +
6069
- '<button class="dtl-btn" onclick="window._launcher.viewPublished(\'' + escapeHtml(viewU) + '\', \'' + escapeHtml(filename) + '\')">' + t('card.view') + '</button>' +
6344
+ '<button class="dtl-btn" onclick="window._launcher.viewPublished(\'' + escapeHtml(viewU) + '\', \'' + jsArg(filename) + '\')">' + t('card.view') + '</button>' +
6070
6345
  (isLatest ? '' : '<button class="dtl-btn" onclick="window._launcher.restoreVersion(' + ownerArg + ', ' + fileArg + ', ' + v.version_number + ')">' + t('card.restore') + '</button>') +
6071
6346
  '<button class="dtl-btn" onclick="window._launcher.forkVersion(' + ownerArg + ', ' + fileArg + ', ' + v.version_number + ')">' + t('card.fork') + '</button>' +
6072
6347
  '</div>' +
@@ -6144,8 +6419,7 @@
6144
6419
  body: JSON.stringify({
6145
6420
  prompt: userPrompt,
6146
6421
  systemPrompt: systemPrompt,
6147
- app_id: 'app-catalog',
6148
- max_tokens: 16000
6422
+ app_id: 'app-catalog'
6149
6423
  })
6150
6424
  })
6151
6425
  .then(function(resp) { return resp.json(); })
@@ -6352,7 +6626,7 @@
6352
6626
  '<span class="version-sub">' + escapeHtml(v.version || '') + (kb ? ' · ' + kb : '') + (when ? ' · ' + when : '') + '</span>' +
6353
6627
  '</div>' +
6354
6628
  '<div class="version-actions">' +
6355
- '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(viewU) + '\', \'' + escapeHtml(filename) + '\')">' + t('card.view') + '</button>' +
6629
+ '<button onclick="window._launcher.viewPublished(\'' + escapeHtml(viewU) + '\', \'' + jsArg(filename) + '\')">' + t('card.view') + '</button>' +
6356
6630
  (isLatest ? '' : '<button onclick="window._launcher.restoreVersion(' + ownerArg + ', ' + fileArg + ', ' + v.version_number + ')" title="Re-publish this version as the new latest">' + t('card.restore') + '</button>') +
6357
6631
  '<button onclick="window._launcher.forkVersion(' + ownerArg + ', ' + fileArg + ', ' + v.version_number + ')" title="Copy this version into a new app">' + t('card.fork') + '</button>' +
6358
6632
  '</div>' +
@@ -6807,26 +7081,32 @@
6807
7081
  pbSourceApp = app || null;
6808
7082
  var overlay = document.getElementById('prompt-builder-overlay');
6809
7083
  var desc = document.getElementById('pb-description');
6810
- var extCheckbox = document.getElementById('pb-ext-checkbox');
6811
- var extList = document.getElementById('pb-ext-list');
6812
7084
 
6813
7085
  // Reset state
6814
7086
  desc.value = '';
6815
- extCheckbox.checked = false;
6816
- extList.classList.remove('open');
7087
+ window.pbTemplate = null;
7088
+ var tplSel0 = document.getElementById('pb-template');
7089
+ if (tplSel0) tplSel0.value = '';
6817
7090
 
6818
- // Set mode based on whether we have a source app
7091
+ // Mode is implicit: "improve" when opened from an app, else "new". The radios
7092
+ // stay in the DOM (hidden) so buildPromptFromBuilder reads the right mode; instead
7093
+ // of a confusing toggle we show a plain "Improving: <name>" note when relevant.
6819
7094
  var radios = document.querySelectorAll('input[name="pb-mode"]');
7095
+ var improving = document.getElementById('pb-improving');
6820
7096
  if (pbSourceApp) {
6821
- radios[1].checked = true; // Improve This App
6822
- desc.placeholder = 'Describe what changes to make to "' + (pbSourceApp.name || 'this app') + '"...';
7097
+ radios[1].checked = true;
7098
+ if (improving) {
7099
+ improving.textContent = '✏️ ' + (t('pb.improving') || 'Improving') + ': ' + (pbSourceApp.name || pbSourceApp.filename || 'this app');
7100
+ improving.style.display = '';
7101
+ }
6823
7102
  } else {
6824
- radios[0].checked = true; // New App
6825
- desc.placeholder = 'Describe what the app should do...';
7103
+ radios[0].checked = true;
7104
+ if (improving) improving.style.display = 'none';
6826
7105
  }
7106
+ desc.placeholder = t('pb.descPh');
6827
7107
 
6828
- // Load Cortex extensions into picker
6829
- loadPbExtensions();
7108
+ // Load authoring templates (booster-kit) into the "Start from a template" picker
7109
+ loadPbTemplates();
6830
7110
 
6831
7111
  overlay.style.display = 'flex';
6832
7112
  desc.focus();
@@ -6838,6 +7118,43 @@
6838
7118
  pbSourceApp = null;
6839
7119
  }
6840
7120
 
7121
+ // Booster-kit: populate the "Start from a template" picker from /v1/app-templates and, on
7122
+ // selection, fetch that template's content into window.pbTemplate (injected by buildPromptFromBuilder).
7123
+ function loadPbTemplates() {
7124
+ var sel = document.getElementById('pb-template');
7125
+ if (!sel) return;
7126
+ var config = loadConfig();
7127
+ if (!config.aimeatUrl) return;
7128
+ var base = config.aimeatUrl.replace(/\/+$/, '');
7129
+ fetch(base + '/v1/app-templates?lang=' + encodeURIComponent(currentLang))
7130
+ .then(function (r) { return r.json(); })
7131
+ .then(function (d) {
7132
+ // Starting points only: full use-case scaffolds first, then blank app-shells (not components).
7133
+ var list = ((d.data && d.data.templates) || []).filter(function (t) { return t.kind !== 'component'; });
7134
+ list.sort(function (a, b) { return (a.kind === 'use-case' ? 0 : 1) - (b.kind === 'use-case' ? 0 : 1); });
7135
+ sel.innerHTML = '<option value="">' + t('pb.noneTemplate') + '</option>';
7136
+ list.forEach(function (t) {
7137
+ var o = document.createElement('option');
7138
+ o.value = t.id;
7139
+ var prefix = t.kind === 'use-case' ? '★ ' : '';
7140
+ // Use-case titles are short ("Marketplace") so append a snippet of their description;
7141
+ // app-shell titles are now self-describing ("Standard app — login + saves your data"),
7142
+ // so don't tack on a second em-dash clause.
7143
+ o.textContent = prefix + t.title + (t.kind === 'use-case' && t.description ? ' — ' + t.description.slice(0, 70) : '');
7144
+ sel.appendChild(o);
7145
+ });
7146
+ sel.onchange = function () {
7147
+ var id = sel.value;
7148
+ if (!id) { window.pbTemplate = null; updatePbPreview(); return; }
7149
+ fetch(base + '/v1/app-templates/' + encodeURIComponent(id))
7150
+ .then(function (r) { return r.json(); })
7151
+ .then(function (d2) { window.pbTemplate = (d2.data && d2.data.template) || null; updatePbPreview(); })
7152
+ .catch(function () { window.pbTemplate = null; });
7153
+ };
7154
+ })
7155
+ .catch(function () { /* templates are optional */ });
7156
+ }
7157
+
6841
7158
  function loadPbExtensions() {
6842
7159
  var list = document.getElementById('pb-ext-list');
6843
7160
  var config = loadConfig();
@@ -6963,10 +7280,15 @@
6963
7280
  var nodeUrl = config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, '') : 'https://your-aimeat-node.example';
6964
7281
  var mode = document.querySelector('input[name="pb-mode"]:checked').value;
6965
7282
  var description = document.getElementById('pb-description').value.trim();
6966
- var selectedExts = document.getElementById('pb-ext-checkbox').checked ? getSelectedExtensions() : [];
6967
7283
 
6968
7284
  var prompt = '';
6969
7285
 
7286
+ // Converse + build the UI in the language the user is browsing in (the build
7287
+ // instructions below stay in English, but the conversation + app text should match).
7288
+ var PB_LANGS = { en: 'English', fi: 'Finnish (Suomi)' };
7289
+ var pbLang = PB_LANGS[currentLang] || 'English';
7290
+ prompt += 'Language: talk to me and write ALL user-facing text (UI labels, buttons, messages) in ' + pbLang + '. These build instructions are in English, but converse with me and build the app interface in ' + pbLang + '.\n\n';
7291
+
6970
7292
  // Header
6971
7293
  var isImprove = (mode === 'improve' && pbSourceApp);
6972
7294
  if (isImprove) {
@@ -7008,59 +7330,40 @@
7008
7330
  prompt += 'Media & misc:\n';
7009
7331
  prompt += '- aimeat-audio.js — audio engine: instruments, synth, soundboard\n';
7010
7332
  prompt += '- aimeat-speech.js — text-to-speech / speech helpers\n';
7333
+ prompt += '- aimeat-markdown.js — render safe markdown to HTML (`AIMEAT.md.render`)\n';
7011
7334
  prompt += '- aimeat-header.js — drop-in canonical site header (nav + theme)\n';
7012
7335
  prompt += '- aimeat-tunnel.js — personal-node tunnel client (advanced)\n\n';
7013
7336
 
7014
- // Cortex extension instructions
7015
- if (selectedExts.length > 0) {
7016
- prompt += '### Cortex Extensions (include these)\n\n';
7017
- selectedExts.forEach(function(ext) {
7018
- prompt += '#### ' + ext.name + '\n';
7019
- prompt += ext.description ? ext.description + '\n' : '';
7020
-
7021
- // Use detailed components if available
7022
- var comps = ext.components;
7023
- if (comps && comps.length) {
7024
- comps.forEach(function(c) {
7025
- if (c.type === 'lib' && c.filename) {
7026
- prompt += 'Load the library:\n```html\n';
7027
- prompt += '<script src="' + nodeUrl + '/v1/cortex/' + encodeURIComponent(ext.name) + '/libs/' + encodeURIComponent(c.filename) + '"></' + 'script>\n';
7028
- prompt += '```\n';
7029
- if (c.api_surface) {
7030
- prompt += 'API Surface:\n```\n' + c.api_surface + '\n```\n';
7031
- }
7032
- } else if (c.type === 'schema') {
7033
- prompt += 'Data schema: `' + (c.key_pattern || '') + '` — data is validated automatically.\n';
7034
- } else if (c.type === 'prompt') {
7035
- prompt += 'Prompt template: ' + (c.name || '') + '\n';
7036
- }
7037
- });
7038
- } else {
7039
- // Fallback: only list-level type info
7040
- var types = ext.component_types || [];
7041
- if (types.indexOf('lib') !== -1) {
7042
- prompt += 'Includes a JS library (load via `<script src="' + nodeUrl + '/v1/cortex/' + encodeURIComponent(ext.name) + '/libs/...">`).\n';
7043
- }
7044
- if (types.indexOf('schema') !== -1) {
7045
- prompt += 'Provides validated data schemas.\n';
7046
- }
7047
- if (types.indexOf('prompt') !== -1) {
7048
- prompt += 'Includes prompt templates.\n';
7049
- }
7050
- }
7051
- prompt += '\n';
7052
- });
7053
- }
7337
+ // Ready-made UI building blocks (node-bundled cortex) — ALWAYS offered so the AI can
7338
+ // reach for them instead of hand-rolling tables/forms/charts. Load only what's used.
7339
+ prompt += 'Ready-made UI (node-bundled — load from ' + nodeUrl + '/v1/cortex/<name>/libs/<name>.js, use only what you need):\n';
7340
+ prompt += '- aimeat-ui-viewers — sortable/filterable DataTable + viewers (`AIMEAT.ui.viewers`)\n';
7341
+ prompt += '- aimeat-ui-forms — form builder with validation (`AIMEAT.ui.forms`)\n';
7342
+ prompt += '- aimeat-ui-layout — responsive layout helpers, master/detail (`AIMEAT.ui.layout`)\n';
7343
+ prompt += '- aimeat-ui-nav — navbars, tabs, menus (`AIMEAT.ui.nav`)\n';
7344
+ prompt += '- aimeat-ui-dialogs — modals, toasts, confirms (`AIMEAT.ui.dialogs`)\n';
7345
+ prompt += '- aimeat-charts — charts / graphs (`AIMEAT.charts`)\n';
7346
+ prompt += '- aimeat-canvas — drawing / freeform canvas (`AIMEAT.canvas`)\n';
7347
+ prompt += 'Example: <script src="' + nodeUrl + '/v1/cortex/aimeat-ui-viewers/libs/aimeat-ui-viewers.js"></' + 'script>\n\n';
7054
7348
 
7055
7349
  // Auth pattern
7056
7350
  prompt += '### Auth Pattern\n';
7351
+ prompt += 'Handle BOTH login paths: a fresh sign-in click (the onLogin callback) AND a page that loads already signed in (restore the session yourself). `onLogin` fires ONLY on a fresh sign-in — it does NOT fire on reload when a session already exists, so a page that relies on onLogin alone shows nothing to an already-logged-in returning user.\n';
7057
7352
  prompt += '```html\n';
7058
7353
  prompt += '<script src="' + nodeUrl + '/v1/libs/aimeat-auth.js"></' + 'script>\n';
7059
7354
  prompt += '<script>\n';
7355
+ prompt += 'function showApp(session) { /* session.owner, session.jwt, session.fetch() */ }\n';
7356
+ prompt += 'function hideApp() { /* hide content, show a "Sign in" message */ }\n';
7357
+ prompt += '\n';
7358
+ prompt += '// Path 1 — fresh sign-in / sign-out via the login button:\n';
7060
7359
  prompt += 'AIMEAT.auth.mountLoginButton("#login", {\n';
7061
- prompt += ' onLogin: function(session) { /* session.owner, session.jwt, session.fetch() */ },\n';
7062
- prompt += ' onLogout: function() { /* hide content */ }\n';
7360
+ prompt += ' onLogin: showApp, // fires ONLY on a fresh sign-in click, NOT on reload\n';
7361
+ prompt += ' onLogout: hideApp\n';
7063
7362
  prompt += '});\n';
7363
+ prompt += '\n';
7364
+ prompt += '// Path 2 — already signed in when the page loads. Restore the stored session\n';
7365
+ prompt += '// explicitly; login() returns the session (or null if not signed in).\n';
7366
+ prompt += 'AIMEAT.auth.login().then(function (session) { if (session) showApp(session); });\n';
7064
7367
  prompt += '</' + 'script>\n';
7065
7368
  prompt += '```\n\n';
7066
7369
 
@@ -7110,11 +7413,15 @@
7110
7413
 
7111
7414
  // Design guidelines
7112
7415
  prompt += '### Design Guidelines\n';
7113
- prompt += 'Use CSS variables so the app themes cleanly. Support light AND dark:\n';
7416
+ prompt += 'Use CSS variables so the app themes cleanly, and RESPECT the user\'s AIMEAT theme: the light/dark choice they made in the AIMEAT pill is saved in localStorage "aimeat-theme" ("light"|"dark"). Define light as the default and dark under [data-theme="dark"], then set that attribute from the saved choice on load (fall back to the OS preference, and live-update if it changes):\n';
7114
7417
  prompt += '```css\n';
7115
7418
  prompt += ':root { --bg:#fafaf8; --card:#fff; --text:#1a1a2e; --accent:#e8564a; --border:#e5e7eb; --radius:12px; }\n';
7116
- prompt += '@media (prefers-color-scheme: dark) {\n';
7117
- prompt += ' :root { --bg:#14141c; --card:#1e1e2a; --text:#ececf4; --border:#2e2e40; } }\n';
7419
+ prompt += ':root[data-theme="dark"] { --bg:#14141c; --card:#1e1e2a; --text:#ececf4; --border:#2e2e40; }\n';
7420
+ prompt += '```\n';
7421
+ prompt += '```js\n';
7422
+ prompt += '(function(){ function apply(t){ document.documentElement.setAttribute("data-theme", t==="dark"?"dark":"light"); }\n';
7423
+ prompt += ' apply(localStorage.getItem("aimeat-theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"));\n';
7424
+ prompt += ' addEventListener("storage", function(e){ if(e.key==="aimeat-theme" && e.newValue) apply(e.newValue); }); })();\n';
7118
7425
  prompt += '```\n';
7119
7426
  prompt += 'Always include <meta name="viewport" content="width=device-width, initial-scale=1.0">. Mobile-first, single self-contained HTML file with embedded CSS + JS.\n\n';
7120
7427
 
@@ -7125,15 +7432,31 @@
7125
7432
  prompt += '- Keep it as a single self-contained HTML file\n';
7126
7433
  prompt += '- Load only the libraries you actually use; load aimeat-auth before libs that need a session\n';
7127
7434
  prompt += '- Gate AI features on AIMEAT.ai.isAvailable() and handle the logged-out / no-key case\n';
7128
- prompt += '- Theme with CSS variables and support both light and dark\n';
7435
+ prompt += '- Theme with CSS variables; respect the user\'s AIMEAT light/dark choice (localStorage "aimeat-theme") with an OS-preference fallback\n';
7129
7436
  prompt += '- Include error handling and loading states for API calls\n\n';
7130
7437
 
7438
+ // After-build: have the AI walk me through publishing — closes the loop for newcomers.
7439
+ if (!isImprove) {
7440
+ prompt += '## When the app is ready — tell me how to publish it\n';
7441
+ prompt += 'After you hand me the finished single HTML file, END your reply by telling me (in my language) to do exactly this:\n';
7442
+ prompt += '1. Open ' + nodeUrl + '/app-catalog.html\n';
7443
+ prompt += '2. Click "+ Add" → open the "Paste" tab → paste the HTML (or drop it as a file). The app name + description fill in automatically.\n';
7444
+ prompt += '3. Click Publish.\n';
7445
+ prompt += 'I will be asked to sign in first — it is fast: one click with Google, or a quick email + password, and a brand-new account is created right there in seconds.\n';
7446
+ prompt += 'What I get: once published, the app is LIVE on my own AIMEAT node and PUBLIC — anyone can find it in the community catalogue and use it, and I get a link to share. From my catalogue I can launch it, publish updates (older versions are always kept), park it (hide it from the public), or delete it. It keeps working with my AIMEAT login, saved data, files, AI and realtime features.\n\n';
7447
+ }
7448
+
7131
7449
  // Source code for improve mode
7132
7450
  if (mode === 'improve' && pbSourceApp && pbSourceApp.blob) {
7133
7451
  var source = decodeURIComponent(escape(atob(pbSourceApp.blob)));
7134
7452
  prompt += '--- Source Code ---\n' + source;
7135
7453
  }
7136
7454
 
7455
+ // Booster-kit: if the user picked a starting template, hand the AI the skeleton to copy from.
7456
+ if (window.pbTemplate && window.pbTemplate.content) {
7457
+ prompt += '\n## Starting template (copy from this)\nUse this skeleton as your base — keep its boot, login pill, and self-hosted theme wiring intact; fill the {{...}} slots; build your views inside <main>. Return the COMPLETE single HTML file based on it.\n```html\n' + window.pbTemplate.content + '\n```\n';
7458
+ }
7459
+
7137
7460
  return prompt;
7138
7461
  }
7139
7462
 
@@ -7265,6 +7588,10 @@
7265
7588
  unpublishApp: unpublishApp,
7266
7589
  deleteServerApp: deleteServerApp,
7267
7590
  toggleParkApp: toggleParkApp,
7591
+ detailAboutEdit: detailAboutEdit,
7592
+ detailAboutCancel: detailAboutCancel,
7593
+ detailAboutSave: detailAboutSave,
7594
+ editAppDetails: editAppDetails,
7268
7595
  showSubdomainModal: showSubdomainModal,
7269
7596
  openConsents: openConsents,
7270
7597
  closeConsents: closeConsents,
@@ -7377,8 +7704,9 @@
7377
7704
  });
7378
7705
 
7379
7706
  // ── Add App button ──────────────────────────────
7707
+ // Step 0: not signed in → open the sign-in/register dialog first, then the Add dialog.
7380
7708
  document.getElementById('add-btn').addEventListener('click', function () {
7381
- showModal();
7709
+ requireSignInThen(showModal);
7382
7710
  });
7383
7711
 
7384
7712
  // ── Generate with AI button ─────────────────────
@@ -7497,6 +7825,19 @@
7497
7825
  var fileInput = document.getElementById('file-input');
7498
7826
  var browseBtn = document.getElementById('browse-btn');
7499
7827
 
7828
+ // Auto-fill the app name from pasted code (AIMEAT manifest / <title>) when the
7829
+ // name field is still empty — the description rides along into the publish step.
7830
+ var pasteArea = document.getElementById('app-paste-code');
7831
+ if (pasteArea) {
7832
+ pasteArea.addEventListener('input', function () {
7833
+ var nameInput = document.getElementById('app-name');
7834
+ if (nameInput && !nameInput.value.trim()) {
7835
+ var meta = parseAppMeta(pasteArea.value);
7836
+ if (meta.name) nameInput.value = meta.name;
7837
+ }
7838
+ });
7839
+ }
7840
+
7500
7841
  dropZone.addEventListener('dragover', function (e) {
7501
7842
  e.preventDefault();
7502
7843
  e.stopPropagation();
@@ -7700,13 +8041,15 @@
7700
8041
  'Media & misc: aimeat-audio.js, aimeat-speech.js, aimeat-header.js, aimeat-tunnel.js.\n' +
7701
8042
  'Real-time multiplayer: no lib — POST/GET ' + nodeUrl + '/v1/realtime/rooms then WebSocket /v1/realtime/ws?room=ID (ICE config at /v1/realtime/ice-servers).\n\n' +
7702
8043
  '### Auth Pattern\n' +
8044
+ 'Handle BOTH login paths: a fresh sign-in click (onLogin) AND a page that loads already signed in (restore the session yourself). `onLogin` fires ONLY on a fresh sign-in — NOT on reload when a session already exists.\n' +
7703
8045
  '```html\n' +
7704
8046
  '<script src="' + nodeUrl + '/v1/libs/aimeat-auth.js"></' + 'script>\n' +
7705
8047
  '<script>\n' +
7706
- 'AIMEAT.auth.mountLoginButton("#login", {\n' +
7707
- ' onLogin: function(session) { /* session.owner, session.jwt, session.fetch() */ },\n' +
7708
- ' onLogout: function() { /* hide content */ }\n' +
7709
- '});\n' +
8048
+ 'function showApp(session) { /* session.owner, session.jwt, session.fetch() */ }\n' +
8049
+ 'function hideApp() { /* hide content, show a "Sign in" message */ }\n' +
8050
+ 'AIMEAT.auth.mountLoginButton("#login", { onLogin: showApp, onLogout: hideApp });\n' +
8051
+ '// Already signed in on load? onLogin will NOT fire — restore the session yourself:\n' +
8052
+ 'AIMEAT.auth.login().then(function (session) { if (session) showApp(session); });\n' +
7710
8053
  '</' + 'script>\n' +
7711
8054
  '```\n\n' +
7712
8055
  '### Data Storage\n' +
@@ -7738,11 +8081,15 @@
7738
8081
  'Notifications, Drag & Drop, Clipboard, Speech API, Fullscreen, Web Workers,\n' +
7739
8082
  'CSS Animations, SVG, Gamepad API, Share API.\n\n' +
7740
8083
  '### Design Guidelines\n' +
7741
- 'Use CSS variables and support light AND dark:\n' +
8084
+ 'Use CSS variables and RESPECT the user\'s AIMEAT theme: their light/dark choice from the AIMEAT pill is saved in localStorage "aimeat-theme" ("light"|"dark"). Light is the default; dark lives under [data-theme="dark"]; set that attribute from the saved choice on load (OS preference as fallback, live-update on change):\n' +
7742
8085
  '```css\n' +
7743
8086
  ':root { --bg:#fafaf8; --card:#fff; --text:#1a1a2e; --accent:#e8564a; --border:#e5e7eb; --radius:12px; }\n' +
7744
- '@media (prefers-color-scheme: dark) {\n' +
7745
- ' :root { --bg:#14141c; --card:#1e1e2a; --text:#ececf4; --border:#2e2e40; } }\n' +
8087
+ ':root[data-theme="dark"] { --bg:#14141c; --card:#1e1e2a; --text:#ececf4; --border:#2e2e40; }\n' +
8088
+ '```\n' +
8089
+ '```js\n' +
8090
+ '(function(){ function apply(t){ document.documentElement.setAttribute("data-theme", t==="dark"?"dark":"light"); }\n' +
8091
+ ' apply(localStorage.getItem("aimeat-theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"));\n' +
8092
+ ' addEventListener("storage", function(e){ if(e.key==="aimeat-theme" && e.newValue) apply(e.newValue); }); })();\n' +
7746
8093
  '```\n' +
7747
8094
  'Always include: <meta name="viewport" content="width=device-width, initial-scale=1.0">\n' +
7748
8095
  'Mobile-first responsive. Single HTML file with embedded CSS and JS.\n\n' +
@@ -7751,7 +8098,7 @@
7751
8098
  '- Never use literal closing script tags in JS comments or strings (breaks HTML parser)\n' +
7752
8099
  '- Keep it as a single self-contained HTML file\n' +
7753
8100
  '- Gate AI features on AIMEAT.ai.isAvailable(); never hardcode an API key\n' +
7754
- '- Theme with CSS variables; support light and dark\n' +
8101
+ '- Theme with CSS variables; respect the user\'s AIMEAT light/dark choice (localStorage "aimeat-theme") with an OS-preference fallback\n' +
7755
8102
  '- Include error handling and loading states for API calls\n\n' +
7756
8103
  '--- Source Code ---\n' + code;
7757
8104
  }
@@ -7850,11 +8197,10 @@
7850
8197
  document.getElementById('pb-copy-btn').addEventListener('click', function() {
7851
8198
  var prompt = buildPromptFromBuilder();
7852
8199
  var btn = this;
8200
+ // Keep the dialog OPEN after copying so the user can read Step 3 (add & publish).
8201
+ var revert = function () { setTimeout(function () { btn.textContent = t('pb.copy'); }, 1400); };
7853
8202
  if (navigator.clipboard && navigator.clipboard.writeText) {
7854
- navigator.clipboard.writeText(prompt).then(function() {
7855
- btn.textContent = 'Copied!';
7856
- setTimeout(function() { btn.textContent = 'Copy Prompt'; closePbPanel(); }, 1200);
7857
- });
8203
+ navigator.clipboard.writeText(prompt).then(function() { btn.textContent = '✔ ' + (t('pb.copied') || 'Copied!'); revert(); });
7858
8204
  } else {
7859
8205
  var ta = document.createElement('textarea');
7860
8206
  ta.value = prompt;
@@ -7862,26 +8208,27 @@
7862
8208
  ta.select();
7863
8209
  document.execCommand('copy');
7864
8210
  document.body.removeChild(ta);
7865
- btn.textContent = 'Copied!';
7866
- setTimeout(function() { btn.textContent = 'Copy Prompt'; closePbPanel(); }, 1200);
8211
+ btn.textContent = '✔ ' + (t('pb.copied') || 'Copied!');
8212
+ revert();
7867
8213
  }
7868
8214
  });
7869
8215
 
7870
- document.getElementById('pb-ext-checkbox').addEventListener('change', function() {
7871
- document.getElementById('pb-ext-list').classList.toggle('open', this.checked);
7872
- updatePbPreview();
8216
+ // Step 3 — close the prompt builder and open the Add App flow on the PASTE tab
8217
+ // (the AI hands back ready-to-paste code; the File tab is right there for a file).
8218
+ document.getElementById('pb-add-btn').addEventListener('click', function() {
8219
+ // Step 0 (sign-in/register) if needed, then open Add on the Paste tab.
8220
+ requireSignInThen(function () {
8221
+ closePbPanel();
8222
+ showModal();
8223
+ try { switchTab('paste'); } catch (e) { /* tab switch is best-effort */ }
8224
+ });
7873
8225
  });
7874
8226
 
8227
+ // Mode radios are hidden (mode is implicit) and the cortex-extensions toggle was
8228
+ // removed — the helpful aimeat-* cortex UI libs are now always documented in the
8229
+ // prompt. Keep the mode radios in sync only for buildPromptFromBuilder's state.
7875
8230
  document.querySelectorAll('input[name="pb-mode"]').forEach(function(radio) {
7876
- radio.addEventListener('change', function() {
7877
- var desc = document.getElementById('pb-description');
7878
- if (this.value === 'improve' && pbSourceApp) {
7879
- desc.placeholder = 'Describe what changes to make to "' + (pbSourceApp.name || 'this app') + '"...';
7880
- } else {
7881
- desc.placeholder = 'Describe what the app should do...';
7882
- }
7883
- updatePbPreview();
7884
- });
8231
+ radio.addEventListener('change', updatePbPreview);
7885
8232
  });
7886
8233
 
7887
8234
  document.getElementById('pb-description').addEventListener('input', updatePbPreview);