synthos 0.10.0 → 0.11.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 (312) hide show
  1. package/README.md +5 -5
  2. package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
  3. package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
  4. package/default-pages/elevenlabs_effects_studio/page.json +13 -11
  5. package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
  6. package/default-pages/elevenlabs_voice_studio/page.html +782 -801
  7. package/default-pages/elevenlabs_voice_studio/page.json +13 -11
  8. package/default-pages/json_tools/chat-history.json +1 -0
  9. package/default-pages/json_tools/page.html +70 -90
  10. package/default-pages/json_tools/page.json +12 -10
  11. package/default-pages/my_notes/chat-history.json +1 -0
  12. package/default-pages/my_notes/page.html +115 -131
  13. package/default-pages/my_notes/page.json +14 -12
  14. package/default-pages/neon_asteroids/chat-history.json +1 -0
  15. package/default-pages/neon_asteroids/page.html +1777 -1803
  16. package/default-pages/neon_asteroids/page.json +14 -12
  17. package/default-pages/oregon_trail/chat-history.json +1 -0
  18. package/default-pages/oregon_trail/page.html +290 -307
  19. package/default-pages/oregon_trail/page.json +14 -12
  20. package/default-pages/solar_explorer/chat-history.json +1 -0
  21. package/default-pages/solar_explorer/page.html +1929 -1951
  22. package/default-pages/solar_explorer/page.json +14 -12
  23. package/default-pages/solar_tutorial/chat-history.json +1 -0
  24. package/default-pages/solar_tutorial/page.html +464 -478
  25. package/default-pages/solar_tutorial/page.json +12 -10
  26. package/default-pages/us_map/chat-history.json +1 -0
  27. package/default-pages/us_map/page.html +170 -193
  28. package/default-pages/us_map/page.json +14 -12
  29. package/default-pages/us_map/page.light.png +0 -0
  30. package/default-pages/us_map_1850/chat-history.json +1 -0
  31. package/default-pages/us_map_1850/page.html +302 -326
  32. package/default-pages/us_map_1850/page.json +14 -12
  33. package/default-pages/western_cities_1850/chat-history.json +1 -0
  34. package/default-pages/western_cities_1850/page.html +503 -527
  35. package/default-pages/western_cities_1850/page.json +14 -12
  36. package/default-themes/aurora-dawn.v3.css +15 -14
  37. package/default-themes/aurora-dusk.v3.css +26 -26
  38. package/default-themes/cosmos-dawn.v3.css +15 -14
  39. package/default-themes/cosmos-dusk.v3.css +26 -26
  40. package/default-themes/elemental-dawn.v3.css +200 -0
  41. package/default-themes/nebula-dawn.v3.css +15 -14
  42. package/default-themes/nebula-dusk.v3.css +24 -24
  43. package/default-themes/solar-flare-dawn.v3.css +15 -14
  44. package/default-themes/solar-flare-dusk.v3.css +26 -26
  45. package/dist/builders/anthropic.d.ts +26 -2
  46. package/dist/builders/anthropic.d.ts.map +1 -1
  47. package/dist/builders/anthropic.js +132 -31
  48. package/dist/builders/anthropic.js.map +1 -1
  49. package/dist/builders/claudecode.d.ts +13 -0
  50. package/dist/builders/claudecode.d.ts.map +1 -0
  51. package/dist/builders/claudecode.js +253 -0
  52. package/dist/builders/claudecode.js.map +1 -0
  53. package/dist/builders/index.d.ts +2 -1
  54. package/dist/builders/index.d.ts.map +1 -1
  55. package/dist/builders/index.js +8 -1
  56. package/dist/builders/index.js.map +1 -1
  57. package/dist/builders/openai.js +2 -1
  58. package/dist/builders/openai.js.map +1 -1
  59. package/dist/builders/types.d.ts +31 -7
  60. package/dist/builders/types.d.ts.map +1 -1
  61. package/dist/builders/types.js +60 -28
  62. package/dist/builders/types.js.map +1 -1
  63. package/dist/connectors/types.d.ts +8 -0
  64. package/dist/connectors/types.d.ts.map +1 -1
  65. package/dist/init.d.ts.map +1 -1
  66. package/dist/init.js +13 -6
  67. package/dist/init.js.map +1 -1
  68. package/dist/migrations.d.ts.map +1 -1
  69. package/dist/migrations.js +161 -14
  70. package/dist/migrations.js.map +1 -1
  71. package/dist/models/anthropic.d.ts +1 -0
  72. package/dist/models/anthropic.d.ts.map +1 -1
  73. package/dist/models/anthropic.js +129 -29
  74. package/dist/models/anthropic.js.map +1 -1
  75. package/dist/models/chainOfThought.d.ts.map +1 -1
  76. package/dist/models/chainOfThought.js +32 -19
  77. package/dist/models/chainOfThought.js.map +1 -1
  78. package/dist/models/index.d.ts +2 -2
  79. package/dist/models/index.d.ts.map +1 -1
  80. package/dist/models/index.js +2 -1
  81. package/dist/models/index.js.map +1 -1
  82. package/dist/models/providers.d.ts +1 -0
  83. package/dist/models/providers.d.ts.map +1 -1
  84. package/dist/models/providers.js +12 -4
  85. package/dist/models/providers.js.map +1 -1
  86. package/dist/models/types.d.ts +15 -1
  87. package/dist/models/types.d.ts.map +1 -1
  88. package/dist/models/types.js.map +1 -1
  89. package/dist/pages.d.ts +57 -8
  90. package/dist/pages.d.ts.map +1 -1
  91. package/dist/pages.js +258 -45
  92. package/dist/pages.js.map +1 -1
  93. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  94. package/dist/service/createCompletePrompt.js +5 -0
  95. package/dist/service/createCompletePrompt.js.map +1 -1
  96. package/dist/service/mediaCache.d.ts +36 -0
  97. package/dist/service/mediaCache.d.ts.map +1 -0
  98. package/dist/service/mediaCache.js +182 -0
  99. package/dist/service/mediaCache.js.map +1 -0
  100. package/dist/service/pageValidator.d.ts +25 -0
  101. package/dist/service/pageValidator.d.ts.map +1 -0
  102. package/dist/service/pageValidator.js +315 -0
  103. package/dist/service/pageValidator.js.map +1 -0
  104. package/dist/service/server.d.ts.map +1 -1
  105. package/dist/service/server.js +4 -0
  106. package/dist/service/server.js.map +1 -1
  107. package/dist/service/sharedTableSchema.d.ts +73 -0
  108. package/dist/service/sharedTableSchema.d.ts.map +1 -0
  109. package/dist/service/sharedTableSchema.js +206 -0
  110. package/dist/service/sharedTableSchema.js.map +1 -0
  111. package/dist/service/transformPage.d.ts +49 -11
  112. package/dist/service/transformPage.d.ts.map +1 -1
  113. package/dist/service/transformPage.js +354 -241
  114. package/dist/service/transformPage.js.map +1 -1
  115. package/dist/service/useApiRoutes.d.ts.map +1 -1
  116. package/dist/service/useApiRoutes.js +288 -34
  117. package/dist/service/useApiRoutes.js.map +1 -1
  118. package/dist/service/useConnectorRoutes.d.ts.map +1 -1
  119. package/dist/service/useConnectorRoutes.js +170 -32
  120. package/dist/service/useConnectorRoutes.js.map +1 -1
  121. package/dist/service/useDataRoutes.d.ts.map +1 -1
  122. package/dist/service/useDataRoutes.js +59 -2
  123. package/dist/service/useDataRoutes.js.map +1 -1
  124. package/dist/service/useExtractRoutes.d.ts +4 -0
  125. package/dist/service/useExtractRoutes.d.ts.map +1 -0
  126. package/dist/service/useExtractRoutes.js +304 -0
  127. package/dist/service/useExtractRoutes.js.map +1 -0
  128. package/dist/service/usePageRoutes.d.ts +17 -0
  129. package/dist/service/usePageRoutes.d.ts.map +1 -1
  130. package/dist/service/usePageRoutes.js +1385 -483
  131. package/dist/service/usePageRoutes.js.map +1 -1
  132. package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
  133. package/dist/service/useSharedDataRoutes.js +54 -2
  134. package/dist/service/useSharedDataRoutes.js.map +1 -1
  135. package/dist/settings.d.ts +27 -0
  136. package/dist/settings.d.ts.map +1 -1
  137. package/dist/settings.js +40 -1
  138. package/dist/settings.js.map +1 -1
  139. package/dist/themes.d.ts +0 -5
  140. package/dist/themes.d.ts.map +1 -1
  141. package/dist/themes.js +3 -95
  142. package/dist/themes.js.map +1 -1
  143. package/migration-rules/v2-to-v3.md +277 -119
  144. package/package.json +5 -1
  145. package/{default-pages/application → required-pages/_shell}/page.html +56 -42
  146. package/required-pages/_shell/page.json +14 -0
  147. package/required-pages/_starters/page.html +534 -0
  148. package/required-pages/_starters/page.json +12 -0
  149. package/required-pages/builder/page.html +353 -43
  150. package/required-pages/builder/page.json +12 -10
  151. package/required-pages/pages/page.html +697 -924
  152. package/required-pages/pages/page.json +12 -10
  153. package/required-pages/settings/page.html +1879 -1753
  154. package/required-pages/settings/page.json +12 -10
  155. package/required-pages/synthos_apis/page.html +834 -845
  156. package/required-pages/synthos_apis/page.json +12 -10
  157. package/required-pages/synthos_scripts/page.html +74 -88
  158. package/required-pages/synthos_scripts/page.json +12 -10
  159. package/scripts/append-instructions.py +90 -0
  160. package/scripts/audit-instructions.py +76 -0
  161. package/scripts/cleanup-shell-markup.mjs +112 -0
  162. package/service-connectors/buffer/connector.json +46 -0
  163. package/service-connectors/canva/connector.json +67 -0
  164. package/service-connectors/elevenlabs/connector.json +1 -1
  165. package/src/builders/anthropic.ts +155 -30
  166. package/src/builders/claudecode.ts +310 -0
  167. package/src/builders/index.ts +7 -1
  168. package/src/builders/openai.ts +2 -1
  169. package/src/builders/types.ts +93 -32
  170. package/src/connectors/types.ts +8 -0
  171. package/src/init.ts +13 -7
  172. package/src/migrations.ts +187 -16
  173. package/src/models/anthropic.ts +140 -30
  174. package/src/models/chainOfThought.ts +33 -18
  175. package/src/models/index.ts +2 -2
  176. package/src/models/providers.ts +12 -3
  177. package/src/models/types.ts +21 -1
  178. package/src/pages.ts +271 -35
  179. package/src/service/createCompletePrompt.ts +6 -0
  180. package/src/service/mediaCache.ts +206 -0
  181. package/src/service/pageValidator.ts +337 -0
  182. package/src/service/server.ts +4 -0
  183. package/src/service/sharedTableSchema.ts +236 -0
  184. package/src/service/transformPage.ts +370 -260
  185. package/src/service/useApiRoutes.ts +282 -32
  186. package/src/service/useConnectorRoutes.ts +189 -34
  187. package/src/service/useDataRoutes.ts +198 -116
  188. package/src/service/useExtractRoutes.ts +331 -0
  189. package/src/service/usePageRoutes.ts +1411 -394
  190. package/src/service/useSharedDataRoutes.ts +184 -109
  191. package/src/settings.ts +65 -0
  192. package/src/themes.ts +78 -180
  193. package/starters/blank_starter/chat-history.json +1 -0
  194. package/starters/blank_starter/page.dark.png +0 -0
  195. package/starters/blank_starter/page.html +47 -0
  196. package/starters/blank_starter/page.json +13 -0
  197. package/starters/blank_starter/page.light.png +0 -0
  198. package/starters/calculator_starter/chat-history.json +1 -0
  199. package/starters/calculator_starter/page.dark.png +0 -0
  200. package/starters/calculator_starter/page.html +232 -0
  201. package/starters/calculator_starter/page.json +13 -0
  202. package/starters/calculator_starter/page.light.png +0 -0
  203. package/starters/calendar_starter/chat-history.json +1 -0
  204. package/starters/calendar_starter/page.dark.png +0 -0
  205. package/starters/calendar_starter/page.html +495 -0
  206. package/starters/calendar_starter/page.json +13 -0
  207. package/starters/calendar_starter/page.light.png +0 -0
  208. package/starters/chat_starter/chat-history.json +1 -0
  209. package/starters/chat_starter/page.dark.png +0 -0
  210. package/starters/chat_starter/page.html +351 -0
  211. package/starters/chat_starter/page.json +13 -0
  212. package/starters/chat_starter/page.light.png +0 -0
  213. package/starters/checklist_starter/chat-history.json +1 -0
  214. package/starters/checklist_starter/page.dark.png +0 -0
  215. package/starters/checklist_starter/page.html +437 -0
  216. package/starters/checklist_starter/page.json +13 -0
  217. package/starters/checklist_starter/page.light.png +0 -0
  218. package/starters/dashboard_starter/chat-history.json +1 -0
  219. package/starters/dashboard_starter/page.dark.png +0 -0
  220. package/starters/dashboard_starter/page.html +195 -0
  221. package/starters/dashboard_starter/page.json +13 -0
  222. package/starters/dashboard_starter/page.light.png +0 -0
  223. package/starters/form_starter/chat-history.json +1 -0
  224. package/starters/form_starter/page.dark.png +0 -0
  225. package/starters/form_starter/page.html +313 -0
  226. package/starters/form_starter/page.json +13 -0
  227. package/starters/form_starter/page.light.png +0 -0
  228. package/starters/gallery_starter/chat-history.json +1 -0
  229. package/starters/gallery_starter/page.dark.png +0 -0
  230. package/starters/gallery_starter/page.html +418 -0
  231. package/starters/gallery_starter/page.json +13 -0
  232. package/starters/gallery_starter/page.light.png +0 -0
  233. package/starters/generator_starter/chat-history.json +1 -0
  234. package/starters/generator_starter/page.dark.png +0 -0
  235. package/starters/generator_starter/page.html +261 -0
  236. package/starters/generator_starter/page.json +13 -0
  237. package/starters/generator_starter/page.light.png +0 -0
  238. package/starters/index.html +538 -0
  239. package/starters/kanban_starter/chat-history.json +1 -0
  240. package/starters/kanban_starter/page.dark.png +0 -0
  241. package/starters/kanban_starter/page.html +432 -0
  242. package/starters/kanban_starter/page.json +13 -0
  243. package/starters/kanban_starter/page.light.png +0 -0
  244. package/starters/presentation_builder/chat-history.json +1 -0
  245. package/starters/presentation_builder/page.dark.png +0 -0
  246. package/starters/presentation_builder/page.html +970 -0
  247. package/starters/presentation_builder/page.json +15 -0
  248. package/starters/presentation_builder/page.light.png +0 -0
  249. package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
  250. package/starters/pulse_starter/chat-history.json +1 -0
  251. package/starters/pulse_starter/page.dark.png +0 -0
  252. package/starters/pulse_starter/page.html +698 -0
  253. package/starters/pulse_starter/page.json +13 -0
  254. package/starters/pulse_starter/page.light.png +0 -0
  255. package/starters/quiz_starter/chat-history.json +1 -0
  256. package/starters/quiz_starter/page.dark.png +0 -0
  257. package/starters/quiz_starter/page.html +292 -0
  258. package/starters/quiz_starter/page.json +13 -0
  259. package/starters/quiz_starter/page.light.png +0 -0
  260. package/starters/reference_starter/chat-history.json +1 -0
  261. package/starters/reference_starter/page.dark.png +0 -0
  262. package/starters/reference_starter/page.html +250 -0
  263. package/starters/reference_starter/page.json +13 -0
  264. package/starters/reference_starter/page.light.png +0 -0
  265. package/starters/retro_game_starter/chat-history.json +1 -0
  266. package/starters/retro_game_starter/page.dark.png +0 -0
  267. package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
  268. package/starters/retro_game_starter/page.json +15 -0
  269. package/starters/retro_game_starter/page.light.png +0 -0
  270. package/starters/roster_starter/chat-history.json +1 -0
  271. package/starters/roster_starter/page.dark.png +0 -0
  272. package/starters/roster_starter/page.html +600 -0
  273. package/starters/roster_starter/page.json +13 -0
  274. package/starters/roster_starter/page.light.png +0 -0
  275. package/starters/server.js +182 -0
  276. package/starters/start.cmd +1 -0
  277. package/starters/timeline_starter/chat-history.json +1 -0
  278. package/starters/timeline_starter/page.dark.png +0 -0
  279. package/starters/timeline_starter/page.html +446 -0
  280. package/starters/timeline_starter/page.json +13 -0
  281. package/starters/timeline_starter/page.light.png +0 -0
  282. package/starters/tutorial_starter/chat-history.json +1 -0
  283. package/starters/tutorial_starter/page.dark.png +0 -0
  284. package/starters/tutorial_starter/page.html +283 -0
  285. package/starters/tutorial_starter/page.json +13 -0
  286. package/starters/tutorial_starter/page.light.png +0 -0
  287. package/static-files/agent.v3.js +122 -0
  288. package/static-files/connector.v3.js +48 -0
  289. package/static-files/extract.v3.js +188 -0
  290. package/static-files/helpers.v3.js +50 -6
  291. package/static-files/page-bridge.js +114 -0
  292. package/static-files/page.v3.js +1292 -1290
  293. package/static-files/script.v3.js +32 -0
  294. package/static-files/server.v3.js +89 -0
  295. package/static-files/shell-bridge.v3.js +174 -0
  296. package/static-files/shell-modals.v3.js +521 -0
  297. package/static-files/{shell.css → shell.v3.css} +271 -22
  298. package/static-files/shell.v3.js +1865 -0
  299. package/static-files/storage.v3.js +176 -0
  300. package/tests/anthropic.spec.ts +42 -7
  301. package/tests/builders.spec.ts +72 -4
  302. package/tests/pageValidator.spec.ts +548 -0
  303. package/tests/profiles.spec.ts +122 -0
  304. package/tests/providers.spec.ts +1 -1
  305. package/tests/sharedTableSchema.spec.ts +242 -0
  306. package/tests/transformPage.spec.ts +62 -81
  307. package/default-pages/application/page.json +0 -10
  308. package/default-pages/retro_game_starter/page.json +0 -12
  309. package/default-pages/sidebar_page/page.html +0 -51
  310. package/default-pages/sidebar_page/page.json +0 -10
  311. package/default-pages/two-panel_page/page.html +0 -68
  312. package/default-pages/two-panel_page/page.json +0 -10
@@ -0,0 +1,534 @@
1
+ <!DOCTYPE html><html lang="en"><head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ <title>SynthOS - Starters Browser</title>
5
+ <script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
6
+ <link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
7
+ <style>
8
+ html, body, .viewer-panel { height: 100%; margin: 0; }
9
+ .starters-app {
10
+ height: 100%;
11
+ display: grid;
12
+ grid-template-columns: 260px 1fr;
13
+ grid-template-rows: 56px 1fr;
14
+ grid-template-areas:
15
+ "sidebar topbar"
16
+ "sidebar main";
17
+ background: var(--bodyBackground, #ffffff);
18
+ color: var(--bodyText, #1f1f1f);
19
+ overflow: hidden;
20
+ }
21
+ .sidebar {
22
+ grid-area: sidebar;
23
+ background: var(--defaultStateBackground, #faf9f8);
24
+ border-right: 1px solid var(--bodyDivider, #edebe9);
25
+ overflow-y: auto;
26
+ display: flex;
27
+ flex-direction: column;
28
+ }
29
+ .sidebar-header {
30
+ padding: 14px 16px;
31
+ border-bottom: 1px solid var(--bodyDivider, #edebe9);
32
+ font-size: 14px;
33
+ font-weight: 600;
34
+ color: var(--bodyText, #1f1f1f);
35
+ }
36
+ .sidebar-list { list-style: none; padding: 8px 0; margin: 0; }
37
+ .sidebar-item {
38
+ list-style: none;
39
+ padding: 10px 16px;
40
+ font-size: 13px;
41
+ color: var(--bodyText, #1f1f1f);
42
+ cursor: pointer;
43
+ border-left: 3px solid transparent;
44
+ user-select: none;
45
+ display: flex;
46
+ flex-wrap: nowrap;
47
+ align-items: center;
48
+ }
49
+ .sidebar-item::marker { content: none; }
50
+ .sidebar-item:hover { background: var(--bodyBackgroundHovered, #f3f2f1); }
51
+ .sidebar-item.active {
52
+ background: var(--bodyBackgroundHovered, #f3f2f1);
53
+ border-left-color: var(--themePrimary, #0078d4);
54
+ font-weight: 600;
55
+ }
56
+ .status-pair {
57
+ display: flex;
58
+ flex: 0 0 auto;
59
+ flex-direction: row;
60
+ flex-wrap: nowrap;
61
+ gap: 3px;
62
+ margin-right: 10px;
63
+ }
64
+ .status {
65
+ flex: 0 0 auto;
66
+ display: inline-block;
67
+ width: 8px;
68
+ height: 8px;
69
+ border-radius: 50%;
70
+ background: var(--bodyDivider, #edebe9);
71
+ border: 1px solid var(--inputBorder, #8a8886);
72
+ }
73
+ .status.captured { background: #107c10; border-color: #107c10; }
74
+ .topbar {
75
+ grid-area: topbar;
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 10px;
79
+ padding: 0 16px;
80
+ border-bottom: 1px solid var(--bodyDivider, #edebe9);
81
+ background: var(--bodyStandoutBackground, #ffffff);
82
+ }
83
+ .topbar .title {
84
+ font-weight: 600;
85
+ font-size: 14px;
86
+ color: var(--bodyText, #1f1f1f);
87
+ margin-right: auto;
88
+ white-space: nowrap;
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+ }
92
+ .topbar select, .topbar button {
93
+ font-family: inherit;
94
+ font-size: 12px;
95
+ padding: 6px 12px;
96
+ border: 1px solid var(--inputBorder, #8a8886);
97
+ border-radius: 4px;
98
+ background: var(--bodyBackground, #ffffff);
99
+ color: var(--bodyText, #1f1f1f);
100
+ cursor: pointer;
101
+ }
102
+ .topbar button:hover {
103
+ background: var(--bodyBackgroundHovered, #f3f2f1);
104
+ }
105
+ .topbar button.primary {
106
+ background: var(--themePrimary, #0078d4);
107
+ color: #ffffff;
108
+ border-color: transparent;
109
+ }
110
+ .topbar button.primary:hover { filter: brightness(1.05); }
111
+ .topbar button:disabled { opacity: 0.5; cursor: not-allowed; }
112
+ .frame-size-label {
113
+ font-size: 11px;
114
+ color: var(--bodySubtext, #605e5c);
115
+ }
116
+ .main {
117
+ grid-area: main;
118
+ background: var(--bodyBackground, #ffffff);
119
+ overflow: auto;
120
+ display: flex;
121
+ align-items: flex-start;
122
+ justify-content: center;
123
+ padding: 24px;
124
+ }
125
+ .frame-wrap {
126
+ position: relative;
127
+ width: 1310px;
128
+ height: 880px;
129
+ min-width: 1310px;
130
+ min-height: 880px;
131
+ background: #ffffff;
132
+ flex: 0 0 auto;
133
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
134
+ }
135
+ .frame-wrap iframe {
136
+ width: 100%;
137
+ height: 100%;
138
+ border: 0;
139
+ background: #ffffff;
140
+ }
141
+ .empty-state {
142
+ font-size: 14px;
143
+ color: var(--bodySubtext, #605e5c);
144
+ }
145
+ .toast {
146
+ position: fixed;
147
+ bottom: 24px;
148
+ left: 50%;
149
+ transform: translateX(-50%);
150
+ background: var(--bodyText, #1f1f1f);
151
+ color: var(--bodyBackground, #ffffff);
152
+ padding: 10px 18px;
153
+ border-radius: 4px;
154
+ font-size: 13px;
155
+ opacity: 0;
156
+ transition: opacity 0.2s;
157
+ pointer-events: none;
158
+ z-index: 1000;
159
+ max-width: 80%;
160
+ }
161
+ .toast.show { opacity: 1; }
162
+ .toast.error { background: #a4262c; }
163
+ </style>
164
+ <script id="page-info" src="/api/page-info.js?page=_starters"></script>
165
+ </head>
166
+ <body>
167
+ <div class="viewer-panel" id="viewerPanel" style="padding: 0;">
168
+ <div class="starters-app">
169
+ <aside class="sidebar">
170
+ <div class="sidebar-header">Starters</div>
171
+ <ul class="sidebar-list" id="sidebarList">
172
+ <li class="sidebar-item" style="cursor: default; color: var(--bodySubtext, #605e5c);">Loading…</li>
173
+ </ul>
174
+ </aside>
175
+
176
+ <header class="topbar">
177
+ <div class="title" id="currentTitle">Pick a starter from the sidebar</div>
178
+ <span class="frame-size-label">Frame:</span>
179
+ <select id="frameSize" title="Frame dimensions approximate the shell viewer panel with the page editor open (toolbar 48px + chat panel 30% of remaining width)">
180
+ <option value="1310x880">1310 × 880 (shell @ 1920, editor open)</option>
181
+ <option value="1760x1180">1760 × 1180 (shell @ 2560, editor open)</option>
182
+ <option value="980x680">980 × 680 (shell @ 1440, editor open)</option>
183
+ <option value="1280x800">1280 × 800</option>
184
+ <option value="1440x900">1440 × 900</option>
185
+ <option value="375x667">375 × 667 (mobile)</option>
186
+ </select>
187
+ <select id="themeSelect" title="Switching theme here updates the global Synthos setting and reloads the preview"></select>
188
+ <button id="captureBtn" class="primary" disabled title="Save as page.light.png or page.dark.png in the starter's folder, based on the current theme">Capture page.light.png</button>
189
+ <button id="captureAllBtn" disabled title="Capture every starter at the current theme. Run twice (once with a light theme, once with a dark theme) to fill in both page.light.png and page.dark.png.">Capture all (light)</button>
190
+ </header>
191
+
192
+ <main class="main">
193
+ <div class="frame-wrap" id="frameWrap" style="display: none;">
194
+ <iframe id="previewFrame" sandbox="allow-same-origin allow-scripts allow-forms" title="Starter Preview"></iframe>
195
+ </div>
196
+ <div class="empty-state" id="emptyState">Select a starter to preview.</div>
197
+ </main>
198
+ </div>
199
+ </div>
200
+
201
+ <div class="toast" id="toast"></div>
202
+
203
+ <div id="instructions" style="display: none;" data-locked="true"></div>
204
+ <div id="thoughts" style="display: none;" data-locked="true"></div>
205
+
206
+ <script>
207
+ (function() {
208
+ 'use strict';
209
+
210
+ // ── DOM refs ──
211
+ var sidebarList = document.getElementById('sidebarList');
212
+ var previewFrame = document.getElementById('previewFrame');
213
+ var frameWrap = document.getElementById('frameWrap');
214
+ var emptyState = document.getElementById('emptyState');
215
+ var currentTitle = document.getElementById('currentTitle');
216
+ var captureBtn = document.getElementById('captureBtn');
217
+ var captureAllBtn = document.getElementById('captureAllBtn');
218
+ var frameSize = document.getElementById('frameSize');
219
+ var themeSelect = document.getElementById('themeSelect');
220
+ var toastEl = document.getElementById('toast');
221
+
222
+ // ── State ──
223
+ var starters = []; // [{name, title, ...}]
224
+ var activeStarter = null; // page name
225
+ var captureStatus = {}; // { name: { light: true, dark: true } }
226
+
227
+ // ── Helpers ──
228
+
229
+ function themeVariant(name) {
230
+ if (!name) return 'light';
231
+ if (name.indexOf('dawn') !== -1 || name === 'high-contrast-light' || name.indexOf('-light') !== -1) return 'light';
232
+ if (name.indexOf('dusk') !== -1 || name === 'high-contrast-dark' || name.indexOf('-dark') !== -1) return 'dark';
233
+ return 'light';
234
+ }
235
+
236
+ function toast(msg, isError) {
237
+ toastEl.textContent = msg;
238
+ toastEl.classList.toggle('error', !!isError);
239
+ toastEl.classList.add('show');
240
+ clearTimeout(toast._t);
241
+ toast._t = setTimeout(function() { toastEl.classList.remove('show'); }, 2400);
242
+ }
243
+
244
+ function displayName(p) { return p.title || p.name; }
245
+
246
+ // ── Sidebar ──
247
+
248
+ function renderSidebar() {
249
+ sidebarList.innerHTML = '';
250
+ if (starters.length === 0) {
251
+ var empty = document.createElement('li');
252
+ empty.className = 'sidebar-item';
253
+ empty.style.cursor = 'default';
254
+ empty.style.color = 'var(--bodySubtext, #605e5c)';
255
+ empty.textContent = 'No starter pages found';
256
+ sidebarList.appendChild(empty);
257
+ return;
258
+ }
259
+ starters.forEach(function(p) {
260
+ var li = document.createElement('li');
261
+ li.className = 'sidebar-item';
262
+ li.dataset.starter = p.name;
263
+ var status = captureStatus[p.name] || {};
264
+ var lightCls = status.light ? ' captured' : '';
265
+ var darkCls = status.dark ? ' captured' : '';
266
+ var pair = document.createElement('span');
267
+ pair.className = 'status-pair';
268
+ pair.title = 'left = page.light.png, right = page.dark.png';
269
+ pair.innerHTML = '<span class="status' + lightCls + '"></span><span class="status' + darkCls + '"></span>';
270
+ var label = document.createElement('span');
271
+ label.textContent = displayName(p);
272
+ li.appendChild(pair);
273
+ li.appendChild(label);
274
+ if (p.name === activeStarter) li.classList.add('active');
275
+ li.addEventListener('click', function() { loadStarter(p.name); });
276
+ sidebarList.appendChild(li);
277
+ });
278
+ }
279
+
280
+ function applyFrameSize() {
281
+ var parts = frameSize.value.split('x').map(Number);
282
+ frameWrap.style.width = parts[0] + 'px';
283
+ frameWrap.style.height = parts[1] + 'px';
284
+ frameWrap.style.minWidth = parts[0] + 'px';
285
+ frameWrap.style.minHeight = parts[1] + 'px';
286
+ }
287
+
288
+ function frameDimensions() {
289
+ var parts = frameSize.value.split('x').map(Number);
290
+ return { w: parts[0], h: parts[1] };
291
+ }
292
+
293
+ function loadStarter(name) {
294
+ activeStarter = name;
295
+ var meta = starters.find(function(p) { return p.name === name; });
296
+ currentTitle.textContent = meta ? displayName(meta) : name;
297
+ emptyState.style.display = 'none';
298
+ frameWrap.style.display = 'block';
299
+ applyFrameSize();
300
+ // ?frame=1 returns the bare iframe content (theme + helpers injected by the server).
301
+ previewFrame.src = '/' + encodeURIComponent(name) + '?frame=1&t=' + Date.now();
302
+ captureBtn.disabled = false;
303
+ renderSidebar();
304
+ }
305
+
306
+ // Inject html2canvas into the iframe so capture runs in the page's own
307
+ // window context. Capturing from the harness produced subtle horizontal
308
+ // misalignment because html2canvas was reading computed styles through
309
+ // the parent window — this keeps the library's `window` aligned with the
310
+ // page's actual layout viewport.
311
+ function injectCaptureLib() {
312
+ var doc, win;
313
+ try {
314
+ doc = previewFrame.contentDocument;
315
+ win = previewFrame.contentWindow;
316
+ } catch (e) { return; }
317
+ if (!doc || !win || win.html2canvas) return;
318
+ var s = doc.createElement('script');
319
+ s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
320
+ doc.head.appendChild(s);
321
+ }
322
+ previewFrame.addEventListener('load', injectCaptureLib);
323
+
324
+ async function waitForFrameCaptureLib(timeoutMs) {
325
+ var win;
326
+ try { win = previewFrame.contentWindow; } catch (e) { return false; }
327
+ if (!win) return false;
328
+ var waited = 0;
329
+ while (!win.html2canvas && waited < timeoutMs) {
330
+ await new Promise(function(r) { setTimeout(r, 100); });
331
+ waited += 100;
332
+ }
333
+ return !!win.html2canvas;
334
+ }
335
+
336
+ // ── Theme switching ──
337
+ // POST /api/settings updates the global theme. The shell + this page were
338
+ // already rendered with the previous theme, so only the inner preview iframe
339
+ // re-fetches /api/theme.css under the new setting. That is intentional —
340
+ // we want isolated theme switching for capture without rebuilding the harness.
341
+
342
+ async function setServerTheme(theme) {
343
+ var res = await fetch('/api/settings', {
344
+ method: 'POST',
345
+ headers: { 'Content-Type': 'application/json' },
346
+ body: JSON.stringify({ theme: theme }),
347
+ redirect: 'manual'
348
+ });
349
+ // express does res.redirect → opaqueredirect under manual; both 0 and 200 are OK here
350
+ if (res.type !== 'opaqueredirect' && !res.ok) {
351
+ throw new Error('Settings save failed: ' + res.status);
352
+ }
353
+ }
354
+
355
+ async function loadThemes() {
356
+ var names;
357
+ try {
358
+ var res = await fetch('/api/themes');
359
+ if (!res.ok) throw new Error('themes failed (' + res.status + ')');
360
+ names = await res.json();
361
+ if (!Array.isArray(names)) throw new Error('themes payload not an array');
362
+ } catch (err) {
363
+ console.error('Failed to load themes:', err);
364
+ names = ['nebula-dawn','nebula-dusk','aurora-dawn','aurora-dusk','cosmos-dawn','cosmos-dusk','solar-flare-dawn','solar-flare-dusk','high-contrast-light','high-contrast-dark'];
365
+ }
366
+ themeSelect.innerHTML = '';
367
+ names.forEach(function(name) {
368
+ var opt = document.createElement('option');
369
+ opt.value = name;
370
+ opt.textContent = name;
371
+ themeSelect.appendChild(opt);
372
+ });
373
+ // Preselect the active theme reported by /api/theme-info.js
374
+ if (window.themeInfo && window.themeInfo.name) {
375
+ themeSelect.value = window.themeInfo.name;
376
+ }
377
+ updateCaptureButtonLabels();
378
+ updateCaptureAllState();
379
+ }
380
+
381
+ // ── Capture ──
382
+
383
+ async function captureCurrent() {
384
+ if (!activeStarter) return;
385
+ var doc = previewFrame.contentDocument;
386
+ var win = previewFrame.contentWindow;
387
+ if (!doc || !doc.body || !win) {
388
+ toast('Iframe not ready', true);
389
+ return;
390
+ }
391
+ var variant = themeVariant(themeSelect.value);
392
+ var filename = 'page.' + variant + '.png';
393
+ captureBtn.disabled = true;
394
+ try {
395
+ // Wait for html2canvas to load INSIDE the iframe. Running the library
396
+ // in the page's own window means it sees the layout the page sees —
397
+ // no parent-frame measurement skew.
398
+ var ready = await waitForFrameCaptureLib(8000);
399
+ if (!ready) throw new Error('Capture library failed to load in page');
400
+ await new Promise(function(r) { setTimeout(r, 150); });
401
+ var dims = frameDimensions();
402
+ var de = doc.documentElement;
403
+ var contentH = Math.max(dims.h, de.scrollHeight, doc.body.scrollHeight);
404
+ var canvas = await win.html2canvas(de, {
405
+ width: dims.w,
406
+ height: contentH,
407
+ windowWidth: dims.w,
408
+ windowHeight: dims.h,
409
+ backgroundColor: '#ffffff',
410
+ useCORS: true,
411
+ allowTaint: false,
412
+ logging: false,
413
+ scale: 1
414
+ });
415
+ var blob = await new Promise(function(resolve) { canvas.toBlob(resolve, 'image/png'); });
416
+ if (!blob) throw new Error('Could not encode PNG');
417
+ await savePng(activeStarter, filename, blob);
418
+ captureStatus[activeStarter] = captureStatus[activeStarter] || {};
419
+ captureStatus[activeStarter][variant] = true;
420
+ renderSidebar();
421
+ } catch (err) {
422
+ console.error(err);
423
+ toast('Capture failed: ' + err.message, true);
424
+ } finally {
425
+ captureBtn.disabled = false;
426
+ }
427
+ }
428
+
429
+ async function savePng(starter, filename, blob) {
430
+ // filename is "page.light.png" or "page.dark.png" — derive variant from it
431
+ var variant = filename === 'page.dark.png' ? 'dark' : 'light';
432
+ var url = '/api/starters/' + encodeURIComponent(starter) + '/screenshot/' + variant;
433
+ var res = await fetch(url, {
434
+ method: 'POST',
435
+ headers: { 'Content-Type': 'image/png' },
436
+ body: blob
437
+ });
438
+ if (!res.ok) {
439
+ var msg = 'HTTP ' + res.status;
440
+ try { var body = await res.json(); if (body && body.error) msg = body.error; } catch (e) {}
441
+ throw new Error(msg);
442
+ }
443
+ var data = {};
444
+ try { data = await res.json(); } catch (e) {}
445
+ var n = (data.paths && data.paths.length) || 0;
446
+ toast('Saved ' + starter + '/' + filename + (n > 1 ? ' (×' + n + ')' : ''));
447
+ }
448
+
449
+ async function captureAll() {
450
+ captureAllBtn.disabled = true;
451
+ captureBtn.disabled = true;
452
+ try {
453
+ for (var i = 0; i < starters.length; i++) {
454
+ var name = starters[i].name;
455
+ loadStarter(name);
456
+ await new Promise(function(resolve) {
457
+ var onLoad = function() {
458
+ previewFrame.removeEventListener('load', onLoad);
459
+ setTimeout(resolve, 800);
460
+ };
461
+ previewFrame.addEventListener('load', onLoad);
462
+ });
463
+ await captureCurrent();
464
+ }
465
+ toast('All starters captured');
466
+ } finally {
467
+ captureAllBtn.disabled = false;
468
+ captureBtn.disabled = !activeStarter;
469
+ }
470
+ }
471
+
472
+ function updateCaptureButtonLabels() {
473
+ var variant = themeVariant(themeSelect.value);
474
+ captureBtn.textContent = 'Capture page.' + variant + '.png';
475
+ captureAllBtn.textContent = 'Capture all (' + variant + ')';
476
+ }
477
+
478
+ function updateCaptureAllState() {
479
+ captureAllBtn.disabled = starters.length === 0;
480
+ captureAllBtn.title = 'Capture and save page.' + themeVariant(themeSelect.value) + '.png for every starter';
481
+ }
482
+
483
+ // ── Page list ──
484
+
485
+ async function loadStarters() {
486
+ try {
487
+ var res = await fetch('/api/pages');
488
+ if (!res.ok) throw new Error('pages failed');
489
+ var allPages = await res.json();
490
+ starters = allPages
491
+ .filter(function(p) {
492
+ return Array.isArray(p.categories) && p.categories.indexOf('_Starters') !== -1;
493
+ })
494
+ .sort(function(a, b) { return displayName(a).localeCompare(displayName(b)); });
495
+ renderSidebar();
496
+ updateCaptureAllState();
497
+ } catch (err) {
498
+ console.error('Failed to load pages:', err);
499
+ toast('Failed to load starters: ' + err.message, true);
500
+ }
501
+ }
502
+
503
+ // ── Wire up ──
504
+
505
+ captureBtn.addEventListener('click', captureCurrent);
506
+ captureAllBtn.addEventListener('click', captureAll);
507
+
508
+ frameSize.addEventListener('change', function() {
509
+ applyFrameSize();
510
+ if (activeStarter) loadStarter(activeStarter);
511
+ });
512
+
513
+ themeSelect.addEventListener('change', async function() {
514
+ var theme = themeSelect.value;
515
+ try {
516
+ await setServerTheme(theme);
517
+ } catch (err) {
518
+ toast('Theme switch failed: ' + err.message, true);
519
+ return;
520
+ }
521
+ updateCaptureButtonLabels();
522
+ updateCaptureAllState();
523
+ // Reload only the preview iframe — the new theme takes effect on next /<page>?frame=1 fetch
524
+ if (activeStarter) loadStarter(activeStarter);
525
+ });
526
+
527
+ applyFrameSize();
528
+ updateCaptureButtonLabels();
529
+ updateCaptureAllState();
530
+ loadThemes();
531
+ loadStarters();
532
+ })();
533
+ </script>
534
+ </body></html>
@@ -0,0 +1,12 @@
1
+ {
2
+ "title": "Starters Browser",
3
+ "categories": [
4
+ "System"
5
+ ],
6
+ "pinned": false,
7
+ "showInAll": false,
8
+ "pageVersion": 3,
9
+ "mode": "locked",
10
+ "greeting": "",
11
+ "firstRunGreeting": ""
12
+ }